PageRenderTime 53ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/Raven.Database/Indexing/MapReduceIndex.cs

https://github.com/nwendel/ravendb
C# | 663 lines | 590 code | 60 blank | 13 comment | 60 complexity | 0bd1ec4164fd1dcc8c95459b68c13691 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, BSD-3-Clause, CC-BY-SA-3.0
  1. //-----------------------------------------------------------------------
  2. // <copyright file="MapReduceIndex.cs" company="Hibernating Rhinos LTD">
  3. // Copyright (c) Hibernating Rhinos LTD. All rights reserved.
  4. // </copyright>
  5. //-----------------------------------------------------------------------
  6. using System;
  7. using System.Collections;
  8. using System.Collections.Concurrent;
  9. using System.Collections.Generic;
  10. using System.ComponentModel;
  11. using System.Diagnostics;
  12. using System.Globalization;
  13. using System.Linq;
  14. using System.Text;
  15. using System.Threading;
  16. using Lucene.Net.Analysis;
  17. using Lucene.Net.Documents;
  18. using Lucene.Net.Index;
  19. using Lucene.Net.Search;
  20. using Lucene.Net.Store;
  21. using Raven.Abstractions.Logging;
  22. using Raven.Database.Extensions;
  23. using Raven.Database.Plugins;
  24. using Raven.Imports.Newtonsoft.Json;
  25. using Raven.Abstractions;
  26. using Raven.Abstractions.Extensions;
  27. using Raven.Abstractions.Data;
  28. using Raven.Abstractions.Indexing;
  29. using Raven.Abstractions.Linq;
  30. using Raven.Database.Data;
  31. using Raven.Database.Linq;
  32. using Raven.Database.Storage;
  33. using Raven.Imports.Newtonsoft.Json.Linq;
  34. using Raven.Json.Linq;
  35. using Spatial4n.Core.Exceptions;
  36. namespace Raven.Database.Indexing
  37. {
  38. public class MapReduceIndex : Index
  39. {
  40. readonly JsonSerializer jsonSerializer;
  41. private class IgnoreFieldable : JsonConverter
  42. {
  43. public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
  44. {
  45. writer.WriteValue("IgnoredLuceueField");
  46. }
  47. public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
  48. {
  49. return null;
  50. }
  51. public override bool CanConvert(Type objectType)
  52. {
  53. return typeof(IFieldable).IsAssignableFrom(objectType) ||
  54. typeof(IEnumerable<AbstractField>).IsAssignableFrom(objectType);
  55. }
  56. }
  57. public MapReduceIndex(Directory directory, int id, IndexDefinition indexDefinition,
  58. AbstractViewGenerator viewGenerator, WorkContext context)
  59. : base(directory, id, indexDefinition, viewGenerator, context)
  60. {
  61. jsonSerializer = new JsonSerializer();
  62. foreach (var jsonConverter in Default.Converters)
  63. {
  64. jsonSerializer.Converters.Add(jsonConverter);
  65. }
  66. jsonSerializer.Converters.Add(new IgnoreFieldable());
  67. }
  68. public override bool IsMapReduce
  69. {
  70. get { return true; }
  71. }
  72. public override void IndexDocuments(
  73. AbstractViewGenerator viewGenerator,
  74. IndexingBatch batch,
  75. IStorageActionsAccessor actions,
  76. DateTime minimumTimestamp)
  77. {
  78. var count = 0;
  79. var sourceCount = 0;
  80. var sw = Stopwatch.StartNew();
  81. var start = SystemTime.UtcNow;
  82. var deleted = new Dictionary<ReduceKeyAndBucket, int>();
  83. RecordCurrentBatch("Current Map", batch.Docs.Count);
  84. var documentsWrapped = batch.Docs.Select(doc =>
  85. {
  86. sourceCount++;
  87. var documentId = doc.__document_id;
  88. actions.MapReduce.DeleteMappedResultsForDocumentId((string)documentId, indexId, deleted);
  89. return doc;
  90. })
  91. .Where(x => x is FilteredDocument == false)
  92. .ToList();
  93. var allReferencedDocs = new ConcurrentQueue<IDictionary<string, HashSet<string>>>();
  94. var allReferenceEtags = new ConcurrentQueue<IDictionary<string, Etag>>();
  95. var allState = new ConcurrentQueue<Tuple<HashSet<ReduceKeyAndBucket>, IndexingWorkStats, Dictionary<string, int>>>();
  96. BackgroundTaskExecuter.Instance.ExecuteAllBuffered(context, documentsWrapped, partition =>
  97. {
  98. var localStats = new IndexingWorkStats();
  99. var localChanges = new HashSet<ReduceKeyAndBucket>();
  100. var statsPerKey = new Dictionary<string, int>();
  101. allState.Enqueue(Tuple.Create(localChanges, localStats, statsPerKey));
  102. using (CurrentIndexingScope.Current = new CurrentIndexingScope(context.Database,PublicName))
  103. {
  104. // we are writing to the transactional store from multiple threads here, and in a streaming fashion
  105. // should result in less memory and better perf
  106. context.TransactionalStorage.Batch(accessor =>
  107. {
  108. var mapResults = RobustEnumerationIndex(partition, viewGenerator.MapDefinitions, localStats);
  109. var currentDocumentResults = new List<object>();
  110. string currentKey = null;
  111. foreach (var currentDoc in mapResults)
  112. {
  113. var documentId = GetDocumentId(currentDoc);
  114. if (documentId != currentKey)
  115. {
  116. count += ProcessBatch(viewGenerator, currentDocumentResults, currentKey, localChanges, accessor, statsPerKey);
  117. currentDocumentResults.Clear();
  118. currentKey = documentId;
  119. }
  120. currentDocumentResults.Add(new DynamicJsonObject(RavenJObject.FromObject(currentDoc, jsonSerializer)));
  121. EnsureValidNumberOfOutputsForDocument(documentId, currentDocumentResults.Count);
  122. Interlocked.Increment(ref localStats.IndexingSuccesses);
  123. }
  124. count += ProcessBatch(viewGenerator, currentDocumentResults, currentKey, localChanges, accessor, statsPerKey);
  125. });
  126. allReferenceEtags.Enqueue(CurrentIndexingScope.Current.ReferencesEtags);
  127. allReferencedDocs.Enqueue(CurrentIndexingScope.Current.ReferencedDocuments);
  128. }
  129. });
  130. UpdateDocumentReferences(actions, allReferencedDocs, allReferenceEtags);
  131. var changed = allState.SelectMany(x => x.Item1).Concat(deleted.Keys)
  132. .Distinct()
  133. .ToList();
  134. var stats = new IndexingWorkStats(allState.Select(x => x.Item2));
  135. var reduceKeyStats = allState.SelectMany(x => x.Item3)
  136. .GroupBy(x => x.Key)
  137. .Select(g => new { g.Key, Count = g.Sum(x => x.Value) })
  138. .ToList();
  139. BackgroundTaskExecuter.Instance.ExecuteAllBuffered(context, reduceKeyStats, enumerator => context.TransactionalStorage.Batch(accessor =>
  140. {
  141. while (enumerator.MoveNext())
  142. {
  143. var reduceKeyStat = enumerator.Current;
  144. accessor.MapReduce.IncrementReduceKeyCounter(indexId, reduceKeyStat.Key, reduceKeyStat.Count);
  145. }
  146. }));
  147. BackgroundTaskExecuter.Instance.ExecuteAllBuffered(context, changed, enumerator => context.TransactionalStorage.Batch(accessor =>
  148. {
  149. while (enumerator.MoveNext())
  150. {
  151. accessor.MapReduce.ScheduleReductions(indexId, 0, enumerator.Current);
  152. }
  153. }));
  154. UpdateIndexingStats(context, stats);
  155. AddindexingPerformanceStat(new IndexingPerformanceStats
  156. {
  157. OutputCount = count,
  158. ItemsCount = sourceCount,
  159. InputCount = documentsWrapped.Count,
  160. Operation = "Map",
  161. Duration = sw.Elapsed,
  162. Started = start
  163. });
  164. BatchCompleted("Current Map");
  165. logIndexing.Debug("Mapped {0} documents for {1}", count, indexId);
  166. }
  167. private int ProcessBatch(AbstractViewGenerator viewGenerator, List<object> currentDocumentResults, string currentKey, HashSet<ReduceKeyAndBucket> changes,
  168. IStorageActionsAccessor actions,
  169. IDictionary<string, int> statsPerKey)
  170. {
  171. if (currentKey == null || currentDocumentResults.Count == 0)
  172. return 0;
  173. var old = CurrentIndexingScope.Current;
  174. try
  175. {
  176. CurrentIndexingScope.Current = null;
  177. if (logIndexing.IsDebugEnabled)
  178. {
  179. var sb = new StringBuilder()
  180. .AppendFormat("Index {0} for document {1} resulted in:", PublicName, currentKey)
  181. .AppendLine();
  182. foreach (var currentDocumentResult in currentDocumentResults)
  183. {
  184. sb.AppendLine(JsonConvert.SerializeObject(currentDocumentResult));
  185. }
  186. logIndexing.Debug(sb.ToString());
  187. }
  188. int count = 0;
  189. var results = RobustEnumerationReduceDuringMapPhase(currentDocumentResults.GetEnumerator(), viewGenerator.ReduceDefinition);
  190. foreach (var doc in results)
  191. {
  192. count++;
  193. var reduceValue = viewGenerator.GroupByExtraction(doc);
  194. if (reduceValue == null)
  195. {
  196. logIndexing.Debug("Field {0} is used as the reduce key and cannot be null, skipping document {1}",
  197. viewGenerator.GroupByExtraction, currentKey);
  198. continue;
  199. }
  200. string reduceKey = ReduceKeyToString(reduceValue);
  201. var data = GetMappedData(doc);
  202. logIndexing.Debug("Index {0} for document {1} resulted in ({2}): {3}", PublicName, currentKey, reduceKey, data);
  203. actions.MapReduce.PutMappedResult(indexId, currentKey, reduceKey, data);
  204. statsPerKey[reduceKey] = statsPerKey.GetOrDefault(reduceKey) + 1;
  205. actions.General.MaybePulseTransaction();
  206. changes.Add(new ReduceKeyAndBucket(IndexingUtil.MapBucket(currentKey), reduceKey));
  207. }
  208. return count;
  209. }
  210. finally
  211. {
  212. CurrentIndexingScope.Current = old;
  213. }
  214. }
  215. private RavenJObject GetMappedData(object doc)
  216. {
  217. if (doc is IDynamicJsonObject)
  218. return ((IDynamicJsonObject)doc).Inner;
  219. var ravenJTokenWriter = new RavenJTokenWriter();
  220. jsonSerializer.Serialize(ravenJTokenWriter, doc);
  221. return (RavenJObject)ravenJTokenWriter.Token;
  222. }
  223. private static readonly ConcurrentDictionary<Type, Func<object, object>> documentIdFetcherCache =
  224. new ConcurrentDictionary<Type, Func<object, object>>();
  225. private static string GetDocumentId(object doc)
  226. {
  227. var docIdFetcher = documentIdFetcherCache.GetOrAdd(doc.GetType(), type =>
  228. {
  229. // document may be DynamicJsonObject if we are using compiled views
  230. if (typeof(DynamicJsonObject) == type)
  231. {
  232. return i => ((dynamic)i).__document_id;
  233. }
  234. var docIdProp = TypeDescriptor.GetProperties(doc).Find(Constants.DocumentIdFieldName, false);
  235. return docIdProp.GetValue;
  236. });
  237. if (docIdFetcher == null)
  238. throw new InvalidOperationException("Could not create document id fetcher for this document");
  239. var documentId = docIdFetcher(doc);
  240. if (documentId == null || documentId is DynamicNullObject)
  241. throw new InvalidOperationException("Could not getdocument id fetcher for this document");
  242. return (string)documentId;
  243. }
  244. internal static string ReduceKeyToString(object reduceValue)
  245. {
  246. if (reduceValue is string)
  247. {
  248. return reduceValue.ToString();
  249. }
  250. if (reduceValue is DateTime)
  251. return ((DateTime)reduceValue).ToString(Default.DateTimeFormatsToWrite);
  252. if (reduceValue is DateTimeOffset)
  253. return ((DateTimeOffset)reduceValue).ToString(Default.DateTimeFormatsToWrite, CultureInfo.InvariantCulture);
  254. if (reduceValue is ValueType)
  255. return reduceValue.ToString();
  256. var dynamicJsonObject = reduceValue as IDynamicJsonObject;
  257. if (dynamicJsonObject != null)
  258. return dynamicJsonObject.Inner.ToString(Formatting.None);
  259. return RavenJToken.FromObject(reduceValue).ToString(Formatting.None);
  260. }
  261. protected override IndexQueryResult RetrieveDocument(Document document, FieldsToFetch fieldsToFetch, ScoreDoc score)
  262. {
  263. fieldsToFetch.EnsureHasField(Constants.ReduceKeyFieldName);
  264. if (fieldsToFetch.IsProjection)
  265. {
  266. return base.RetrieveDocument(document, fieldsToFetch, score);
  267. }
  268. var field = document.GetField(Constants.ReduceValueFieldName);
  269. if (field == null)
  270. {
  271. fieldsToFetch = fieldsToFetch.CloneWith(document.GetFields().Select(x => x.Name).ToArray());
  272. return base.RetrieveDocument(document, fieldsToFetch, score);
  273. }
  274. return new IndexQueryResult
  275. {
  276. Projection = RavenJObject.Parse(field.StringValue),
  277. Score = score.Score
  278. };
  279. }
  280. protected override void HandleCommitPoints(IndexedItemsInfo itemsInfo, IndexSegmentsInfo segmentsInfo)
  281. {
  282. // MapReduce index does not store and use any commit points
  283. }
  284. protected override bool IsUpToDateEnoughToWriteToDisk(Etag highestETag)
  285. {
  286. // for map/reduce indexes, we always write to disk, the in memory optimization
  287. // isn't really doing much for us, since we already write the intermediate results
  288. // to disk anyway, so it doesn't matter
  289. return true;
  290. }
  291. public override void Remove(string[] keys, WorkContext context)
  292. {
  293. context.TransactionalStorage.Batch(actions =>
  294. {
  295. var reduceKeyAndBuckets = new Dictionary<ReduceKeyAndBucket, int>();
  296. foreach (var key in keys)
  297. {
  298. actions.MapReduce.DeleteMappedResultsForDocumentId(key, indexId, reduceKeyAndBuckets);
  299. }
  300. actions.MapReduce.UpdateRemovedMapReduceStats(indexId, reduceKeyAndBuckets);
  301. foreach (var reduceKeyAndBucket in reduceKeyAndBuckets)
  302. {
  303. actions.MapReduce.ScheduleReductions(indexId, 0, reduceKeyAndBucket.Key);
  304. }
  305. });
  306. Write((writer, analyzer, stats) =>
  307. {
  308. stats.Operation = IndexingWorkStats.Status.Ignore;
  309. logIndexing.Debug(() => string.Format("Deleting ({0}) from {1}", string.Join(", ", keys), PublicName));
  310. writer.DeleteDocuments(keys.Select(k => new Term(Constants.ReduceKeyFieldName, k.ToLowerInvariant())).ToArray());
  311. return new IndexedItemsInfo(null)
  312. {
  313. ChangedDocs = keys.Length
  314. };
  315. });
  316. }
  317. public class ReduceDocuments
  318. {
  319. private readonly MapReduceIndex parent;
  320. private readonly int inputCount;
  321. private readonly int indexId;
  322. readonly AnonymousObjectToLuceneDocumentConverter anonymousObjectToLuceneDocumentConverter;
  323. private readonly Document luceneDoc = new Document();
  324. private readonly Field reduceValueField = new Field(Constants.ReduceValueFieldName, "dummy",
  325. Field.Store.YES, Field.Index.NO);
  326. private readonly Field reduceKeyField = new Field(Constants.ReduceKeyFieldName, "dummy",
  327. Field.Store.NO, Field.Index.NOT_ANALYZED_NO_NORMS);
  328. private PropertyDescriptorCollection properties = null;
  329. private readonly List<AbstractIndexUpdateTriggerBatcher> batchers;
  330. public ReduceDocuments(MapReduceIndex parent, AbstractViewGenerator viewGenerator, IEnumerable<IGrouping<int, object>> mappedResultsByBucket, int level, WorkContext context, IStorageActionsAccessor actions, HashSet<string> reduceKeys, int inputCount)
  331. {
  332. this.parent = parent;
  333. this.inputCount = inputCount;
  334. indexId = this.parent.indexId;
  335. ViewGenerator = viewGenerator;
  336. MappedResultsByBucket = mappedResultsByBucket;
  337. Level = level;
  338. Context = context;
  339. Actions = actions;
  340. ReduceKeys = reduceKeys;
  341. anonymousObjectToLuceneDocumentConverter = new AnonymousObjectToLuceneDocumentConverter(this.parent.context.Database, this.parent.indexDefinition, ViewGenerator, logIndexing);
  342. if (Level == 2)
  343. {
  344. batchers = Context.IndexUpdateTriggers.Select(x => x.CreateBatcher(indexId))
  345. .Where(x => x != null)
  346. .ToList();
  347. }
  348. }
  349. public AbstractViewGenerator ViewGenerator { get; private set; }
  350. public IEnumerable<IGrouping<int, object>> MappedResultsByBucket { get; private set; }
  351. public int Level { get; private set; }
  352. public WorkContext Context { get; private set; }
  353. public IStorageActionsAccessor Actions { get; private set; }
  354. public HashSet<string> ReduceKeys { get; private set; }
  355. private string ExtractReduceKey(AbstractViewGenerator viewGenerator, object doc)
  356. {
  357. try
  358. {
  359. object reduceKey = viewGenerator.GroupByExtraction(doc);
  360. if (reduceKey == null)
  361. {
  362. throw new InvalidOperationException("Could not find reduce key for " + indexId + " in the result: " + doc);
  363. }
  364. return ReduceKeyToString(reduceKey);
  365. }
  366. catch (Exception e)
  367. {
  368. throw new InvalidOperationException("Could not extract reduce key from reduce result!", e);
  369. }
  370. }
  371. private IEnumerable<AbstractField> GetFields(object doc, out float boost)
  372. {
  373. boost = 1;
  374. var boostedValue = doc as BoostedValue;
  375. if (boostedValue != null)
  376. {
  377. doc = boostedValue.Value;
  378. boost = boostedValue.Boost;
  379. }
  380. IEnumerable<AbstractField> fields = null;
  381. try
  382. {
  383. if (doc is IDynamicJsonObject)
  384. {
  385. fields = anonymousObjectToLuceneDocumentConverter.Index(((IDynamicJsonObject)doc).Inner, Field.Store.NO);
  386. }
  387. else
  388. {
  389. properties = properties ?? TypeDescriptor.GetProperties(doc);
  390. fields = anonymousObjectToLuceneDocumentConverter.Index(doc, properties, Field.Store.NO);
  391. }
  392. }
  393. catch (InvalidShapeException)
  394. {
  395. }
  396. if (Math.Abs(boost - 1) > float.Epsilon)
  397. {
  398. var abstractFields = fields.ToList();
  399. foreach (var abstractField in abstractFields)
  400. {
  401. abstractField.OmitNorms = false;
  402. }
  403. return abstractFields;
  404. }
  405. return fields;
  406. }
  407. private static RavenJObject ToJsonDocument(object doc)
  408. {
  409. var boostedValue = doc as BoostedValue;
  410. if (boostedValue != null)
  411. {
  412. doc = boostedValue.Value;
  413. }
  414. var dynamicJsonObject = doc as IDynamicJsonObject;
  415. if (dynamicJsonObject != null)
  416. {
  417. return dynamicJsonObject.Inner;
  418. }
  419. var ravenJObject = doc as RavenJObject;
  420. if (ravenJObject != null)
  421. return ravenJObject;
  422. var jsonDocument = RavenJObject.FromObject(doc);
  423. MergeArrays(jsonDocument);
  424. // remove _, __, etc fields
  425. foreach (var prop in jsonDocument.Where(x => x.Key.All(ch => ch == '_')).ToArray())
  426. {
  427. jsonDocument.Remove(prop.Key);
  428. }
  429. return jsonDocument;
  430. }
  431. private static void MergeArrays(RavenJToken token)
  432. {
  433. if (token == null)
  434. return;
  435. switch (token.Type)
  436. {
  437. case JTokenType.Array:
  438. var arr = (RavenJArray)token;
  439. for (int i = 0; i < arr.Length; i++)
  440. {
  441. var current = arr[i];
  442. if (current == null || current.Type != JTokenType.Array)
  443. continue;
  444. arr.RemoveAt(i);
  445. i--;
  446. var j = Math.Max(0, i);
  447. foreach (var item in (RavenJArray)current)
  448. {
  449. arr.Insert(j++, item);
  450. }
  451. }
  452. break;
  453. case JTokenType.Object:
  454. foreach (var kvp in ((RavenJObject)token))
  455. {
  456. MergeArrays(kvp.Value);
  457. }
  458. break;
  459. }
  460. }
  461. public void ExecuteReduction()
  462. {
  463. var count = 0;
  464. var sourceCount = 0;
  465. var sw = Stopwatch.StartNew();
  466. var start = SystemTime.UtcNow;
  467. parent.Write((indexWriter, analyzer, stats) =>
  468. {
  469. stats.Operation = IndexingWorkStats.Status.Reduce;
  470. try
  471. {
  472. parent.RecordCurrentBatch("Current Reduce #" + Level, MappedResultsByBucket.Sum(x => x.Count()));
  473. if (Level == 2)
  474. {
  475. RemoveExistingReduceKeysFromIndex(indexWriter);
  476. }
  477. foreach (var mappedResults in MappedResultsByBucket)
  478. {
  479. var input = mappedResults.Select(x =>
  480. {
  481. sourceCount++;
  482. return x;
  483. });
  484. foreach (var doc in parent.RobustEnumerationReduce(input.GetEnumerator(), ViewGenerator.ReduceDefinition, Actions, stats))
  485. {
  486. count++;
  487. string reduceKeyAsString = ExtractReduceKey(ViewGenerator, doc);
  488. switch (Level)
  489. {
  490. case 0:
  491. case 1:
  492. Actions.MapReduce.PutReducedResult(indexId, reduceKeyAsString, Level + 1, mappedResults.Key, mappedResults.Key / 1024, ToJsonDocument(doc));
  493. Actions.General.MaybePulseTransaction();
  494. break;
  495. case 2:
  496. WriteDocumentToIndex(doc, indexWriter, analyzer);
  497. break;
  498. default:
  499. throw new InvalidOperationException("Unknown level: " + Level);
  500. }
  501. stats.ReduceSuccesses++;
  502. }
  503. }
  504. }
  505. catch (Exception e)
  506. {
  507. if (Level == 2)
  508. {
  509. batchers.ApplyAndIgnoreAllErrors(
  510. ex =>
  511. {
  512. logIndexing.WarnException("Failed to notify index update trigger batcher about an error", ex);
  513. Context.AddError(indexId, parent.indexDefinition.Name, null, ex.Message, "AnErrorOccured Trigger");
  514. },
  515. x => x.AnErrorOccured(e));
  516. }
  517. throw;
  518. }
  519. finally
  520. {
  521. if (Level == 2)
  522. {
  523. batchers.ApplyAndIgnoreAllErrors(
  524. e =>
  525. {
  526. logIndexing.WarnException("Failed to dispose on index update trigger", e);
  527. Context.AddError(indexId, parent.indexDefinition.Name, null, e.Message, "Dispose Trigger");
  528. },
  529. x => x.Dispose());
  530. }
  531. parent.BatchCompleted("Current Reduce #" + Level);
  532. }
  533. return new IndexedItemsInfo(null)
  534. {
  535. ChangedDocs = count + ReduceKeys.Count
  536. };
  537. });
  538. parent.AddindexingPerformanceStat(new IndexingPerformanceStats
  539. {
  540. OutputCount = count,
  541. ItemsCount = sourceCount,
  542. InputCount = inputCount,
  543. Duration = sw.Elapsed,
  544. Operation = "Reduce Level " + Level,
  545. Started = start
  546. });
  547. logIndexing.Debug(() => string.Format("Reduce resulted in {0} entries for {1} for reduce keys: {2}", count, indexId, string.Join(", ", ReduceKeys)));
  548. }
  549. private void WriteDocumentToIndex(object doc, RavenIndexWriter indexWriter, Analyzer analyzer)
  550. {
  551. float boost;
  552. List<AbstractField> fields;
  553. try
  554. {
  555. fields = GetFields(doc, out boost).ToList();
  556. }
  557. catch (Exception e)
  558. {
  559. Context.AddError(indexId,
  560. parent.PublicName,
  561. TryGetDocKey(doc),
  562. e.Message,
  563. "Reduce"
  564. );
  565. logIndexing.WarnException("Could not get fields to during reduce for " + parent.PublicName, e);
  566. return;
  567. }
  568. string reduceKeyAsString = ExtractReduceKey(ViewGenerator, doc);
  569. reduceKeyField.SetValue(reduceKeyAsString);
  570. reduceValueField.SetValue(ToJsonDocument(doc).ToString(Formatting.None));
  571. luceneDoc.GetFields().Clear();
  572. luceneDoc.Boost = boost;
  573. luceneDoc.Add(reduceKeyField);
  574. luceneDoc.Add(reduceValueField);
  575. foreach (var field in fields)
  576. {
  577. luceneDoc.Add(field);
  578. }
  579. batchers.ApplyAndIgnoreAllErrors(
  580. exception =>
  581. {
  582. logIndexing.WarnException(
  583. string.Format("Error when executed OnIndexEntryCreated trigger for index '{0}', key: '{1}'",
  584. indexId, reduceKeyAsString),
  585. exception);
  586. Context.AddError(indexId, parent.PublicName, reduceKeyAsString, exception.Message, "OnIndexEntryCreated Trigger");
  587. },
  588. trigger => trigger.OnIndexEntryCreated(reduceKeyAsString, luceneDoc));
  589. parent.LogIndexedDocument(reduceKeyAsString, luceneDoc);
  590. parent.AddDocumentToIndex(indexWriter, luceneDoc, analyzer);
  591. }
  592. private void RemoveExistingReduceKeysFromIndex(RavenIndexWriter indexWriter)
  593. {
  594. foreach (var reduceKey in ReduceKeys)
  595. {
  596. var entryKey = reduceKey;
  597. parent.InvokeOnIndexEntryDeletedOnAllBatchers(batchers, new Term(Constants.ReduceKeyFieldName, entryKey));
  598. indexWriter.DeleteDocuments(new Term(Constants.ReduceKeyFieldName, entryKey));
  599. }
  600. }
  601. }
  602. }
  603. }