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

/Raven.Database/Server/Controllers/IndexController.cs

https://github.com/nwendel/ravendb
C# | 701 lines | 583 code | 113 blank | 5 comment | 116 complexity | 6aa2fef491d8215085843b0150be016a MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, BSD-3-Clause, CC-BY-SA-3.0
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.Globalization;
  6. using System.Linq;
  7. using System.Net;
  8. using System.Net.Http;
  9. using System.Text;
  10. using System.Text.RegularExpressions;
  11. using System.Threading;
  12. using System.Threading.Tasks;
  13. using System.Web.Http;
  14. using ICSharpCode.NRefactory.CSharp;
  15. using Lucene.Net.Documents;
  16. using Raven.Abstractions;
  17. using Raven.Abstractions.Data;
  18. using Raven.Abstractions.Exceptions;
  19. using Raven.Abstractions.Extensions;
  20. using Raven.Abstractions.Indexing;
  21. using Raven.Abstractions.Logging;
  22. using Raven.Abstractions.MEF;
  23. using Raven.Client.Indexes;
  24. using Raven.Database.Data;
  25. using Raven.Database.Extensions;
  26. using Raven.Database.Indexing;
  27. using Raven.Database.Linq;
  28. using Raven.Database.Linq.PrivateExtensions;
  29. using Raven.Database.Plugins;
  30. using Raven.Database.Queries;
  31. using Raven.Database.Server.WebApi.Attributes;
  32. using Raven.Database.Storage;
  33. using Raven.Json.Linq;
  34. namespace Raven.Database.Server.Controllers
  35. {
  36. public class IndexController : RavenDbApiController
  37. {
  38. [HttpGet]
  39. [Route("indexes")]
  40. [Route("databases/{databaseName}/indexes")]
  41. public HttpResponseMessage IndexesGet()
  42. {
  43. var namesOnlyString = GetQueryStringValue("namesOnly");
  44. bool namesOnly;
  45. RavenJArray indexes;
  46. if (bool.TryParse(namesOnlyString, out namesOnly) && namesOnly)
  47. indexes = Database.Indexes.GetIndexNames(GetStart(), GetPageSize(Database.Configuration.MaxPageSize));
  48. else
  49. indexes = Database.Indexes.GetIndexes(GetStart(), GetPageSize(Database.Configuration.MaxPageSize));
  50. return GetMessageWithObject(indexes);
  51. }
  52. [HttpGet]
  53. [Route("indexes/{*id}")]
  54. [Route("databases/{databaseName}/indexes/{*id}")]
  55. public HttpResponseMessage IndexGet(string id)
  56. {
  57. using (var cts = new CancellationTokenSource())
  58. using (cts.TimeoutAfter(DatabasesLandlord.SystemConfiguration.DatabaseOperationTimeout))
  59. {
  60. var index = id;
  61. if (string.IsNullOrEmpty(GetQueryStringValue("definition")) == false)
  62. return GetIndexDefinition(index);
  63. if (string.IsNullOrEmpty(GetQueryStringValue("source")) == false)
  64. return GetIndexSource(index);
  65. if (string.IsNullOrEmpty(GetQueryStringValue("debug")) == false)
  66. return DebugIndex(index);
  67. if (string.IsNullOrEmpty(GetQueryStringValue("explain")) == false)
  68. return GetExplanation(index);
  69. return GetIndexQueryResult(index, cts.Token);
  70. }
  71. }
  72. [HttpPut]
  73. [Route("indexes/{*id}")]
  74. [Route("databases/{databaseName}/indexes/{*id}")]
  75. public async Task<HttpResponseMessage> IndexPut(string id)
  76. {
  77. var index = id;
  78. var data = await ReadJsonObjectAsync<IndexDefinition>();
  79. if (data == null || (data.Map == null && (data.Maps == null || data.Maps.Count == 0)))
  80. return GetMessageWithString("Expected json document with 'Map' or 'Maps' property", HttpStatusCode.BadRequest);
  81. try
  82. {
  83. Database.Indexes.PutIndex(index, data);
  84. return GetMessageWithObject(new { Index = index }, HttpStatusCode.Created);
  85. }
  86. catch (Exception ex)
  87. {
  88. var compilationException = ex as IndexCompilationException;
  89. return GetMessageWithObject(new
  90. {
  91. ex.Message,
  92. IndexDefinitionProperty = compilationException != null ? compilationException.IndexDefinitionProperty : "",
  93. ProblematicText = compilationException != null ? compilationException.ProblematicText : "",
  94. Error = ex.ToString()
  95. }, HttpStatusCode.BadRequest);
  96. }
  97. }
  98. [HttpHead]
  99. [Route("indexes/{*id}")]
  100. [Route("databases/{databaseName}/indexes/{*id}")]
  101. public HttpResponseMessage IndexHead(string id)
  102. {
  103. var index = id;
  104. if (Database.IndexDefinitionStorage.IndexNames.Contains(index, StringComparer.OrdinalIgnoreCase) == false)
  105. return GetEmptyMessage(HttpStatusCode.NotFound);
  106. return GetEmptyMessage();
  107. }
  108. [HttpPost]
  109. [Route("indexes/{*id}")]
  110. [Route("databases/{databaseName}/indexes/{*id}")]
  111. public async Task<HttpResponseMessage >IndexPost(string id)
  112. {
  113. var index = id;
  114. if ("forceWriteToDisk".Equals(GetQueryStringValue("op"), StringComparison.InvariantCultureIgnoreCase))
  115. {
  116. Database.IndexStorage.ForceWriteToDisk(index);
  117. return GetEmptyMessage();
  118. }
  119. if ("hasChanged".Equals(GetQueryStringValue("op"), StringComparison.InvariantCultureIgnoreCase))
  120. {
  121. var data = await ReadJsonObjectAsync<IndexDefinition>();
  122. if (data == null || (data.Map == null && (data.Maps == null || data.Maps.Count == 0)))
  123. return GetMessageWithString("Expected json document with 'Map' or 'Maps' property", HttpStatusCode.BadRequest);
  124. return GetMessageWithObject(new { Name = index, Changed = Database.Indexes.IndexHasChanged(index, data) });
  125. }
  126. if ("lockModeChange".Equals(GetQueryStringValue("op"), StringComparison.InvariantCultureIgnoreCase))
  127. return HandleIndexLockModeChange(index);
  128. if ("true".Equals(GetQueryStringValue("postQuery"), StringComparison.InvariantCultureIgnoreCase))
  129. {
  130. var postedQuery = await ReadStringAsync();
  131. SetPostRequestQuery(postedQuery);
  132. return IndexGet(id);
  133. }
  134. return GetMessageWithString("Not idea how to handle a POST on " + index + " with op=" +
  135. (GetQueryStringValue("op") ?? "<no val specified>"));
  136. }
  137. [HttpReset]
  138. [Route("indexes/{*id}")]
  139. [Route("databases/{databaseName}/indexes/{*id}")]
  140. public HttpResponseMessage IndexReset(string id)
  141. {
  142. var index = id;
  143. Database.Indexes.ResetIndex(index);
  144. return GetMessageWithObject(new { Reset = index });
  145. }
  146. [HttpDelete]
  147. [Route("indexes/{*id}")]
  148. [Route("databases/{databaseName}/indexes/{*id}")]
  149. public HttpResponseMessage IndexDelete(string id)
  150. {
  151. var index = id;
  152. Database.Indexes.DeleteIndex(index);
  153. return GetEmptyMessage(HttpStatusCode.NoContent);
  154. }
  155. [HttpPost]
  156. [Route("indexes/set-priority/{*id}")]
  157. [Route("databases/{databaseName}/indexes/set-priority/{*id}")]
  158. public HttpResponseMessage SetPriority(string id)
  159. {
  160. var index = id;
  161. IndexingPriority indexingPriority;
  162. if (Enum.TryParse(GetQueryStringValue("priority"), out indexingPriority) == false)
  163. {
  164. return GetMessageWithObject(new
  165. {
  166. Error = "Could not parse priority value: " + GetQueryStringValue("priority")
  167. }, HttpStatusCode.BadRequest);
  168. }
  169. var instance = Database.IndexStorage.GetIndexInstance(index);
  170. Database.TransactionalStorage.Batch(accessor => accessor.Indexing.SetIndexPriority(instance.indexId, indexingPriority));
  171. return GetEmptyMessage();
  172. }
  173. [HttpGet]
  174. [Route("c-sharp-index-definition/{*fullIndexName}")]
  175. [Route("databases/{databaseName}/c-sharp-index-definition/{*fullIndexName}")]
  176. public HttpResponseMessage GenerateCSharpIndexDefinition(string fullIndexName)
  177. {
  178. var indexDefinition = Database.Indexes.GetIndexDefinition(fullIndexName);
  179. if (indexDefinition == null)
  180. return GetEmptyMessage(HttpStatusCode.NotFound);
  181. var text = new IndexDefinitionCodeGenerator(indexDefinition).Generate();
  182. return GetMessageWithObject(text);
  183. }
  184. private HttpResponseMessage GetIndexDefinition(string index)
  185. {
  186. var indexDefinition = Database.Indexes.GetIndexDefinition(index);
  187. if (indexDefinition == null)
  188. return GetEmptyMessage(HttpStatusCode.NotFound);
  189. indexDefinition.Fields = Database.Indexes.GetIndexFields(index);
  190. return GetMessageWithObject(new
  191. {
  192. Index = indexDefinition,
  193. });
  194. }
  195. private HttpResponseMessage GetIndexSource(string index)
  196. {
  197. var viewGenerator = Database.IndexDefinitionStorage.GetViewGenerator(index);
  198. if (viewGenerator == null)
  199. return GetEmptyMessage(HttpStatusCode.NotFound);
  200. return GetMessageWithObject(viewGenerator.SourceCode);
  201. }
  202. private HttpResponseMessage DebugIndex(string index)
  203. {
  204. switch (GetQueryStringValue("debug").ToLowerInvariant())
  205. {
  206. case "docs":
  207. return GetDocsStartsWith(index);
  208. case "map":
  209. return GetIndexMappedResult(index);
  210. case "reduce":
  211. return GetIndexReducedResult(index);
  212. case "schedules":
  213. return GetIndexScheduledReduces(index);
  214. case "keys":
  215. return GetIndexKeysStats(index);
  216. case "entries":
  217. return GetIndexEntries(index);
  218. case "stats":
  219. return GetIndexStats(index);
  220. default:
  221. return GetMessageWithString("Unknown debug option " + GetQueryStringValue("debug"), HttpStatusCode.BadRequest);
  222. }
  223. }
  224. private HttpResponseMessage GetDocsStartsWith(string index)
  225. {
  226. var definition = Database.IndexDefinitionStorage.GetIndexDefinition(index);
  227. if (definition == null)
  228. return GetEmptyMessage(HttpStatusCode.NotFound);
  229. var prefix = GetQueryStringValue("startsWith");
  230. List<string> keys = null;
  231. Database.TransactionalStorage.Batch(accessor =>
  232. {
  233. keys = accessor.MapReduce.GetSourcesForIndexForDebug(definition.IndexId, prefix, GetPageSize(Database.Configuration.MaxPageSize))
  234. .ToList();
  235. });
  236. return GetMessageWithObject(new { keys.Count, Results = keys });
  237. }
  238. private HttpResponseMessage GetIndexMappedResult(string index)
  239. {
  240. var definition = Database.IndexDefinitionStorage.GetIndexDefinition(index);
  241. if (definition == null)
  242. return GetEmptyMessage(HttpStatusCode.NotFound);
  243. var key = GetQueryStringValue("key");
  244. if (string.IsNullOrEmpty(key))
  245. {
  246. var startsWith = GetQueryStringValue("startsWith");
  247. var sourceId = GetQueryStringValue("sourceId");
  248. List<string> keys = null;
  249. Database.TransactionalStorage.Batch(accessor =>
  250. {
  251. keys = accessor.MapReduce.GetKeysForIndexForDebug(definition.IndexId, startsWith, sourceId, GetStart(), GetPageSize(Database.Configuration.MaxPageSize))
  252. .ToList();
  253. });
  254. return GetMessageWithObject(new
  255. {
  256. keys.Count,
  257. Results = keys
  258. });
  259. }
  260. List<MappedResultInfo> mappedResult = null;
  261. Database.TransactionalStorage.Batch(accessor =>
  262. {
  263. mappedResult = accessor.MapReduce.GetMappedResultsForDebug(definition.IndexId, key, GetStart(), GetPageSize(Database.Configuration.MaxPageSize))
  264. .ToList();
  265. });
  266. return GetMessageWithObject(new
  267. {
  268. mappedResult.Count,
  269. Results = mappedResult
  270. });
  271. }
  272. private HttpResponseMessage GetExplanation(string index)
  273. {
  274. var dynamicIndex = index.StartsWith("dynamic/", StringComparison.OrdinalIgnoreCase) ||
  275. index.Equals("dynamic", StringComparison.OrdinalIgnoreCase);
  276. if (dynamicIndex == false)
  277. {
  278. return GetMessageWithObject(new
  279. {
  280. Error = "Explain can only work on dynamic indexes"
  281. }, HttpStatusCode.BadRequest);
  282. }
  283. var indexQuery = GetIndexQuery(Database.Configuration.MaxPageSize);
  284. string entityName = null;
  285. if (index.StartsWith("dynamic/", StringComparison.OrdinalIgnoreCase))
  286. entityName = index.Substring("dynamic/".Length);
  287. var explanations = Database.ExplainDynamicIndexSelection(entityName, indexQuery);
  288. return GetMessageWithObject(explanations);
  289. }
  290. private HttpResponseMessage GetIndexQueryResult(string index, CancellationToken token)
  291. {
  292. Etag indexEtag;
  293. var msg = GetEmptyMessage();
  294. var queryResult = ExecuteQuery(index, out indexEtag, msg, token);
  295. if (queryResult == null)
  296. return msg;
  297. var includes = GetQueryStringValues("include") ?? new string[0];
  298. var loadedIds = new HashSet<string>(
  299. queryResult.Results
  300. .Where(x => x != null && x["@metadata"] != null)
  301. .Select(x => x["@metadata"].Value<string>("@id"))
  302. .Where(x => x != null)
  303. );
  304. var command = new AddIncludesCommand(Database, GetRequestTransaction(),
  305. (etag, doc) => queryResult.Includes.Add(doc), includes, loadedIds);
  306. foreach (var result in queryResult.Results)
  307. {
  308. command.Execute(result);
  309. }
  310. command.AlsoInclude(queryResult.IdsToInclude);
  311. return GetMessageWithObject(queryResult, queryResult.NonAuthoritativeInformation ? HttpStatusCode.NonAuthoritativeInformation : HttpStatusCode.OK, indexEtag);
  312. }
  313. private QueryResultWithIncludes ExecuteQuery(string index, out Etag indexEtag, HttpResponseMessage msg, CancellationToken token)
  314. {
  315. var indexQuery = GetIndexQuery(Database.Configuration.MaxPageSize);
  316. RewriteDateQueriesFromOldClients(indexQuery);
  317. var sp = Stopwatch.StartNew();
  318. var result = index.StartsWith("dynamic/", StringComparison.OrdinalIgnoreCase) || index.Equals("dynamic", StringComparison.OrdinalIgnoreCase) ?
  319. PerformQueryAgainstDynamicIndex(index, indexQuery, out indexEtag, msg, token) :
  320. PerformQueryAgainstExistingIndex(index, indexQuery, out indexEtag, msg, token);
  321. sp.Stop();
  322. Log.Debug(() =>
  323. {
  324. var sb = new StringBuilder();
  325. ReportQuery(sb, indexQuery, sp, result);
  326. return sb.ToString();
  327. });
  328. AddRequestTraceInfo(sb => ReportQuery(sb, indexQuery, sp, result));
  329. return result;
  330. }
  331. private static void ReportQuery(StringBuilder sb, IndexQuery indexQuery, Stopwatch sp, QueryResultWithIncludes result)
  332. {
  333. sb.Append("\tQuery: ")
  334. .Append(indexQuery.Query)
  335. .AppendLine();
  336. sb.Append("\t").AppendFormat("Time: {0:#,#;;0} ms", sp.ElapsedMilliseconds).AppendLine();
  337. if (result == null)
  338. return;
  339. sb.Append("\tIndex: ")
  340. .AppendLine(result.IndexName);
  341. sb.Append("\t").AppendFormat("Results: {0:#,#;;0} returned out of {1:#,#;;0} total.", result.Results.Count, result.TotalResults).AppendLine();
  342. }
  343. private QueryResultWithIncludes PerformQueryAgainstExistingIndex(string index, IndexQuery indexQuery, out Etag indexEtag, HttpResponseMessage msg, CancellationToken token)
  344. {
  345. indexEtag = Database.Indexes.GetIndexEtag(index, null, indexQuery.ResultsTransformer);
  346. if (MatchEtag(indexEtag))
  347. {
  348. Database.IndexStorage.MarkCachedQuery(index);
  349. msg.StatusCode = HttpStatusCode.NotModified;
  350. return null;
  351. }
  352. var queryResult = Database.Queries.Query(index, indexQuery, token);
  353. indexEtag = Database.Indexes.GetIndexEtag(index, queryResult.ResultEtag, indexQuery.ResultsTransformer);
  354. return queryResult;
  355. }
  356. private QueryResultWithIncludes PerformQueryAgainstDynamicIndex(string index, IndexQuery indexQuery, out Etag indexEtag, HttpResponseMessage msg, CancellationToken token)
  357. {
  358. string entityName;
  359. var dynamicIndexName = GetDynamicIndexName(index, indexQuery, out entityName);
  360. if (dynamicIndexName != null && Database.IndexStorage.HasIndex(dynamicIndexName))
  361. {
  362. indexEtag = Database.Indexes.GetIndexEtag(dynamicIndexName, null, indexQuery.ResultsTransformer);
  363. if (MatchEtag(indexEtag))
  364. {
  365. Database.IndexStorage.MarkCachedQuery(dynamicIndexName);
  366. msg.StatusCode = HttpStatusCode.NotModified;
  367. return null;
  368. }
  369. }
  370. if (dynamicIndexName == null && // would have to create a dynamic index
  371. Database.Configuration.CreateAutoIndexesForAdHocQueriesIfNeeded == false) // but it is disabled
  372. {
  373. indexEtag = Etag.InvalidEtag;
  374. var explanations = Database.ExplainDynamicIndexSelection(entityName, indexQuery);
  375. msg.StatusCode = HttpStatusCode.BadRequest;
  376. var target = entityName == null ? "all documents" : entityName + " documents";
  377. msg.Content = JsonContent(RavenJToken.FromObject(
  378. new
  379. {
  380. Error =
  381. "Executing the query " + indexQuery.Query + " on " + target +
  382. " require creation of temporary index, and it has been explicitly disabled.",
  383. Explanations = explanations
  384. }));
  385. return null;
  386. }
  387. var queryResult = Database.ExecuteDynamicQuery(entityName, indexQuery, token);
  388. // have to check here because we might be getting the index etag just
  389. // as we make a switch from temp to auto, and we need to refresh the etag
  390. // if that is the case. This can also happen when the optimizer
  391. // decided to switch indexes for a query.
  392. indexEtag = (dynamicIndexName == null || queryResult.IndexName == dynamicIndexName)
  393. ? Database.Indexes.GetIndexEtag(queryResult.IndexName, queryResult.ResultEtag, indexQuery.ResultsTransformer)
  394. : Etag.InvalidEtag;
  395. return queryResult;
  396. }
  397. private string GetDynamicIndexName(string index, IndexQuery indexQuery, out string entityName)
  398. {
  399. entityName = null;
  400. if (index.StartsWith("dynamic/", StringComparison.OrdinalIgnoreCase))
  401. entityName = index.Substring("dynamic/".Length);
  402. var dynamicIndexName = Database.FindDynamicIndexName(entityName, indexQuery);
  403. return dynamicIndexName;
  404. }
  405. static Regex oldDateTimeFormat = new Regex(@"(\:|\[|{|TO\s) \s* (\d{17})", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace);
  406. private void RewriteDateQueriesFromOldClients(IndexQuery indexQuery)
  407. {
  408. var clientVersion = GetQueryStringValue("Raven-Client-Version");
  409. if (string.IsNullOrEmpty(clientVersion) == false) // new client
  410. return;
  411. var matches = oldDateTimeFormat.Matches(indexQuery.Query);
  412. if (matches.Count == 0)
  413. return;
  414. var builder = new StringBuilder(indexQuery.Query);
  415. for (int i = matches.Count - 1; i >= 0; i--) // working in reverse so as to avoid invalidating previous indexes
  416. {
  417. var dateTimeString = matches[i].Groups[2].Value;
  418. DateTime time;
  419. if (DateTime.TryParseExact(dateTimeString, "yyyyMMddHHmmssfff", CultureInfo.InvariantCulture, DateTimeStyles.None, out time) == false)
  420. continue;
  421. builder.Remove(matches[i].Groups[2].Index, matches[i].Groups[2].Length);
  422. var newDateTimeFormat = time.ToString(Default.DateTimeFormatsToWrite);
  423. builder.Insert(matches[i].Groups[2].Index, newDateTimeFormat);
  424. }
  425. indexQuery.Query = builder.ToString();
  426. }
  427. private HttpResponseMessage HandleIndexLockModeChange(string index)
  428. {
  429. var lockModeStr = GetQueryStringValue("mode");
  430. IndexLockMode indexLockMode;
  431. if (Enum.TryParse(lockModeStr, out indexLockMode) == false)
  432. return GetMessageWithString("Cannot understand index lock mode: " + lockModeStr, HttpStatusCode.BadRequest);
  433. var indexDefinition = Database.IndexDefinitionStorage.GetIndexDefinition(index);
  434. if (indexDefinition == null)
  435. return GetMessageWithString("Cannot find index : " + index, HttpStatusCode.NotFound);
  436. var definition = indexDefinition.Clone();
  437. definition.LockMode = indexLockMode;
  438. Database.IndexDefinitionStorage.UpdateIndexDefinitionWithoutUpdatingCompiledIndex(definition);
  439. return GetEmptyMessage();
  440. }
  441. private HttpResponseMessage GetIndexReducedResult(string index)
  442. {
  443. var definition = Database.IndexDefinitionStorage.GetIndexDefinition(index);
  444. if (definition == null)
  445. return GetEmptyMessage(HttpStatusCode.NotFound);
  446. var key = GetQueryStringValue("key");
  447. if (string.IsNullOrEmpty(key))
  448. return GetMessageWithString("Query string argument 'key' is required", HttpStatusCode.BadRequest);
  449. int level;
  450. if (int.TryParse(GetQueryStringValue("level"), out level) == false || (level != 1 && level != 2))
  451. return GetMessageWithString("Query string argument 'level' is required and must be 1 or 2",
  452. HttpStatusCode.BadRequest);
  453. List<MappedResultInfo> mappedResult = null;
  454. Database.TransactionalStorage.Batch(accessor =>
  455. {
  456. mappedResult = accessor.MapReduce.GetReducedResultsForDebug(definition.IndexId, key, level, GetStart(), GetPageSize(Database.Configuration.MaxPageSize))
  457. .ToList();
  458. });
  459. return GetMessageWithObject(new
  460. {
  461. mappedResult.Count,
  462. Results = mappedResult
  463. });
  464. }
  465. private HttpResponseMessage GetIndexScheduledReduces(string index)
  466. {
  467. List<ScheduledReductionDebugInfo> mappedResult = null;
  468. Database.TransactionalStorage.Batch(accessor =>
  469. {
  470. var instance = Database.IndexStorage.GetIndexInstance(index);
  471. mappedResult = accessor.MapReduce.GetScheduledReductionForDebug(instance.indexId, GetStart(), GetPageSize(Database.Configuration.MaxPageSize))
  472. .ToList();
  473. });
  474. return GetMessageWithObject(new
  475. {
  476. mappedResult.Count,
  477. Results = mappedResult
  478. });
  479. }
  480. private HttpResponseMessage GetIndexKeysStats(string index)
  481. {
  482. var definition = Database.IndexDefinitionStorage.GetIndexDefinition(index);
  483. if (definition == null)
  484. {
  485. return GetEmptyMessage(HttpStatusCode.NotFound);
  486. }
  487. List<ReduceKeyAndCount> keys = null;
  488. Database.TransactionalStorage.Batch(accessor =>
  489. {
  490. keys = accessor.MapReduce.GetKeysStats(definition.IndexId,
  491. GetStart(),
  492. GetPageSize(Database.Configuration.MaxPageSize))
  493. .ToList();
  494. });
  495. return GetMessageWithObject(new
  496. {
  497. keys.Count,
  498. Results = keys
  499. });
  500. }
  501. private HttpResponseMessage GetIndexEntries(string index)
  502. {
  503. var indexQuery = GetIndexQuery(Database.Configuration.MaxPageSize);
  504. var reduceKeysArray = GetQueryStringValue("reduceKeys");
  505. if (string.IsNullOrEmpty(indexQuery.Query) == false && string.IsNullOrEmpty(reduceKeysArray))
  506. {
  507. return GetMessageWithObject(new
  508. {
  509. Error = "Cannot specity 'query' and 'reducedKeys' at the same time"
  510. }, HttpStatusCode.BadRequest);
  511. }
  512. List<string> reduceKeys = null;
  513. if (reduceKeysArray != null)
  514. {
  515. reduceKeys = reduceKeysArray.Split(',').Select(x => x.Trim()).ToList();
  516. // overwrite indexQueryPagining as __reduce_key field is not indexed, and we don't have simple method to obtain column alias
  517. indexQuery.Start = 0;
  518. indexQuery.PageSize = int.MaxValue;
  519. }
  520. var totalResults = new Reference<int>();
  521. var isDynamic = index.StartsWith("dynamic/", StringComparison.OrdinalIgnoreCase)
  522. || index.Equals("dynamic", StringComparison.OrdinalIgnoreCase);
  523. if (isDynamic)
  524. return GetIndexEntriesForDynamicIndex(index, indexQuery, reduceKeys, totalResults);
  525. return GetIndexEntriesForExistingIndex(index, indexQuery, reduceKeys, totalResults);
  526. }
  527. private HttpResponseMessage GetIndexEntriesForDynamicIndex(string index, IndexQuery indexQuery, List<string> reduceKeys, Reference<int> totalResults)
  528. {
  529. string entityName;
  530. var dynamicIndexName = GetDynamicIndexName(index, indexQuery, out entityName);
  531. if (dynamicIndexName == null)
  532. return GetEmptyMessage(HttpStatusCode.NotFound);
  533. return GetIndexEntriesForExistingIndex(dynamicIndexName, indexQuery, reduceKeys, totalResults);
  534. }
  535. private HttpResponseMessage GetIndexEntriesForExistingIndex(string index, IndexQuery indexQuery, List<string> reduceKeys, Reference<int> totalResults)
  536. {
  537. var results = Database
  538. .IndexStorage
  539. .IndexEntires(index, indexQuery, reduceKeys, Database.IndexQueryTriggers, totalResults)
  540. .ToArray();
  541. Tuple<DateTime, Etag> indexTimestamp = null;
  542. bool isIndexStale = false;
  543. var definition = Database.IndexDefinitionStorage.GetIndexDefinition(index);
  544. Database.TransactionalStorage.Batch(
  545. accessor =>
  546. {
  547. isIndexStale = accessor.Staleness.IsIndexStale(definition.IndexId, indexQuery.Cutoff, indexQuery.CutoffEtag);
  548. if (isIndexStale == false && indexQuery.Cutoff == null && indexQuery.CutoffEtag == null)
  549. {
  550. var indexInstance = Database.IndexStorage.GetIndexInstance(index);
  551. isIndexStale = isIndexStale || (indexInstance != null && indexInstance.IsMapIndexingInProgress);
  552. }
  553. indexTimestamp = accessor.Staleness.IndexLastUpdatedAt(definition.IndexId);
  554. });
  555. var indexEtag = Database.Indexes.GetIndexEtag(index, null, indexQuery.ResultsTransformer);
  556. return GetMessageWithObject(
  557. new
  558. {
  559. Count = results.Length,
  560. Results = results,
  561. Includes = new string[0],
  562. IndexTimestamp = indexTimestamp.Item1,
  563. IndexEtag = indexTimestamp.Item2,
  564. TotalResults = totalResults.Value,
  565. SkippedResults = 0,
  566. NonAuthoritativeInformation = false,
  567. ResultEtag = indexEtag,
  568. IsStale = isIndexStale,
  569. IndexName = index,
  570. LastQueryTime = Database.IndexStorage.GetLastQueryTime(index)
  571. }, HttpStatusCode.OK, indexEtag);
  572. }
  573. private HttpResponseMessage GetIndexStats(string index)
  574. {
  575. IndexStats stats = null;
  576. var instance = Database.IndexStorage.GetIndexInstance(index);
  577. Database.TransactionalStorage.Batch(accessor =>
  578. {
  579. stats = accessor.Indexing.GetIndexStats(instance.indexId);
  580. });
  581. if (stats == null)
  582. return GetEmptyMessage(HttpStatusCode.NotFound);
  583. stats.LastQueryTimestamp = Database.IndexStorage.GetLastQueryTime(instance.indexId);
  584. stats.Performance = Database.IndexStorage.GetIndexingPerformance(instance.indexId);
  585. return GetMessageWithObject(stats);
  586. }
  587. }
  588. }