コード例 #1
0
        private void AppendToStreamExpectedVersionNoStream(int expectedVersion, NewStreamMessage[] newMessages)
        {
            if (_messages.Count > 0)
            {
                //Already committed Messages, do idempotency check
                if (newMessages.Length > _messages.Count)
                {
                    throw new WrongExpectedVersionException(
                              ErrorMessages.AppendFailedWrongExpectedVersion(_streamId, expectedVersion),
                              _streamId,
                              expectedVersion);
                }

                if (newMessages.Where((message, index) => _messages[index].MessageId != message.MessageId).Any())
                {
                    throw new WrongExpectedVersionException(
                              ErrorMessages.AppendFailedWrongExpectedVersion(_streamId, expectedVersion),
                              _streamId,
                              expectedVersion);
                }
                return;
            }

            // None of the Messages were written previously...
            AppendEvents(newMessages);
        }
コード例 #2
0
        protected override async Task <AppendResult> AppendToStreamInternal(
            string streamId,
            int expectedVersion,
            NewStreamMessage[] messages,
            CancellationToken cancellationToken)
        {
            var streamIdInfo = new StreamIdInfo(streamId);

            try
            {
                return(messages.Length == 0
                    ? await CreateEmptyStream(streamIdInfo, expectedVersion, cancellationToken)
                    : await AppendMessagesToStream(streamIdInfo, expectedVersion, messages, cancellationToken));
            }
            catch (MySqlException ex) when(ex.IsWrongExpectedVersion())
            {
                throw new WrongExpectedVersionException(
                          ErrorMessages.AppendFailedWrongExpectedVersion(
                              streamIdInfo.MySqlStreamId.IdOriginal,
                              expectedVersion),
                          streamIdInfo.MySqlStreamId.IdOriginal,
                          expectedVersion,
                          ex);
            }
        }
コード例 #3
0
        private async Task <SqliteAppendResult> AppendToStreamEmpty(string streamId, NewStreamMessage[] messages, CancellationToken cancellationToken)
        {
            using (var conn = OpenConnection(false))
            {
                var stream    = conn.Streams(streamId);
                var allStream = conn.AllStream();

                var length = await stream.Length(cancellationToken);

                if (length > StreamVersion.Start)
                {
                    throw new WrongExpectedVersionException(
                              ErrorMessages.AppendFailedWrongExpectedVersion(
                                  streamId,
                                  StreamVersion.Start),
                              streamId,
                              StreamVersion.Start);
                }

                using (allStream.WithTransaction())
                {
                    var result = await allStream.Append(streamId, messages);

                    await allStream.Commit(cancellationToken);

                    return(result);
                }
            }
        }
コード例 #4
0
        private async Task <AppendResult> AppendMessagesToStream(
            StreamIdInfo streamId,
            int expectedVersion,
            NewStreamMessage[] messages,
            CancellationToken cancellationToken)
        {
            var appendResult        = new AppendResult(StreamVersion.End, Position.End);
            var nextExpectedVersion = expectedVersion;

            using (var connection = await OpenConnection(cancellationToken))
                using (var transaction = await connection
                                         .BeginTransactionAsync(cancellationToken)
                                         .NotOnCapturedContext())
                {
                    var throwIfAdditionalMessages = false;

                    for (var i = 0; i < messages.Length; i++)
                    {
                        bool messageExists;
                        (nextExpectedVersion, appendResult, messageExists) = await AppendMessageToStream(
                            streamId,
                            nextExpectedVersion,
                            messages[i],
                            transaction,
                            cancellationToken);

                        if (i == 0)
                        {
                            throwIfAdditionalMessages = messageExists;
                        }
                        else
                        {
                            if (throwIfAdditionalMessages && !messageExists)
                            {
                                throw new WrongExpectedVersionException(
                                          ErrorMessages.AppendFailedWrongExpectedVersion(
                                              streamId.MySqlStreamId.IdOriginal,
                                              expectedVersion),
                                          streamId.MySqlStreamId.IdOriginal,
                                          expectedVersion);
                            }
                        }
                    }

                    await transaction.CommitAsync(cancellationToken).NotOnCapturedContext();
                }

            if (_settings.ScavengeAsynchronously)
            {
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
                Task.Run(() => TryScavenge(streamId, cancellationToken), cancellationToken);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
            }
            else
            {
                await TryScavenge(streamId, cancellationToken).NotOnCapturedContext();
            }

            return(appendResult);
        }
コード例 #5
0
        private AppendResult AppendToStreamInternal(
            string streamId,
            int expectedVersion,
            NewStreamMessage[] messages)
        {
            InMemoryStream inMemoryStream;

            if (expectedVersion == ExpectedVersion.NoStream || expectedVersion == ExpectedVersion.Any)
            {
                if (_streams.TryGetValue(streamId, out inMemoryStream))
                {
                    inMemoryStream.AppendToStream(expectedVersion, messages);
                }
                else
                {
                    inMemoryStream = new InMemoryStream(
                        streamId,
                        _allStream,
                        _getUtcNow,
                        _onStreamAppended,
                        () => _currentPosition++);
                    inMemoryStream.AppendToStream(expectedVersion, messages);
                    _streams.Add(streamId, inMemoryStream);
                }
                return(new AppendResult(inMemoryStream.CurrentVersion));
            }

            if (!_streams.TryGetValue(streamId, out inMemoryStream))
            {
                throw new WrongExpectedVersionException(
                          ErrorMessages.AppendFailedWrongExpectedVersion(streamId, expectedVersion));
            }
            inMemoryStream.AppendToStream(expectedVersion, messages);
            return(new AppendResult(inMemoryStream.CurrentVersion));
        }
コード例 #6
0
        private void DeleteStream(string streamId, int expectedVersion)
        {
            if (!_streams.ContainsKey(streamId))
            {
                if (expectedVersion >= 0)
                {
                    throw new WrongExpectedVersionException(
                              ErrorMessages.AppendFailedWrongExpectedVersion(streamId, expectedVersion));
                }
                return;
            }
            if (expectedVersion != ExpectedVersion.Any &&
                _streams[streamId].Events.Last().StreamVersion != expectedVersion)
            {
                throw new WrongExpectedVersionException(
                          ErrorMessages.AppendFailedWrongExpectedVersion(streamId, expectedVersion));
            }
            InMemoryStream inMemoryStream = _streams[streamId];

            _streams.Remove(streamId);
            inMemoryStream.DeleteAllEvents(ExpectedVersion.Any);

            var streamDeletedEvent = CreateStreamDeletedMessage(streamId);

            AppendToStreamInternal(DeletedStreamId, ExpectedVersion.Any, new[] { streamDeletedEvent });
        }
コード例 #7
0
        private void AppendToStreamExpectedVersionAny(int expectedVersion, NewStreamMessage[] newMessages)
        {
            if (newMessages?.Length > 0)
            {
                // idemponcy check - have messages already been written?
                if (_messagesById.TryGetValue(newMessages[0].MessageId, out var item))
                {
                    int i = _messages.IndexOf(item);
                    if (i + newMessages.Length > _messages.Count)
                    {
                        throw new WrongExpectedVersionException(
                                  ErrorMessages.AppendFailedWrongExpectedVersion(_streamId, expectedVersion),
                                  _streamId,
                                  expectedVersion);
                    }

                    for (int n = 1; n < newMessages.Length; n++)
                    {
                        if (newMessages[n].MessageId != _messages[i + n].MessageId)
                        {
                            throw new WrongExpectedVersionException(
                                      ErrorMessages.AppendFailedWrongExpectedVersion(_streamId, expectedVersion),
                                      _streamId,
                                      expectedVersion);
                        }
                    }

                    return;
                }
            }

            // None of the Messages were written previously...
            AppendEvents(newMessages);
        }
コード例 #8
0
        private async Task <SqliteAppendResult> AppendToNonexistentStream(string streamId, NewStreamMessage[] messages, CancellationToken cancellationToken)
        {
            using (var connection = OpenConnection(false))
            {
                var stream = connection.Streams(streamId);

                if (await stream.Exists())
                {
                    var position = await stream.AllStreamPosition(ReadDirection.Forward, StreamVersion.Start);

                    var eventIds = (await stream.Read(ReadDirection.Forward, position, false, int.MaxValue - 1))
                                   .Select(message => message.MessageId)
                                   .ToArray();

                    if (eventIds.Length > 0)
                    {
                        for (var i = 0; i < Math.Min(eventIds.Length, messages.Length); i++)
                        {
                            if (eventIds[i] != messages[i].MessageId)
                            {
                                throw new WrongExpectedVersionException(
                                          ErrorMessages.AppendFailedWrongExpectedVersion(
                                              streamId,
                                              ExpectedVersion.NoStream),
                                          streamId,
                                          ExpectedVersion.NoStream);
                            }
                        }

                        if (eventIds.Length < messages.Length)
                        {
                            throw new WrongExpectedVersionException(
                                      ErrorMessages.AppendFailedWrongExpectedVersion(
                                          streamId,
                                          ExpectedVersion.NoStream),
                                      streamId,
                                      ExpectedVersion.NoStream);
                        }

                        var header = stream.Properties(false, cancellationToken)
                                     .GetAwaiter().GetResult();

                        return(new SqliteAppendResult(header.Version, header.Position, null));
                    }
                }

                using (stream.WithTransaction())
                {
                    // this will create the stream information so messages can be appended.
                    stream.Properties(true, cancellationToken)
                    .GetAwaiter().GetResult();

                    await stream.Commit(cancellationToken);
                }
            }

            return(await AppendToStreamEmpty(streamId, messages, cancellationToken));
        }
コード例 #9
0
        public async Task When_append_stream_second_time_with_no_stream_expected_and_different_inital_messages_then_should_throw()
        {
            const string streamId = "stream-1";
            await Store
            .AppendToStream(streamId, ExpectedVersion.NoStream, CreateNewStreamMessages(1, 2));

            var exception = await Record.ExceptionAsync(() =>
                                                        Store.AppendToStream(streamId, ExpectedVersion.NoStream, CreateNewStreamMessages(2)));

            exception.ShouldBeOfType <WrongExpectedVersionException>(
                ErrorMessages.AppendFailedWrongExpectedVersion(streamId, ExpectedVersion.NoStream));
        }
コード例 #10
0
        public async Task When_append_stream_with_expected_version_any_and_some_of_the_messages_previously_committed_and_with_additional_messages_then_should_throw()
        {
            const string streamId = "stream-1";
            await Store
            .AppendToStream(streamId, ExpectedVersion.Any, CreateNewStreamMessages(1, 2, 3));

            var exception = await Record.ExceptionAsync(() =>
                                                        Store.AppendToStream(streamId, ExpectedVersion.Any, CreateNewStreamMessages(2, 3, 4)));

            exception.ShouldBeOfType <WrongExpectedVersionException>(
                ErrorMessages.AppendFailedWrongExpectedVersion(streamId, ExpectedVersion.Any));
        }
コード例 #11
0
        public async Task When_append_stream_with_higher_wrong_expected_version_then_should_throw()
        {
            const string streamId = "stream-1";
            await Store
            .AppendToStream(streamId, ExpectedVersion.NoStream, CreateNewStreamMessages(1, 2, 3));

            var exception = await Record.ExceptionAsync(() =>
                                                        Store.AppendToStream(streamId, 10, CreateNewStreamMessages(4)));

            exception.ShouldBeOfType <WrongExpectedVersionException>(
                ErrorMessages.AppendFailedWrongExpectedVersion(streamId, 10));
        }
コード例 #12
0
        private async Task <AppendResult> AppendMessagesToStream(
            StreamIdInfo streamId,
            int expectedVersion,
            NewStreamMessage[] messages,
            CancellationToken cancellationToken)
        {
            var appendResult        = new AppendResult(StreamVersion.End, Position.End);
            var nextExpectedVersion = expectedVersion;

            using (var connection = await OpenConnection(cancellationToken))
                using (var transaction = await connection
                                         .BeginTransactionAsync(cancellationToken)
                                         .ConfigureAwait(false))
                {
                    var throwIfAdditionalMessages = false;

                    for (var i = 0; i < messages.Length; i++)
                    {
                        bool messageExists;
                        (nextExpectedVersion, appendResult, messageExists) = await AppendMessageToStream(
                            streamId,
                            nextExpectedVersion,
                            messages[i],
                            transaction,
                            cancellationToken);

                        if (i == 0)
                        {
                            throwIfAdditionalMessages = messageExists;
                        }
                        else
                        {
                            if (throwIfAdditionalMessages && !messageExists)
                            {
                                throw new WrongExpectedVersionException(
                                          ErrorMessages.AppendFailedWrongExpectedVersion(
                                              streamId.MySqlStreamId.IdOriginal,
                                              expectedVersion),
                                          streamId.MySqlStreamId.IdOriginal,
                                          expectedVersion);
                            }
                        }
                    }

                    await transaction.CommitAsync(cancellationToken).NotOnCapturedContext();
                }

            await TryScavenge(streamId, cancellationToken).NotOnCapturedContext();

            return(appendResult);
        }
コード例 #13
0
        public async Task When_append_stream_with_correct_expected_version_second_time_with_additional_messages_then_should_throw()
        {
            const string streamId = "stream-1";
            await Store.AppendToStream(
                streamId, ExpectedVersion.NoStream, CreateNewStreamMessages(1, 2, 3));

            await Store.AppendToStream(
                streamId, 2, CreateNewStreamMessages(4, 5, 6));

            var exception = await Record.ExceptionAsync(() =>
                                                        Store.AppendToStream(streamId, 2, CreateNewStreamMessages(4, 5, 6, 7)));

            exception.ShouldBeOfType <WrongExpectedVersionException>(
                ErrorMessages.AppendFailedWrongExpectedVersion(streamId, 2));
        }
コード例 #14
0
        internal void DeleteAllEvents(int expectedVersion)
        {
            if (expectedVersion > 0 && expectedVersion != CurrentVersion)
            {
                throw new WrongExpectedVersionException(
                          ErrorMessages.AppendFailedWrongExpectedVersion(_streamId, expectedVersion));
            }

            foreach (var inMemorymessage in _messages)
            {
                _inMemoryAllStream.Remove(inMemorymessage);
            }
            _messages.Clear();
            _messagesById.Clear();
        }
コード例 #15
0
        private void AppendToStreamExpectedVersion(int expectedVersion, NewStreamMessage[] newMessages)
        {
            // Need to do optimistic concurrency check...
            if (expectedVersion > CurrentVersion)
            {
                throw new WrongExpectedVersionException(
                          ErrorMessages.AppendFailedWrongExpectedVersion(_streamId, expectedVersion),
                          _streamId,
                          expectedVersion);
            }

            if (CurrentVersion >= 0 && expectedVersion < CurrentVersion)
            {
                // expectedVersion < currentVersion, Idempotency test
                for (int i = 0; i < newMessages.Length; i++)
                {
                    int index = expectedVersion + i + 1;
                    if (index >= _messages.Count)
                    {
                        throw new WrongExpectedVersionException(
                                  ErrorMessages.AppendFailedWrongExpectedVersion(_streamId, expectedVersion),
                                  _streamId,
                                  expectedVersion);
                    }
                    if (_messages[index].MessageId != newMessages[i].MessageId)
                    {
                        throw new WrongExpectedVersionException(
                                  ErrorMessages.AppendFailedWrongExpectedVersion(_streamId, expectedVersion),
                                  _streamId,
                                  expectedVersion);
                    }
                }
                return;
            }

            // expectedVersion == currentVersion)
            if (newMessages.Any(newmessage => _messagesById.ContainsKey(newmessage.MessageId)))
            {
                throw new WrongExpectedVersionException(
                          ErrorMessages.AppendFailedWrongExpectedVersion(_streamId, expectedVersion),
                          _streamId,
                          expectedVersion);
            }

            AppendEvents(newMessages);
        }
        public async Task When_append_stream_with_expected_version_and_duplicate_message_Id_then_should_throw()
        {
            using (var fixture = GetFixture())
            {
                using (var store = await fixture.GetStreamStore())
                {
                    const string streamId = "stream-1";
                    await store
                    .AppendToStream(streamId, ExpectedVersion.NoStream, CreateNewStreamMessages(1, 2, 3));

                    var exception = await Record.ExceptionAsync(() =>
                                                                store.AppendToStream(streamId, 2, CreateNewStreamMessages(1)));

                    exception.ShouldBeOfType <WrongExpectedVersionException>(
                        ErrorMessages.AppendFailedWrongExpectedVersion(streamId, 2));
                }
            }
        }
コード例 #17
0
        When_append_stream_second_time_with_no_stream_expected_and_additional_messages_then_should_throw()
        {
            // Idempotency
            using (var fixture = GetFixture())
            {
                using (var store = await fixture.GetStreamStore())
                {
                    const string streamId = "stream-1";
                    await store
                    .AppendToStream(streamId, ExpectedVersion.NoStream, CreateNewStreamMessages(1, 2));

                    var exception = await Record.ExceptionAsync(() =>
                                                                store.AppendToStream(streamId, ExpectedVersion.NoStream, CreateNewStreamMessages(1, 2, 3)));

                    exception.ShouldBeOfType <WrongExpectedVersionException>(
                        ErrorMessages.AppendFailedWrongExpectedVersion(streamId, ExpectedVersion.NoStream));
                }
            }
        }
コード例 #18
0
        private void AppendToStreamExpectedVersionAny(int expectedVersion, NewStreamMessage[] newMessages)
        {
            // idemponcy check - how many newMessages have already been written?
            var newEventIds = new HashSet <Guid>(newMessages.Select(e => e.MessageId));

            newEventIds.ExceptWith(_messagesById.Keys);

            if (newEventIds.Count == 0)
            {
                // All Messages have already been written, we're idempotent
                return;
            }

            if (newEventIds.Count != newMessages.Length)
            {
                // Some of the Messages have already been written, bad request
                throw new WrongExpectedVersionException(
                          ErrorMessages.AppendFailedWrongExpectedVersion(_streamId, expectedVersion));
            }

            // None of the Messages were written previously...
            AppendEvents(newMessages);
        }
コード例 #19
0
        private async Task <MsSqlAppendResult> AppendToStreamExpectedVersion(
            SqlConnection connection,
            SqlTransaction transaction,
            SqlStreamId sqlStreamId,
            int expectedVersion,
            NewStreamMessage[] messages,
            CancellationToken cancellationToken)
        {
            var sqlDataRecords = CreateSqlDataRecords(messages);

            using (var command = new SqlCommand(_scripts.AppendStreamExpectedVersion, connection, transaction))
            {
                command.Parameters.Add(new SqlParameter("streamId", SqlDbType.Char, 42)
                {
                    Value = sqlStreamId.Id
                });
                command.Parameters.AddWithValue("expectedStreamVersion", expectedVersion);
                var eventsParam = CreateNewMessagesSqlParameter(sqlDataRecords);
                command.Parameters.Add(eventsParam);

                try
                {
                    using (var reader = await command
                                        .ExecuteReaderAsync(cancellationToken)
                                        .NotOnCapturedContext())
                    {
                        await reader.ReadAsync(cancellationToken).NotOnCapturedContext();

                        var currentVersion  = reader.GetInt32(0);
                        var currentPosition = reader.GetInt64(1);
                        int?maxCount        = null;

                        await reader.NextResultAsync(cancellationToken);

                        if (await reader.ReadAsync(cancellationToken).NotOnCapturedContext())
                        {
                            var jsonData        = reader.GetString(0);
                            var metadataMessage = SimpleJson.DeserializeObject <MetadataMessage>(jsonData);
                            maxCount = metadataMessage.MaxCount;
                        }

                        return(new MsSqlAppendResult(maxCount, currentVersion, currentPosition));
                    }
                }
                catch (SqlException ex)
                {
                    if (ex.Errors.Count == 1)
                    {
                        var sqlError = ex.Errors[0];
                        if (sqlError.Message == "WrongExpectedVersion")
                        {
                            // Idempotency handling. Check if the Messages have already been written.

                            var page = await ReadStreamInternal(
                                sqlStreamId,
                                expectedVersion + 1,
                                // when reading for already written Messages, it's from the one after the expected
                                messages.Length,
                                ReadDirection.Forward,
                                false,
                                null,
                                connection,
                                transaction,
                                cancellationToken);

                            if (messages.Length > page.Messages.Length)
                            {
                                throw new WrongExpectedVersionException(
                                          ErrorMessages.AppendFailedWrongExpectedVersion(sqlStreamId.IdOriginal, expectedVersion),
                                          ex);
                            }

                            // Iterate all messages an check to see if all message ids match
                            for (int i = 0; i < Math.Min(messages.Length, page.Messages.Length); i++)
                            {
                                if (messages[i].MessageId != page.Messages[i].MessageId)
                                {
                                    throw new WrongExpectedVersionException(
                                              ErrorMessages.AppendFailedWrongExpectedVersion(sqlStreamId.IdOriginal, expectedVersion),
                                              ex);
                                }
                            }

                            return(new MsSqlAppendResult(
                                       null,
                                       page.LastStreamVersion,
                                       page.LastStreamPosition));
                        }
                    }
                    if (ex.IsUniqueConstraintViolation())
                    {
                        throw new WrongExpectedVersionException(
                                  ErrorMessages.AppendFailedWrongExpectedVersion(sqlStreamId.IdOriginal, expectedVersion),
                                  ex);
                    }
                    throw;
                }
            }
        }
コード例 #20
0
        private async Task <MsSqlAppendResult> AppendToStreamExpectedVersionNoStream(
            SqlConnection connection,
            SqlTransaction transaction,
            SqlStreamId sqlStreamId,
            NewStreamMessage[] messages,
            CancellationToken cancellationToken)
        {
            using (var command = new SqlCommand(_scripts.AppendStreamExpectedVersionNoStream, connection, transaction))
            {
                command.Parameters.Add(new SqlParameter("streamId", SqlDbType.Char, 42)
                {
                    Value = sqlStreamId.Id
                });
                command.Parameters.AddWithValue("streamIdOriginal", sqlStreamId.IdOriginal);

                if (messages.Length != 0)
                {
                    var sqlDataRecords = CreateSqlDataRecords(messages);
                    var eventsParam    = CreateNewMessagesSqlParameter(sqlDataRecords);
                    command.Parameters.Add(eventsParam);
                    command.Parameters.AddWithValue("hasMessages", true);
                }
                else
                {
                    // Must use a null value for the table-valued param if there are no records
                    var eventsParam = CreateNewMessagesSqlParameter(null);
                    command.Parameters.Add(eventsParam);
                    command.Parameters.AddWithValue("hasMessages", false);
                }

                try
                {
                    using (var reader = await command
                                        .ExecuteReaderAsync(cancellationToken)
                                        .NotOnCapturedContext())
                    {
                        await reader.ReadAsync(cancellationToken).NotOnCapturedContext();

                        var currentVersion  = reader.GetInt32(0);
                        var currentPosition = reader.GetInt64(1);
                        int?maxCount        = null;

                        if (await reader.ReadAsync(cancellationToken).NotOnCapturedContext())
                        {
                            var jsonData        = reader.GetString(0);
                            var metadataMessage = SimpleJson.DeserializeObject <MetadataMessage>(jsonData);
                            maxCount = metadataMessage.MaxCount;
                        }
                        return(new MsSqlAppendResult(maxCount, currentVersion, currentPosition));
                    }
                }
                catch (SqlException ex)
                {
                    // Check for unique constraint violation on
                    // https://technet.microsoft.com/en-us/library/aa258747%28v=sql.80%29.aspx
                    if (ex.IsUniqueConstraintViolationOnIndex("IX_Streams_Id"))
                    {
                        // Idempotency handling. Check if the Messages have already been written.
                        var page = await ReadStreamInternal(
                            sqlStreamId,
                            StreamVersion.Start,
                            messages.Length,
                            ReadDirection.Forward,
                            false,
                            null,
                            connection,
                            transaction,
                            cancellationToken)
                                   .NotOnCapturedContext();

                        if (messages.Length > page.Messages.Length)
                        {
                            throw new WrongExpectedVersionException(
                                      ErrorMessages.AppendFailedWrongExpectedVersion(sqlStreamId.IdOriginal,
                                                                                     ExpectedVersion.NoStream),
                                      ex);
                        }

                        for (int i = 0; i < Math.Min(messages.Length, page.Messages.Length); i++)
                        {
                            if (messages[i].MessageId != page.Messages[i].MessageId)
                            {
                                throw new WrongExpectedVersionException(
                                          ErrorMessages.AppendFailedWrongExpectedVersion(sqlStreamId.IdOriginal,
                                                                                         ExpectedVersion.NoStream),
                                          ex);
                            }
                        }

                        return(new MsSqlAppendResult(
                                   null,
                                   page.LastStreamVersion,
                                   page.LastStreamPosition));
                    }

                    if (ex.IsUniqueConstraintViolation())
                    {
                        throw new WrongExpectedVersionException(
                                  ErrorMessages.AppendFailedWrongExpectedVersion(sqlStreamId.IdOriginal,
                                                                                 ExpectedVersion.NoStream),
                                  ex);
                    }

                    throw;
                }
            }
        }
コード例 #21
0
        private async Task <int?> AppendToStreamExpectedVersionAny(
            SqlConnection connection,
            SqlTransaction transaction,
            SqlStreamId sqlStreamId,
            NewStreamMessage[] messages,
            CancellationToken cancellationToken)
        {
            using (var command = new SqlCommand(_scripts.AppendStreamExpectedVersionAny, connection, transaction))
            {
                command.Parameters.AddWithValue("streamId", sqlStreamId.Id);
                command.Parameters.AddWithValue("streamIdOriginal", sqlStreamId.IdOriginal);
                var eventsParam = CreateNewMessagesSqlParameter(CreateSqlDataRecords(messages));
                command.Parameters.Add(eventsParam);

                try
                {
                    using (var reader = await command
                                        .ExecuteReaderAsync(cancellationToken)
                                        .NotOnCapturedContext())
                    {
                        if (await reader.ReadAsync(cancellationToken).NotOnCapturedContext())
                        {
                            var jsonData        = reader.GetString(0);
                            var metadataMessage = SimpleJson.DeserializeObject <MetadataMessage>(jsonData);
                            return(metadataMessage.MaxCount);
                        }
                    }
                }

                // Check for unique constraint violation on
                // https://technet.microsoft.com/en-us/library/aa258747%28v=sql.80%29.aspx
                catch (SqlException ex)
                    when(ex.IsUniqueConstraintViolationOnIndex("IX_Messages_StreamIdInternal_Id"))
                    {
                        // Idempotency handling. Check if the Messages have already been written.
                        var page = await ReadStreamInternal(
                            sqlStreamId,
                            StreamVersion.Start,
                            messages.Length,
                            ReadDirection.Forward,
                            connection,
                            cancellationToken)
                                   .NotOnCapturedContext();

                        if (messages.Length > page.Messages.Length)
                        {
                            throw new WrongExpectedVersionException(
                                      ErrorMessages.AppendFailedWrongExpectedVersion(sqlStreamId.IdOriginal, ExpectedVersion.Any),
                                      ex);
                        }

                        for (int i = 0; i < Math.Min(messages.Length, page.Messages.Length); i++)
                        {
                            if (messages[i].MessageId != page.Messages[i].MessageId)
                            {
                                throw new WrongExpectedVersionException(
                                          ErrorMessages.AppendFailedWrongExpectedVersion(sqlStreamId.IdOriginal, ExpectedVersion.Any),
                                          ex);
                            }
                        }
                    }
                catch (SqlException ex) when(ex.IsUniqueConstraintViolation())
                {
                    throw new WrongExpectedVersionException(
                              ErrorMessages.AppendFailedWrongExpectedVersion(sqlStreamId.IdOriginal, ExpectedVersion.Any),
                              ex);
                }
                return(null);
            }
        }
コード例 #22
0
        private SqliteAppendResult AppendToStreamExpectedVersion(string streamId, int expectedVersion, NewStreamMessage[] messages, CancellationToken cancellationToken)
        {
            using (var connection = OpenConnection(false))
            {
                var stream = connection.Streams(streamId);

                if (!stream.Exists().GetAwaiter().GetResult())
                {
                    throw new WrongExpectedVersionException(
                              ErrorMessages.AppendFailedWrongExpectedVersion(
                                  streamId,
                                  expectedVersion),
                              streamId,
                              expectedVersion);
                }

                var props = connection.Streams(streamId)
                            .Properties(initializeIfNotFound: false, cancellationToken)
                            .GetAwaiter().GetResult();

                if (messages.Length == 1)
                {
                    var msg = messages.First();

                    // tries to fix "When_append_single_message_to_stream_with_correct_expected_version_second_time_with_same_initial_messages_then_should_have_expected_result"
                    if (stream.ExistsAtExpectedPosition(msg.MessageId, expectedVersion).GetAwaiter().GetResult())
                    {
                        return(new SqliteAppendResult(
                                   props.Version,
                                   props.Position,
                                   null
                                   ));
                    }
                    // end - tries to fix "When_append_single_message_to_stream_with_correct_expected_version_second_time_with_same_initial_messages_then_should_have_expected_result"


                    // tries to fix "When_append_stream_with_expected_version_and_duplicate_message_Id_then_should_throw"
                    if (stream.Exists(msg.MessageId, expectedVersion).GetAwaiter().GetResult())
                    {
                        throw new WrongExpectedVersionException(
                                  ErrorMessages.AppendFailedWrongExpectedVersion(
                                      streamId,
                                      expectedVersion),
                                  streamId,
                                  expectedVersion);
                    }
                    // end - tries to fix "When_append_stream_with_expected_version_and_duplicate_message_Id_then_should_throw"

                    var eventIds = stream.Read(ReadDirection.Forward, expectedVersion, false, int.MaxValue)
                                   .GetAwaiter().GetResult()
                                   .Select(message => message.MessageId)
                                   .ToArray();

                    if (eventIds.Contains(msg.MessageId))
                    {
                        if (eventIds.Length > messages.Length)
                        {
                            throw new WrongExpectedVersionException(
                                      ErrorMessages.AppendFailedWrongExpectedVersion(
                                          streamId,
                                          ExpectedVersion.NoStream),
                                      streamId,
                                      ExpectedVersion.NoStream);
                        }
                        return(new SqliteAppendResult(
                                   props.Version,
                                   props.Position,
                                   null
                                   ));
                    }
                }

                if (expectedVersion != props.Version)
                {
                    var msg = messages.First();

                    var position = stream.AllStreamPosition(ReadDirection.Forward, msg.MessageId)
                                   .GetAwaiter().GetResult();

                    // retrieve next series of messages from the first message being requested to
                    var eventIds = position.HasValue
                        ? stream.Read(ReadDirection.Forward, position, false, messages.Length)
                                   .GetAwaiter().GetResult()
                                   .Select(message => message.MessageId)
                                   .ToArray()
                        : new Guid[0];

                    if (messages.Length != eventIds.Length)
                    {
                        throw new WrongExpectedVersionException(
                                  ErrorMessages.AppendFailedWrongExpectedVersion(
                                      streamId,
                                      expectedVersion),
                                  streamId,
                                  expectedVersion);
                    }

                    // tests for positional inequality between what we know and what is stored.
                    for (var i = 0; i < Math.Min(messages.Length, eventIds.Length); i++)
                    {
                        var nextMessageId = eventIds.Skip(i).Take(1).SingleOrDefault();
                        if (messages[i].MessageId != nextMessageId)
                        {
                            throw new WrongExpectedVersionException(
                                      ErrorMessages.AppendFailedWrongExpectedVersion(
                                          streamId,
                                          expectedVersion),
                                      streamId,
                                      expectedVersion);
                        }
                    }

                    // we seem to be equal.
                    return(new SqliteAppendResult(
                               props.Version,
                               props.Position,
                               props.MaxCount
                               ));
                }

                var allStream = connection.AllStream();

                using (allStream.WithTransaction())
                {
                    var result = allStream.Append(streamId, messages)
                                 .GetAwaiter().GetResult();

                    allStream.Commit(cancellationToken)
                    .GetAwaiter().GetResult();

                    return(result);
                }
            }
        }
コード例 #23
0
        private Task <SqliteAppendResult> AppendToStreamAnyVersion(
            string streamId,
            NewStreamMessage[] messages,
            CancellationToken cancellationToken)
        {
            using (var connection = OpenConnection(false))
            {
                var stream    = connection.Streams(streamId);
                var allStream = connection.AllStream();

                var props = stream.Properties(true, cancellationToken)
                            .GetAwaiter().GetResult();

                if (messages.Length == 1)
                {
                    var msg = messages[0];

                    var exists = stream.Contains(msg.MessageId)
                                 .GetAwaiter().GetResult();

                    if (exists)
                    {
                        return(Task.FromResult(new SqliteAppendResult(props.Version, props.Position, null)));
                    }
                }
                else if (messages.Any())
                {
                    var msg1     = messages.First();
                    var position = stream.AllStreamPosition(ReadDirection.Forward, msg1.MessageId)
                                   .GetAwaiter().GetResult() ?? long.MaxValue;

                    var sMessages = stream.Read(ReadDirection.Forward, position, false, messages.Length)
                                    .GetAwaiter()
                                    .GetResult();
                    var eventIds = sMessages.Select(m => m.MessageId).ToArray();

                    if (eventIds.Length > 0)
                    {
                        for (var i = 0; i < Math.Min(eventIds.Length, messages.Length); i++)
                        {
                            if (eventIds[i] != messages[i].MessageId)
                            {
                                throw new WrongExpectedVersionException(
                                          ErrorMessages.AppendFailedWrongExpectedVersion(
                                              streamId,
                                              StreamVersion.Start),
                                          streamId,
                                          StreamVersion.Start);
                            }
                        }

                        if (eventIds.Length < messages.Length && eventIds.Length > 0)
                        {
                            throw new WrongExpectedVersionException(
                                      ErrorMessages.AppendFailedWrongExpectedVersion(
                                          streamId,
                                          StreamVersion.Start),
                                      streamId,
                                      StreamVersion.Start);
                        }

                        return(Task.FromResult(new SqliteAppendResult(props.Version, props.Position, null)));
                    }
                }

                using (allStream.WithTransaction())
                {
                    var result = allStream.Append(streamId, messages)
                                 .GetAwaiter().GetResult();

                    allStream.Commit(cancellationToken)
                    .GetAwaiter().GetResult();

                    return(Task.FromResult(result));
                }
            }
        }
コード例 #24
0
        private async Task <MsSqlAppendResult> AppendToStreamExpectedVersionAny(
            SqlConnection connection,
            SqlTransaction transaction,
            SqlStreamId sqlStreamId,
            NewStreamMessage[] messages,
            CancellationToken cancellationToken)
        {
            using (var command = new SqlCommand(_scripts.AppendStreamExpectedVersionAny, connection, transaction))
            {
                command.CommandTimeout = _commandTimeout;
                command.Parameters.Add(new SqlParameter("streamId", SqlDbType.Char, 42)
                {
                    Value = sqlStreamId.Id
                });
                command.Parameters.AddWithValue("streamIdOriginal", sqlStreamId.IdOriginal);

                if (messages.Any())
                {
                    var sqlDataRecords = CreateSqlDataRecords(messages);
                    var eventsParam    = CreateNewMessagesSqlParameter(sqlDataRecords);
                    command.Parameters.Add(eventsParam);
                    command.Parameters.AddWithValue("hasMessages", true);
                }
                else
                {
                    // Must use a null value for the table-valued param if there are no records
                    var eventsParam = CreateNewMessagesSqlParameter(null);
                    command.Parameters.Add(eventsParam);
                    command.Parameters.AddWithValue("hasMessages", false);
                }

                try
                {
                    using (var reader = await command
                                        .ExecuteReaderAsync(cancellationToken)
                                        .ConfigureAwait(false))
                    {
                        await reader.ReadAsync(cancellationToken).ConfigureAwait(false);

                        var currentVersion  = reader.GetInt32(0);
                        var currentPosition = reader.GetInt64(1);
                        var maxCount        = reader.GetNullableInt32(2);

                        return(new MsSqlAppendResult(maxCount, currentVersion, currentPosition, false));
                    }
                }

                // Check for unique constraint violation on
                // https://technet.microsoft.com/en-us/library/aa258747%28v=sql.80%29.aspx
                catch (SqlException ex)
                    when(ex.IsUniqueConstraintViolationOnIndex("IX_Messages_StreamIdInternal_Id"))
                    {
                        var streamVersion = await GetStreamVersionOfMessageId(
                            connection,
                            transaction,
                            sqlStreamId,
                            messages[0].MessageId,
                            cancellationToken);

                        // Idempotency handling. Check if the Messages have already been written.
                        var(page, meta) = await ReadStreamInternal(
                            sqlStreamId,
                            streamVersion,
                            messages.Length,
                            ReadDirection.Forward,
                            false,
                            null,
                            connection,
                            transaction,
                            cancellationToken)
                                          .ConfigureAwait(false);

                        if (messages.Length > page.Messages.Length)
                        {
                            throw new WrongExpectedVersionException(
                                      ErrorMessages.AppendFailedWrongExpectedVersion(sqlStreamId.IdOriginal, ExpectedVersion.Any),
                                      sqlStreamId.IdOriginal,
                                      ExpectedVersion.Any,
                                      ex);
                        }

                        for (int i = 0; i < Math.Min(messages.Length, page.Messages.Length); i++)
                        {
                            if (messages[i].MessageId != page.Messages[i].MessageId)
                            {
                                throw new WrongExpectedVersionException(
                                          ErrorMessages.AppendFailedWrongExpectedVersion(sqlStreamId.IdOriginal, ExpectedVersion.Any),
                                          sqlStreamId.IdOriginal,
                                          ExpectedVersion.Any,
                                          ex);
                            }
                        }

                        return(new MsSqlAppendResult(
                                   meta.MaxCount,
                                   page.LastStreamVersion,
                                   page.LastStreamPosition,
                                   true));
                    }
                catch (SqlException ex) when(ex.IsUniqueConstraintViolation())
                {
                    throw new WrongExpectedVersionException(
                              ErrorMessages.AppendFailedWrongExpectedVersion(sqlStreamId.IdOriginal, ExpectedVersion.Any),
                              sqlStreamId.IdOriginal,
                              ExpectedVersion.Any,
                              ex);
                }
            }
        }
コード例 #25
0
        protected override async Task <AppendResult> AppendToStreamInternal(
            string streamId,
            int expectedVersion,
            NewStreamMessage[] messages,
            CancellationToken cancellationToken)
        {
            var streamIdInfo = new StreamIdInfo(streamId);

            if (_settings.AppendDeadlockRetryAttempts == 0)
            {
                try
                {
                    return(messages.Length == 0
                        ? await CreateEmptyStream(streamIdInfo, expectedVersion, cancellationToken)
                        : await AppendMessagesToStream(streamIdInfo, expectedVersion, messages, cancellationToken));
                }
                catch (MySqlException ex) when(ex.IsWrongExpectedVersion())
                {
                    throw new WrongExpectedVersionException(
                              ErrorMessages.AppendFailedWrongExpectedVersion(
                                  streamIdInfo.MySqlStreamId.IdOriginal,
                                  expectedVersion),
                              streamIdInfo.MySqlStreamId.IdOriginal,
                              expectedVersion,
                              ex);
                }
            }

            var retryableExceptions = new List <Exception>();

            while (retryableExceptions.Count <= _settings.AppendDeadlockRetryAttempts)
            {
                try
                {
                    return(messages.Length == 0
                        ? await CreateEmptyStream(streamIdInfo, expectedVersion, cancellationToken)
                        : await AppendMessagesToStream(streamIdInfo, expectedVersion, messages, cancellationToken));
                }
                catch (MySqlException ex) when(ex.IsDeadlock())
                {
                    retryableExceptions.Add(ex);
                }
                catch (MySqlException ex) when(ex.IsWrongExpectedVersion())
                {
                    throw new WrongExpectedVersionException(
                              ErrorMessages.AppendFailedWrongExpectedVersion(
                                  streamIdInfo.MySqlStreamId.IdOriginal,
                                  expectedVersion),
                              streamIdInfo.MySqlStreamId.IdOriginal,
                              expectedVersion,
                              ex);
                }
            }
            throw new WrongExpectedVersionException(
                      MySqlErrorMessages.AppendFailedDeadlock(
                          streamIdInfo.MySqlStreamId.IdOriginal,
                          expectedVersion,
                          _settings.AppendDeadlockRetryAttempts),
                      streamIdInfo.MySqlStreamId.IdOriginal,
                      expectedVersion,
                      new AggregateException(retryableExceptions));
        }
コード例 #26
0
        protected override async Task <AppendResult> AppendToStreamInternal(
            string streamId,
            int expectedVersion,
            NewStreamMessage[] messages,
            CancellationToken cancellationToken)
        {
            int       maxRetries = 2; //TODO too much? too little? configurable?
            Exception exception;

            int retryCount = 0;

            do
            {
                try
                {
                    AppendResult result;
                    var          streamIdInfo = new StreamIdInfo(streamId);

                    using (var connection = await OpenConnection(cancellationToken))
                        using (var transaction = connection.BeginTransaction())
                            using (var command = BuildFunctionCommand(
                                       _schema.AppendToStream,
                                       transaction,
                                       Parameters.StreamId(streamIdInfo.PostgresqlStreamId),
                                       Parameters.StreamIdOriginal(streamIdInfo.PostgresqlStreamId),
                                       Parameters.MetadataStreamId(streamIdInfo.MetadataPosgresqlStreamId),
                                       Parameters.ExpectedVersion(expectedVersion),
                                       Parameters.CreatedUtc(_settings.GetUtcNow?.Invoke()),
                                       Parameters.NewStreamMessages(messages)))
                            {
                                try
                                {
                                    using (var reader =
                                               await command.ExecuteReaderAsync(cancellationToken).NotOnCapturedContext())
                                    {
                                        await reader.ReadAsync(cancellationToken).NotOnCapturedContext();

                                        result = new AppendResult(reader.GetInt32(0), reader.GetInt64(1));
                                    }

                                    await transaction.CommitAsync(cancellationToken).NotOnCapturedContext();
                                }
                                catch (PostgresException ex) when(ex.IsWrongExpectedVersion())
                                {
                                    await transaction.RollbackAsync(cancellationToken).NotOnCapturedContext();

                                    throw new WrongExpectedVersionException(
                                              ErrorMessages.AppendFailedWrongExpectedVersion(
                                                  streamIdInfo.PostgresqlStreamId.IdOriginal,
                                                  expectedVersion),
                                              ex);
                                }
                            }

                    if (_settings.ScavengeAsynchronously)
                    {
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
                        Task.Run(() => TryScavenge(streamIdInfo, cancellationToken), cancellationToken);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
                    }
                    else
                    {
                        await TryScavenge(streamIdInfo, cancellationToken).NotOnCapturedContext();
                    }

                    return(result);
                }
                catch (PostgresException ex) when(ex.IsDeadlock())
                {
                    exception = ex;
                    retryCount++;
                }
            } while(retryCount < maxRetries);

            ExceptionDispatchInfo.Capture(exception).Throw();
            return(default); // never actually run
コード例 #27
0
        protected override async Task <SetStreamMetadataResult> SetStreamMetadataInternal(
            string streamId,
            int expectedStreamMetadataVersion,
            int?maxAge,
            int?maxCount,
            string metadataJson,
            CancellationToken cancellationToken)
        {
            var metadata = new MetadataMessage
            {
                StreamId = streamId,
                MaxAge   = maxAge,
                MaxCount = maxCount,
                MetaJson = metadataJson
            };

            var metadataMessageJsonData = SimpleJson.SerializeObject(metadata);

            var streamIdInfo = new StreamIdInfo(streamId);

            var currentVersion = Parameters.CurrentVersion();

            try
            {
                using (var connection = await OpenConnection(cancellationToken))
                    using (var transaction = await connection.BeginTransactionAsync(cancellationToken).ConfigureAwait(false))
                        using (var command = BuildStoredProcedureCall(
                                   _schema.SetStreamMetadata,
                                   transaction,
                                   Parameters.StreamId(streamIdInfo.MySqlStreamId),
                                   Parameters.MetadataStreamId(streamIdInfo.MetadataMySqlStreamId),
                                   Parameters.MetadataStreamIdOriginal(streamIdInfo.MetadataMySqlStreamId),
                                   Parameters.OptionalMaxAge(metadata.MaxAge),
                                   Parameters.OptionalMaxCount(metadata.MaxCount),
                                   Parameters.ExpectedVersion(expectedStreamMetadataVersion),
                                   Parameters.CreatedUtc(_settings.GetUtcNow?.Invoke()),
                                   Parameters.MetadataMessageMessageId(
                                       streamIdInfo.MetadataMySqlStreamId,
                                       expectedStreamMetadataVersion,
                                       metadataMessageJsonData),
                                   Parameters.MetadataMessageType(),
                                   Parameters.MetadataMessageJsonData(metadataMessageJsonData),
                                   currentVersion,
                                   Parameters.CurrentPosition()))
                        {
                            await command.ExecuteNonQueryAsync(cancellationToken).NotOnCapturedContext();

                            await transaction.CommitAsync(cancellationToken).NotOnCapturedContext();
                        }

                await TryScavenge(streamIdInfo, cancellationToken);

                return(new SetStreamMetadataResult((int)currentVersion.Value));
            }
            catch (MySqlException ex) when(ex.IsWrongExpectedVersion())
            {
                throw new WrongExpectedVersionException(
                          ErrorMessages.AppendFailedWrongExpectedVersion(
                              streamIdInfo.MetadataMySqlStreamId.IdOriginal,
                              expectedStreamMetadataVersion),
                          streamIdInfo.MetadataMySqlStreamId.IdOriginal,
                          ExpectedVersion.Any,
                          ex);
            }
        }