public async Task DeleteSnapshots(string streamId, ulong olderThanVersion, CancellationToken cancellationToken = default) { await this.ReadHeader(streamId, cancellationToken); var query = this.Client.CreateDocumentQuery <SnapshotDocument>(this.DocumentCollectionUri, new FeedOptions { PartitionKey = this.PartitionKey }) .Where(x => x.StreamId == streamId) .Where(x => x.DocumentType == DocumentType.Snapshot) .Where(x => x.Version < olderThanVersion) .AsDocumentQuery(); while (query.HasMoreResults) { var page = await query.ExecuteNextAsync <Document>(cancellationToken); foreach (var document in page) { if (this.DeleteMode == DeleteMode.SoftDelete) { var doc = EveneumDocument.Parse(document, this.JsonSerializerSettings); doc.Deleted = true; await this.Client.UpsertDocumentAsync(this.DocumentCollectionUri, doc, new RequestOptions { PartitionKey = this.PartitionKey }, disableAutomaticIdGeneration : true, cancellationToken); } else { await this.Client.DeleteDocumentAsync(UriFactory.CreateDocumentUri(this.Database, this.Collection, document.Id), new RequestOptions { PartitionKey = this.PartitionKey }, cancellationToken); } } } }
public async Task ThenTheEventInVersionInStreamIsReplaced(ulong version, string streamId) { var currentDocuments = await CosmosSetup.QueryAllDocumentsInStream(this.Context.Client, this.Context.Database, this.Context.Container, streamId, DocumentType.Event); var eventDocument = currentDocuments.SingleOrDefault(x => x.Id == EveneumDocument.GenerateEventId(streamId, version)); Assert.AreEqual(DocumentType.Event, eventDocument.DocumentType); Assert.AreEqual(streamId, eventDocument.StreamId); Assert.AreEqual(this.Context.EventStoreOptions.TypeProvider.GetIdentifierForType(this.Context.ReplacedEvent.Body.GetType()), eventDocument.BodyType); Assert.NotNull(eventDocument.Body); Assert.AreEqual(JToken.FromObject(this.Context.ReplacedEvent.Body, JsonSerializer.Create(this.Context.JsonSerializerSettings)), eventDocument.Body); Assert.NotNull(eventDocument.ETag); Assert.False(eventDocument.Deleted); if (this.Context.ReplacedEvent.Metadata == null) { Assert.IsNull(eventDocument.MetadataType); Assert.IsNull(eventDocument.Metadata); } else { Assert.AreEqual(this.Context.EventStoreOptions.TypeProvider.GetIdentifierForType(this.Context.ReplacedEvent.Metadata.GetType()), eventDocument.MetadataType); Assert.NotNull(eventDocument.Metadata); Assert.AreEqual(JToken.FromObject(this.Context.ReplacedEvent.Metadata, JsonSerializer.Create(this.Context.JsonSerializerSettings)), eventDocument.Metadata); } }
private async Task LoadChangeFeed(Func <IEnumerable <EveneumDocument>, Task> callback, string token = null, CancellationToken cancellationToken = default) { PartitionKeyRange partitionKeyRange = this.PartitionKey != null ? null : (await this.GetPartitionKeyRanges()).FirstOrDefault(); var changeFeed = this.Client.CreateDocumentChangeFeedQuery(this.DocumentCollectionUri, new ChangeFeedOptions { PartitionKeyRangeId = partitionKeyRange?.Id, PartitionKey = this.PartitionKey, RequestContinuation = token, StartFromBeginning = true, MaxItemCount = 10000, }); Task callbackTask = null; while (changeFeed.HasMoreResults) { var page = await changeFeed.ExecuteNextAsync <Document>(cancellationToken); if (callbackTask != null) { await callbackTask; } callbackTask = callback(page.Select(x => EveneumDocument.Parse(x, this.JsonSerializerSettings))); } }
public Snapshot DeserializeSnapshot(EveneumDocument document) { var metadata = DeserializeObject(document.MetadataType, document.Metadata); var body = DeserializeObject(document.BodyType, document.Body); return(new Snapshot(body, metadata, document.Version)); }
public EventData DeserializeEvent(EveneumDocument document) { var metadata = DeserializeObject(document.MetadataType, document.Metadata); var body = DeserializeObject(document.BodyType, document.Body); return(new EventData(document.StreamId, body, metadata, document.Version)); }
public async Task ThenTheSnapshotForVersionIsPersisted(ulong version) { var streamId = ScenarioContext.Current.GetStreamId(); var snapshot = ScenarioContext.Current.GetSnapshot(); var snapshotUri = UriFactory.CreateDocumentUri(this.Context.Database, this.Context.Collection, SnapshotDocument.GenerateId(streamId, version)); var snapshotDocumentResponse = await this.Context.Client.ReadDocumentAsync <SnapshotDocument>(snapshotUri, new RequestOptions { PartitionKey = this.Context.PartitionKey }); Assert.IsNotNull(snapshotDocumentResponse.Document); var snapshotDocument = snapshotDocumentResponse.Document; var snapshotMetadata = ScenarioContext.Current.GetSnapshotMetadata(); Assert.AreEqual(this.Context.Partition, snapshotDocument.Partition); Assert.AreEqual(DocumentType.Snapshot, snapshotDocument.DocumentType); Assert.AreEqual(streamId, snapshotDocument.StreamId); Assert.AreEqual(version, snapshotDocument.Version); Assert.AreEqual(version + EveneumDocument.GetOrderingFraction(DocumentType.Snapshot), snapshotDocument.SortOrder); if (snapshotMetadata == null) { Assert.IsNull(snapshotDocument.MetadataType); Assert.IsFalse(snapshotDocument.Metadata.HasValues); } else { Assert.AreEqual(snapshotMetadata.GetType().AssemblyQualifiedName, snapshotDocument.MetadataType); Assert.AreEqual(JToken.FromObject(snapshotMetadata), snapshotDocument.Metadata); } Assert.AreEqual(snapshot.GetType().AssemblyQualifiedName, snapshotDocument.BodyType); Assert.AreEqual(JToken.FromObject(snapshot), snapshotDocument.Body); Assert.False(snapshotDocument.Deleted); Assert.IsNotNull(snapshotDocument.ETag); }
internal void SerializeHeaderMetadata(EveneumDocument header, object metadata) { if (metadata != null) { header.MetadataType = this.TypeProvider.GetIdentifierForType(metadata.GetType()); header.Metadata = JToken.FromObject(metadata, this.JsonSerializer); } }
public async Task LoadEvents(string sql, Func <IReadOnlyCollection <EventData>, Task> callback, CancellationToken cancellationToken = default) { var query = this.Client.CreateDocumentQuery <Document>(this.DocumentCollectionUri, sql, new FeedOptions { PartitionKey = this.PartitionKey, MaxItemCount = -1 }).AsDocumentQuery(); do { var page = await query.ExecuteNextAsync <Document>(cancellationToken); await callback(page.Select(x => EveneumDocument.Parse(x, this.JsonSerializerSettings)).Where(x => !x.Deleted).OfType <EventDocument>().Select(Deserialize).ToList()); }while (query.HasMoreResults); }
public static async Task <IReadOnlyCollection <EveneumDocument> > All(this IDocumentQuery <Document> query, JsonSerializerSettings jsonSerializerSettings) { var documents = new List <EveneumDocument>(); while (query.HasMoreResults) { var page = await query.ExecuteNextAsync <Document>(); documents.AddRange(page.Select(x => EveneumDocument.Parse(x, jsonSerializerSettings))); } return(documents); }
public async Task DeleteStream(string streamId, ulong expectedVersion, CancellationToken cancellationToken = default) { var header = new HeaderDocument { Partition = this.Partition, StreamId = streamId, Version = expectedVersion }; var existingHeader = await this.ReadHeader(streamId, cancellationToken); if (existingHeader.Deleted) { throw new StreamNotFoundException(streamId); } if (existingHeader.Version != expectedVersion) { throw new OptimisticConcurrencyException(streamId, expectedVersion, existingHeader.Version); } string etag = existingHeader.ETag; var query = this.Client.CreateDocumentQuery <EveneumDocument>(this.DocumentCollectionUri, new FeedOptions { PartitionKey = this.PartitionKey }) .Where(x => x.StreamId == streamId) .AsDocumentQuery(); while (query.HasMoreResults) { var page = await query.ExecuteNextAsync <Document>(cancellationToken); foreach (var document in page) { if (this.DeleteMode == DeleteMode.SoftDelete) { var doc = EveneumDocument.Parse(document, this.JsonSerializerSettings); doc.Deleted = true; await this.Client.UpsertDocumentAsync(this.DocumentCollectionUri, doc, new RequestOptions { PartitionKey = this.PartitionKey }, disableAutomaticIdGeneration : true, cancellationToken); } else { await this.Client.DeleteDocumentAsync(document.SelfLink, new RequestOptions { PartitionKey = this.PartitionKey }, cancellationToken); } } } }
private static async Task <List <TDocument> > Query <TDocument>(IDocumentClient client, string database, string collection, string query, FeedOptions feedOptions) where TDocument : EveneumDocument { var documentQuery = client.CreateDocumentQuery <Document>(UriFactory.CreateDocumentCollectionUri(database, collection), query, feedOptions).AsDocumentQuery(); var documents = new List <TDocument>(); do { var page = await documentQuery.ExecuteNextAsync <Document>(); documents.AddRange(page.Select(x => EveneumDocument.Parse(x, new JsonSerializerSettings())).OfType <TDocument>()); }while (documentQuery.HasMoreResults); return(documents); }
public async Task ThenTheHeaderVersionWithNoMetadataIsPersisted(ulong version) { var headerDocuments = await CosmosSetup.QueryAllDocumentsInStream(this.Context.Client, this.Context.Database, this.Context.Container, this.Context.StreamId, DocumentType.Header); Assert.AreEqual(1, headerDocuments.Count); var headerDocument = headerDocuments[0]; Assert.AreEqual(DocumentType.Header, headerDocument.DocumentType); Assert.AreEqual(this.Context.StreamId, headerDocument.StreamId); Assert.AreEqual(version, headerDocument.Version); Assert.AreEqual(version + EveneumDocument.GetOrderingFraction(DocumentType.Header), headerDocument.SortOrder); Assert.IsNull(headerDocument.MetadataType); Assert.IsFalse(headerDocument.Metadata.HasValues); Assert.NotNull(headerDocument.ETag); Assert.False(headerDocument.Deleted); }
internal EveneumDocument SerializeSnapshot(object snapshot, object metadata, ulong version, string streamId) { var document = new EveneumDocument(DocumentType.Snapshot) { StreamId = streamId, Version = version, BodyType = this.TypeProvider.GetIdentifierForType(snapshot.GetType()), Body = JToken.FromObject(snapshot, this.JsonSerializer) }; if (metadata != null) { document.MetadataType = this.TypeProvider.GetIdentifierForType(metadata.GetType()); document.Metadata = JToken.FromObject(metadata, this.JsonSerializer); } return(document); }
internal EveneumDocument SerializeEvent(EventData @event, string streamId) { var document = new EveneumDocument(DocumentType.Event) { StreamId = streamId, Version = @event.Version, BodyType = this.TypeProvider.GetIdentifierForType(@event.Body.GetType()), Body = JToken.FromObject(@event.Body, this.JsonSerializer) }; if (@event.Metadata != null) { document.MetadataType = this.TypeProvider.GetIdentifierForType(@event.Metadata.GetType()); document.Metadata = JToken.FromObject(@event.Metadata, this.JsonSerializer); } return(document); }
public async Task ThenTheHeaderVersionWithNoMetadataIsPersisted(ulong version) { var headerDocumentResponse = await this.Context.Client.ReadDocumentAsync <HeaderDocument>(UriFactory.CreateDocumentUri(this.Context.Database, this.Context.Collection, ScenarioContext.Current.GetStreamId()), new RequestOptions { PartitionKey = this.Context.PartitionKey }); Assert.IsNotNull(headerDocumentResponse.Document); var headerDocument = headerDocumentResponse.Document; Assert.AreEqual(this.Context.Partition, headerDocument.Partition); Assert.AreEqual(DocumentType.Header, headerDocument.DocumentType); Assert.AreEqual(ScenarioContext.Current.GetStreamId(), headerDocument.StreamId); Assert.AreEqual(version, headerDocument.Version); Assert.AreEqual(version + EveneumDocument.GetOrderingFraction(DocumentType.Header), headerDocument.SortOrder); Assert.IsNull(headerDocument.MetadataType); Assert.IsFalse(headerDocument.Metadata.HasValues); Assert.NotNull(headerDocument.ETag); Assert.False(headerDocument.Deleted); }
public async Task ThenTheHeaderVersionWithMetadataIsPersisted(ulong version) { var headerDocuments = await CosmosSetup.QueryAllDocumentsInStream(this.Context.Client, this.Context.Database, this.Context.Container, this.Context.StreamId, DocumentType.Header); Assert.AreEqual(1, headerDocuments.Count); var headerDocument = headerDocuments[0]; Assert.AreEqual(DocumentType.Header, headerDocument.DocumentType); Assert.AreEqual(this.Context.StreamId, headerDocument.StreamId); Assert.AreEqual(version, headerDocument.Version); Assert.AreEqual(version + EveneumDocument.GetOrderingFraction(DocumentType.Header), headerDocument.SortOrder); Assert.AreEqual(this.Context.EventStoreOptions.TypeProvider.GetIdentifierForType(typeof(SampleMetadata)), headerDocument.MetadataType); Assert.NotNull(headerDocument.Metadata); Assert.AreEqual(JToken.FromObject(this.Context.HeaderMetadata), headerDocument.Metadata); Assert.NotNull(headerDocument.ETag); Assert.False(headerDocument.Deleted); }
public async Task ThenNewEventsAreAppended() { var streamId = this.Context.StreamId; var currentDocuments = await CosmosSetup.QueryAllDocumentsInStream(this.Context.Client, this.Context.Database, this.Context.Container, streamId, DocumentType.Event); var existingDocumentIds = this.Context.ExistingDocuments.Select(x => x.Id); var newEventDocuments = currentDocuments.Where(x => !existingDocumentIds.Contains(x.Id)).ToList(); var newEvents = this.Context.NewEvents; Assert.AreEqual(newEventDocuments.Count, newEvents.Length); foreach (var newEvent in newEvents) { var eventDocument = newEventDocuments.Find(x => x.Id == EveneumDocument.GenerateEventId(streamId, newEvent.Version)); Assert.IsNotNull(eventDocument); Assert.AreEqual(DocumentType.Event, eventDocument.DocumentType); Assert.AreEqual(streamId, eventDocument.StreamId); Assert.AreEqual(this.Context.EventStoreOptions.TypeProvider.GetIdentifierForType(newEvent.Body.GetType()), eventDocument.BodyType); Assert.NotNull(eventDocument.Body); Assert.AreEqual(JToken.FromObject(newEvent.Body, JsonSerializer.Create(this.Context.JsonSerializerSettings)), eventDocument.Body); Assert.NotNull(eventDocument.ETag); Assert.False(eventDocument.Deleted); if (newEvent.Metadata == null) { Assert.IsNull(eventDocument.MetadataType); Assert.IsNull(eventDocument.Metadata); } else { Assert.AreEqual(this.Context.EventStoreOptions.TypeProvider.GetIdentifierForType(newEvent.Metadata.GetType()), eventDocument.MetadataType); Assert.NotNull(eventDocument.Metadata); Assert.AreEqual(JToken.FromObject(newEvent.Metadata, JsonSerializer.Create(this.Context.JsonSerializerSettings)), eventDocument.Metadata); } } }
public async Task ThenTheSnapshotForVersionIsPersisted(ulong version) { var streamId = this.Context.StreamId; var snapshot = this.Context.Snapshot; var snapshotDocuments = await CosmosSetup.QueryAllDocumentsInStream(this.Context.Client, this.Context.Database, this.Context.Container, this.Context.StreamId, DocumentType.Snapshot); Assert.IsNotEmpty(snapshotDocuments); var snapshotDocument = snapshotDocuments.Find(x => x.Version == version); Assert.IsNotNull(snapshotDocument); var snapshotMetadata = this.Context.SnapshotMetadata; Assert.AreEqual(DocumentType.Snapshot, snapshotDocument.DocumentType); Assert.AreEqual(streamId, snapshotDocument.StreamId); Assert.AreEqual(version, snapshotDocument.Version); Assert.AreEqual(version + EveneumDocument.GetOrderingFraction(DocumentType.Snapshot), snapshotDocument.SortOrder); if (snapshotMetadata == null) { Assert.IsNull(snapshotDocument.MetadataType); Assert.IsFalse(snapshotDocument.Metadata.HasValues); } else { Assert.AreEqual(snapshotMetadata.GetType().AssemblyQualifiedName, snapshotDocument.MetadataType); Assert.AreEqual(JToken.FromObject(snapshotMetadata), snapshotDocument.Metadata); } Assert.AreEqual(snapshot.GetType().AssemblyQualifiedName, snapshotDocument.BodyType); Assert.AreEqual(JToken.FromObject(snapshot), snapshotDocument.Body); Assert.False(snapshotDocument.Deleted); Assert.IsNotNull(snapshotDocument.ETag); }
public async Task <Stream?> ReadStream(string streamId, CancellationToken cancellationToken = default) { if (streamId == null) { throw new ArgumentNullException(nameof(streamId)); } var sql = $"SELECT * FROM x WHERE x.{nameof(EveneumDocument.StreamId)} = '{streamId}' ORDER BY x.{nameof(EveneumDocument.SortOrder)} DESC"; var query = this.Client.CreateDocumentQuery <Document>(this.DocumentCollectionUri, sql, new FeedOptions { PartitionKey = this.PartitionKey }).AsDocumentQuery(); var documents = new List <EveneumDocument>(); var finishLoading = false; do { var page = await query.ExecuteNextAsync <Document>(cancellationToken); foreach (var document in page) { var eveneumDoc = EveneumDocument.Parse(document, this.JsonSerializerSettings); if (eveneumDoc is HeaderDocument && eveneumDoc.Deleted) { throw new StreamNotFoundException(streamId); } if (eveneumDoc.Deleted) { continue; } documents.Add(eveneumDoc); if (eveneumDoc is SnapshotDocument) { finishLoading = true; break; } } if (finishLoading) { break; } }while (query.HasMoreResults); if (documents.Count == 0) { return(null); } var headerDocument = documents.First() as HeaderDocument; var events = documents.OfType <EventDocument>().Select(this.Deserialize).Reverse().ToArray(); var snapshot = documents.OfType <SnapshotDocument>().Select(this.Deserialize).Cast <Snapshot?>().FirstOrDefault(); object metadata = null; if (!string.IsNullOrEmpty(headerDocument.MetadataType)) { metadata = headerDocument.Metadata.ToObject(this.TypeCache.Resolve(headerDocument.MetadataType), this.JsonSerializer); } return(new Stream(streamId, headerDocument.Version, metadata, events, snapshot)); }
public async Task <Response> ReplaceEvent(EventData newEvent, CancellationToken cancellationToken = default) { try { var response = await this.Container.ReplaceItemAsync(this.Serializer.SerializeEvent(newEvent, newEvent.StreamId), EveneumDocument.GenerateEventId(newEvent.StreamId, newEvent.Version), new PartitionKey(newEvent.StreamId), cancellationToken : cancellationToken); return(new Response(response.RequestCharge)); } catch (CosmosException ex) { throw new WriteException(newEvent.StreamId, ex.RequestCharge, ex.Message, ex.StatusCode, ex); } }
public DocumentResponse(EveneumDocument document, double requestCharge) : base(requestCharge) { this.Document = document; }
public async Task <Response> WriteToStream(string streamId, EventData[] events, ulong?expectedVersion = null, object metadata = null, CancellationToken cancellationToken = default) { if (!this.IsInitialized) { throw new NotInitializedException(); } var transaction = this.Container.CreateTransactionalBatch(new PartitionKey(streamId)); double requestCharge = 0; // Existing stream if (expectedVersion.HasValue) { var headerResponse = await this.ReadHeader(streamId, cancellationToken); var header = headerResponse.Document; requestCharge += headerResponse.RequestCharge; if (header.Version != expectedVersion) { throw new OptimisticConcurrencyException(streamId, expectedVersion.Value, header.Version); } header.Version += (ulong)events.Length; this.Serializer.SerializeHeaderMetadata(header, metadata); transaction.ReplaceItem(header.Id, header, new TransactionalBatchItemRequestOptions { IfMatchEtag = header.ETag }); } else { var header = new EveneumDocument(DocumentType.Header) { StreamId = streamId, Version = (ulong)events.Length }; this.Serializer.SerializeHeaderMetadata(header, metadata); transaction.CreateItem(header); } var firstBatch = events.Take(this.BatchSize - 1).Select(@event => this.Serializer.SerializeEvent(@event, streamId)); foreach (var document in firstBatch) { transaction.CreateItem(document); } var response = await transaction.ExecuteAsync(cancellationToken); requestCharge += response.RequestCharge; if (response.StatusCode == System.Net.HttpStatusCode.Conflict) { throw new StreamAlreadyExistsException(streamId); } else if (!response.IsSuccessStatusCode) { throw new WriteException(response.ErrorMessage, response.StatusCode); } foreach (var batch in events.Skip(this.BatchSize - 1).Select(@event => this.Serializer.SerializeEvent(@event, streamId)).Batch(this.BatchSize)) { transaction = this.Container.CreateTransactionalBatch(new PartitionKey(streamId)); foreach (var document in batch) { transaction.CreateItem(document); } response = await transaction.ExecuteAsync(cancellationToken); requestCharge += response.RequestCharge; if (!response.IsSuccessStatusCode) { throw new WriteException(response.ErrorMessage, response.StatusCode); } } return(new Response(requestCharge)); }