private void DeleteOldRevisions(DocumentsOperationContext context, Table table, Slice prefixSlice, CollectionName collectionName, RevisionsCollectionConfiguration configuration, long revisionsCount, NonPersistentDocumentFlags nonPersistentFlags, string changeVector, long lastModifiedTicks) { if ((nonPersistentFlags & NonPersistentDocumentFlags.FromSmuggler) == NonPersistentDocumentFlags.FromSmuggler) { return; } if (configuration.MinimumRevisionsToKeep.HasValue == false && configuration.MinimumRevisionAgeToKeep.HasValue == false) { return; } var numberOfRevisionsToDelete = revisionsCount - configuration.MinimumRevisionsToKeep ?? 0; if (numberOfRevisionsToDelete <= 0) { return; } var deletedRevisionsCount = DeleteRevisions(context, table, prefixSlice, collectionName, numberOfRevisionsToDelete, configuration.MinimumRevisionAgeToKeep, changeVector, lastModifiedTicks); Debug.Assert(numberOfRevisionsToDelete >= deletedRevisionsCount); IncrementCountOfRevisions(context, prefixSlice, -deletedRevisionsCount); }
private void WriteRevisionsCollectionConfiguration(RevisionsCollectionConfiguration collectionConfiguration) { if (collectionConfiguration == null) { _writer.WriteNull(); return; } _writer.WriteStartObject(); if (collectionConfiguration.MinimumRevisionsToKeep.HasValue) { _writer.WritePropertyName(nameof(collectionConfiguration.MinimumRevisionsToKeep)); _writer.WriteInteger(collectionConfiguration.MinimumRevisionsToKeep.Value); _writer.WriteComma(); } if (collectionConfiguration.MinimumRevisionAgeToKeep.HasValue) { _writer.WritePropertyName(nameof(collectionConfiguration.MinimumRevisionAgeToKeep)); _writer.WriteString(collectionConfiguration.MinimumRevisionAgeToKeep.Value.ToString()); _writer.WriteComma(); } _writer.WritePropertyName(nameof(collectionConfiguration.Disabled)); _writer.WriteBool(collectionConfiguration.Disabled); _writer.WriteComma(); _writer.WritePropertyName(nameof(collectionConfiguration.PurgeOnDelete)); _writer.WriteBool(collectionConfiguration.PurgeOnDelete); _writer.WriteEndObject(); }
public ConfigureRevisionsForConflictsCommand( DocumentConventions conventions, string database, RevisionsCollectionConfiguration configuration) { _conventions = conventions ?? throw new ArgumentNullException(nameof(conventions)); _databaseName = database ?? throw new ArgumentNullException(nameof(database)); _configuration = configuration; }
private void DeleteOldRevisions(DocumentsOperationContext context, Table table, Slice lowerId, CollectionName collectionName, RevisionsCollectionConfiguration configuration, NonPersistentDocumentFlags nonPersistentFlags, string changeVector, long lastModifiedTicks) { using (GetKeyPrefix(context, lowerId, out Slice prefixSlice)) { // We delete the old revisions after we put the current one, // because in case that MinimumRevisionsToKeep is 3 or lower we may get a revision document from replication // which is old. But because we put it first, we make sure to clean this document, because of the order to the revisions. var revisionsCount = IncrementCountOfRevisions(context, prefixSlice, 1); DeleteOldRevisions(context, table, prefixSlice, collectionName, configuration, revisionsCount, nonPersistentFlags, changeVector, lastModifiedTicks); } }
public bool ShouldVersionDocument(CollectionName collectionName, NonPersistentDocumentFlags nonPersistentFlags, BlittableJsonReaderObject existingDocument, BlittableJsonReaderObject document, ref DocumentFlags documentFlags, out RevisionsCollectionConfiguration configuration) { configuration = GetRevisionsConfiguration(collectionName.Name); if (configuration.Disabled) { return(false); } try { if ((nonPersistentFlags & NonPersistentDocumentFlags.FromSmuggler) != NonPersistentDocumentFlags.FromSmuggler) { return(true); } if (existingDocument == null) { if ((nonPersistentFlags & NonPersistentDocumentFlags.SkipRevisionCreation) == NonPersistentDocumentFlags.SkipRevisionCreation) { // Smuggler is configured to avoid creating new revisions during import return(false); } // we are not going to create a revision if it's an import from v3 // (since this import is going to import revisions as well) return((nonPersistentFlags & NonPersistentDocumentFlags.LegacyHasRevisions) != NonPersistentDocumentFlags.LegacyHasRevisions); } // compare the contents of the existing and the new document if (DocumentCompare.IsEqualTo(existingDocument, document, false) != DocumentCompareResult.NotEqual) { // no need to create a new revision, both documents have identical content return(false); } return(true); } finally { documentFlags |= DocumentFlags.HasRevisions; } }
public void Put(DocumentsOperationContext context, string id, BlittableJsonReaderObject document, DocumentFlags flags, NonPersistentDocumentFlags nonPersistentFlags, string changeVector, long lastModifiedTicks, RevisionsCollectionConfiguration configuration = null, CollectionName collectionName = null) { Debug.Assert(changeVector != null, "Change vector must be set"); Debug.Assert(lastModifiedTicks != DateTime.MinValue.Ticks, "last modified ticks must be set"); BlittableJsonReaderObject.AssertNoModifications(document, id, assertChildren: true); if (collectionName == null) { collectionName = _database.DocumentsStorage.ExtractCollectionName(context, document); } using (DocumentIdWorker.GetLowerIdSliceAndStorageKey(context, id, out Slice lowerId, out Slice idPtr)) { var fromSmuggler = (nonPersistentFlags & NonPersistentDocumentFlags.FromSmuggler) == NonPersistentDocumentFlags.FromSmuggler; var fromReplication = (nonPersistentFlags & NonPersistentDocumentFlags.FromReplication) == NonPersistentDocumentFlags.FromReplication; var table = EnsureRevisionTableCreated(context.Transaction.InnerTransaction, collectionName); // We want the revision's attachments to have a lower etag than the revision itself if ((flags & DocumentFlags.HasAttachments) == DocumentFlags.HasAttachments && fromSmuggler == false) { using (Slice.From(context.Allocator, changeVector, out Slice changeVectorSlice)) { if (table.VerifyKeyExists(changeVectorSlice) == false) { _documentsStorage.AttachmentsStorage.RevisionAttachments(context, lowerId, changeVectorSlice); } } } if (fromReplication) { void PutFromRevisionIfChangeVectorIsGreater() { bool hasDoc; TableValueReader tvr; try { hasDoc = _documentsStorage.GetTableValueReaderForDocument(context, lowerId, throwOnConflict: true, tvr: out tvr); } catch (DocumentConflictException) { // Do not modify the document. return; } if (hasDoc == false) { PutFromRevision(); return; } var docChangeVector = TableValueToChangeVector(context, (int)DocumentsTable.ChangeVector, ref tvr); if (ChangeVectorUtils.GetConflictStatus(changeVector, docChangeVector) == ConflictStatus.Update) { PutFromRevision(); } void PutFromRevision() { _documentsStorage.Put(context, id, null, document, lastModifiedTicks, changeVector, flags & ~DocumentFlags.Revision, nonPersistentFlags | NonPersistentDocumentFlags.FromRevision); } } PutFromRevisionIfChangeVectorIsGreater(); } flags |= DocumentFlags.Revision; var newEtag = _database.DocumentsStorage.GenerateNextEtag(); var newEtagSwapBytes = Bits.SwapBytes(newEtag); using (table.Allocate(out TableValueBuilder tvb)) using (Slice.From(context.Allocator, changeVector, out var cv)) { tvb.Add(cv.Content.Ptr, cv.Size); tvb.Add(lowerId); tvb.Add(SpecialChars.RecordSeparator); tvb.Add(newEtagSwapBytes); tvb.Add(idPtr); tvb.Add(document.BasePointer, document.Size); tvb.Add((int)flags); tvb.Add(NotDeletedRevisionMarker); tvb.Add(lastModifiedTicks); tvb.Add(context.GetTransactionMarker()); if (flags.Contain(DocumentFlags.Resolved)) { tvb.Add((int)DocumentFlags.Resolved); } else { tvb.Add(0); } tvb.Add(Bits.SwapBytes(lastModifiedTicks)); var isNew = table.Set(tvb); if (isNew == false) { // It might be just an update from replication as we call this twice, both for the doc delete and for deleteRevision. return; } } if (configuration == null) { configuration = GetRevisionsConfiguration(collectionName.Name); } DeleteOldRevisions(context, table, lowerId, collectionName, configuration, nonPersistentFlags, changeVector, lastModifiedTicks); } }
public async Task Foo() { using (var documentStore = new DocumentStore()) { using (var session = documentStore.OpenSession()) { #region operation_sync // Create a configuration for the Employees collection var employeesRevConfig = new RevisionsCollectionConfiguration() { MinimumRevisionAgeToKeep = new TimeSpan(hours: 1, minutes: 23, seconds: 45), MinimumRevisionsToKeep = 42, PurgeOnDelete = true }; // Create a configuration for the Products collection var productsRevConfig = new RevisionsCollectionConfiguration() { Disabled = true }; // Create a default collection configuration var defaultRevConfig = new RevisionsCollectionConfiguration() { MinimumRevisionAgeToKeep = new TimeSpan(days: 7, 0, 0, 0), MinimumRevisionsToKeep = 100, PurgeOnDelete = false }; // Combine to create a configuration for the database var northwindRevConfig = new RevisionsConfiguration() { Collections = new Dictionary <string, RevisionsCollectionConfiguration>() { { "Employees", employeesRevConfig }, { "Products", productsRevConfig } }, Default = defaultRevConfig }; // Execute the operation to update the database documentStore.Maintenance.Send(new ConfigureRevisionsOperation(northwindRevConfig)); #endregion } using (var asyncSession = documentStore.OpenAsyncSession()) { #region operation_async // Create a configuration for the Employees collection var employeesRevConfig = new RevisionsCollectionConfiguration() { MinimumRevisionAgeToKeep = new TimeSpan(hours: 1, minutes: 23, seconds: 45), MinimumRevisionsToKeep = 42, PurgeOnDelete = true }; // Create a configuration for the Products collection var productsRevConfig = new RevisionsCollectionConfiguration() { Disabled = true }; // Create a default collection configuration var defaultRevConfig = new RevisionsCollectionConfiguration() { MinimumRevisionAgeToKeep = new TimeSpan(days: 7, 0, 0, 0), MinimumRevisionsToKeep = 100, PurgeOnDelete = false }; // Combine to create a configuration for the database var northwindRevConfig = new RevisionsConfiguration() { Collections = new Dictionary <string, RevisionsCollectionConfiguration>() { { "Employees", employeesRevConfig }, { "Products", productsRevConfig } }, Default = defaultRevConfig }; // Execute the operation to update the database await documentStore.Maintenance.SendAsync(new ConfigureRevisionsOperation(northwindRevConfig)); #endregion } } }
public ConfigureRevisionsForConflictsOperation(string database, RevisionsCollectionConfiguration configuration) { ResourceNameValidator.AssertValidDatabaseName(database); _database = database; _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); }
public EditRevisionsForConflictsConfigurationCommand(RevisionsCollectionConfiguration configuration, string databaseName, string uniqueRequestId) : base(databaseName, uniqueRequestId) { Configuration = configuration; }