PageRenderTime 54ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/Raven.Database/Impl/DocumentRetriever.cs

https://github.com/kairogyn/ravendb
C# | 363 lines | 311 code | 39 blank | 13 comment | 81 complexity | 5376a2133add4a5c27d685064a975ac0 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> queryInputs;
  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> queryInputs = null,
  42. HashSet<string> itemsToInclude = null)
  43. {
  44. this.actions = actions;
  45. this.triggers = triggers;
  46. this.inFlightTransactionalState = inFlightTransactionalState;
  47. this.queryInputs = queryInputs ?? new Dictionary<string, RavenJToken>();
  48. this.itemsToInclude = itemsToInclude ?? new HashSet<string>();
  49. }
  50. public JsonDocument RetrieveDocumentForQuery(IndexQueryResult queryResult, IndexDefinition indexDefinition, FieldsToFetch fieldsToFetch)
  51. {
  52. return ExecuteReadTriggers(ProcessReadVetoes(
  53. RetrieveDocumentInternal(queryResult, loadedIdsForRetrieval, fieldsToFetch, indexDefinition),
  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. {
  95. var queryScore = queryResult.Score;
  96. if (float.IsNaN(queryScore))
  97. queryScore = 0f;
  98. if (queryResult.Projection == null)
  99. {
  100. // duplicate document, filter it out
  101. if (loadedIds.Add(queryResult.Key) == false)
  102. return null;
  103. var document = GetDocumentWithCaching(queryResult.Key);
  104. if (document != null)
  105. {
  106. document.Metadata[Constants.TemporaryScoreValue] = queryScore;
  107. }
  108. return document;
  109. }
  110. JsonDocument doc = null;
  111. if (fieldsToFetch.IsProjection)
  112. {
  113. if (indexDefinition.IsMapReduce == false)
  114. {
  115. bool hasStoredFields = false;
  116. FieldStorage value;
  117. if (indexDefinition.Stores.TryGetValue(Constants.AllFields, out value))
  118. {
  119. hasStoredFields = value != FieldStorage.No;
  120. }
  121. foreach (var fieldToFetch in fieldsToFetch.Fields)
  122. {
  123. if (indexDefinition.Stores.TryGetValue(fieldToFetch, out value) == false && value != FieldStorage.No) continue;
  124. hasStoredFields = true;
  125. }
  126. if (hasStoredFields == false)
  127. {
  128. // duplicate document, filter it out
  129. if (loadedIds.Add(queryResult.Key) == false) return null;
  130. }
  131. }
  132. // We have to load the document if user explicitly asked for the id, since
  133. // we normalize the casing for the document id on the index, and we need to return
  134. // the id to the user with the same casing they gave us.
  135. var fetchingId = fieldsToFetch.HasField(Constants.DocumentIdFieldName);
  136. var fieldsToFetchFromDocument = fieldsToFetch.Fields.Where(fieldToFetch => queryResult.Projection[fieldToFetch] == null).ToArray();
  137. if (fieldsToFetchFromDocument.Length > 0 || fetchingId)
  138. {
  139. doc = GetDocumentWithCaching(queryResult.Key);
  140. if (doc != null)
  141. {
  142. if (fetchingId)
  143. {
  144. queryResult.Projection[Constants.DocumentIdFieldName] = doc.Key;
  145. }
  146. var result = doc.DataAsJson.SelectTokenWithRavenSyntax(fieldsToFetchFromDocument.ToArray());
  147. foreach (var property in result)
  148. {
  149. if (property.Value == null || property.Value.Type == JTokenType.Null) continue;
  150. queryResult.Projection[property.Key] = property.Value;
  151. }
  152. }
  153. }
  154. }
  155. else if (fieldsToFetch.FetchAllStoredFields && string.IsNullOrEmpty(queryResult.Key) == false
  156. && (fieldsToFetch.Query== null || fieldsToFetch.Query.AllowMultipleIndexEntriesForSameDocumentToResultTransformer == false)
  157. )
  158. {
  159. // duplicate document, filter it out
  160. if (loadedIds.Add(queryResult.Key) == false)
  161. return null;
  162. doc = GetDocumentWithCaching(queryResult.Key);
  163. }
  164. var metadata = GetMetadata(doc);
  165. metadata.Remove("@id");
  166. metadata[Constants.TemporaryScoreValue] = queryScore;
  167. return new JsonDocument
  168. {
  169. Key = queryResult.Key,
  170. DataAsJson = queryResult.Projection,
  171. Metadata = metadata
  172. };
  173. }
  174. private static RavenJObject GetMetadata(JsonDocument doc)
  175. {
  176. if (doc == null)
  177. return new RavenJObject();
  178. if (doc.Metadata.IsSnapshot)
  179. return (RavenJObject) doc.Metadata.CreateSnapshot();
  180. return doc.Metadata;
  181. }
  182. private JsonDocument GetDocumentWithCaching(string key)
  183. {
  184. if (key == null)
  185. return null;
  186. JsonDocument doc;
  187. if (disableCache == false && cache.TryGetValue(key, out doc))
  188. return doc;
  189. doc = actions.Documents.DocumentByKey(key, null);
  190. EnsureIdInMetadata(doc);
  191. var nonAuthoritativeInformationBehavior = inFlightTransactionalState.GetNonAuthoritativeInformationBehavior<JsonDocument>(null, key);
  192. if (nonAuthoritativeInformationBehavior != null)
  193. doc = nonAuthoritativeInformationBehavior(doc);
  194. if(disableCache == false)
  195. cache[key] = doc;
  196. if (cache.Count > 2048)
  197. {
  198. // we are probably doing a stream here, no point in trying to cache things, we might be
  199. // going through the entire db here!
  200. disableCache = true;
  201. cache.Clear();
  202. }
  203. return doc;
  204. }
  205. public static void EnsureIdInMetadata(IJsonDocumentMetadata doc)
  206. {
  207. if (doc == null || doc.Metadata == null)
  208. return;
  209. if (doc.Metadata.IsSnapshot)
  210. {
  211. doc.Metadata = (RavenJObject)doc.Metadata.CreateSnapshot();
  212. }
  213. doc.Metadata["@id"] = doc.Key;
  214. }
  215. public bool ShouldIncludeResultInQuery(IndexQueryResult arg, IndexDefinition indexDefinition, FieldsToFetch fieldsToFetch)
  216. {
  217. var doc = RetrieveDocumentInternal(arg, loadedIdsForFilter, fieldsToFetch, indexDefinition);
  218. if (doc == null)
  219. return false;
  220. doc = ProcessReadVetoes(doc, null, ReadOperation.Query);
  221. return doc != null;
  222. }
  223. public T ProcessReadVetoes<T>(T document, TransactionInformation transactionInformation, ReadOperation operation)
  224. where T : class, IJsonDocumentMetadata, new()
  225. {
  226. if (document == null)
  227. return null;
  228. foreach (var readTrigger in triggers)
  229. {
  230. var readVetoResult = readTrigger.Value.AllowRead(document.Key, document.Metadata, operation, transactionInformation);
  231. switch (readVetoResult.Veto)
  232. {
  233. case ReadVetoResult.ReadAllow.Allow:
  234. break;
  235. case ReadVetoResult.ReadAllow.Deny:
  236. return new T
  237. {
  238. Etag = Etag.Empty,
  239. LastModified = DateTime.MinValue,
  240. NonAuthoritativeInformation = false,
  241. Key = document.Key,
  242. Metadata = new RavenJObject
  243. {
  244. {
  245. "Raven-Read-Veto", new RavenJObject
  246. {
  247. {"Reason", readVetoResult.Reason},
  248. {"Trigger", readTrigger.ToString()}
  249. }
  250. }
  251. }
  252. };
  253. case ReadVetoResult.ReadAllow.Ignore:
  254. log.Debug("Trigger {0} asked us to ignore {1}", readTrigger.Value, document.Key);
  255. return null;
  256. default:
  257. throw new ArgumentOutOfRangeException(readVetoResult.Veto.ToString());
  258. }
  259. }
  260. return document;
  261. }
  262. public dynamic Include(object maybeId)
  263. {
  264. if (maybeId == null || maybeId is DynamicNullObject)
  265. return new DynamicNullObject();
  266. var id = maybeId as string;
  267. if (id != null)
  268. return Include(id);
  269. var jId = maybeId as RavenJValue;
  270. if (jId != null)
  271. return Include(jId.Value.ToString());
  272. foreach (var itemId in (IEnumerable)maybeId)
  273. {
  274. Include(itemId);
  275. }
  276. return new DynamicNullObject();
  277. }
  278. public dynamic Include(string id)
  279. {
  280. itemsToInclude.Add(id);
  281. return new DynamicNullObject();
  282. }
  283. public dynamic Include(IEnumerable<string> ids)
  284. {
  285. foreach (var id in ids)
  286. {
  287. itemsToInclude.Add(id);
  288. }
  289. return new DynamicNullObject();
  290. }
  291. public dynamic Load(string id)
  292. {
  293. var document = GetDocumentWithCaching(id);
  294. if (document == null)
  295. {
  296. Etag = Etag.HashWith(Etag.Empty);
  297. return new DynamicNullObject();
  298. }
  299. Etag = Etag.HashWith(document.Etag);
  300. return new DynamicJsonObject(document.ToJson());
  301. }
  302. public dynamic Load(object maybeId)
  303. {
  304. if (maybeId == null || maybeId is DynamicNullObject)
  305. {
  306. Etag = Etag.HashWith(Etag.Empty);
  307. return new DynamicNullObject();
  308. }
  309. var id = maybeId as string;
  310. if (id != null)
  311. return Load(id);
  312. var jId = maybeId as RavenJValue;
  313. if (jId != null)
  314. return Load(jId.Value.ToString());
  315. var items = new List<dynamic>();
  316. foreach (var itemId in (IEnumerable)maybeId)
  317. {
  318. items.Add(Load(itemId));
  319. }
  320. return new DynamicList(items.Select(x => (object)x).ToArray());
  321. }
  322. public Dictionary<string, RavenJToken> QueryInputs { get { return this.queryInputs; } }
  323. }
  324. }