PageRenderTime 48ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/ToMigrate/Raven.Database/Impl/DocumentRetriever.cs

http://github.com/ayende/ravendb
C# | 407 lines | 346 code | 48 blank | 13 comment | 88 complexity | 2a754969a26e60b2654e976b3a8fc9a9 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="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.Exceptions;
  10. using Raven.Abstractions.Logging;
  11. using Raven.Database.Config;
  12. using Raven.Imports.Newtonsoft.Json.Linq;
  13. using Raven.Abstractions.Data;
  14. using Raven.Abstractions.Indexing;
  15. using Raven.Abstractions.Linq;
  16. using Raven.Abstractions.MEF;
  17. using Raven.Database.Data;
  18. using Raven.Database.Indexing;
  19. using Raven.Database.Linq;
  20. using Raven.Database.Plugins;
  21. using Raven.Database.Storage;
  22. using Raven.Abstractions.Json;
  23. using System.Linq;
  24. using Raven.Json.Linq;
  25. namespace Raven.Database.Impl
  26. {
  27. internal class DocumentRetriever : ITranslatorDatabaseAccessor
  28. {
  29. private static readonly ILog log = LogManager.GetCurrentClassLogger();
  30. private readonly IDictionary<string, JsonDocument> cache = new Dictionary<string, JsonDocument>(StringComparer.OrdinalIgnoreCase);
  31. private readonly HashSet<string> loadedIdsForRetrieval = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
  32. private readonly HashSet<string> loadedIdsForFilter = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
  33. private readonly RavenConfiguration configuration;
  34. private readonly IStorageActionsAccessor actions;
  35. private readonly OrderedPartCollection<AbstractReadTrigger> triggers;
  36. private readonly Dictionary<string, RavenJToken> transformerParameters;
  37. private readonly HashSet<string> itemsToInclude;
  38. private bool disableCache;
  39. public Etag Etag = Etag.Empty;
  40. public DocumentRetriever(RavenConfiguration configuration, IStorageActionsAccessor actions, OrderedPartCollection<AbstractReadTrigger> triggers,
  41. Dictionary<string, RavenJToken> transformerParameters = null,
  42. HashSet<string> itemsToInclude = null)
  43. {
  44. this.configuration = configuration;
  45. this.actions = actions;
  46. this.triggers = triggers;
  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(
  53. ProcessReadVetoes(
  54. RetrieveDocumentInternal(queryResult, loadedIdsForRetrieval, fieldsToFetch, indexDefinition, skipDuplicateCheck), ReadOperation.Query), ReadOperation.Query);
  55. }
  56. public JsonDocument ExecuteReadTriggers(JsonDocument document, ReadOperation operation)
  57. {
  58. return ExecuteReadTriggersOnRead(ProcessReadVetoes(document, operation), operation);
  59. }
  60. private JsonDocument ExecuteReadTriggersOnRead(JsonDocument resultingDocument, ReadOperation operation)
  61. {
  62. if (resultingDocument == null)
  63. return null;
  64. var doc = new JsonDocument
  65. {
  66. Key = resultingDocument.Key,
  67. Etag = resultingDocument.Etag,
  68. LastModified = resultingDocument.LastModified,
  69. SerializedSizeOnDisk = resultingDocument.SerializedSizeOnDisk,
  70. SkipDeleteFromIndex = resultingDocument.SkipDeleteFromIndex,
  71. TempIndexScore = resultingDocument.TempIndexScore,
  72. DataAsJson = resultingDocument.DataAsJson.IsSnapshot
  73. ? (RavenJObject)resultingDocument.DataAsJson.CreateSnapshot()
  74. : resultingDocument.DataAsJson,
  75. Metadata = resultingDocument.Metadata.IsSnapshot
  76. ? (RavenJObject)resultingDocument.Metadata.CreateSnapshot()
  77. : resultingDocument.Metadata,
  78. };
  79. triggers.Apply(
  80. trigger =>
  81. trigger.OnRead(doc.Key, doc.DataAsJson, doc.Metadata, operation));
  82. return doc;
  83. }
  84. private JsonDocument RetrieveDocumentInternal(
  85. IndexQueryResult queryResult,
  86. HashSet<string> loadedIds,
  87. FieldsToFetch fieldsToFetch,
  88. IndexDefinition indexDefinition,
  89. bool skipDuplicateCheck)
  90. {
  91. var queryScore = queryResult.Score;
  92. if (float.IsNaN(queryScore))
  93. queryScore = 0f;
  94. if (queryResult.Projection == null)
  95. {
  96. // duplicate document, filter it out
  97. if (skipDuplicateCheck == false && loadedIds.Add(queryResult.Key) == false)
  98. return null;
  99. var document = GetDocumentWithCaching(queryResult);
  100. if (document == null)
  101. return null;
  102. document.Metadata = GetMetadata(document);
  103. if (skipDuplicateCheck == false)
  104. document.Metadata[Constants.TemporaryScoreValue] = queryScore;
  105. return document;
  106. }
  107. JsonDocument doc = null;
  108. if (fieldsToFetch.IsProjection)
  109. {
  110. if (indexDefinition.IsMapReduce == false)
  111. {
  112. bool hasStoredFields = false;
  113. FieldStorage value;
  114. if (indexDefinition.Stores.TryGetValue(Constants.AllFields, out value))
  115. {
  116. hasStoredFields = value != FieldStorage.No;
  117. }
  118. foreach (var fieldToFetch in fieldsToFetch.Fields)
  119. {
  120. if (indexDefinition.Stores.TryGetValue(fieldToFetch, out value) == false && value != FieldStorage.No) continue;
  121. hasStoredFields = true;
  122. }
  123. if (hasStoredFields == false)
  124. {
  125. // duplicate document, filter it out
  126. if (loadedIds.Add(queryResult.Key) == false) return null;
  127. }
  128. }
  129. // We have to load the document if user explicitly asked for the id, since
  130. // we normalize the casing for the document id on the index, and we need to return
  131. // the id to the user with the same casing they gave us.
  132. var fetchingId = fieldsToFetch.HasField(Constants.DocumentIdFieldName);
  133. var fieldsToFetchFromDocument = fieldsToFetch.Fields.Where(fieldToFetch => queryResult.Projection[fieldToFetch] == null).ToArray();
  134. if (fieldsToFetchFromDocument.Length > 0 || fetchingId)
  135. {
  136. switch (configuration.Core.ImplicitFetchFieldsFromDocumentMode)
  137. {
  138. case ImplicitFetchFieldsMode.Enabled:
  139. doc = GetDocumentWithCaching(queryResult);
  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 ) continue;
  150. queryResult.Projection[property.Key] = property.Value;
  151. }
  152. }
  153. break;
  154. case ImplicitFetchFieldsMode.DoNothing:
  155. break;
  156. case ImplicitFetchFieldsMode.Exception:
  157. string message = string.Format("Implicit fetching of fields from the document is disabled." + Environment.NewLine +
  158. "Check your index ({0}) to make sure that all fields you want to project are stored in the index." + Environment.NewLine +
  159. "You can control this behavior using the Raven/ImplicitFetchFieldsFromDocumentMode setting." + Environment.NewLine +
  160. "Fields to fetch from document are: {1}" + Environment.NewLine +
  161. "Fetching id: {2}", indexDefinition.Name, string.Join(", ", fieldsToFetchFromDocument), fetchingId);
  162. throw new ImplicitFetchFieldsFromDocumentNotAllowedException(message);
  163. default:
  164. throw new ArgumentOutOfRangeException(configuration.Core.ImplicitFetchFieldsFromDocumentMode.ToString());
  165. }
  166. }
  167. }
  168. else if (fieldsToFetch.FetchAllStoredFields && string.IsNullOrEmpty(queryResult.Key) == false
  169. && (fieldsToFetch.Query == null || fieldsToFetch.Query.AllowMultipleIndexEntriesForSameDocumentToResultTransformer == false)
  170. )
  171. {
  172. // duplicate document, filter it out
  173. if (loadedIds.Add(queryResult.Key) == false)
  174. return null;
  175. doc = GetDocumentWithCaching(queryResult);
  176. }
  177. var metadata = GetMetadata(doc);
  178. metadata.Remove("@id");
  179. metadata[Constants.TemporaryScoreValue] = queryScore;
  180. return new JsonDocument
  181. {
  182. Key = queryResult.Key,
  183. DataAsJson = queryResult.Projection,
  184. Metadata = metadata
  185. };
  186. }
  187. private static RavenJObject GetMetadata(JsonDocument doc)
  188. {
  189. if (doc == null)
  190. return new RavenJObject();
  191. if (doc.Metadata.IsSnapshot)
  192. return (RavenJObject)doc.Metadata.CreateSnapshot();
  193. return doc.Metadata;
  194. }
  195. private JsonDocument GetDocumentWithCaching(IndexQueryResult iqr)
  196. {
  197. if (iqr.DocumentLoaded)
  198. return iqr.Document;
  199. iqr.DocumentLoaded = true;
  200. iqr.Document = GetDocumentWithCaching(iqr.Key);
  201. if (iqr.Document != null)
  202. iqr.Key = iqr.Document.Key;// to get the actual document id in the right case sensitive manner
  203. return iqr.Document;
  204. }
  205. private JsonDocument GetDocumentWithCaching(string key)
  206. {
  207. if (key == null)
  208. return null;
  209. JsonDocument doc;
  210. if (disableCache == false && cache.TryGetValue(key, out doc))
  211. return doc;
  212. doc = actions.Documents.DocumentByKey(key);
  213. JsonDocument.EnsureIdInMetadata(doc);
  214. if (doc != null && doc.Metadata != null)
  215. doc.Metadata.EnsureCannotBeChangeAndEnableSnapshotting();
  216. if (disableCache == false)
  217. cache[key] = doc;
  218. if (cache.Count > 2048)
  219. {
  220. // we are probably doing a stream here, no point in trying to cache things, we might be
  221. // going through the entire db here!
  222. disableCache = true;
  223. cache.Clear();
  224. }
  225. return doc;
  226. }
  227. public bool ShouldIncludeResultInQuery(IndexQueryResult arg, IndexDefinition indexDefinition, FieldsToFetch fieldsToFetch, bool skipDuplicateCheck)
  228. {
  229. JsonDocument doc;
  230. if (arg.DocumentLoaded)
  231. {
  232. doc = arg.Document;
  233. }
  234. else
  235. {
  236. doc = RetrieveDocumentInternal(arg, loadedIdsForFilter, fieldsToFetch, indexDefinition, skipDuplicateCheck);
  237. arg.Document = doc;
  238. arg.DocumentLoaded = true;
  239. }
  240. if (doc == null)
  241. return false;
  242. doc = ProcessReadVetoes(doc, ReadOperation.Query);
  243. return doc != null;
  244. }
  245. public T ProcessReadVetoes<T>(T document, ReadOperation operation)
  246. where T : class, IJsonDocumentMetadata, new()
  247. {
  248. if (document == null)
  249. return null;
  250. foreach (var readTrigger in triggers)
  251. {
  252. var readVetoResult = readTrigger.Value.AllowRead(document.Key, document.Metadata, operation);
  253. switch (readVetoResult.Veto)
  254. {
  255. case ReadVetoResult.ReadAllow.Allow:
  256. break;
  257. case ReadVetoResult.ReadAllow.Deny:
  258. return new T
  259. {
  260. Etag = Etag.Empty,
  261. LastModified = DateTime.MinValue,
  262. Key = document.Key,
  263. Metadata = new RavenJObject
  264. {
  265. {
  266. "Raven-Read-Veto", new RavenJObject
  267. {
  268. {"Reason", readVetoResult.Reason},
  269. {"Trigger", readTrigger.ToString()}
  270. }
  271. }
  272. }
  273. };
  274. case ReadVetoResult.ReadAllow.Ignore:
  275. if (log.IsDebugEnabled)
  276. log.Debug("Trigger {0} asked us to ignore {1}", readTrigger.Value, document.Key);
  277. return null;
  278. default:
  279. throw new ArgumentOutOfRangeException(readVetoResult.Veto.ToString());
  280. }
  281. }
  282. return document;
  283. }
  284. public dynamic Include(object maybeId)
  285. {
  286. if (maybeId == null || maybeId is DynamicNullObject)
  287. return new DynamicNullObject();
  288. var id = maybeId as string;
  289. if (id != null)
  290. return Include(id);
  291. var jId = maybeId as RavenJValue;
  292. if (jId != null)
  293. return Include(jId.Value.ToString());
  294. var items = new List<dynamic>();
  295. foreach (var itemId in (IEnumerable)maybeId)
  296. {
  297. var include = Include(itemId);// this is where the real work happens
  298. items.Add(include);
  299. }
  300. return new DynamicList(items);
  301. }
  302. public dynamic Include(string id)
  303. {
  304. ItemsToInclude.Add(id);
  305. return Load(id);
  306. }
  307. public dynamic Include(IEnumerable<string> ids)
  308. {
  309. var items = new List<dynamic>();
  310. foreach (var itemId in ids)
  311. {
  312. var include = Include(itemId);// this is where the real work happens
  313. items.Add(include);
  314. }
  315. return new DynamicList(items);
  316. }
  317. public dynamic Load(string id)
  318. {
  319. var document = GetDocumentWithCaching(id);
  320. document = ProcessReadVetoes(document, ReadOperation.Load);
  321. if (document == null)
  322. {
  323. Etag = Etag.HashWith(Etag.Empty);
  324. return new DynamicNullObject();
  325. }
  326. Etag = Etag.HashWith(document.Etag);
  327. if (document.Metadata.ContainsKey("Raven-Read-Veto"))
  328. {
  329. return new DynamicNullObject();
  330. }
  331. return new DynamicJsonObject(document.ToJson());
  332. }
  333. public dynamic Load(object maybeId)
  334. {
  335. if (maybeId == null || maybeId is DynamicNullObject)
  336. {
  337. Etag = Etag.HashWith(Etag.Empty);
  338. return new DynamicNullObject();
  339. }
  340. var id = maybeId as string;
  341. if (id != null)
  342. return Load(id);
  343. var jId = maybeId as RavenJValue;
  344. if (jId != null)
  345. return Load(jId.Value.ToString());
  346. var items = new List<dynamic>();
  347. foreach (var itemId in (IEnumerable)maybeId)
  348. {
  349. items.Add(Load(itemId));
  350. }
  351. return new DynamicList(items.Select(x => (object)x).ToArray());
  352. }
  353. public Dictionary<string, RavenJToken> TransformerParameters { get { return this.transformerParameters; } }
  354. public HashSet<string> ItemsToInclude
  355. {
  356. get { return itemsToInclude; }
  357. }
  358. }
  359. }