PageRenderTime 49ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/ToMigrate/Raven.Database/Actions/QueryActions.cs

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