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