PageRenderTime 52ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/Raven.Database/Actions/QueryActions.cs

https://github.com/nwendel/ravendb
C# | 348 lines | 296 code | 46 blank | 6 comment | 51 complexity | 128c021920d1dd6a2ff79ee857c1afde MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, BSD-3-Clause, CC-BY-SA-3.0
  1. // -----------------------------------------------------------------------
  2. // <copyright file="QueryActions.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.Diagnostics;
  9. using System.Linq;
  10. using System.Threading;
  11. using Raven.Abstractions;
  12. using Raven.Abstractions.Data;
  13. using Raven.Abstractions.Exceptions;
  14. using Raven.Abstractions.Extensions;
  15. using Raven.Abstractions.Logging;
  16. using Raven.Database.Data;
  17. using Raven.Database.Impl;
  18. using Raven.Database.Indexing;
  19. using Raven.Database.Linq;
  20. using Raven.Database.Server.RavenFS.Extensions;
  21. using Raven.Database.Storage;
  22. using Raven.Database.Util;
  23. using Raven.Json.Linq;
  24. namespace Raven.Database.Actions
  25. {
  26. public class QueryActions : ActionsBase
  27. {
  28. public QueryActions(DocumentDatabase database, SizeLimitedConcurrentDictionary<string, TouchedDocumentInfo> recentTouches, IUuidGenerator uuidGenerator, ILog log)
  29. : base(database, recentTouches, uuidGenerator, log)
  30. {
  31. }
  32. public IEnumerable<string> QueryDocumentIds(string index, IndexQuery query, CancellationTokenSource tokenSource, out bool stale)
  33. {
  34. var queryStat = AddToCurrentlyRunningQueryList(index, query, tokenSource);
  35. try
  36. {
  37. bool isStale = false;
  38. HashSet<string> loadedIds = null;
  39. TransactionalStorage.Batch(
  40. actions =>
  41. {
  42. var definition = IndexDefinitionStorage.GetIndexDefinition(index);
  43. if (definition == null)
  44. throw new ArgumentException("specified index definition was not found", "index");
  45. isStale = actions.Staleness.IsIndexStale(definition.IndexId, query.Cutoff, null);
  46. if (isStale == false && query.Cutoff == null)
  47. {
  48. var indexInstance = Database.IndexStorage.GetIndexInstance(index);
  49. isStale = isStale || (indexInstance != null && indexInstance.IsMapIndexingInProgress);
  50. }
  51. var indexFailureInformation = actions.Indexing.GetFailureRate(definition.IndexId);
  52. if (indexFailureInformation.IsInvalidIndex)
  53. {
  54. throw new IndexDisabledException(indexFailureInformation);
  55. }
  56. loadedIds = new HashSet<string>(from queryResult in Database.IndexStorage.Query(index, query, result => true, new FieldsToFetch(null, false, Constants.DocumentIdFieldName), Database.IndexQueryTriggers, tokenSource.Token)
  57. select queryResult.Key);
  58. });
  59. stale = isStale;
  60. return loadedIds;
  61. }
  62. finally
  63. {
  64. RemoveFromCurrentlyRunningQueryList(index, queryStat);
  65. }
  66. }
  67. public QueryResultWithIncludes Query(string index, IndexQuery query, CancellationToken externalCancellationToken)
  68. {
  69. QueryResultWithIncludes result = null;
  70. using (var cts = CancellationTokenSource.CreateLinkedTokenSource(externalCancellationToken, WorkContext.CancellationToken))
  71. {
  72. TransactionalStorage.Batch(
  73. accessor =>
  74. {
  75. using (var op = new DatabaseQueryOperation(Database, index, query, accessor, cts)
  76. {
  77. ShouldSkipDuplicateChecking = query.SkipDuplicateChecking
  78. })
  79. {
  80. var list = new List<RavenJObject>();
  81. op.Init();
  82. op.Execute(list.Add);
  83. op.Result.Results = list;
  84. result = op.Result;
  85. }
  86. });
  87. }
  88. return result;
  89. }
  90. public class DatabaseQueryOperation : IDisposable
  91. {
  92. public bool ShouldSkipDuplicateChecking = false;
  93. private readonly DocumentDatabase database;
  94. private readonly string indexName;
  95. private readonly IndexQuery query;
  96. private readonly IStorageActionsAccessor actions;
  97. private readonly CancellationToken cancellationToken;
  98. private readonly ExecutingQueryInfo queryStat;
  99. public QueryResultWithIncludes Result = new QueryResultWithIncludes();
  100. public QueryHeaderInformation Header;
  101. private bool stale;
  102. private IEnumerable<RavenJObject> results;
  103. private DocumentRetriever docRetriever;
  104. private Stopwatch duration;
  105. private List<string> transformerErrors;
  106. private bool nonAuthoritativeInformation;
  107. private Etag resultEtag;
  108. private Tuple<DateTime, Etag> indexTimestamp;
  109. private Dictionary<string, Dictionary<string, string[]>> highlightings;
  110. private Dictionary<string, string> scoreExplanations;
  111. private HashSet<string> idsToLoad;
  112. private readonly Dictionary<QueryTimings, double> executionTimes = new Dictionary<QueryTimings, double>();
  113. public DatabaseQueryOperation(DocumentDatabase database, string indexName, IndexQuery query, IStorageActionsAccessor actions, CancellationTokenSource cancellationTokenSource)
  114. {
  115. this.database = database;
  116. this.indexName = indexName != null ? indexName.Trim() : null;
  117. this.query = query;
  118. this.actions = actions;
  119. cancellationToken = cancellationTokenSource.Token;
  120. queryStat = database.Queries.AddToCurrentlyRunningQueryList(indexName, query, cancellationTokenSource);
  121. if (query.ShowTimings == false)
  122. return;
  123. executionTimes[QueryTimings.Lucene] = 0;
  124. executionTimes[QueryTimings.LoadDocuments] = 0;
  125. executionTimes[QueryTimings.TransformResults] = 0;
  126. }
  127. public void Init()
  128. {
  129. highlightings = new Dictionary<string, Dictionary<string, string[]>>();
  130. scoreExplanations = new Dictionary<string, string>();
  131. Func<IndexQueryResult, object> tryRecordHighlightingAndScoreExplanation = queryResult =>
  132. {
  133. if (queryResult.Key == null)
  134. return null;
  135. if (queryResult.Highligtings != null)
  136. highlightings.Add(queryResult.Key, queryResult.Highligtings);
  137. if (queryResult.ScoreExplanation != null)
  138. scoreExplanations.Add(queryResult.Key, queryResult.ScoreExplanation);
  139. return null;
  140. };
  141. stale = false;
  142. indexTimestamp = Tuple.Create(DateTime.MinValue, Etag.Empty);
  143. resultEtag = Etag.Empty;
  144. nonAuthoritativeInformation = false;
  145. if (string.IsNullOrEmpty(query.ResultsTransformer) == false)
  146. {
  147. query.FieldsToFetch = new[] { Constants.AllFields };
  148. }
  149. duration = Stopwatch.StartNew();
  150. idsToLoad = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
  151. var viewGenerator = database.IndexDefinitionStorage.GetViewGenerator(indexName);
  152. var index = database.IndexDefinitionStorage.GetIndexDefinition(indexName);
  153. if (viewGenerator == null)
  154. throw new IndexDoesNotExistsException("Could not find index named: " + indexName);
  155. resultEtag = database.Indexes.GetIndexEtag(index.Name, null, query.ResultsTransformer);
  156. stale = actions.Staleness.IsIndexStale(index.IndexId, query.Cutoff, query.CutoffEtag);
  157. if (stale == false && query.Cutoff == null && query.CutoffEtag == null)
  158. {
  159. var indexInstance = database.IndexStorage.GetIndexInstance(indexName);
  160. stale = stale || (indexInstance != null && indexInstance.IsMapIndexingInProgress);
  161. }
  162. indexTimestamp = actions.Staleness.IndexLastUpdatedAt(index.IndexId);
  163. var indexFailureInformation = actions.Indexing.GetFailureRate(index.IndexId);
  164. if (indexFailureInformation.IsInvalidIndex)
  165. {
  166. throw new IndexDisabledException(indexFailureInformation);
  167. }
  168. docRetriever = new DocumentRetriever(actions, database.ReadTriggers, database.InFlightTransactionalState, query.TransformerParameters, idsToLoad);
  169. var fieldsToFetch = new FieldsToFetch(query,
  170. viewGenerator.ReduceDefinition == null
  171. ? Constants.DocumentIdFieldName
  172. : Constants.ReduceKeyFieldName);
  173. Func<IndexQueryResult, bool> shouldIncludeInResults =
  174. result => docRetriever.ShouldIncludeResultInQuery(result, index, fieldsToFetch, ShouldSkipDuplicateChecking);
  175. var indexQueryResults = database.IndexStorage.Query(indexName, query, shouldIncludeInResults, fieldsToFetch, database.IndexQueryTriggers, cancellationToken);
  176. indexQueryResults = new ActiveEnumerable<IndexQueryResult>(indexQueryResults);
  177. if (query.ShowTimings)
  178. indexQueryResults = new TimedEnumerable<IndexQueryResult>(indexQueryResults, timeInMilliseconds => executionTimes[QueryTimings.Lucene] = timeInMilliseconds);
  179. var docs = from queryResult in indexQueryResults
  180. let doc = docRetriever.RetrieveDocumentForQuery(queryResult, index, fieldsToFetch, ShouldSkipDuplicateChecking)
  181. where doc != null
  182. let _ = nonAuthoritativeInformation |= (doc.NonAuthoritativeInformation ?? false)
  183. let __ = tryRecordHighlightingAndScoreExplanation(queryResult)
  184. select doc;
  185. transformerErrors = new List<string>();
  186. results = database
  187. .Queries
  188. .GetQueryResults(query, viewGenerator, docRetriever, docs, transformerErrors, timeInMilliseconds => executionTimes[QueryTimings.LoadDocuments] = timeInMilliseconds, timeInMilliseconds => executionTimes[QueryTimings.TransformResults] = timeInMilliseconds, query.ShowTimings, cancellationToken);
  189. Header = new QueryHeaderInformation
  190. {
  191. Index = indexName,
  192. IsStale = stale,
  193. ResultEtag = resultEtag,
  194. IndexTimestamp = indexTimestamp.Item1,
  195. IndexEtag = indexTimestamp.Item2,
  196. TotalResults = query.TotalSize.Value
  197. };
  198. }
  199. public void Execute(Action<RavenJObject> onResult)
  200. {
  201. using (new CurrentTransformationScope(database, docRetriever))
  202. {
  203. foreach (var result in results)
  204. {
  205. cancellationToken.ThrowIfCancellationRequested();
  206. onResult(result);
  207. }
  208. if (transformerErrors.Count > 0)
  209. {
  210. throw new InvalidOperationException("The transform results function failed.\r\n" + string.Join("\r\n", transformerErrors));
  211. }
  212. }
  213. Result = new QueryResultWithIncludes
  214. {
  215. IndexName = indexName,
  216. IsStale = stale,
  217. NonAuthoritativeInformation = nonAuthoritativeInformation,
  218. SkippedResults = query.SkippedResults.Value,
  219. TotalResults = query.TotalSize.Value,
  220. IndexTimestamp = indexTimestamp.Item1,
  221. IndexEtag = indexTimestamp.Item2,
  222. ResultEtag = resultEtag,
  223. IdsToInclude = idsToLoad,
  224. LastQueryTime = SystemTime.UtcNow,
  225. Highlightings = highlightings,
  226. DurationMilliseconds = duration.ElapsedMilliseconds,
  227. ScoreExplanations = scoreExplanations,
  228. TimingsInMilliseconds = NormalizeTimings()
  229. };
  230. }
  231. private Dictionary<string, double> NormalizeTimings()
  232. {
  233. if (query.ShowTimings)
  234. {
  235. var luceneTime = executionTimes[QueryTimings.Lucene];
  236. var loadDocumentsTime = executionTimes[QueryTimings.LoadDocuments];
  237. var transformResultsTime = executionTimes[QueryTimings.TransformResults];
  238. executionTimes[QueryTimings.LoadDocuments] -= loadDocumentsTime > 0 ? luceneTime : 0;
  239. executionTimes[QueryTimings.TransformResults] -= transformResultsTime > 0 ? loadDocumentsTime + luceneTime : 0;
  240. }
  241. return executionTimes.ToDictionary(x => x.Key.GetDescription(), x => x.Value >= 0 ? Math.Round(x.Value) : 0);
  242. }
  243. public void Dispose()
  244. {
  245. database.Queries.RemoveFromCurrentlyRunningQueryList(indexName, queryStat);
  246. var resultsAsDisposable = results as IDisposable;
  247. if (resultsAsDisposable != null)
  248. resultsAsDisposable.Dispose();
  249. }
  250. }
  251. private void RemoveFromCurrentlyRunningQueryList(string index, ExecutingQueryInfo queryStat)
  252. {
  253. ConcurrentSet<ExecutingQueryInfo> set;
  254. if (WorkContext.CurrentlyRunningQueries.TryGetValue(index, out set) == false)
  255. return;
  256. set.TryRemove(queryStat);
  257. }
  258. private ExecutingQueryInfo AddToCurrentlyRunningQueryList(string index, IndexQuery query, CancellationTokenSource externalTokenSource)
  259. {
  260. var set = WorkContext.CurrentlyRunningQueries.GetOrAdd(index, x => new ConcurrentSet<ExecutingQueryInfo>());
  261. var queryStartTime = DateTime.UtcNow;
  262. var queryId = WorkContext.GetNextQueryId();
  263. var executingQueryInfo = new ExecutingQueryInfo(queryStartTime, query, queryId, externalTokenSource);
  264. set.Add(executingQueryInfo);
  265. return executingQueryInfo;
  266. }
  267. private IEnumerable<RavenJObject> GetQueryResults(IndexQuery query, AbstractViewGenerator viewGenerator, DocumentRetriever docRetriever, IEnumerable<JsonDocument> results, List<string> transformerErrors, Action<double> loadingDocumentsFinish, Action<double> transformerFinish, bool showTimings, CancellationToken token)
  268. {
  269. if (query.PageSize <= 0) // maybe they just want the stats?
  270. {
  271. return Enumerable.Empty<RavenJObject>();
  272. }
  273. IndexingFunc transformFunc = null;
  274. // Check an explicitly declared one first
  275. if (string.IsNullOrEmpty(query.ResultsTransformer) == false)
  276. {
  277. var transformGenerator = IndexDefinitionStorage.GetTransformer(query.ResultsTransformer);
  278. if (transformGenerator != null && transformGenerator.TransformResultsDefinition != null)
  279. transformFunc = transformGenerator.TransformResultsDefinition;
  280. else
  281. throw new InvalidOperationException("The transformer " + query.ResultsTransformer + " was not found");
  282. }
  283. if (transformFunc == null)
  284. {
  285. var resultsWithoutTransformer = results.Select(x => x.ToJson());
  286. return showTimings ? new TimedEnumerable<RavenJObject>(resultsWithoutTransformer, loadingDocumentsFinish) : resultsWithoutTransformer;
  287. }
  288. var dynamicJsonObjects = results.Select(x => new DynamicLuceneOrParentDocumntObject(docRetriever, x.ToJson()));
  289. var robustEnumerator = new RobustEnumerator(token, 100)
  290. {
  291. OnError =
  292. (exception, o) =>
  293. transformerErrors.Add(string.Format("Doc '{0}', Error: {1}", Index.TryGetDocKey(o),
  294. exception.Message))
  295. };
  296. var resultsWithTransformer = robustEnumerator
  297. .RobustEnumeration(dynamicJsonObjects.Cast<object>().GetEnumerator(), transformFunc)
  298. .Select(JsonExtensions.ToJObject);
  299. return showTimings ? new TimedEnumerable<RavenJObject>(resultsWithTransformer, transformerFinish) : resultsWithTransformer;
  300. }
  301. }
  302. }