PageRenderTime 46ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/ToMigrate/Raven.Database/Actions/DocumentActions.cs

http://github.com/ayende/ravendb
C# | 846 lines | 674 code | 159 blank | 13 comment | 134 complexity | e0eeb0347cf50dc9a23ac0409ff4e9f5 MD5 | raw file
Possible License(s): GPL-3.0, MPL-2.0-no-copyleft-exception, LGPL-2.1, Apache-2.0, BSD-3-Clause, CC-BY-SA-3.0
  1. // -----------------------------------------------------------------------
  2. // <copyright file="DocumentActions.cs" company="Hibernating Rhinos LTD">
  3. // Copyright (c) Hibernating Rhinos LTD. All rights reserved.
  4. // </copyright>
  5. // -----------------------------------------------------------------------
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Linq;
  9. using System.Threading;
  10. using Raven.Abstractions.Data;
  11. using Raven.Abstractions.Exceptions;
  12. using Raven.Abstractions.Extensions;
  13. using Raven.Abstractions.Linq;
  14. using Raven.Abstractions.Logging;
  15. using Raven.Database.Data;
  16. using Raven.Database.Extensions;
  17. using Raven.Database.Impl;
  18. using Raven.Database.Indexing;
  19. using Raven.Database.Linq;
  20. using Raven.Database.Plugins;
  21. using Raven.Database.Storage;
  22. using Raven.Database.Tasks;
  23. using Raven.Database.Util;
  24. using Raven.Json.Linq;
  25. namespace Raven.Database.Actions
  26. {
  27. public class DocumentActions : ActionsBase
  28. {
  29. public DocumentActions(DocumentDatabase database, SizeLimitedConcurrentDictionary<string, TouchedDocumentInfo> recentTouches, IUuidGenerator uuidGenerator, ILog log)
  30. : base(database, recentTouches, uuidGenerator, log)
  31. {
  32. }
  33. public long GetNextIdentityValueWithoutOverwritingOnExistingDocuments(string key,
  34. IStorageActionsAccessor actions)
  35. {
  36. int tries;
  37. return GetNextIdentityValueWithoutOverwritingOnExistingDocuments(key, actions, out tries);
  38. }
  39. public long GetNextIdentityValueWithoutOverwritingOnExistingDocuments(string key,
  40. IStorageActionsAccessor actions,
  41. out int tries)
  42. {
  43. long nextIdentityValue = actions.General.GetNextIdentityValue(key);
  44. if (actions.Documents.DocumentMetadataByKey(key + nextIdentityValue) == null)
  45. {
  46. tries = 1;
  47. return nextIdentityValue;
  48. }
  49. tries = 1;
  50. // there is already a document with this id, this means that we probably need to search
  51. // for an opening in potentially large data set.
  52. var lastKnownBusy = nextIdentityValue;
  53. var maybeFree = nextIdentityValue * 2;
  54. var lastKnownFree = long.MaxValue;
  55. while (true)
  56. {
  57. tries++;
  58. if (actions.Documents.DocumentMetadataByKey(key + maybeFree) == null)
  59. {
  60. if (lastKnownBusy + 1 == maybeFree)
  61. {
  62. actions.General.SetIdentityValue(key, maybeFree);
  63. return maybeFree;
  64. }
  65. lastKnownFree = maybeFree;
  66. maybeFree = Math.Max(maybeFree - (maybeFree - lastKnownBusy) / 2, lastKnownBusy + 1);
  67. }
  68. else
  69. {
  70. lastKnownBusy = maybeFree;
  71. maybeFree = Math.Min(lastKnownFree, maybeFree * 2);
  72. }
  73. }
  74. }
  75. private void AssertPutOperationNotVetoed(string key, RavenJObject metadata, RavenJObject document)
  76. {
  77. var vetoResult = Database.PutTriggers
  78. .Select(trigger => new { Trigger = trigger, VetoResult = trigger.AllowPut(key, document, metadata) })
  79. .FirstOrDefault(x => x.VetoResult.IsAllowed == false);
  80. if (vetoResult != null)
  81. {
  82. throw new OperationVetoedException("PUT vetoed on document " + key + " by " + vetoResult.Trigger + " because: " + vetoResult.VetoResult.Reason);
  83. }
  84. }
  85. public RavenJArray GetDocumentsWithIdStartingWith(string idPrefix, string matches, string exclude, int start,
  86. int pageSize, CancellationToken token, ref int nextStart,
  87. string transformer = null, Dictionary<string, RavenJToken> transformerParameters = null,
  88. string skipAfter = null)
  89. {
  90. var list = new RavenJArray();
  91. GetDocumentsWithIdStartingWith(idPrefix, matches, exclude, start, pageSize, token, ref nextStart,
  92. doc => { if (doc != null) list.Add(doc.ToJson()); }, transformer, transformerParameters, skipAfter);
  93. return list;
  94. }
  95. public void GetDocumentsWithIdStartingWith(string idPrefix, string matches, string exclude, int start, int pageSize,
  96. CancellationToken token, ref int nextStart, Action<JsonDocument> addDoc,
  97. string transformer = null, Dictionary<string, RavenJToken> transformerParameters = null,
  98. string skipAfter = null)
  99. {
  100. if (idPrefix == null)
  101. throw new ArgumentNullException("idPrefix");
  102. idPrefix = idPrefix.Trim();
  103. var canPerformRapidPagination = nextStart > 0 && start == nextStart;
  104. var actualStart = canPerformRapidPagination ? start : 0;
  105. var addedDocs = 0;
  106. var docCountOnLastAdd = 0;
  107. var matchedDocs = 0;
  108. TransactionalStorage.Batch(
  109. actions =>
  110. {
  111. var docsToSkip = canPerformRapidPagination ? 0 : start;
  112. int docCount;
  113. AbstractTransformer storedTransformer = null;
  114. if (transformer != null)
  115. {
  116. storedTransformer = IndexDefinitionStorage.GetTransformer(transformer);
  117. if (storedTransformer == null)
  118. throw new InvalidOperationException("No transformer with the name: " + transformer);
  119. }
  120. do
  121. {
  122. Database.WorkContext.UpdateFoundWork();
  123. docCount = 0;
  124. var docs = actions.Documents.GetDocumentsWithIdStartingWith(idPrefix, actualStart, pageSize, string.IsNullOrEmpty(skipAfter) ? null : skipAfter);
  125. var documentRetriever = new DocumentRetriever(Database.Configuration, actions, Database.ReadTriggers, transformerParameters);
  126. foreach (var doc in docs)
  127. {
  128. token.ThrowIfCancellationRequested();
  129. docCount++;
  130. if (docCount - docCountOnLastAdd > 1000)
  131. {
  132. addDoc(null); // heartbeat
  133. }
  134. var keyTest = doc.Key.Substring(idPrefix.Length);
  135. if (!WildcardMatcher.Matches(matches, keyTest) || WildcardMatcher.MatchesExclusion(exclude, keyTest))
  136. continue;
  137. JsonDocument.EnsureIdInMetadata(doc);
  138. var document = documentRetriever.ExecuteReadTriggers(doc, ReadOperation.Load);
  139. if (document == null)
  140. continue;
  141. matchedDocs++;
  142. if (matchedDocs <= docsToSkip)
  143. continue;
  144. token.ThrowIfCancellationRequested();
  145. document = TransformDocumentIfNeeded(document, storedTransformer, documentRetriever);
  146. addDoc(document);
  147. addedDocs++;
  148. docCountOnLastAdd = docCount;
  149. if (addedDocs >= pageSize)
  150. break;
  151. }
  152. actualStart += pageSize;
  153. }
  154. while (docCount > 0 && addedDocs < pageSize && actualStart > 0 && actualStart < int.MaxValue);
  155. });
  156. if (addedDocs != pageSize)
  157. nextStart = start; // will mark as last page
  158. else if (canPerformRapidPagination)
  159. nextStart = start + matchedDocs;
  160. else
  161. nextStart = actualStart;
  162. }
  163. private JsonDocument TransformDocumentIfNeeded(JsonDocument document, AbstractTransformer storedTransformer, DocumentRetriever documentRetriever)
  164. {
  165. if (storedTransformer == null)
  166. return document;
  167. using (new CurrentTransformationScope(Database, documentRetriever))
  168. {
  169. var transformed = storedTransformer
  170. .TransformResultsDefinition(new[] { new DynamicJsonObject(document.ToJson()) })
  171. .Select<dynamic, dynamic>(x => JsonExtensions.ToJObject((object)x))
  172. .ToArray();
  173. RavenJObject ravenJObject;
  174. switch (transformed.Length)
  175. {
  176. case 0:
  177. throw new InvalidOperationException("The transform results function failed on a document: " + document.Key);
  178. case 1:
  179. ravenJObject = transformed[0];
  180. break;
  181. default:
  182. ravenJObject = new RavenJObject { { "$values", new RavenJArray(transformed) } };
  183. break;
  184. }
  185. return new JsonDocument
  186. {
  187. Etag = document.Etag.CombineHashWith(storedTransformer.GetHashCodeBytes()).HashWith(documentRetriever.Etag),
  188. LastModified = document.LastModified,
  189. DataAsJson = ravenJObject
  190. };
  191. }
  192. }
  193. private void RemoveMetadataReservedProperties(RavenJObject metadata)
  194. {
  195. RemoveReservedProperties(metadata);
  196. metadata.Remove("Raven-Last-Modified");
  197. metadata.Remove("Last-Modified");
  198. }
  199. private void RemoveReservedProperties(RavenJObject document)
  200. {
  201. document.Remove(string.Empty);
  202. var toRemove = document.Keys.Where(propertyName => propertyName.StartsWith("@") || HeadersToIgnoreServer.Contains(propertyName) || Database.Configuration.Core.HeadersToIgnore.Contains(propertyName)).ToList();
  203. foreach (var propertyName in toRemove)
  204. {
  205. document.Remove(propertyName);
  206. }
  207. }
  208. private void AssertDeleteOperationNotVetoed(string key)
  209. {
  210. var vetoResult = Database.DeleteTriggers
  211. .Select(trigger => new { Trigger = trigger, VetoResult = trigger.AllowDelete(key) })
  212. .FirstOrDefault(x => x.VetoResult.IsAllowed == false);
  213. if (vetoResult != null)
  214. {
  215. throw new OperationVetoedException("DELETE vetoed on document " + key + " by " + vetoResult.Trigger +
  216. " because: " + vetoResult.VetoResult.Reason);
  217. }
  218. }
  219. public int BulkInsert(BulkInsertOptions options, IEnumerable<IEnumerable<JsonDocument>> docBatches, Guid operationId, CancellationToken token, CancellationTimeout timeout = null)
  220. {
  221. var documents = 0;
  222. Database.Notifications.RaiseNotifications(new BulkInsertChangeNotification
  223. {
  224. OperationId = operationId,
  225. Type = DocumentChangeTypes.BulkInsertStarted
  226. });
  227. using (var cts = CancellationTokenSource.CreateLinkedTokenSource(token, WorkContext.CancellationToken))
  228. {
  229. foreach (var docs in docBatches)
  230. {
  231. cts.Token.ThrowIfCancellationRequested();
  232. var docsToInsert = docs.ToArray();
  233. var batch = 0;
  234. var keys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
  235. var collectionsAndEtags = new Dictionary<string, Etag>(StringComparer.OrdinalIgnoreCase);
  236. if (timeout != null)
  237. timeout.Pause();
  238. using (Database.DocumentLock.Lock())
  239. {
  240. if (timeout != null)
  241. timeout.Resume();
  242. TransactionalStorage.Batch(accessor =>
  243. {
  244. var inserts = 0;
  245. foreach (var doc in docsToInsert)
  246. {
  247. try
  248. {
  249. if (string.IsNullOrEmpty(doc.Key))
  250. throw new InvalidOperationException("Cannot try to bulk insert a document without a key");
  251. RemoveReservedProperties(doc.DataAsJson);
  252. RemoveMetadataReservedProperties(doc.Metadata);
  253. if (options.CheckReferencesInIndexes)
  254. keys.Add(doc.Key);
  255. documents++;
  256. batch++;
  257. AssertPutOperationNotVetoed(doc.Key, doc.Metadata, doc.DataAsJson);
  258. if (options.OverwriteExisting && options.SkipOverwriteIfUnchanged)
  259. {
  260. var existingDoc = accessor.Documents.DocumentByKey(doc.Key);
  261. if (IsTheSameDocument(doc, existingDoc))
  262. continue;
  263. }
  264. foreach (var trigger in Database.PutTriggers)
  265. {
  266. trigger.Value.OnPut(doc.Key, doc.DataAsJson, doc.Metadata);
  267. }
  268. var result = accessor.Documents.InsertDocument(doc.Key, doc.DataAsJson, doc.Metadata, options.OverwriteExisting);
  269. if (result.Updated == false)
  270. inserts++;
  271. doc.Etag = result.Etag;
  272. doc.Metadata.EnsureSnapshot(
  273. "Metadata was written to the database, cannot modify the document after it was written (changes won't show up in the db). Did you forget to call CreateSnapshot() to get a clean copy?");
  274. doc.DataAsJson.EnsureSnapshot(
  275. "Document was written to the database, cannot modify the document after it was written (changes won't show up in the db). Did you forget to call CreateSnapshot() to get a clean copy?");
  276. var entityName = doc.Metadata.Value<string>(Constants.RavenEntityName);
  277. Etag highestEtagInCollection;
  278. if (string.IsNullOrEmpty(entityName) == false && (collectionsAndEtags.TryGetValue(entityName, out highestEtagInCollection) == false ||
  279. result.Etag.CompareTo(highestEtagInCollection) > 0))
  280. {
  281. collectionsAndEtags[entityName] = result.Etag;
  282. }
  283. foreach (var trigger in Database.PutTriggers)
  284. {
  285. trigger.Value.AfterPut(doc.Key, doc.DataAsJson, doc.Metadata, result.Etag);
  286. }
  287. Database.WorkContext.UpdateFoundWork();
  288. }
  289. catch (Exception e)
  290. {
  291. Database.Notifications.RaiseNotifications(new BulkInsertChangeNotification
  292. {
  293. OperationId = operationId,
  294. Message = e.Message,
  295. Etag = doc.Etag,
  296. Id = doc.Key,
  297. Type = DocumentChangeTypes.BulkInsertError
  298. });
  299. throw;
  300. }
  301. }
  302. if (options.CheckReferencesInIndexes)
  303. {
  304. foreach (var key in keys)
  305. {
  306. Database.Indexes.CheckReferenceBecauseOfDocumentUpdate(key, accessor);
  307. }
  308. }
  309. accessor.Documents.IncrementDocumentCount(inserts);
  310. });
  311. foreach (var collectionEtagPair in collectionsAndEtags)
  312. {
  313. Database.LastCollectionEtags.Update(collectionEtagPair.Key, collectionEtagPair.Value);
  314. }
  315. WorkContext.ShouldNotifyAboutWork(() => "BulkInsert batch of " + batch + " docs");
  316. WorkContext.NotifyAboutWork(); // forcing notification so we would start indexing right away
  317. WorkContext.UpdateFoundWork();
  318. }
  319. }
  320. }
  321. Database.Notifications.RaiseNotifications(new BulkInsertChangeNotification
  322. {
  323. OperationId = operationId,
  324. Type = DocumentChangeTypes.BulkInsertEnded
  325. });
  326. if (documents > 0)
  327. WorkContext.ShouldNotifyAboutWork(() => "BulkInsert of " + documents + " docs");
  328. return documents;
  329. }
  330. private bool IsTheSameDocument(JsonDocument doc, JsonDocument existingDoc)
  331. {
  332. if (existingDoc == null)
  333. return false;
  334. if (RavenJToken.DeepEquals(doc.DataAsJson, existingDoc.DataAsJson) == false)
  335. return false;
  336. var existingMetadata = (RavenJObject) existingDoc.Metadata.CloneToken();
  337. var newMetadata = (RavenJObject) doc.Metadata.CloneToken();
  338. // in order to compare metadata we need to remove metadata records created by triggers
  339. foreach (var trigger in Database.PutTriggers)
  340. {
  341. var metadataToIgnore = trigger.Value.GeneratedMetadataNames;
  342. if (metadataToIgnore == null)
  343. continue;
  344. foreach (var toIgnore in metadataToIgnore)
  345. {
  346. existingMetadata.Remove(toIgnore);
  347. newMetadata.Remove(toIgnore);
  348. }
  349. }
  350. return RavenJToken.DeepEquals(newMetadata, existingMetadata);
  351. }
  352. public TouchedDocumentInfo GetRecentTouchesFor(string key)
  353. {
  354. TouchedDocumentInfo info;
  355. RecentTouches.TryGetValue(key, out info);
  356. return info;
  357. }
  358. public RavenJArray GetDocumentsAsJson(int start, int pageSize, Etag etag, CancellationToken token)
  359. {
  360. var list = new RavenJArray();
  361. GetDocuments(start, pageSize, etag, token, doc =>
  362. {
  363. if (doc != null) list.Add(doc.ToJson());
  364. return true;
  365. });
  366. return list;
  367. }
  368. public Etag GetDocuments(int start, int pageSize, Etag etag, CancellationToken token, Func<JsonDocument, bool> addDocument, string transformer = null, Dictionary<string, RavenJToken> transformerParameters = null)
  369. {
  370. Etag lastDocumentReadEtag = null;
  371. TransactionalStorage.Batch(actions =>
  372. {
  373. AbstractTransformer storedTransformer = null;
  374. if (transformer != null)
  375. {
  376. storedTransformer = IndexDefinitionStorage.GetTransformer(transformer);
  377. if (storedTransformer == null)
  378. throw new InvalidOperationException("No transformer with the name: " + transformer);
  379. }
  380. bool returnedDocs = false;
  381. while (true)
  382. {
  383. var documents = etag == null
  384. ? actions.Documents.GetDocumentsByReverseUpdateOrder(start, pageSize)
  385. : actions.Documents.GetDocumentsAfter(etag, pageSize, token);
  386. var documentRetriever = new DocumentRetriever(Database.Configuration, actions, Database.ReadTriggers, transformerParameters);
  387. int docCount = 0;
  388. int docCountOnLastAdd = 0;
  389. foreach (var doc in documents)
  390. {
  391. docCount++;
  392. token.ThrowIfCancellationRequested();
  393. if (docCount - docCountOnLastAdd > 1000)
  394. {
  395. addDocument(null); // heartbeat
  396. }
  397. if (etag != null)
  398. etag = doc.Etag;
  399. JsonDocument.EnsureIdInMetadata(doc);
  400. var document = documentRetriever.ExecuteReadTriggers(doc, ReadOperation.Load);
  401. if (document == null)
  402. continue;
  403. returnedDocs = true;
  404. Database.WorkContext.UpdateFoundWork();
  405. document = TransformDocumentIfNeeded(document, storedTransformer, documentRetriever);
  406. bool canContinue = addDocument(document);
  407. if (!canContinue)
  408. break;
  409. lastDocumentReadEtag = etag;
  410. docCountOnLastAdd = docCount;
  411. }
  412. if (returnedDocs || docCount == 0)
  413. break;
  414. // No document was found that matches the requested criteria
  415. // If we had a failure happen, we update the etag as we don't need to process those documents again (no matches there anyways).
  416. if (lastDocumentReadEtag != null)
  417. etag = lastDocumentReadEtag;
  418. start += docCount;
  419. }
  420. });
  421. return lastDocumentReadEtag;
  422. }
  423. public Etag GetDocumentsWithIdStartingWith(string idPrefix, int pageSize, Etag etag, CancellationToken token, Func<JsonDocument, bool> addDocument)
  424. {
  425. Etag lastDocumentReadEtag = null;
  426. TransactionalStorage.Batch(actions =>
  427. {
  428. bool returnedDocs = false;
  429. while (true)
  430. {
  431. var documents = actions.Documents.GetDocumentsAfterWithIdStartingWith(etag, idPrefix, pageSize, token, timeout: TimeSpan.FromSeconds(2), lastProcessedDocument: x => lastDocumentReadEtag = x );
  432. var documentRetriever = new DocumentRetriever(Database.Configuration, actions, Database.ReadTriggers);
  433. int docCount = 0;
  434. int docCountOnLastAdd = 0;
  435. foreach (var doc in documents)
  436. {
  437. docCount++;
  438. if (docCount - docCountOnLastAdd > 1000)
  439. {
  440. addDocument(null); // heartbeat
  441. }
  442. token.ThrowIfCancellationRequested();
  443. etag = doc.Etag;
  444. JsonDocument.EnsureIdInMetadata(doc);
  445. var document = documentRetriever.ExecuteReadTriggers(doc, ReadOperation.Load);
  446. if (document == null)
  447. continue;
  448. returnedDocs = true;
  449. Database.WorkContext.UpdateFoundWork();
  450. bool canContinue = addDocument(document);
  451. docCountOnLastAdd = docCount;
  452. if (!canContinue)
  453. break;
  454. }
  455. if (returnedDocs)
  456. break;
  457. // No document was found that matches the requested criteria
  458. if ( docCount == 0 )
  459. {
  460. // If we had a failure happen, we update the etag as we don't need to process those documents again (no matches there anyways).
  461. if (lastDocumentReadEtag != null)
  462. etag = lastDocumentReadEtag;
  463. break;
  464. }
  465. }
  466. });
  467. return etag;
  468. }
  469. public JsonDocument Get(string key)
  470. {
  471. if (key == null)
  472. throw new ArgumentNullException("key");
  473. key = key.Trim();
  474. JsonDocument document = null;
  475. TransactionalStorage.Batch(actions =>
  476. {
  477. document = actions.Documents.DocumentByKey(key);
  478. });
  479. JsonDocument.EnsureIdInMetadata(document);
  480. return new DocumentRetriever(null, null, Database.ReadTriggers)
  481. .ExecuteReadTriggers(document, ReadOperation.Load);
  482. }
  483. public JsonDocumentMetadata GetDocumentMetadata(string key)
  484. {
  485. if (key == null)
  486. throw new ArgumentNullException("key");
  487. key = key.Trim();
  488. JsonDocumentMetadata document = null;
  489. TransactionalStorage.Batch(actions =>
  490. {
  491. document = actions.Documents.DocumentMetadataByKey(key);
  492. });
  493. JsonDocument.EnsureIdInMetadata(document);
  494. return new DocumentRetriever(null, null, Database.ReadTriggers)
  495. .ProcessReadVetoes(document, ReadOperation.Load);
  496. }
  497. public Etag GetLastEtagForCollection(string collectionName)
  498. {
  499. Etag value = Etag.Empty;
  500. TransactionalStorage.Batch(accessor =>
  501. {
  502. var dbvalue = accessor.Lists.Read("Raven/Collection/Etag", collectionName);
  503. if (dbvalue != null)
  504. {
  505. value = Etag.Parse(dbvalue.Data.Value<Byte[]>("Etag"));
  506. }
  507. });
  508. return value;
  509. }
  510. public JsonDocument GetWithTransformer(string key, string transformer, Dictionary<string, RavenJToken> transformerParameters, out HashSet<string> itemsToInclude)
  511. {
  512. JsonDocument result = null;
  513. DocumentRetriever docRetriever = null;
  514. TransactionalStorage.Batch(
  515. actions =>
  516. {
  517. docRetriever = new DocumentRetriever(Database.Configuration, actions, Database.ReadTriggers, transformerParameters);
  518. using (new CurrentTransformationScope(Database, docRetriever))
  519. {
  520. var document = Get(key);
  521. if (document == null)
  522. return;
  523. if (document.Metadata.ContainsKey("Raven-Read-Veto") || document.Metadata.ContainsKey(Constants.RavenReplicationConflict))
  524. {
  525. result = document;
  526. return;
  527. }
  528. var storedTransformer = IndexDefinitionStorage.GetTransformer(transformer);
  529. if (storedTransformer == null)
  530. throw new InvalidOperationException("No transformer with the name: " + transformer);
  531. var transformed = storedTransformer.TransformResultsDefinition(new[] { new DynamicJsonObject(document.ToJson()) })
  532. .Select(x => JsonExtensions.ToJObject(x))
  533. .ToArray();
  534. if (transformed.Length == 0)
  535. return;
  536. result = new JsonDocument
  537. {
  538. Etag = document.Etag.CombineHashWith(storedTransformer.GetHashCodeBytes()).HashWith(docRetriever.Etag),
  539. LastModified = document.LastModified,
  540. DataAsJson = new RavenJObject { { "$values", new RavenJArray(transformed.Cast<Object>().ToArray()) } },
  541. };
  542. }
  543. });
  544. itemsToInclude = docRetriever.ItemsToInclude;
  545. return result;
  546. }
  547. public PutResult Put(string key, Etag etag, RavenJObject document, RavenJObject metadata, string[] participatingIds = null)
  548. {
  549. WorkContext.MetricsCounters.DocsPerSecond.Mark();
  550. key = string.IsNullOrWhiteSpace(key) ? Guid.NewGuid().ToString() : key.Trim();
  551. RemoveReservedProperties(document);
  552. RemoveMetadataReservedProperties(metadata);
  553. var newEtag = Etag.Empty;
  554. using (Database.DocumentLock.Lock())
  555. {
  556. TransactionalStorage.Batch(actions =>
  557. {
  558. if (key.EndsWith("/"))
  559. {
  560. key += GetNextIdentityValueWithoutOverwritingOnExistingDocuments(key, actions);
  561. }
  562. AssertPutOperationNotVetoed(key, metadata, document);
  563. Database.PutTriggers.Apply(trigger => trigger.OnPut(key, document, metadata));
  564. var addDocumentResult = actions.Documents.AddDocument(key, etag, document, metadata);
  565. newEtag = addDocumentResult.Etag;
  566. Database.Indexes.CheckReferenceBecauseOfDocumentUpdate(key, actions, participatingIds);
  567. metadata[Constants.LastModified] = addDocumentResult.SavedAt;
  568. metadata.EnsureSnapshot("Metadata was written to the database, cannot modify the document after it was written (changes won't show up in the db). Did you forget to call CreateSnapshot() to get a clean copy?");
  569. document.EnsureSnapshot("Document was written to the database, cannot modify the document after it was written (changes won't show up in the db). Did you forget to call CreateSnapshot() to get a clean copy?");
  570. actions.AfterStorageCommitBeforeWorkNotifications(new JsonDocument
  571. {
  572. Metadata = metadata,
  573. Key = key,
  574. DataAsJson = document,
  575. Etag = newEtag,
  576. LastModified = addDocumentResult.SavedAt,
  577. SkipDeleteFromIndex = addDocumentResult.Updated == false
  578. }, documents =>
  579. {
  580. if(Database.IndexDefinitionStorage.IndexesCount == 0 || Database.WorkContext.RunIndexing == false)
  581. return;
  582. Database.Prefetcher.AfterStorageCommitBeforeWorkNotifications(PrefetchingUser.Indexer, documents);
  583. });
  584. Database.PutTriggers.Apply(trigger => trigger.AfterPut(key, document, metadata, newEtag));
  585. TransactionalStorage
  586. .ExecuteImmediatelyOrRegisterForSynchronization(() =>
  587. {
  588. Database.PutTriggers.Apply(trigger => trigger.AfterCommit(key, document, metadata, newEtag));
  589. var newDocumentChangeNotification =
  590. new DocumentChangeNotification
  591. {
  592. Id = key,
  593. Type = DocumentChangeTypes.Put,
  594. TypeName = metadata.Value<string>(Constants.RavenClrType),
  595. CollectionName = metadata.Value<string>(Constants.RavenEntityName),
  596. Etag = newEtag
  597. };
  598. Database.Notifications.RaiseNotifications(newDocumentChangeNotification, metadata);
  599. });
  600. WorkContext.ShouldNotifyAboutWork(() => "PUT " + key);
  601. });
  602. if (Log.IsDebugEnabled)
  603. Log.Debug("Put document {0} with etag {1}", key, newEtag);
  604. return new PutResult
  605. {
  606. Key = key,
  607. ETag = newEtag
  608. };
  609. }
  610. }
  611. public bool Delete(string key, Etag etag, string[] participatingIds = null)
  612. {
  613. RavenJObject metadata;
  614. return Delete(key, etag, out metadata, participatingIds);
  615. }
  616. public bool Delete(string key, Etag etag, out RavenJObject metadata, string[] participatingIds = null)
  617. {
  618. if (key == null)
  619. throw new ArgumentNullException("key");
  620. key = key.Trim();
  621. var deleted = false;
  622. if (Log.IsDebugEnabled)
  623. Log.Debug("Delete a document with key: {0} and etag {1}", key, etag);
  624. RavenJObject metadataVar = null;
  625. using (Database.DocumentLock.Lock())
  626. {
  627. TransactionalStorage.Batch(actions =>
  628. {
  629. AssertDeleteOperationNotVetoed(key);
  630. Database.DeleteTriggers.Apply(trigger => trigger.OnDelete(key));
  631. string collection = null;
  632. Etag deletedETag;
  633. if (actions.Documents.DeleteDocument(key, etag, out metadataVar, out deletedETag))
  634. {
  635. deleted = true;
  636. actions.Indexing.RemoveAllDocumentReferencesFrom(key);
  637. WorkContext.MarkDeleted(key);
  638. Database.Indexes.CheckReferenceBecauseOfDocumentUpdate(key, actions, participatingIds);
  639. collection = metadataVar.Value<string>(Constants.RavenEntityName);
  640. DeleteDocumentFromIndexesForCollection(key, collection, actions);
  641. if (deletedETag != null)
  642. Database.Prefetcher.AfterDelete(key, deletedETag);
  643. Database.DeleteTriggers.Apply(trigger => trigger.AfterDelete(key));
  644. }
  645. TransactionalStorage
  646. .ExecuteImmediatelyOrRegisterForSynchronization(() =>
  647. {
  648. Database.DeleteTriggers.Apply(trigger => trigger.AfterCommit(key));
  649. if (string.IsNullOrEmpty(collection) == false)
  650. Database.LastCollectionEtags.Update(collection);
  651. Database.Notifications.RaiseNotifications(new DocumentChangeNotification
  652. {
  653. Id = key,
  654. Type = DocumentChangeTypes.Delete,
  655. TypeName = (metadataVar != null) ? metadataVar.Value<string>(Constants.RavenClrType) : null,
  656. CollectionName = (metadataVar != null) ? metadataVar.Value<string>(Constants.RavenEntityName) : null
  657. }, metadataVar);
  658. });
  659. WorkContext.ShouldNotifyAboutWork(() => "DEL " + key);
  660. });
  661. metadata = metadataVar;
  662. return deleted;
  663. }
  664. }
  665. internal void DeleteDocumentFromIndexesForCollection(string key, string collection, IStorageActionsAccessor actions)
  666. {
  667. foreach (var indexName in IndexDefinitionStorage.IndexNames)
  668. {
  669. AbstractViewGenerator abstractViewGenerator =
  670. IndexDefinitionStorage.GetViewGenerator(indexName);
  671. if (abstractViewGenerator == null)
  672. continue;
  673. if (collection != null && // the document has a entity name
  674. abstractViewGenerator.ForEntityNames.Count > 0)
  675. // the index operations on specific entities
  676. {
  677. if (abstractViewGenerator.ForEntityNames.Contains(collection) == false)
  678. continue;
  679. }
  680. var instance = IndexDefinitionStorage.GetIndexDefinition(indexName);
  681. var task = actions.GetTask(x => x.Index == instance.IndexId,
  682. new RemoveFromIndexTask(instance.IndexId));
  683. task.AddKey(key);
  684. }
  685. }
  686. }
  687. }