PageRenderTime 57ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/Raven.Database/Impl/DocumentRetriever.cs

https://github.com/nwendel/ravendb
C# | 381 lines | 323 code | 45 blank | 13 comment | 89 complexity | 55cc875b03f65f3e108e70388bda6d65 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, BSD-3-Clause, CC-BY-SA-3.0
  1. //-----------------------------------------------------------------------
  2. // <copyright file="DocumentRetriever.cs" company="Hibernating Rhinos LTD">
  3. // Copyright (c) Hibernating Rhinos LTD. All rights reserved.
  4. // </copyright>
  5. //-----------------------------------------------------------------------
  6. using System;
  7. using System.Collections;
  8. using System.Collections.Generic;
  9. using Raven.Abstractions.Logging;
  10. using Raven.Database.Impl.DTC;
  11. using Raven.Imports.Newtonsoft.Json.Linq;
  12. using Raven.Abstractions.Data;
  13. using Raven.Abstractions.Indexing;
  14. using Raven.Abstractions.Linq;
  15. using Raven.Abstractions.MEF;
  16. using Raven.Database.Data;
  17. using Raven.Database.Indexing;
  18. using Raven.Database.Linq;
  19. using Raven.Database.Plugins;
  20. using Raven.Database.Storage;
  21. using Raven.Abstractions.Json;
  22. using System.Linq;
  23. using Raven.Json.Linq;
  24. namespace Raven.Database.Impl
  25. {
  26. public class DocumentRetriever : ITranslatorDatabaseAccessor
  27. {
  28. private static readonly ILog log = LogManager.GetCurrentClassLogger();
  29. private readonly IDictionary<string, JsonDocument> cache = new Dictionary<string, JsonDocument>(StringComparer.OrdinalIgnoreCase);
  30. private readonly HashSet<string> loadedIdsForRetrieval = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
  31. private readonly HashSet<string> loadedIdsForFilter = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
  32. private readonly IStorageActionsAccessor actions;
  33. private readonly OrderedPartCollection<AbstractReadTrigger> triggers;
  34. private readonly InFlightTransactionalState inFlightTransactionalState;
  35. private readonly Dictionary<string, RavenJToken> transformerParameters;
  36. private readonly HashSet<string> itemsToInclude;
  37. private bool disableCache;
  38. public Etag Etag = Etag.Empty;
  39. public DocumentRetriever(IStorageActionsAccessor actions, OrderedPartCollection<AbstractReadTrigger> triggers,
  40. InFlightTransactionalState inFlightTransactionalState,
  41. Dictionary<string, RavenJToken> transformerParameters = null,
  42. HashSet<string> itemsToInclude = null)
  43. {
  44. this.actions = actions;
  45. this.triggers = triggers;
  46. this.inFlightTransactionalState = inFlightTransactionalState;
  47. this.transformerParameters = transformerParameters ?? new Dictionary<string, RavenJToken>();
  48. this.itemsToInclude = itemsToInclude ?? new HashSet<string>();
  49. }
  50. public JsonDocument RetrieveDocumentForQuery(IndexQueryResult queryResult, IndexDefinition indexDefinition, FieldsToFetch fieldsToFetch, bool skipDuplicateCheck)
  51. {
  52. return ExecuteReadTriggers(ProcessReadVetoes(
  53. RetrieveDocumentInternal(queryResult, loadedIdsForRetrieval, fieldsToFetch, indexDefinition, skipDuplicateCheck),
  54. null, ReadOperation.Query), null, ReadOperation.Query);
  55. }
  56. public JsonDocument ExecuteReadTriggers(JsonDocument document, TransactionInformation transactionInformation, ReadOperation operation)
  57. {
  58. return ExecuteReadTriggersOnRead(ProcessReadVetoes(document, transactionInformation, operation),
  59. transactionInformation, operation);
  60. }
  61. private JsonDocument ExecuteReadTriggersOnRead(JsonDocument resultingDocument, TransactionInformation transactionInformation, ReadOperation operation)
  62. {
  63. if (resultingDocument == null)
  64. return null;
  65. var doc = new JsonDocument
  66. {
  67. Key = resultingDocument.Key,
  68. Etag = resultingDocument.Etag,
  69. LastModified = resultingDocument.LastModified,
  70. SerializedSizeOnDisk = resultingDocument.SerializedSizeOnDisk,
  71. SkipDeleteFromIndex = resultingDocument.SkipDeleteFromIndex,
  72. NonAuthoritativeInformation = resultingDocument.NonAuthoritativeInformation,
  73. TempIndexScore = resultingDocument.TempIndexScore,
  74. DataAsJson =
  75. resultingDocument.DataAsJson.IsSnapshot
  76. ? (RavenJObject) resultingDocument.DataAsJson.CreateSnapshot()
  77. : resultingDocument.DataAsJson,
  78. Metadata =
  79. resultingDocument.Metadata.IsSnapshot
  80. ? (RavenJObject) resultingDocument.Metadata.CreateSnapshot()
  81. : resultingDocument.Metadata,
  82. };
  83. triggers.Apply(
  84. trigger =>
  85. trigger.OnRead(doc.Key, doc.DataAsJson, doc.Metadata, operation,
  86. transactionInformation));
  87. return doc;
  88. }
  89. private JsonDocument RetrieveDocumentInternal(
  90. IndexQueryResult queryResult,
  91. HashSet<string> loadedIds,
  92. FieldsToFetch fieldsToFetch,
  93. IndexDefinition indexDefinition,
  94. bool skipDuplicateCheck)
  95. {
  96. var queryScore = queryResult.Score;
  97. if (float.IsNaN(queryScore))
  98. queryScore = 0f;
  99. if (queryResult.Projection == null)
  100. {
  101. // duplicate document, filter it out
  102. if (skipDuplicateCheck == false && loadedIds.Add(queryResult.Key) == false)
  103. return null;
  104. var document = GetDocumentWithCaching(queryResult.Key);
  105. if (document == null)
  106. return null;
  107. document.Metadata = GetMetadata(document);
  108. if (skipDuplicateCheck == false)
  109. document.Metadata[Constants.TemporaryScoreValue] = queryScore;
  110. return document;
  111. }
  112. JsonDocument doc = null;
  113. if (fieldsToFetch.IsProjection)
  114. {
  115. if (indexDefinition.IsMapReduce == false)
  116. {
  117. bool hasStoredFields = false;
  118. FieldStorage value;
  119. if (indexDefinition.Stores.TryGetValue(Constants.AllFields, out value))
  120. {
  121. hasStoredFields = value != FieldStorage.No;
  122. }
  123. foreach (var fieldToFetch in fieldsToFetch.Fields)
  124. {
  125. if (indexDefinition.Stores.TryGetValue(fieldToFetch, out value) == false && value != FieldStorage.No) continue;
  126. hasStoredFields = true;
  127. }
  128. if (hasStoredFields == false)
  129. {
  130. // duplicate document, filter it out
  131. if (loadedIds.Add(queryResult.Key) == false) return null;
  132. }
  133. }
  134. // We have to load the document if user explicitly asked for the id, since
  135. // we normalize the casing for the document id on the index, and we need to return
  136. // the id to the user with the same casing they gave us.
  137. var fetchingId = fieldsToFetch.HasField(Constants.DocumentIdFieldName);
  138. var fieldsToFetchFromDocument = fieldsToFetch.Fields.Where(fieldToFetch => queryResult.Projection[fieldToFetch] == null).ToArray();
  139. if (fieldsToFetchFromDocument.Length > 0 || fetchingId)
  140. {
  141. doc = GetDocumentWithCaching(queryResult.Key);
  142. if (doc != null)
  143. {
  144. if (fetchingId)
  145. {
  146. queryResult.Projection[Constants.DocumentIdFieldName] = doc.Key;
  147. }
  148. var result = doc.DataAsJson.SelectTokenWithRavenSyntax(fieldsToFetchFromDocument.ToArray());
  149. foreach (var property in result)
  150. {
  151. if (property.Value == null || property.Value.Type == JTokenType.Null) continue;
  152. queryResult.Projection[property.Key] = property.Value;
  153. }
  154. }
  155. }
  156. }
  157. else if (fieldsToFetch.FetchAllStoredFields && string.IsNullOrEmpty(queryResult.Key) == false
  158. && (fieldsToFetch.Query== null || fieldsToFetch.Query.AllowMultipleIndexEntriesForSameDocumentToResultTransformer == false)
  159. )
  160. {
  161. // duplicate document, filter it out
  162. if (loadedIds.Add(queryResult.Key) == false)
  163. return null;
  164. doc = GetDocumentWithCaching(queryResult.Key);
  165. }
  166. var metadata = GetMetadata(doc);
  167. metadata.Remove("@id");
  168. metadata[Constants.TemporaryScoreValue] = queryScore;
  169. return new JsonDocument
  170. {
  171. Key = queryResult.Key,
  172. DataAsJson = queryResult.Projection,
  173. Metadata = metadata
  174. };
  175. }
  176. private static RavenJObject GetMetadata(JsonDocument doc)
  177. {
  178. if (doc == null)
  179. return new RavenJObject();
  180. if (doc.Metadata.IsSnapshot)
  181. return (RavenJObject) doc.Metadata.CreateSnapshot();
  182. return doc.Metadata;
  183. }
  184. private JsonDocument GetDocumentWithCaching(string key)
  185. {
  186. if (key == null)
  187. return null;
  188. JsonDocument doc;
  189. if (disableCache == false && cache.TryGetValue(key, out doc))
  190. return doc;
  191. doc = actions.Documents.DocumentByKey(key, null);
  192. EnsureIdInMetadata(doc);
  193. if (doc != null && doc.Metadata != null)
  194. doc.Metadata.EnsureCannotBeChangeAndEnableSnapshotting();
  195. var nonAuthoritativeInformationBehavior = inFlightTransactionalState.GetNonAuthoritativeInformationBehavior<JsonDocument>(null, key);
  196. if (nonAuthoritativeInformationBehavior != null)
  197. doc = nonAuthoritativeInformationBehavior(doc);
  198. if(disableCache == false)
  199. cache[key] = doc;
  200. if (cache.Count > 2048)
  201. {
  202. // we are probably doing a stream here, no point in trying to cache things, we might be
  203. // going through the entire db here!
  204. disableCache = true;
  205. cache.Clear();
  206. }
  207. return doc;
  208. }
  209. public static void EnsureIdInMetadata(IJsonDocumentMetadata doc)
  210. {
  211. if (doc == null || doc.Metadata == null)
  212. return;
  213. if (doc.Metadata.IsSnapshot)
  214. {
  215. doc.Metadata = (RavenJObject)doc.Metadata.CreateSnapshot();
  216. }
  217. doc.Metadata["@id"] = doc.Key;
  218. }
  219. public bool ShouldIncludeResultInQuery(IndexQueryResult arg, IndexDefinition indexDefinition, FieldsToFetch fieldsToFetch, bool skipDuplicateCheck)
  220. {
  221. var doc = RetrieveDocumentInternal(arg, loadedIdsForFilter, fieldsToFetch, indexDefinition, skipDuplicateCheck);
  222. if (doc == null)
  223. return false;
  224. doc = ProcessReadVetoes(doc, null, ReadOperation.Query);
  225. return doc != null;
  226. }
  227. public T ProcessReadVetoes<T>(T document, TransactionInformation transactionInformation, ReadOperation operation)
  228. where T : class, IJsonDocumentMetadata, new()
  229. {
  230. if (document == null)
  231. return null;
  232. foreach (var readTrigger in triggers)
  233. {
  234. var readVetoResult = readTrigger.Value.AllowRead(document.Key, document.Metadata, operation, transactionInformation);
  235. switch (readVetoResult.Veto)
  236. {
  237. case ReadVetoResult.ReadAllow.Allow:
  238. break;
  239. case ReadVetoResult.ReadAllow.Deny:
  240. return new T
  241. {
  242. Etag = Etag.Empty,
  243. LastModified = DateTime.MinValue,
  244. NonAuthoritativeInformation = false,
  245. Key = document.Key,
  246. Metadata = new RavenJObject
  247. {
  248. {
  249. "Raven-Read-Veto", new RavenJObject
  250. {
  251. {"Reason", readVetoResult.Reason},
  252. {"Trigger", readTrigger.ToString()}
  253. }
  254. }
  255. }
  256. };
  257. case ReadVetoResult.ReadAllow.Ignore:
  258. log.Debug("Trigger {0} asked us to ignore {1}", readTrigger.Value, document.Key);
  259. return null;
  260. default:
  261. throw new ArgumentOutOfRangeException(readVetoResult.Veto.ToString());
  262. }
  263. }
  264. return document;
  265. }
  266. public dynamic Include(object maybeId)
  267. {
  268. if (maybeId == null || maybeId is DynamicNullObject)
  269. return new DynamicNullObject();
  270. var id = maybeId as string;
  271. if (id != null)
  272. return Include(id);
  273. var jId = maybeId as RavenJValue;
  274. if (jId != null)
  275. return Include(jId.Value.ToString());
  276. var items = new List<dynamic>();
  277. foreach (var itemId in (IEnumerable)maybeId)
  278. {
  279. var include = Include(itemId);// this is where the real work happens
  280. items.Add(include);
  281. }
  282. return new DynamicList(items);
  283. }
  284. public dynamic Include(string id)
  285. {
  286. ItemsToInclude.Add(id);
  287. return Load(id);
  288. }
  289. public dynamic Include(IEnumerable<string> ids)
  290. {
  291. var items = new List<dynamic>();
  292. foreach (var itemId in ids)
  293. {
  294. var include = Include(itemId);// this is where the real work happens
  295. items.Add(include);
  296. }
  297. return new DynamicList(items);
  298. }
  299. public dynamic Load(string id)
  300. {
  301. var document = GetDocumentWithCaching(id);
  302. if (document == null)
  303. {
  304. Etag = Etag.HashWith(Etag.Empty);
  305. return new DynamicNullObject();
  306. }
  307. Etag = Etag.HashWith(document.Etag);
  308. return new DynamicJsonObject(document.ToJson());
  309. }
  310. public dynamic Load(object maybeId)
  311. {
  312. if (maybeId == null || maybeId is DynamicNullObject)
  313. {
  314. Etag = Etag.HashWith(Etag.Empty);
  315. return new DynamicNullObject();
  316. }
  317. var id = maybeId as string;
  318. if (id != null)
  319. return Load(id);
  320. var jId = maybeId as RavenJValue;
  321. if (jId != null)
  322. return Load(jId.Value.ToString());
  323. var items = new List<dynamic>();
  324. foreach (var itemId in (IEnumerable)maybeId)
  325. {
  326. items.Add(Load(itemId));
  327. }
  328. return new DynamicList(items.Select(x => (object)x).ToArray());
  329. }
  330. public Dictionary<string, RavenJToken> TransformerParameters { get { return this.transformerParameters; } }
  331. public HashSet<string> ItemsToInclude
  332. {
  333. get { return itemsToInclude; }
  334. }
  335. }
  336. }