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); }
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); } }
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); } } }
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); }
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)); }
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 }); }
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); }
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)); }
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)); }
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)); }
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)); }
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); }
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)); }
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(); }
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)); } } }
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)); } } }
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); }
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; } } }
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; } } }
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); } }
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); } } }
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)); } } }
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); } } }
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)); }
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
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); } }