private bool TryGetLatestDocument(
            IEnumerable <VersionedDbDocument> documents,
            VersionedDocumentReadOptions options,
            out VersionedDbDocument document,
            out DateTime createdTime,
            out DateTime modifiedTime)
        {
            var ordered = documents.OrderBy(x => x.Version).ToArray();

            if (ordered.Length == 0)
            {
                document     = null;
                createdTime  = default(DateTime);
                modifiedTime = default(DateTime);
                return(false);
            }

            var first = ordered[0];
            var last  = ordered[ordered.Length - 1];

            if (last.Deleted && !options.IncludeDeleted)
            {
                document     = null;
                createdTime  = default(DateTime);
                modifiedTime = default(DateTime);
                return(false);
            }

            createdTime  = first.Timestamp;
            modifiedTime = last.Timestamp;
            document     = last;
            return(true);
        }
        /// <inheritdoc />
        public async Task <VersionedDocumentUpsertResult <TDocument> > UpsertDocumentAsync <TDocument>(
            TDocument document,
            DocumentTypeMapping <TDocument> mapping,
            OperationOptions operationOptions)
        {
            if (document == null)
            {
                throw new ArgumentNullException(nameof(document));
            }
            if (mapping == null)
            {
                throw new ArgumentNullException(nameof(mapping));
            }

            operationOptions = operationOptions ?? new OperationOptions();

            var documentId = mapping.IdMapper(document);

            // Get the newest document version if one exists.
            var existingDocument = await GetDocumentIncludingDeleted(documentId, mapping);

            if (existingDocument != null && operationOptions.CheckVersion.HasValue && operationOptions.CheckVersion != existingDocument.Version)
            {
                throw new NebulaStoreConcurrencyException("Existing document version does not match the specified check version.");
            }

            var version = CalculateNextVersion(existingDocument);

            var dbRecord = new VersionedDbDocument();

            dbRecord.Id           = CreateRecordId(documentId, version, mapping);
            dbRecord.DocumentId   = documentId;
            dbRecord.Service      = DbAccess.ConfigManager.ServiceName;
            dbRecord.PartitionKey = mapping.PartitionKeyMapper(document);
            dbRecord.Version      = version;
            dbRecord.Actor        = GetActorId();
            dbRecord.Timestamp    = DateTime.UtcNow;

            SetDocumentContent(dbRecord, document, mapping);

            await CreateDocumentAsync(dbRecord, existingDocument);

            var updatedDocument = await GetDocumentAsync(documentId, version, mapping);

            if (updatedDocument == null)
            {
                throw new NebulaStoreException("Failed to retrieve document after successful upsert");
            }

            if (updatedDocument.ResultType == DocumentReadResultType.Failed)
            {
                throw new NebulaStoreException($"Failed to retrieve document: {updatedDocument.FailureDetails.Message}");
            }

            return(new VersionedDocumentUpsertResult <TDocument>(documentId, updatedDocument.Metadata, updatedDocument.Document));
        }
        /// <inheritdoc />
        public async Task DeleteDocumentAsync <TDocument>(string id, DocumentTypeMapping <TDocument> mapping, OperationOptions operationOptions)
        {
            if (id == null)
            {
                throw new ArgumentNullException(nameof(id));
            }
            if (mapping == null)
            {
                throw new ArgumentNullException(nameof(mapping));
            }

            operationOptions = operationOptions ?? new OperationOptions();

            var query     = QueryClient.CreateQueryById(id, mapping);
            var documents = await ExecuteQueryAsync(query);

            var existingDocument = FindLatestDocumentIncludingDeleted(documents);

            if (existingDocument == null)
            {
                // Document not found. Treated similarly to already deleted.
                return;
            }

            if (operationOptions.CheckVersion.HasValue && operationOptions.CheckVersion != existingDocument.Version)
            {
                throw new NebulaStoreConcurrencyException("Existing document version does not match the specified check version");
            }

            // Only perform removal if it is not already deleted.
            if (existingDocument.Deleted)
            {
                // Document already deleted.
                return;
            }

            // Document not deleted. Create deletion record.

            var version = CalculateNextVersion(existingDocument);

            var dbRecord = new VersionedDbDocument();

            dbRecord.Id           = CreateRecordId(id, version, mapping);
            dbRecord.DocumentId   = existingDocument.DocumentId;
            dbRecord.Service      = DbAccess.ConfigManager.ServiceName;
            dbRecord.PartitionKey = existingDocument.PartitionKey;
            dbRecord.Version      = version;
            dbRecord.Deleted      = true;
            dbRecord.Actor        = GetActorId();
            dbRecord.Timestamp    = DateTime.UtcNow;

            SetDocumentContentFromExisting(dbRecord, existingDocument, mapping);

            await CreateDocumentAsync(dbRecord, existingDocument);
        }
        private int CalculateNextVersion(VersionedDbDocument document)
        {
            int version = 1;

            if (document != null)
            {
                version = document.Version + 1;
            }

            return(version);
        }
        private bool TryGetLatestDocument(
            IEnumerable <VersionedDbDocument> documents,
            VersionedDocumentReadOptions options,
            out VersionedDbDocument document,
            out DateTime createdTime,
            out DateTime modifiedTime)
        {
            var ordered = documents.OrderBy(x => x.Version).ToArray();

            if (ordered.Length == 0)
            {
                document     = null;
                createdTime  = default(DateTime);
                modifiedTime = default(DateTime);
                return(false);
            }

            var first = ordered[0];
            var last  = ordered[ordered.Length - 1];

            if (last.Deleted && !options.IncludeDeleted)
            {
                document     = null;
                createdTime  = default(DateTime);
                modifiedTime = default(DateTime);
                return(false);
            }

            if (ordered.Length == 1 && !last.Latest)
            {
                // There is only one document returned by the query and that document is not the
                // latest. That means the query did not match the latest version of the document
                // because the first document version is always included in latest queries.
                document     = null;
                createdTime  = default(DateTime);
                modifiedTime = default(DateTime);
                return(false);
            }

            createdTime  = first.Timestamp;
            modifiedTime = last.Timestamp;
            document     = last;
            return(true);
        }
        private VersionedDocumentReadResult <TDocument> CreateReadResult <TDocument>(
            string id,
            VersionedDbDocument document,
            DocumentTypeMapping <TDocument> mapping,
            DateTime createdTime,
            DateTime modifiedTime)
        {
            var metadata = new VersionedDocumentMetadata(document.Version, document.Deleted, createdTime, modifiedTime, document.Actor);

            TDocument content;
            DocumentReadFailureDetails failure;

            if (!TryGetDocumentContent(document, mapping, out content, out failure))
            {
                return(VersionedDocumentReadResult <TDocument> .CreateFailure(id, metadata, failure));
            }

            return(VersionedDocumentReadResult <TDocument> .CreateOkay(id, metadata, content));
        }
 private async Task CreateDocumentAsync(VersionedDbDocument newRecord, VersionedDbDocument existingRecord)
 {
     await ExecuteStoredProcedureAsync <CreateDocumentStoredProcedure>(newRecord.PartitionKey, newRecord, existingRecord);
 }