PageRenderTime 65ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 1ms

/ToMigrate/Raven.Database/Actions/IndexActions.cs

http://github.com/ayende/ravendb
C# | 869 lines | 704 code | 122 blank | 43 comment | 107 complexity | 5bff0fcbc564d25be91929303d8c4280 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="IndexActions.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.Globalization;
  10. using System.IO;
  11. using System.Linq;
  12. using System.Runtime.CompilerServices;
  13. using System.Text;
  14. using System.Threading;
  15. using System.Threading.Tasks;
  16. using Raven.Abstractions;
  17. using Raven.Abstractions.Data;
  18. using Raven.Abstractions.Exceptions;
  19. using Raven.Abstractions.Indexing;
  20. using Raven.Abstractions.Logging;
  21. using Raven.Abstractions.Util.Encryptors;
  22. using Raven.Database.Data;
  23. using Raven.Database.Extensions;
  24. using Raven.Database.Impl;
  25. using Raven.Database.Indexing;
  26. using Raven.Database.Linq;
  27. using Raven.Database.Queries;
  28. using Raven.Database.Storage;
  29. using Raven.Database.Util;
  30. using Raven.Json.Linq;
  31. using Sparrow;
  32. namespace Raven.Database.Actions
  33. {
  34. public class IndexActions : ActionsBase
  35. {
  36. private volatile bool isPrecomputedBatchForNewIndexIsRunning;
  37. private readonly object precomputedLock = new object();
  38. public IndexActions(DocumentDatabase database, SizeLimitedConcurrentDictionary<string, TouchedDocumentInfo> recentTouches, IUuidGenerator uuidGenerator, ILog log)
  39. : base(database, recentTouches, uuidGenerator, log)
  40. {
  41. }
  42. internal IndexDefinition[] Definitions
  43. {
  44. get { return Database.IndexDefinitionStorage.IndexDefinitions.Select(inx => inx.Value).ToArray(); }
  45. }
  46. public string[] GetIndexFields(string index)
  47. {
  48. var abstractViewGenerator = IndexDefinitionStorage.GetViewGenerator(index);
  49. return abstractViewGenerator == null ? new string[0] : abstractViewGenerator.Fields;
  50. }
  51. public Etag GetIndexEtag(string indexName, Etag previousEtag, string resultTransformer = null)
  52. {
  53. Etag lastDocEtag = Etag.Empty;
  54. Etag lastIndexedEtag = null;
  55. Etag lastReducedEtag = null;
  56. bool isStale = false;
  57. int touchCount = 0;
  58. TransactionalStorage.Batch(accessor =>
  59. {
  60. var indexInstance = Database.IndexStorage.GetIndexInstance(indexName);
  61. if (indexInstance == null)
  62. return;
  63. isStale = (indexInstance.IsMapIndexingInProgress) ||
  64. accessor.Staleness.IsIndexStale(indexInstance.indexId, null, null);
  65. lastDocEtag = accessor.Staleness.GetMostRecentDocumentEtag();
  66. var indexStats = accessor.Indexing.GetIndexStats(indexInstance.indexId);
  67. if (indexStats != null)
  68. {
  69. lastReducedEtag = indexStats.LastReducedEtag;
  70. lastIndexedEtag = indexStats.LastIndexedEtag;
  71. }
  72. touchCount = accessor.Staleness.GetIndexTouchCount(indexInstance.indexId);
  73. });
  74. var indexDefinition = GetIndexDefinition(indexName);
  75. if (indexDefinition == null)
  76. return Etag.Empty; // this ensures that we will get the normal reaction of IndexNotFound later on.
  77. var list = new List<byte>();
  78. list.AddRange(indexDefinition.GetIndexHash());
  79. list.AddRange(Encoding.Unicode.GetBytes(indexName));
  80. if (string.IsNullOrWhiteSpace(resultTransformer) == false)
  81. {
  82. var abstractTransformer = IndexDefinitionStorage.GetTransformer(resultTransformer);
  83. if (abstractTransformer == null)
  84. throw new InvalidOperationException("The result transformer: " + resultTransformer + " was not found");
  85. list.AddRange(abstractTransformer.GetHashCodeBytes());
  86. }
  87. list.AddRange(lastDocEtag.ToByteArray());
  88. list.AddRange(BitConverter.GetBytes(touchCount));
  89. list.AddRange(BitConverter.GetBytes(isStale));
  90. if (lastReducedEtag != null)
  91. {
  92. list.AddRange(lastReducedEtag.ToByteArray());
  93. }
  94. if (lastIndexedEtag != null)
  95. {
  96. list.AddRange(lastIndexedEtag.ToByteArray());
  97. }
  98. list.AddRange(BitConverter.GetBytes(UuidGenerator.LastDocumentTransactionEtag));
  99. var indexEtag = Etag.FromHash(Hashing.Metro128.Calculate(list.ToArray()));
  100. if (previousEtag != null && previousEtag != indexEtag)
  101. {
  102. // the index changed between the time when we got it and the time
  103. // we actually call this, we need to return something random so that
  104. // the next time we won't get 304
  105. return Etag.InvalidEtag;
  106. }
  107. return indexEtag;
  108. }
  109. internal void CheckReferenceBecauseOfDocumentUpdate(string key, IStorageActionsAccessor actions, string[] participatingIds = null)
  110. {
  111. TouchedDocumentInfo touch;
  112. RecentTouches.TryRemove(key, out touch);
  113. Stopwatch sp = null;
  114. int count = 0;
  115. using (Database.TransactionalStorage.DisableBatchNesting())
  116. {
  117. // in external transaction number of references will be >= from current transaction references
  118. Database.TransactionalStorage.Batch(externalActions =>
  119. {
  120. var referencingKeys = externalActions.Indexing.GetDocumentsReferencing(key);
  121. if (participatingIds != null)
  122. referencingKeys = referencingKeys.Except(participatingIds);
  123. foreach (var referencing in referencingKeys)
  124. {
  125. Etag preTouchEtag = null;
  126. Etag afterTouchEtag = null;
  127. try
  128. {
  129. count++;
  130. actions.Documents.TouchDocument(referencing, out preTouchEtag, out afterTouchEtag);
  131. if (afterTouchEtag != null)
  132. {
  133. var docMetadata = actions.Documents.DocumentMetadataByKey(referencing);
  134. if (docMetadata != null)
  135. {
  136. var entityName = docMetadata.Metadata.Value<string>(Constants.RavenEntityName);
  137. if (string.IsNullOrEmpty(entityName) == false)
  138. Database.LastCollectionEtags.Update(entityName, afterTouchEtag);
  139. }
  140. }
  141. }
  142. catch (ConcurrencyException)
  143. {
  144. }
  145. if (preTouchEtag == null || afterTouchEtag == null)
  146. continue;
  147. if (actions.General.MaybePulseTransaction())
  148. {
  149. if (sp == null)
  150. sp = Stopwatch.StartNew();
  151. if (sp.Elapsed >= TimeSpan.FromSeconds(30))
  152. {
  153. throw new TimeoutException("Early failure when checking references for document '" + key + "', we waited over 30 seconds to touch all of the documents referenced by this document.\r\n" +
  154. "The operation (and transaction) has been aborted, since to try longer (we already touched " + count + " documents) risk a thread abort.\r\n" +
  155. "Consider restructuring your indexes to avoid LoadDocument on such a popular document.");
  156. }
  157. }
  158. RecentTouches.Set(referencing, new TouchedDocumentInfo
  159. {
  160. PreTouchEtag = preTouchEtag,
  161. TouchedEtag = afterTouchEtag
  162. });
  163. }
  164. });
  165. }
  166. }
  167. private static void IsIndexNameValid(string name)
  168. {
  169. var error = string.Format("Index name {0} not permitted. ", name).Replace("//", "__");
  170. if (name.StartsWith("dynamic/", StringComparison.OrdinalIgnoreCase))
  171. {
  172. throw new ArgumentException(error + "Index names starting with dynamic_ or dynamic/ are reserved!", "name");
  173. }
  174. if (name.Equals("dynamic", StringComparison.OrdinalIgnoreCase))
  175. {
  176. throw new ArgumentException(error + "Index name dynamic is reserved!", "name");
  177. }
  178. if (name.Contains("//"))
  179. {
  180. throw new ArgumentException(error + "Index name cannot contain // (double slashes)", "name");
  181. }
  182. }
  183. public bool IndexHasChanged(string name, IndexDefinition definition)
  184. {
  185. if (name == null)
  186. throw new ArgumentNullException("name");
  187. name = name.Trim();
  188. IsIndexNameValid(name);
  189. var existingIndex = IndexDefinitionStorage.GetIndexDefinition(name);
  190. if (existingIndex == null)
  191. return true;
  192. var creationOption = FindIndexCreationOptions(definition, ref name);
  193. return creationOption != IndexCreationOptions.Noop;
  194. }
  195. // only one index can be created at any given time
  196. // the method already handle attempts to create the same index, so we don't have to
  197. // worry about this.
  198. [MethodImpl(MethodImplOptions.Synchronized)]
  199. public string PutIndex(string name, IndexDefinition definition)
  200. {
  201. return PutIndexInternal(name, definition);
  202. }
  203. private string PutIndexInternal(string name, IndexDefinition definition, bool disableIndexBeforePut = false, bool isUpdateBySideSide = false, IndexCreationOptions? creationOptions = null)
  204. {
  205. if (name == null)
  206. throw new ArgumentNullException("name");
  207. name = name.Trim();
  208. IsIndexNameValid(name);
  209. var existingIndex = IndexDefinitionStorage.GetIndexDefinition(name);
  210. if (existingIndex != null)
  211. {
  212. switch (existingIndex.LockMode)
  213. {
  214. case IndexLockMode.SideBySide:
  215. if (isUpdateBySideSide == false)
  216. {
  217. Log.Info("Index {0} not saved because it might be only updated by side-by-side index");
  218. throw new InvalidOperationException("Can not overwrite locked index: " + name + ". This index can be only updated by side-by-side index.");
  219. }
  220. break;
  221. case IndexLockMode.LockedIgnore:
  222. Log.Info("Index {0} not saved because it was lock (with ignore)", name);
  223. return null;
  224. case IndexLockMode.LockedError:
  225. throw new InvalidOperationException("Can not overwrite locked index: " + name);
  226. }
  227. }
  228. AssertAnalyzersValid(definition);
  229. switch (creationOptions ?? FindIndexCreationOptions(definition, ref name))
  230. {
  231. case IndexCreationOptions.Noop:
  232. return null;
  233. case IndexCreationOptions.UpdateWithoutUpdatingCompiledIndex:
  234. // ensure that the code can compile
  235. new DynamicViewCompiler(definition.Name, definition, Database.Extensions, IndexDefinitionStorage.IndexDefinitionsPath, Database.Configuration).GenerateInstance();
  236. IndexDefinitionStorage.UpdateIndexDefinitionWithoutUpdatingCompiledIndex(definition);
  237. return null;
  238. case IndexCreationOptions.Update:
  239. // ensure that the code can compile
  240. new DynamicViewCompiler(definition.Name, definition, Database.Extensions, IndexDefinitionStorage.IndexDefinitionsPath, Database.Configuration).GenerateInstance();
  241. DeleteIndex(name);
  242. break;
  243. }
  244. PutNewIndexIntoStorage(name, definition, disableIndexBeforePut);
  245. WorkContext.ClearErrorsFor(name);
  246. TransactionalStorage.ExecuteImmediatelyOrRegisterForSynchronization(() => Database.Notifications.RaiseNotifications(new IndexChangeNotification
  247. {
  248. Name = name,
  249. Type = IndexChangeTypes.IndexAdded,
  250. Version = definition.IndexVersion
  251. }));
  252. return name;
  253. }
  254. [MethodImpl(MethodImplOptions.Synchronized)]
  255. public string[] PutIndexes(IndexToAdd[] indexesToAdd)
  256. {
  257. var createdIndexes = new List<string>();
  258. var prioritiesList = new List<IndexingPriority>();
  259. try
  260. {
  261. foreach (var indexToAdd in indexesToAdd)
  262. {
  263. var nameToAdd = PutIndexInternal(indexToAdd.Name, indexToAdd.Definition, disableIndexBeforePut: true);
  264. if (nameToAdd == null)
  265. continue;
  266. createdIndexes.Add(nameToAdd);
  267. prioritiesList.Add(indexToAdd.Priority);
  268. }
  269. var indexesIds = createdIndexes.Select(x => Database.IndexStorage.GetIndexInstance(x).indexId).ToArray();
  270. Database.TransactionalStorage.Batch(accessor => accessor.Indexing.SetIndexesPriority(indexesIds, prioritiesList.ToArray()));
  271. for (var i = 0; i < createdIndexes.Count; i++)
  272. {
  273. var index = createdIndexes[i];
  274. var priority = prioritiesList[i];
  275. var instance = Database.IndexStorage.GetIndexInstance(index);
  276. instance.Priority = priority;
  277. }
  278. return createdIndexes.ToArray();
  279. }
  280. catch (Exception e)
  281. {
  282. Log.WarnException("Could not create index batch", e);
  283. foreach (var index in createdIndexes)
  284. {
  285. DeleteIndex(index);
  286. }
  287. throw;
  288. }
  289. }
  290. [MethodImpl(MethodImplOptions.Synchronized)]
  291. public SideBySideIndexInfo[] PutSideBySideIndexes(IndexToAdd[] indexesToAdd)
  292. {
  293. var createdIndexes = new List<SideBySideIndexInfo>();
  294. var prioritiesList = new List<IndexingPriority>();
  295. try
  296. {
  297. foreach (var indexToAdd in indexesToAdd)
  298. {
  299. var originalIndexName = indexToAdd.Name.Trim();
  300. var indexName = Constants.SideBySideIndexNamePrefix + originalIndexName;
  301. var isSideBySide = true;
  302. IndexCreationOptions? creationOptions = null;
  303. //if there is no existing side by side index, we might need to update the old index
  304. if (IndexDefinitionStorage.GetIndexDefinition(indexName) == null)
  305. {
  306. var originalIndexCreationOptions = FindIndexCreationOptions(indexToAdd.Definition, ref originalIndexName);
  307. switch (originalIndexCreationOptions)
  308. {
  309. case IndexCreationOptions.Noop:
  310. continue;
  311. case IndexCreationOptions.Create:
  312. case IndexCreationOptions.UpdateWithoutUpdatingCompiledIndex:
  313. //cases in which we don't need to create a side by side index:
  314. //1) index doesn't exist => need to create a new regular index
  315. //2) there is an existing index and we need to update its definition without reindexing
  316. indexName = originalIndexName;
  317. isSideBySide = false;
  318. creationOptions = originalIndexCreationOptions;
  319. break;
  320. }
  321. }
  322. var nameToAdd = PutIndexInternal(indexName, indexToAdd.Definition, disableIndexBeforePut: true, isUpdateBySideSide: true, creationOptions: creationOptions);
  323. if (nameToAdd == null)
  324. continue;
  325. createdIndexes.Add(new SideBySideIndexInfo
  326. {
  327. OriginalName = originalIndexName,
  328. Name = nameToAdd,
  329. IsSideBySide = isSideBySide
  330. });
  331. prioritiesList.Add(indexToAdd.Priority);
  332. }
  333. var indexesIds = createdIndexes.Select(x => Database.IndexStorage.GetIndexInstance(x.Name).indexId).ToArray();
  334. Database.TransactionalStorage.Batch(accessor => accessor.Indexing.SetIndexesPriority(indexesIds, prioritiesList.ToArray()));
  335. for (var i = 0; i < createdIndexes.Count; i++)
  336. {
  337. var index = createdIndexes[i].Name;
  338. var priority = prioritiesList[i];
  339. var instance = Database.IndexStorage.GetIndexInstance(index);
  340. instance.Priority = priority;
  341. }
  342. return createdIndexes.ToArray();
  343. }
  344. catch (Exception e)
  345. {
  346. Log.WarnException("Could not create index batch", e);
  347. foreach (var index in createdIndexes)
  348. {
  349. DeleteIndex(index.Name);
  350. }
  351. throw;
  352. }
  353. }
  354. public class SideBySideIndexInfo
  355. {
  356. public string OriginalName { get; set; }
  357. public string Name { get; set; }
  358. public bool IsSideBySide { get; set; }
  359. }
  360. private static void AssertAnalyzersValid(IndexDefinition indexDefinition)
  361. {
  362. foreach (var analyzer in indexDefinition.Analyzers)
  363. {
  364. //this throws if the type cannot be found
  365. IndexingExtensions.GetAnalyzerType(analyzer.Key, analyzer.Value);
  366. }
  367. }
  368. internal void PutNewIndexIntoStorage(string name, IndexDefinition definition, bool disableIndex = false)
  369. {
  370. Debug.Assert(Database.IndexStorage != null);
  371. Debug.Assert(TransactionalStorage != null);
  372. Debug.Assert(WorkContext != null);
  373. Index index = null;
  374. TransactionalStorage.Batch(actions =>
  375. {
  376. var maxId = 0;
  377. if (Database.IndexStorage.Indexes.Length > 0)
  378. {
  379. maxId = Database.IndexStorage.Indexes.Max();
  380. }
  381. definition.IndexId = (int)Database.Documents.GetNextIdentityValueWithoutOverwritingOnExistingDocuments("IndexId", actions);
  382. if (definition.IndexId <= maxId)
  383. {
  384. actions.General.SetIdentityValue("IndexId", maxId + 1);
  385. definition.IndexId = (int)Database.Documents.GetNextIdentityValueWithoutOverwritingOnExistingDocuments("IndexId", actions);
  386. }
  387. IndexDefinitionStorage.RegisterNewIndexInThisSession(name, definition);
  388. // this has to happen in this fashion so we will expose the in memory status after the commit, but
  389. // before the rest of the world is notified about this.
  390. IndexDefinitionStorage.CreateAndPersistIndex(definition);
  391. Database.IndexStorage.CreateIndexImplementation(definition);
  392. index = Database.IndexStorage.GetIndexInstance(definition.IndexId);
  393. // If we execute multiple indexes at once and want to activate them all at once we will disable the index from the endpoint
  394. if (disableIndex)
  395. index.Priority = IndexingPriority.Disabled;
  396. //ensure that we don't start indexing it right away, let the precomputation run first, if applicable
  397. index.IsMapIndexingInProgress = true;
  398. if (definition.IsTestIndex)
  399. index.MarkQueried(); // test indexes should be mark queried, so the cleanup task would not delete them immediately
  400. InvokeSuggestionIndexing(name, definition, index);
  401. actions.Indexing.AddIndex(definition.IndexId, definition.IsMapReduce);
  402. });
  403. Debug.Assert(index != null);
  404. Action precomputeTask = null;
  405. if (WorkContext.RunIndexing &&
  406. name.Equals(Constants.DocumentsByEntityNameIndex, StringComparison.InvariantCultureIgnoreCase) == false &&
  407. Database.IndexStorage.HasIndex(Constants.DocumentsByEntityNameIndex) && isPrecomputedBatchForNewIndexIsRunning == false)
  408. {
  409. // optimization of handling new index creation when the number of document in a database is significantly greater than
  410. // number of documents that this index applies to - let us use built-in RavenDocumentsByEntityName to get just appropriate documents
  411. precomputeTask = TryCreateTaskForApplyingPrecomputedBatchForNewIndex(index, definition);
  412. }
  413. else
  414. {
  415. index.IsMapIndexingInProgress = false;// we can't apply optimization, so we'll make it eligible for running normally
  416. }
  417. // The act of adding it here make it visible to other threads
  418. // we have to do it in this way so first we prepare all the elements of the
  419. // index, then we add it to the storage in a way that make it public
  420. IndexDefinitionStorage.AddIndex(definition.IndexId, definition);
  421. // we start the precomuteTask _after_ we finished adding the index
  422. if (precomputeTask != null)
  423. {
  424. precomputeTask();
  425. }
  426. WorkContext.ShouldNotifyAboutWork(() => "PUT INDEX " + name);
  427. WorkContext.NotifyAboutWork();
  428. }
  429. private Action TryCreateTaskForApplyingPrecomputedBatchForNewIndex(Index index, IndexDefinition definition)
  430. {
  431. var generator = IndexDefinitionStorage.GetViewGenerator(definition.IndexId);
  432. if (generator.ForEntityNames.Count == 0 && index.IsTestIndex == false)
  433. {
  434. // we don't optimize if we don't have what to optimize _on_, we know this is going to return all docs.
  435. // no need to try to optimize that, then
  436. index.IsMapIndexingInProgress = false;
  437. return null;
  438. }
  439. lock (precomputedLock)
  440. {
  441. if (isPrecomputedBatchForNewIndexIsRunning)
  442. {
  443. index.IsMapIndexingInProgress = false;
  444. return null;
  445. }
  446. isPrecomputedBatchForNewIndexIsRunning = true;
  447. }
  448. try
  449. {
  450. var cts = new CancellationTokenSource();
  451. var task = new Task(() =>
  452. {
  453. try
  454. {
  455. ApplyPrecomputedBatchForNewIndex(index, generator, index.IsTestIndex == false ? Database.Configuration.Core.MaxNumberOfItemsToProcessInSingleBatch : Database.Configuration.Indexing.MaxNumberOfItemsToProcessInTestIndexes, cts);
  456. }
  457. catch (Exception e)
  458. {
  459. Log.Warn("Could not apply precomputed batch for index " + index, e);
  460. }
  461. finally
  462. {
  463. isPrecomputedBatchForNewIndexIsRunning = false;
  464. index.IsMapIndexingInProgress = false;
  465. WorkContext.ShouldNotifyAboutWork(() => "Precomputed indexing batch for " + index.PublicName + " is completed");
  466. WorkContext.NotifyAboutWork();
  467. }
  468. }, TaskCreationOptions.LongRunning);
  469. return () =>
  470. {
  471. try
  472. {
  473. task.Start();
  474. long id;
  475. Database
  476. .Tasks
  477. .AddTask(
  478. task,
  479. new TaskBasedOperationState(task),
  480. new TaskActions.PendingTaskDescription
  481. {
  482. StartTime = DateTime.UtcNow,
  483. Payload = index.PublicName,
  484. TaskType = TaskActions.PendingTaskType.NewIndexPrecomputedBatch
  485. },
  486. out id,
  487. cts);
  488. }
  489. catch (Exception)
  490. {
  491. index.IsMapIndexingInProgress = false;
  492. isPrecomputedBatchForNewIndexIsRunning = false;
  493. throw;
  494. }
  495. };
  496. }
  497. catch (Exception)
  498. {
  499. index.IsMapIndexingInProgress = false;
  500. isPrecomputedBatchForNewIndexIsRunning = false;
  501. throw;
  502. }
  503. }
  504. private void ApplyPrecomputedBatchForNewIndex(Index index, AbstractViewGenerator generator, int pageSize, CancellationTokenSource cts)
  505. {
  506. PrecomputedIndexingBatch result = null;
  507. var docsToIndex = new List<JsonDocument>();
  508. TransactionalStorage.Batch(actions =>
  509. {
  510. var query = GetQueryForAllMatchingDocumentsForIndex(generator);
  511. using (var linked = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, WorkContext.CancellationToken))
  512. using (var op = new QueryActions.DatabaseQueryOperation(Database, Constants.DocumentsByEntityNameIndex, new IndexQuery
  513. {
  514. Query = query,
  515. PageSize = pageSize
  516. }, actions, linked)
  517. {
  518. ShouldSkipDuplicateChecking = true
  519. })
  520. {
  521. op.Init();
  522. if (op.Header.TotalResults > Database.Configuration.Core.MaxNumberOfItemsToProcessInSingleBatch)
  523. {
  524. // we don't apply this optimization if the total number of results
  525. // to index is more than the max numbers to index in a single batch.
  526. // The idea here is that we need to keep the amount
  527. // of memory we use to a manageable level even when introducing a new index to a BIG
  528. // database
  529. try
  530. {
  531. cts.Cancel();
  532. // we have to run just a little bit of the query to properly setup the disposal
  533. op.Execute(o => { });
  534. }
  535. catch (OperationCanceledException)
  536. {
  537. }
  538. return;
  539. }
  540. if (Log.IsDebugEnabled)
  541. Log.Debug("For new index {0}, using precomputed indexing batch optimization for {1} docs", index,
  542. op.Header.TotalResults);
  543. op.Execute(document =>
  544. {
  545. var metadata = document.Value<RavenJObject>(Constants.Metadata);
  546. var key = metadata.Value<string>("@id");
  547. var etag = Etag.Parse(metadata.Value<string>("@etag"));
  548. var lastModified = DateTime.Parse(metadata.Value<string>(Constants.LastModified));
  549. document.Remove(Constants.Metadata);
  550. var doc = new JsonDocument
  551. {
  552. DataAsJson = document,
  553. Etag = etag,
  554. Key = key,
  555. LastModified = lastModified,
  556. SkipDeleteFromIndex = true,
  557. Metadata = metadata
  558. };
  559. docsToIndex.Add(doc);
  560. });
  561. result = new PrecomputedIndexingBatch
  562. {
  563. LastIndexed = op.Header.IndexEtag,
  564. LastModified = op.Header.IndexTimestamp,
  565. Documents = docsToIndex,
  566. Index = index
  567. };
  568. }
  569. });
  570. if (result != null && result.Documents != null && result.Documents.Count >= 0)
  571. {
  572. using (var linked = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, WorkContext.CancellationToken))
  573. {
  574. Database.IndexingExecuter.IndexPrecomputedBatch(result, linked.Token);
  575. if (index.IsTestIndex)
  576. TransactionalStorage.Batch(accessor => accessor.Indexing.TouchIndexEtag(index.IndexId));
  577. }
  578. }
  579. }
  580. private string GetQueryForAllMatchingDocumentsForIndex(AbstractViewGenerator generator)
  581. {
  582. var terms = new TermsQueryRunner(Database)
  583. .GetTerms(Constants.DocumentsByEntityNameIndex, "Tag", null, int.MaxValue);
  584. var sb = new StringBuilder();
  585. foreach (var entityName in generator.ForEntityNames)
  586. {
  587. bool added = false;
  588. foreach (var term in terms)
  589. {
  590. if (string.Equals(entityName, term, StringComparison.OrdinalIgnoreCase))
  591. {
  592. AppendTermToQuery(term, sb);
  593. added = true;
  594. }
  595. }
  596. if (added == false)
  597. AppendTermToQuery(entityName, sb);
  598. }
  599. return sb.ToString();
  600. }
  601. private static void AppendTermToQuery(string term, StringBuilder sb)
  602. {
  603. if (sb.Length != 0)
  604. sb.Append(" OR ");
  605. sb.Append("Tag:[[").Append(term).Append("]]");
  606. }
  607. private void InvokeSuggestionIndexing(string name, IndexDefinition definition, Index index)
  608. {
  609. foreach (var suggestion in definition.SuggestionsOptions)
  610. {
  611. var field = suggestion;
  612. var indexExtensionKey = MonoHttpUtility.UrlEncode(field);
  613. var suggestionQueryIndexExtension = new SuggestionQueryIndexExtension(
  614. index,
  615. WorkContext,
  616. Path.Combine(Database.Configuration.Core.IndexStoragePath, "Raven-Suggestions", name, indexExtensionKey),
  617. Database.Configuration.Core.RunInMemory,
  618. field);
  619. Database.IndexStorage.SetIndexExtension(name, indexExtensionKey, suggestionQueryIndexExtension);
  620. }
  621. }
  622. private IndexCreationOptions FindIndexCreationOptions(IndexDefinition definition, ref string name)
  623. {
  624. definition.Name = name;
  625. definition.RemoveDefaultValues();
  626. IndexDefinitionStorage.ResolveAnalyzers(definition);
  627. var findIndexCreationOptions = IndexDefinitionStorage.FindIndexCreationOptions(definition);
  628. return findIndexCreationOptions;
  629. }
  630. internal Task StartDeletingIndexDataAsync(int id, string indexName)
  631. {
  632. var sp = Stopwatch.StartNew();
  633. //remove the header information in a sync process
  634. TransactionalStorage.Batch(actions => actions.Indexing.PrepareIndexForDeletion(id));
  635. var deleteIndexTask = Task.Run(() =>
  636. {
  637. Debug.Assert(Database.IndexStorage != null);
  638. Log.Info("Starting async deletion of index {0}", indexName);
  639. Database.IndexStorage.DeleteIndexData(id); // Data can take a while
  640. TransactionalStorage.Batch(actions =>
  641. {
  642. // And Esent data can take a while too
  643. actions.Indexing.DeleteIndex(id, WorkContext.CancellationToken);
  644. if (WorkContext.CancellationToken.IsCancellationRequested)
  645. return;
  646. actions.Lists.Remove("Raven/Indexes/PendingDeletion", id.ToString(CultureInfo.InvariantCulture));
  647. });
  648. });
  649. long taskId;
  650. Database.Tasks.AddTask(deleteIndexTask, null, new TaskActions.PendingTaskDescription
  651. {
  652. StartTime = SystemTime.UtcNow,
  653. TaskType = TaskActions.PendingTaskType.IndexDeleteOperation,
  654. Payload = indexName
  655. }, out taskId);
  656. deleteIndexTask.ContinueWith(t =>
  657. {
  658. if (t.IsFaulted || t.IsCanceled)
  659. {
  660. Log.WarnException("Failure when deleting index " + indexName, t.Exception);
  661. }
  662. else
  663. {
  664. Log.Info("The async deletion of index {0} was completed in {1}", indexName, sp.Elapsed);
  665. }
  666. });
  667. return deleteIndexTask;
  668. }
  669. public RavenJArray GetIndexNames(int start, int pageSize)
  670. {
  671. return new RavenJArray(
  672. IndexDefinitionStorage.IndexNames.Skip(start).Take(pageSize)
  673. .Select(s => new RavenJValue(s))
  674. );
  675. }
  676. public RavenJArray GetIndexes(int start, int pageSize)
  677. {
  678. return new RavenJArray(
  679. from indexName in IndexDefinitionStorage.IndexNames.Skip(start).Take(pageSize)
  680. let indexDefinition = IndexDefinitionStorage.GetIndexDefinition(indexName)
  681. select new RavenJObject
  682. {
  683. {"name", new RavenJValue(indexName)},
  684. {"definition", indexDefinition != null ? RavenJObject.FromObject(indexDefinition) : null},
  685. });
  686. }
  687. public IndexDefinition GetIndexDefinition(string index)
  688. {
  689. return IndexDefinitionStorage.GetIndexDefinition(index);
  690. }
  691. [MethodImpl(MethodImplOptions.Synchronized)]
  692. public void ResetIndex(string index)
  693. {
  694. var indexDefinition = IndexDefinitionStorage.GetIndexDefinition(index);
  695. if (indexDefinition == null)
  696. throw new InvalidOperationException("There is no index named: " + index);
  697. DeleteIndex(index);
  698. PutIndex(index, indexDefinition);
  699. }
  700. [MethodImpl(MethodImplOptions.Synchronized)]
  701. public bool DeleteIndex(string name)
  702. {
  703. var instance = IndexDefinitionStorage.GetIndexDefinition(name);
  704. if (instance == null)
  705. return false;
  706. DeleteIndex(instance);
  707. return true;
  708. }
  709. internal void DeleteIndex(IndexDefinition instance, bool removeByNameMapping = true, bool clearErrors = true, bool removeIndexReplaceDocument = true, bool isSideBySideReplacement = false)
  710. {
  711. using (IndexDefinitionStorage.TryRemoveIndexContext())
  712. {
  713. if (instance == null)
  714. return;
  715. // Set up a flag to signal that this is something we're doing
  716. TransactionalStorage.Batch(actions => actions.Lists.Set("Raven/Indexes/PendingDeletion", instance.IndexId.ToString(CultureInfo.InvariantCulture), (RavenJObject.FromObject(new
  717. {
  718. TimeOfOriginalDeletion = SystemTime.UtcNow,
  719. instance.IndexId,
  720. IndexName = instance.Name,
  721. instance.IndexVersion
  722. })), UuidType.Tasks));
  723. // Delete the main record synchronously
  724. IndexDefinitionStorage.RemoveIndex(instance.IndexId, removeByNameMapping);
  725. Database.IndexStorage.DeleteIndex(instance.IndexId);
  726. if (clearErrors)
  727. WorkContext.ClearErrorsFor(instance.Name);
  728. if (removeIndexReplaceDocument && instance.IsSideBySideIndex)
  729. {
  730. Database.Documents.Delete(Constants.IndexReplacePrefix + instance.Name, null, null);
  731. }
  732. // And delete the data in the background
  733. StartDeletingIndexDataAsync(instance.IndexId, instance.Name);
  734. var indexChangeType = isSideBySideReplacement ? IndexChangeTypes.SideBySideReplace : IndexChangeTypes.IndexRemoved;
  735. // We raise the notification now because as far as we're concerned it is done *now*
  736. TransactionalStorage.ExecuteImmediatelyOrRegisterForSynchronization(() =>
  737. Database.Notifications.RaiseNotifications(new IndexChangeNotification
  738. {
  739. Name = instance.Name,
  740. Type = indexChangeType,
  741. Version = instance.IndexVersion
  742. })
  743. );
  744. }
  745. }
  746. }
  747. }