private static unsafe long CalculateIndexEtag(Index index, StaticIndexBase compiled, int length, byte *indexEtagBytes, byte *writePos, DocumentsOperationContext documentsContext, TransactionOperationContext indexContext) { foreach (var collection in index.Collections) { if (compiled.ReferencedCollections.TryGetValue(collection, out HashSet <CollectionName> referencedCollections) == false) { continue; } foreach (var referencedCollection in referencedCollections) { var lastDocEtag = documentsContext.DocumentDatabase.DocumentsStorage.GetLastDocumentEtag(documentsContext.Transaction.InnerTransaction, referencedCollection.Name); var lastProcessedReferenceEtag = IndexStorage.ReadLastProcessedReferenceEtag(indexContext.Transaction.InnerTransaction, collection, referencedCollection); var lastTombstoneEtag = documentsContext.DocumentDatabase.DocumentsStorage.GetLastTombstoneEtag(documentsContext.Transaction.InnerTransaction, referencedCollection.Name); var lastProcessedTombstoneEtag = IndexStorage.ReadLastProcessedReferenceTombstoneEtag(indexContext.Transaction.InnerTransaction, collection, referencedCollection); *(long *)writePos = lastDocEtag; writePos += sizeof(long); *(long *)writePos = lastProcessedReferenceEtag; writePos += sizeof(long); *(long *)writePos = lastTombstoneEtag; writePos += sizeof(long); *(long *)writePos = lastProcessedTombstoneEtag; } } unchecked { return((long)Hashing.XXHash64.Calculate(indexEtagBytes, (ulong)length)); } }
public AnonymousObjectToBlittableMapResultsEnumerableWrapper(MapReduceIndex index, TransactionOperationContext indexContext) { _indexContext = indexContext; _groupByFields = index.Definition.GroupByFields; _isMultiMap = index.IsMultiMap; _reduceKeyProcessor = new ReduceKeyProcessor(index.Definition.GroupByFields.Count, index._unmanagedBuffersPool); _compiledIndex = index._compiled; }
private static bool IsStale(Index index, StaticIndexBase compiled, DocumentsOperationContext databaseContext, TransactionOperationContext indexContext, long?cutoff) { foreach (var collection in index.Collections) { HashSet <CollectionName> referencedCollections; if (compiled.ReferencedCollections.TryGetValue(collection, out referencedCollections) == false) { continue; } var lastIndexedEtag = index._indexStorage.ReadLastIndexedEtag(indexContext.Transaction, collection); // we haven't handled references for that collection yet // in theory we could check what is the last etag for that collection in documents store // but this was checked earlier by the base index class if (lastIndexedEtag == 0) { continue; } foreach (var referencedCollection in referencedCollections) { var lastDocEtag = databaseContext.DocumentDatabase.DocumentsStorage.GetLastDocumentEtag(databaseContext, referencedCollection.Name); var lastProcessedReferenceEtag = index._indexStorage.ReadLastProcessedReferenceEtag(indexContext.Transaction, collection, referencedCollection); if (cutoff == null) { if (lastDocEtag > lastProcessedReferenceEtag) { return(true); } var lastTombstoneEtag = databaseContext.DocumentDatabase.DocumentsStorage.GetLastTombstoneEtag(databaseContext, referencedCollection.Name); var lastProcessedTombstoneEtag = index._indexStorage.ReadLastProcessedReferenceTombstoneEtag(indexContext.Transaction, collection, referencedCollection); if (lastTombstoneEtag > lastProcessedTombstoneEtag) { return(true); } } else { if (Math.Min(cutoff.Value, lastDocEtag) > lastProcessedReferenceEtag) { return(true); } if (databaseContext.DocumentDatabase.DocumentsStorage.GetNumberOfTombstonesWithDocumentEtagLowerThan(databaseContext, referencedCollection.Name, cutoff.Value) > 0) { return(true); } } } } return(false); }
protected MapReduceIndex(MapReduceIndexDefinition definition, StaticIndexBase compiled) : base(definition.IndexDefinition.Type, definition) { _compiled = compiled; if (_compiled.ReferencedCollections == null) return; foreach (var collection in _compiled.ReferencedCollections) { foreach (var referencedCollection in collection.Value) _referencedCollections.Add(referencedCollection.Name); } }
private MapReduceIndex(int indexId, MapReduceIndexDefinition definition, StaticIndexBase compiled) : base(indexId, IndexType.MapReduce, definition) { _compiled = compiled; if (_compiled.ReferencedCollections == null) { return; } foreach (var collection in _compiled.ReferencedCollections) { foreach (var referencedCollection in collection.Value) { _referencedCollections.Add(referencedCollection); } } }
protected override int GetFields <T>(T instance, LazyStringValue key, object document, JsonOperationContext indexContext, IWriteOperationBuffer writeBuffer) { if (!(document is ObjectInstance documentToProcess)) { return(0); } int newFields = 0; if (key != null) { instance.Add(GetOrCreateKeyField(key)); newFields++; } if (_reduceOutput) { var reduceResult = JsBlittableBridge.Translate(indexContext, documentToProcess.Engine, documentToProcess); instance.Add(GetReduceResultValueField(reduceResult, writeBuffer)); newFields++; } foreach (var(property, propertyDescriptor) in documentToProcess.GetOwnProperties()) { if (_fields.TryGetValue(property, out var field) == false) { field = _fields[property] = IndexField.Create(property, new IndexFieldOptions(), _allFields); } object value; var actualValue = propertyDescriptor.Value; if (actualValue.IsObject() && actualValue.IsArray() == false) { //In case TryDetectDynamicFieldCreation finds a dynamic field it will populate 'field.Name' with the actual property name //so we must use field.Name and not property from this point on. var val = TryDetectDynamicFieldCreation(property, actualValue.AsObject(), field); if (val != null) { if (val.IsObject() && val.AsObject().TryGetValue("$spatial", out _)) { actualValue = val; //Here we populate the dynamic spatial field that will be handled below. } else { value = TypeConverter.ToBlittableSupportedType(val, flattenArrays: false, forIndexing: true, engine: documentToProcess.Engine, context: indexContext); newFields += GetRegularFields(instance, field, value, indexContext, out _); continue; } } var objectValue = actualValue.AsObject(); if (objectValue.HasOwnProperty("$spatial") && objectValue.TryGetValue("$spatial", out var inner)) { SpatialField spatialField; IEnumerable <AbstractField> spatial; if (inner.IsString()) { spatialField = StaticIndexBase.GetOrCreateSpatialField(field.Name); spatial = StaticIndexBase.CreateSpatialField(spatialField, inner.AsString()); } else if (inner.IsObject()) { var innerObject = inner.AsObject(); if (innerObject.HasOwnProperty("Lat") && innerObject.HasOwnProperty("Lng") && innerObject.TryGetValue("Lat", out var lat) && lat.IsNumber() && innerObject.TryGetValue("Lng", out var lng) && lng.IsNumber()) { spatialField = StaticIndexBase.GetOrCreateSpatialField(field.Name); spatial = StaticIndexBase.CreateSpatialField(spatialField, lat.AsNumber(), lng.AsNumber()); } else { continue; //Ignoring bad spatial field } } else { continue; //Ignoring bad spatial field } newFields += GetRegularFields(instance, field, spatial, indexContext, out _); continue; } } value = TypeConverter.ToBlittableSupportedType(propertyDescriptor.Value, flattenArrays: false, forIndexing: true, engine: documentToProcess.Engine, context: indexContext); newFields += GetRegularFields(instance, field, value, indexContext, out _); if (value is IDisposable toDispose) { // the value was converted to a lucene field and isn't needed anymore toDispose.Dispose(); } } return(newFields); }
private static bool IsStaleDueToReferences(Index index, StaticIndexBase compiled, DocumentsOperationContext databaseContext, TransactionOperationContext indexContext, long?referenceCutoff, List <string> stalenessReasons) { foreach (var collection in index.Collections) { if (compiled.ReferencedCollections.TryGetValue(collection, out HashSet <CollectionName> referencedCollections) == false) { continue; } var lastIndexedEtag = index._indexStorage.ReadLastIndexedEtag(indexContext.Transaction, collection); // we haven't handled references for that collection yet // in theory we could check what is the last etag for that collection in documents store // but this was checked earlier by the base index class if (lastIndexedEtag == 0) { continue; } foreach (var referencedCollection in referencedCollections) { var lastDocEtag = databaseContext.DocumentDatabase.DocumentsStorage.GetLastDocumentEtag(databaseContext.Transaction.InnerTransaction, referencedCollection.Name); var lastProcessedReferenceEtag = IndexStorage.ReadLastProcessedReferenceEtag(indexContext.Transaction.InnerTransaction, collection, referencedCollection); var lastProcessedTombstoneEtag = IndexStorage.ReadLastProcessedReferenceTombstoneEtag(indexContext.Transaction.InnerTransaction, collection, referencedCollection); if (referenceCutoff == null) { if (lastDocEtag > lastProcessedReferenceEtag) { if (stalenessReasons == null) { return(true); } var lastDoc = databaseContext.DocumentDatabase.DocumentsStorage.GetByEtag(databaseContext, lastDocEtag); stalenessReasons.Add($"There are still some document references to process from collection '{referencedCollection.Name}'. " + $"The last document etag in that collection is '{lastDocEtag:#,#;;0}' " + $"({Constants.Documents.Metadata.Id}: '{lastDoc.Id}', " + $"{Constants.Documents.Metadata.LastModified}: '{lastDoc.LastModified}'), " + $"but last processed document etag for that collection is '{lastProcessedReferenceEtag:#,#;;0}'."); } var lastTombstoneEtag = databaseContext.DocumentDatabase.DocumentsStorage.GetLastTombstoneEtag(databaseContext.Transaction.InnerTransaction, referencedCollection.Name); if (lastTombstoneEtag > lastProcessedTombstoneEtag) { if (stalenessReasons == null) { return(true); } var lastTombstone = databaseContext.DocumentDatabase.DocumentsStorage.GetTombstoneByEtag(databaseContext, lastTombstoneEtag); stalenessReasons.Add($"There are still some tombstone references to process from collection '{referencedCollection.Name}'. " + $"The last tombstone etag in that collection is '{lastTombstoneEtag:#,#;;0}' " + $"({Constants.Documents.Metadata.Id}: '{lastTombstone.LowerId}', " + $"{Constants.Documents.Metadata.LastModified}: '{lastTombstone.LastModified}'), " + $"but last processed tombstone etag for that collection is '{lastProcessedTombstoneEtag:#,#;;0}'."); } } else { var minDocEtag = Math.Min(referenceCutoff.Value, lastDocEtag); if (minDocEtag > lastProcessedReferenceEtag) { if (stalenessReasons == null) { return(true); } var lastDoc = databaseContext.DocumentDatabase.DocumentsStorage.GetByEtag(databaseContext, lastDocEtag); stalenessReasons.Add($"There are still some document references to process from collection '{referencedCollection.Name}'. " + $"The last document etag in that collection is '{lastDocEtag:#,#;;0}' " + $"({Constants.Documents.Metadata.Id}: '{lastDoc.Id}', " + $"{Constants.Documents.Metadata.LastModified}: '{lastDoc.LastModified}') " + $"with cutoff set to '{referenceCutoff.Value}', " + $"but last processed document etag for that collection is '{lastProcessedReferenceEtag:#,#;;0}'."); } var hasTombstones = databaseContext.DocumentDatabase.DocumentsStorage.HasTombstonesWithEtagGreaterThanStartAndLowerThanOrEqualToEnd(databaseContext, referencedCollection.Name, lastProcessedTombstoneEtag, referenceCutoff.Value); if (hasTombstones) { if (stalenessReasons == null) { return(true); } stalenessReasons.Add($"There are still tombstones to process from collection '{referencedCollection.Name}' with etag range '{lastProcessedTombstoneEtag} - {referenceCutoff.Value}'."); } } } } return(stalenessReasons?.Count > 0); }
public static void ValidateReduceResultsCollectionName(IndexDefinition definition, StaticIndexBase index, DocumentDatabase database) { var outputReduceToCollection = definition.OutputReduceToCollection; if (string.IsNullOrWhiteSpace(outputReduceToCollection)) { return; } var collections = index.Maps.Keys.ToHashSet(StringComparer.OrdinalIgnoreCase); if (collections.Contains(Constants.Documents.Collections.AllDocumentsCollection)) { throw new IndexInvalidException($"It is forbidden to create the '{definition.Name}' index " + $"which would output reduce results to documents in the '{outputReduceToCollection}' collection, " + $"as this index is mapping all documents " + $"and this will result in an infinite loop."); } foreach (var referencedCollection in index.ReferencedCollections) { foreach (var collectionName in referencedCollection.Value) { collections.Add(collectionName.Name); } } if (collections.Contains(outputReduceToCollection)) { throw new IndexInvalidException($"It is forbidden to create the '{definition.Name}' index " + $"which would output reduce results to documents in the '{outputReduceToCollection}' collection, " + $"as this index is mapping or referencing the '{outputReduceToCollection}' collection " + $"and this will result in an infinite loop."); } var indexes = database.IndexStore.GetIndexes() .Where(x => x.Type == IndexType.MapReduce) .Cast <MapReduceIndex>() .Where(mapReduceIndex => string.IsNullOrWhiteSpace(mapReduceIndex.Definition.OutputReduceToCollection) == false && mapReduceIndex.Name != definition.Name) .ToList(); foreach (var otherIndex in indexes) { if (otherIndex.Definition.OutputReduceToCollection.Equals(outputReduceToCollection, StringComparison.OrdinalIgnoreCase)) { var sideBySideIndex = definition.Name.StartsWith(Constants.Documents.Indexing.SideBySideIndexNamePrefix, StringComparison.OrdinalIgnoreCase); if (sideBySideIndex) { throw new IndexInvalidException($"In order to create the '{definition.Name}' side by side index " + $"you firstly need to set {nameof(IndexDefinition.OutputReduceToCollection)} to be null " + $"on the '{otherIndex.Name}' index " + $"and than delete all of the documents in the '{otherIndex.Definition.OutputReduceToCollection}' collection."); } throw new IndexInvalidException($"It is forbidden to create the '{definition.Name}' index " + $"which would output reduce results to documents in the '{outputReduceToCollection}' collection, " + $"as there is another index named '{otherIndex.Name}' " + $"which also output reduce results to documents in the same '{outputReduceToCollection}' collection. " + $"{nameof(IndexDefinition.OutputReduceToCollection)} must by set to unique value for each index or be null."); } var otherIndexCollections = new HashSet <string>(otherIndex.Collections); foreach (var referencedCollection in otherIndex.GetReferencedCollections()) { foreach (var collectionName in referencedCollection.Value) { otherIndexCollections.Add(collectionName.Name); } } if (otherIndexCollections.Contains(outputReduceToCollection) && CheckIfThereIsAnIndexWhichWillOutputReduceDocumentsWhichWillBeUsedAsMapOnTheSpecifiedIndex(otherIndex, collections, indexes, out string description)) { description += Environment.NewLine + $"--> {definition.Name}: {string.Join(",", collections)} => *{outputReduceToCollection}*"; throw new IndexInvalidException($"It is forbidden to create the '{definition.Name}' index " + $"which would output reduce results to documents in the '{outputReduceToCollection}' collection, " + $"as '{outputReduceToCollection}' collection is consumed by other index in a way that would " + $"lead to an infinite loop." + Environment.NewLine + description); } } using (database.DocumentsStorage.ContextPool.AllocateOperationContext(out DocumentsOperationContext context)) using (context.OpenReadTransaction()) { var stats = database.DocumentsStorage.GetCollection(outputReduceToCollection, context); if (stats.Count > 0) { throw new IndexInvalidException($"In order to create the '{definition.Name}' index " + $"which would output reduce results to documents in the '{outputReduceToCollection}' collection, " + $"you firstly need to delete all of the documents in the '{stats.Name}' collection " + $"(currently have {stats.Count} document{(stats.Count == 1 ? "" : "s")})."); } } }
private static void ThrowMissingGroupByFieldsInMapOutput(object output, HashSet <string> groupByFields, StaticIndexBase compiledIndex) { throw new InvalidOperationException( $"The output of the mapping function does not contain all fields that the index is supposed to group by.{Environment.NewLine}" + $"Output: {output}{Environment.NewLine}" + $"Group by fields: {string.Join(",", groupByFields)}{Environment.NewLine}" + $"Compiled index def:{Environment.NewLine}{compiledIndex.Source}"); }
protected override int GetFields <T>(T instance, LazyStringValue key, object doc, JsonOperationContext indexContext, IWriteOperationBuffer writeBuffer) { int newFields = 0; var document = (Document)doc; if (key != null) { Debug.Assert(document.LowerId == null || (key == document.LowerId)); instance.Add(GetOrCreateKeyField(key)); newFields++; } if (_reduceOutput) { instance.Add(GetReduceResultValueField(document.Data, writeBuffer)); newFields++; } foreach (var indexField in _fields.Values) { object value; if (indexField.Spatial is AutoSpatialOptions spatialOptions) { var spatialField = CurrentIndexingScope.Current.GetOrCreateSpatialField(indexField.Name); switch (spatialOptions.MethodType) { case AutoSpatialOptions.AutoSpatialMethodType.Wkt: if (BlittableJsonTraverserHelper.TryRead(_blittableTraverser, document, spatialOptions.MethodArguments[0], out var wktValue) == false) { continue; } value = StaticIndexBase.CreateSpatialField(spatialField, wktValue); break; case AutoSpatialOptions.AutoSpatialMethodType.Point: if (BlittableJsonTraverserHelper.TryRead(_blittableTraverser, document, spatialOptions.MethodArguments[0], out var latValue) == false) { continue; } if (BlittableJsonTraverserHelper.TryRead(_blittableTraverser, document, spatialOptions.MethodArguments[1], out var lngValue) == false) { continue; } value = StaticIndexBase.CreateSpatialField(spatialField, latValue, lngValue); break; default: throw new ArgumentOutOfRangeException(); } } else { if (BlittableJsonTraverserHelper.TryRead(_blittableTraverser, document, indexField.OriginalName ?? indexField.Name, out value) == false) { continue; } } newFields += GetRegularFields(instance, indexField, value, indexContext, out _); } return(newFields); }
public static void ValidateReduceResultsCollectionName(IndexDefinition definition, StaticIndexBase index, DocumentDatabase database, bool checkIfCollectionEmpty) { var outputReduceToCollection = definition.OutputReduceToCollection; if (string.IsNullOrWhiteSpace(outputReduceToCollection)) { return; } if (outputReduceToCollection.Equals(definition.PatternReferencesCollectionName, StringComparison.OrdinalIgnoreCase)) { throw new IndexInvalidException($"Collection defined in {nameof(definition.PatternReferencesCollectionName)} must not be the same as in {nameof(definition.OutputReduceToCollection)}. Collection name: '{outputReduceToCollection}'"); } var collections = index.Maps.Keys.ToHashSet(StringComparer.OrdinalIgnoreCase); if (collections.Contains(Constants.Documents.Collections.AllDocumentsCollection)) { throw new IndexInvalidException($"It is forbidden to create the '{definition.Name}' index " + $"which would output reduce results to documents in the '{outputReduceToCollection}' collection, " + $"as this index is mapping all documents " + $"and this will result in an infinite loop."); } foreach (var referencedCollection in index.ReferencedCollections) { foreach (var collectionName in referencedCollection.Value) { collections.Add(collectionName.Name); } } if (collections.Contains(outputReduceToCollection)) { throw new IndexInvalidException($"It is forbidden to create the '{definition.Name}' index " + $"which would output reduce results to documents in the '{outputReduceToCollection}' collection, " + $"as this index is mapping or referencing the '{outputReduceToCollection}' collection " + $"and this will result in an infinite loop."); } var indexes = database.IndexStore.GetIndexes() .Where(x => x.Type.IsStatic() && x.Type.IsMapReduce()) .Cast <MapReduceIndex>() .Where(mapReduceIndex => { // we have handling for side by side indexing with OutputReduceToCollection so we're checking only other indexes string existingIndexName = mapReduceIndex.Name.Replace(Constants.Documents.Indexing.SideBySideIndexNamePrefix, string.Empty, StringComparison.OrdinalIgnoreCase); string newIndexName = definition.Name.Replace(Constants.Documents.Indexing.SideBySideIndexNamePrefix, string.Empty, StringComparison.OrdinalIgnoreCase); return(string.IsNullOrWhiteSpace(mapReduceIndex.Definition.OutputReduceToCollection) == false && string.Equals(existingIndexName, newIndexName, StringComparison.OrdinalIgnoreCase) == false); }) .ToList(); foreach (var otherIndex in indexes) { if (otherIndex.Definition.OutputReduceToCollection.Equals(outputReduceToCollection, StringComparison.OrdinalIgnoreCase)) { throw new IndexInvalidException($"It is forbidden to create the '{definition.Name}' index " + $"which would output reduce results to documents in the '{outputReduceToCollection}' collection, " + $"as there is another index named '{otherIndex.Name}' " + $"which also output reduce results to documents in the same '{outputReduceToCollection}' collection. " + $"{nameof(IndexDefinition.OutputReduceToCollection)} must by set to unique value for each index or be null."); } var otherIndexCollections = new HashSet <string>(otherIndex.Collections); foreach (var referencedCollection in otherIndex.GetReferencedCollections()) { foreach (var collectionName in referencedCollection.Value) { otherIndexCollections.Add(collectionName.Name); } } if (otherIndexCollections.Contains(outputReduceToCollection) && CheckIfThereIsAnIndexWhichWillOutputReduceDocumentsWhichWillBeUsedAsMapOnTheSpecifiedIndex(otherIndex, collections, indexes, out string description)) { description += Environment.NewLine + $"--> {definition.Name}: {string.Join(",", collections)} => *{outputReduceToCollection}*"; throw new IndexInvalidException($"It is forbidden to create the '{definition.Name}' index " + $"which would output reduce results to documents in the '{outputReduceToCollection}' collection, " + $"as '{outputReduceToCollection}' collection is consumed by other index in a way that would " + $"lead to an infinite loop." + Environment.NewLine + description); } } var existingIndexOrSideBySide = database.IndexStore.GetIndexes() .Where(x => x.Type.IsStatic() && x.Type.IsMapReduce()) .Cast <MapReduceIndex>() .FirstOrDefault(x => { var name = definition.Name.Replace(Constants.Documents.Indexing.SideBySideIndexNamePrefix, string.Empty, StringComparison.OrdinalIgnoreCase); return(x.Name.Equals(name, StringComparison.OrdinalIgnoreCase) && x.Definition.ReduceOutputIndex != null); // legacy index definitions don't have this field - side by side indexing isn't supported then }); if (existingIndexOrSideBySide != null) { if (definition.OutputReduceToCollection.Equals(existingIndexOrSideBySide.Definition.OutputReduceToCollection, StringComparison.OrdinalIgnoreCase)) // we have handling for side by side indexing with OutputReduceToCollection { checkIfCollectionEmpty = false; } } if (checkIfCollectionEmpty) { using (database.DocumentsStorage.ContextPool.AllocateOperationContext(out DocumentsOperationContext context)) using (context.OpenReadTransaction()) { var stats = database.DocumentsStorage.GetCollection(outputReduceToCollection, context); if (stats.Count > 0) { throw new IndexInvalidException( $"Index '{definition.Name}' is defined to output the Reduce results to documents in Collection '{outputReduceToCollection}'. " + $"This collection currently has {stats.Count} document{(stats.Count == 1 ? ' ' : 's')}. " + $"All documents in Collection '{stats.Name}' must be deleted first."); } } } }
public static void ValidateReduceResultsCollectionName(IndexDefinition definition, StaticIndexBase index, DocumentDatabase database) { var outputReduceToCollection = definition.OutputReduceToCollection; if (string.IsNullOrWhiteSpace(outputReduceToCollection)) { return; } var collections = index.Maps.Keys.ToArray(); if (collections.Contains(Constants.Documents.Collections.AllDocumentsCollection, StringComparer.OrdinalIgnoreCase)) { throw new IndexInvalidException($"Cannot output documents from index ({definition.Name}) to the collection name ({outputReduceToCollection}) because the index is mapping all documents and this will result in an infinite loop."); } if (collections.Contains(outputReduceToCollection, StringComparer.OrdinalIgnoreCase)) { throw new IndexInvalidException($"The collection name ({outputReduceToCollection}) cannot be used as this index ({definition.Name}) is mapping this collection and this will result in an infinite loop."); } var indexes = database.IndexStore.GetIndexes() .Where(x => x.Type == IndexType.MapReduce) .Cast <MapReduceIndex>() .Where(mapReduceIndex => string.IsNullOrWhiteSpace(mapReduceIndex.Definition.OutputReduceToCollection) == false && mapReduceIndex.Name != definition.Name) .ToList(); foreach (var otherIndex in indexes) { if (otherIndex.Definition.OutputReduceToCollection.Equals(outputReduceToCollection, StringComparison.OrdinalIgnoreCase)) { throw new IndexInvalidException($"The collection name ({outputReduceToCollection}) which will be used to output documents results should be unique to only one index but it is already used by another index ({otherIndex.Name})."); } if (otherIndex.Collections.Contains(outputReduceToCollection, StringComparer.OrdinalIgnoreCase) && CheckIfThereIsAnIndexWhichWillOutputReduceDocumentsWhichWillBeUsedAsMapOnTheSpecifiedIndex(otherIndex, collections, indexes, out string description)) { description += Environment.NewLine + $"--> {definition.Name}: {string.Join(",", collections)} => *{outputReduceToCollection}*"; throw new IndexInvalidException($"The collection name ({outputReduceToCollection}) cannot be used to output documents results as it is consumed by other index that will also output results which will lead to an infinite loop:" + Environment.NewLine + description); } } }