diff --git a/src/CoreTests/adding_custom_schema_objects.cs b/src/CoreTests/adding_custom_schema_objects.cs index 6190d771d6..918e9d6a8a 100644 --- a/src/CoreTests/adding_custom_schema_objects.cs +++ b/src/CoreTests/adding_custom_schema_objects.cs @@ -126,7 +126,7 @@ public async Task enable_an_extension_with_multitenancy_no_tenants_upfront_does_ martenException.InnerException.ShouldNotBeNull(); martenException.InnerException.As().ShouldSatisfyAllConditions( e => e.SqlState.ShouldBe(PostgresErrorCodes.UndefinedFunction), - e => e.MessageText.ShouldBe("function unaccent(unknown) does not exist") + e => e.MessageText.ShouldContain("unaccent(unknown)") ); } diff --git a/src/DocumentDbTests/Indexes/UniqueIndexTests.cs b/src/DocumentDbTests/Indexes/UniqueIndexTests.cs index feb4fcbe6c..8e4a354fa9 100644 --- a/src/DocumentDbTests/Indexes/UniqueIndexTests.cs +++ b/src/DocumentDbTests/Indexes/UniqueIndexTests.cs @@ -51,8 +51,6 @@ public class UniqueUser public class UniqueIndexTests: OneOffConfigurationsContext { - public const string UniqueSqlState = "23505"; - public UniqueIndexTests() { StoreOptions(opts => @@ -90,7 +88,7 @@ public async Task } catch (DocumentAlreadyExistsException exception) { - ((PostgresException)exception.InnerException)?.SqlState.ShouldBe(UniqueSqlState); + ((PostgresException)exception.InnerException)?.SqlState.ShouldBe(PostgresErrorCodes.UniqueViolation); } } @@ -116,7 +114,7 @@ public async Task } catch (DocumentAlreadyExistsException exception) { - ((PostgresException)exception.InnerException)?.SqlState.ShouldBe(UniqueSqlState); + ((PostgresException)exception.InnerException)?.SqlState.ShouldBe(PostgresErrorCodes.UniqueViolation); } } @@ -146,7 +144,7 @@ public async Task } catch (MartenCommandException exception) { - ((PostgresException)exception.InnerException)?.SqlState.ShouldBe(UniqueSqlState); + ((PostgresException)exception.InnerException)?.SqlState.ShouldBe(PostgresErrorCodes.UniqueViolation); } } @@ -173,7 +171,7 @@ public async Task } catch (DocumentAlreadyExistsException exception) { - ((PostgresException)exception.InnerException)?.SqlState.ShouldBe(UniqueSqlState); + ((PostgresException)exception.InnerException)?.SqlState.ShouldBe(PostgresErrorCodes.UniqueViolation); } } } diff --git a/src/DocumentDbTests/MultiTenancy/UniqueIndexMultiTenantTests.cs b/src/DocumentDbTests/MultiTenancy/UniqueIndexMultiTenantTests.cs index 8c95f6720c..96577c33de 100644 --- a/src/DocumentDbTests/MultiTenancy/UniqueIndexMultiTenantTests.cs +++ b/src/DocumentDbTests/MultiTenancy/UniqueIndexMultiTenantTests.cs @@ -12,8 +12,6 @@ namespace DocumentDbTests.MultiTenancy; public class UniqueIndexMultiTenantTests: OneOffConfigurationsContext { - public const string UniqueSqlState = "23505"; - public class Project { public Project() @@ -69,7 +67,7 @@ public async Task given_two_documents_for_different_tenants_succeeds_using_attri } catch (DocumentAlreadyExistsException exception) { - ((PostgresException)exception.InnerException)?.SqlState.ShouldBe(UniqueSqlState); + ((PostgresException)exception.InnerException)?.SqlState.ShouldBe(PostgresErrorCodes.UniqueViolation); } } @@ -100,7 +98,7 @@ public async Task } catch (DocumentAlreadyExistsException exception) { - ((PostgresException)exception.InnerException).SqlState.ShouldBe(UniqueSqlState); + ((PostgresException)exception.InnerException).SqlState.ShouldBe(PostgresErrorCodes.UniqueViolation); } } @@ -124,7 +122,7 @@ public async Task } catch (DocumentAlreadyExistsException exception) { - ((PostgresException)exception.InnerException).SqlState.ShouldBe(UniqueSqlState); + ((PostgresException)exception.InnerException).SqlState.ShouldBe(PostgresErrorCodes.UniqueViolation); } } } @@ -157,7 +155,7 @@ public async Task } catch (DocumentAlreadyExistsException exception) { - ((PostgresException)exception.InnerException)?.SqlState.ShouldBe(UniqueSqlState); + ((PostgresException)exception.InnerException)?.SqlState.ShouldBe(PostgresErrorCodes.UniqueViolation); } } @@ -181,7 +179,7 @@ public async Task } catch (DocumentAlreadyExistsException exception) { - ((PostgresException)exception.InnerException).SqlState.ShouldBe(UniqueSqlState); + ((PostgresException)exception.InnerException).SqlState.ShouldBe(PostgresErrorCodes.UniqueViolation); } } } diff --git a/src/EventSourcingTests/EventStreamUnexpectedMaxEventIdExceptionTransformTest.cs b/src/EventSourcingTests/EventStreamUnexpectedMaxEventIdExceptionTransformTest.cs index b69986c634..4946399639 100644 --- a/src/EventSourcingTests/EventStreamUnexpectedMaxEventIdExceptionTransformTest.cs +++ b/src/EventSourcingTests/EventStreamUnexpectedMaxEventIdExceptionTransformTest.cs @@ -15,7 +15,7 @@ public EventStreamUnexpectedMaxEventIdExceptionTransformTest(DefaultStoreFixture { } - //[Fact] -- TODO -- too unreliable on CI + [Fact(Skip = "TODO -- too unreliable on CI")] public async Task throw_transformed_exception_with_details_redacted() { await theStore.Storage.ApplyAllConfiguredChangesToDatabaseAsync(); @@ -38,10 +38,10 @@ await Parallel.ForEachAsync(Enumerable.Range(1, 10), async (_, token) => }; (await Should.ThrowAsync(forceEventStreamUnexpectedMaxEventIdException)) - .Message.ShouldBe("duplicate key value violates unique constraint \"pk_mt_events_stream_and_version\""); + .Message.ShouldContain("pk_mt_events_stream_and_version"); } - //[Fact] -- TODO -- too unreliable on CI + [Fact(Skip = "TODO -- too unreliable on CI")] public async Task throw_transformed_exception_with_details_available() { await theStore.Storage.ApplyAllConfiguredChangesToDatabaseAsync(); diff --git a/src/Marten/Events/EventStore.ConcurrentAppends.cs b/src/Marten/Events/EventStore.ConcurrentAppends.cs index fbe97311b9..22fbe70a65 100644 --- a/src/Marten/Events/EventStore.ConcurrentAppends.cs +++ b/src/Marten/Events/EventStore.ConcurrentAppends.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Marten.Exceptions; using Marten.Storage; +using Npgsql; using Weasel.Postgresql; namespace Marten.Events; @@ -99,7 +100,10 @@ public async Task AppendExclusive(string streamKey, CancellationToken token, par } catch (Exception e) { - if (e.Message.Contains(MartenCommandException.MaybeLockedRowsMessage) || e.Message.Contains("current transaction is aborted")) + if (e.Message.Contains(MartenCommandException.MaybeLockedRowsMessage) || e.InnerException is NpgsqlException + { + SqlState: PostgresErrorCodes.InFailedSqlTransaction + }) { throw new StreamLockedException(streamKey, e.InnerException); } @@ -129,10 +133,14 @@ public async Task AppendExclusive(Guid streamId, CancellationToken token, params } catch (Exception e) { - if (e.Message.Contains(MartenCommandException.MaybeLockedRowsMessage) || e.Message.Contains("current transaction is aborted")) + if (e.Message.Contains(MartenCommandException.MaybeLockedRowsMessage) || e.InnerException is NpgsqlException + { + SqlState: PostgresErrorCodes.InFailedSqlTransaction + }) { throw new StreamLockedException(streamId, e.InnerException); } + throw; } } diff --git a/src/Marten/Events/Fetching/FetchAsyncPlan.cs b/src/Marten/Events/Fetching/FetchAsyncPlan.cs index 1e3e6a8847..18a6afa4bf 100644 --- a/src/Marten/Events/Fetching/FetchAsyncPlan.cs +++ b/src/Marten/Events/Fetching/FetchAsyncPlan.cs @@ -157,7 +157,7 @@ public async Task> FetchForWriting(DocumentSessionBase sessio } catch (Exception e) { - if (e.InnerException is NpgsqlException inner && inner.Message.Contains("current transaction is aborted")) + if (e.InnerException is NpgsqlException { SqlState: PostgresErrorCodes.InFailedSqlTransaction }) { throw new StreamLockedException(id, e.InnerException); } @@ -256,7 +256,7 @@ public async Task> FetchForWriting(DocumentSessionBase sessio } catch (Exception e) { - if (e.InnerException is NpgsqlException inner && inner.Message.Contains("current transaction is aborted")) + if (e.InnerException is NpgsqlException { SqlState: PostgresErrorCodes.InFailedSqlTransaction }) { throw new StreamLockedException(id, e.InnerException); } diff --git a/src/Marten/Events/Fetching/FetchInlinedPlan.cs b/src/Marten/Events/Fetching/FetchInlinedPlan.cs index 13bd26ccf1..8d74b3f3e8 100644 --- a/src/Marten/Events/Fetching/FetchInlinedPlan.cs +++ b/src/Marten/Events/Fetching/FetchInlinedPlan.cs @@ -80,7 +80,7 @@ public async Task> FetchForWriting(DocumentSessionBase sessio } catch (Exception e) { - if (e.InnerException is NpgsqlException inner && inner.Message.Contains("current transaction is aborted")) + if (e.InnerException is NpgsqlException { SqlState: PostgresErrorCodes.InFailedSqlTransaction }) { throw new StreamLockedException(id, e.InnerException); } @@ -154,7 +154,7 @@ public async Task> FetchForWriting(DocumentSessionBase sessio } catch (Exception e) { - if (e.InnerException is NpgsqlException inner && inner.Message.Contains("current transaction is aborted")) + if (e.InnerException is NpgsqlException { SqlState: PostgresErrorCodes.InFailedSqlTransaction }) { throw new StreamLockedException(id, e.InnerException); } diff --git a/src/Marten/Events/Fetching/FetchLivePlan.cs b/src/Marten/Events/Fetching/FetchLivePlan.cs index 8bf7e69ee0..d2760c91b4 100644 --- a/src/Marten/Events/Fetching/FetchLivePlan.cs +++ b/src/Marten/Events/Fetching/FetchLivePlan.cs @@ -77,7 +77,7 @@ public async Task> FetchForWriting(DocumentSessionBase sessio } catch (Exception e) { - if (e.InnerException is NpgsqlException inner && inner.Message.Contains("current transaction is aborted")) + if (e.InnerException is NpgsqlException { SqlState: PostgresErrorCodes.InFailedSqlTransaction }) { throw new StreamLockedException(id, e.InnerException); } @@ -142,7 +142,7 @@ public async Task> FetchForWriting(DocumentSessionBase sessio } catch (Exception e) { - if (e.InnerException is NpgsqlException inner && inner.Message.Contains("current transaction is aborted")) + if (e.InnerException is NpgsqlException { SqlState: PostgresErrorCodes.InFailedSqlTransaction }) { throw new StreamLockedException(id, e.InnerException); } diff --git a/src/Marten/Events/Operations/InsertStreamBase.cs b/src/Marten/Events/Operations/InsertStreamBase.cs index f4b6249550..41fc750811 100644 --- a/src/Marten/Events/Operations/InsertStreamBase.cs +++ b/src/Marten/Events/Operations/InsertStreamBase.cs @@ -4,10 +4,12 @@ using System.Threading; using System.Threading.Tasks; using JasperFx.Core.Exceptions; +using Marten.Events.Schema; using Marten.Exceptions; using Marten.Internal; using Marten.Internal.Operations; using Marten.Services; +using Npgsql; using Weasel.Postgresql; namespace Marten.Events.Operations; @@ -47,8 +49,11 @@ public override string ToString() private static bool matches(Exception e) { - return e.Message.Contains("23505: duplicate key value violates unique constraint") && - e.Message.Contains("streams"); + return e is PostgresException + { + SqlState: PostgresErrorCodes.UniqueViolation, + TableName: StreamsTable.TableName + }; } public bool TryTransform(Exception original, out Exception transformed) diff --git a/src/Marten/Internal/Operations/StorageOperation.cs b/src/Marten/Internal/Operations/StorageOperation.cs index ee8e582032..25235da78a 100644 --- a/src/Marten/Internal/Operations/StorageOperation.cs +++ b/src/Marten/Internal/Operations/StorageOperation.cs @@ -22,9 +22,6 @@ public interface IRevisionedOperation public abstract class StorageOperation: IDocumentStorageOperation, IExceptionTransform, IRevisionedOperation { - private const string ExpectedMessage = "23505: duplicate key value violates unique constraint"; - - private readonly T _document; protected readonly TId _id; private readonly string _tableName; @@ -249,16 +246,11 @@ public bool TryTransform(Exception original, out Exception transformed) original = m.InnerException; } - if (original.Message.Contains(ExpectedMessage)) + if (original is PostgresException { SqlState: PostgresErrorCodes.UniqueViolation } postgresException && + postgresException.TableName == _tableName) { - if (original is PostgresException e) - { - if (e.TableName == _tableName) - { - transformed = new DocumentAlreadyExistsException(original, typeof(T), _id); - return true; - } - } + transformed = new DocumentAlreadyExistsException(original, typeof(T), _id); + return true; } return false; diff --git a/src/Marten/Internal/ProviderGraph.cs b/src/Marten/Internal/ProviderGraph.cs index e76fba3981..d6cbc17fb5 100644 --- a/src/Marten/Internal/ProviderGraph.cs +++ b/src/Marten/Internal/ProviderGraph.cs @@ -85,7 +85,7 @@ internal DocumentProvider CreateDocumentProvider() where T : notnull } catch (Exception e) { - if (e.Message.Contains("is inaccessible due to its protection level")) + if (e is InvalidOperationException && !mapping.DocumentType.IsPublic) { throw new InvalidOperationException( $"Requested document type '{mapping.DocumentType.FullNameInCode()}' must be scoped as 'public' in order to be used as a document type inside of Marten", diff --git a/src/Marten/Internal/Storage/DocumentStorage.cs b/src/Marten/Internal/Storage/DocumentStorage.cs index 674bffc48a..10f0e31558 100644 --- a/src/Marten/Internal/Storage/DocumentStorage.cs +++ b/src/Marten/Internal/Storage/DocumentStorage.cs @@ -162,7 +162,7 @@ public void TruncateDocumentStorage(IMartenDatabase database) } catch (PostgresException e) { - if (!e.Message.Contains("does not exist")) + if (e.SqlState != PostgresErrorCodes.UndefinedTable) { throw; } @@ -178,7 +178,7 @@ public async Task TruncateDocumentStorageAsync(IMartenDatabase database, Cancell } catch (PostgresException e) { - if (!e.Message.Contains("does not exist")) + if (e.SqlState != PostgresErrorCodes.UndefinedTable) { throw; } diff --git a/src/Marten/Schema/InsertExceptionTransform.cs b/src/Marten/Schema/InsertExceptionTransform.cs index c8a1ae4e1d..7bb4f05f0e 100644 --- a/src/Marten/Schema/InsertExceptionTransform.cs +++ b/src/Marten/Schema/InsertExceptionTransform.cs @@ -1,12 +1,12 @@ using System; using JasperFx.Core.Exceptions; using Marten.Exceptions; +using Npgsql; namespace Marten.Schema; public sealed class InsertExceptionTransform: IExceptionTransform { - private const string ExpectedMessage = "23505: duplicate key value violates unique constraint"; private readonly object id; private readonly string tableName; @@ -20,8 +20,8 @@ public bool TryTransform(Exception original, out Exception transformed) { transformed = null; - if (original.Message?.IndexOf(ExpectedMessage, StringComparison.OrdinalIgnoreCase) > -1 && - original.Message?.IndexOf(tableName, StringComparison.Ordinal) > -1) + if (original is PostgresException { SqlState: PostgresErrorCodes.UniqueViolation } postgresException && + postgresException.TableName == tableName) { transformed = new DocumentAlreadyExistsException(original, typeof(T), id); return true; diff --git a/src/Marten/Services/EventStreamUnexpectedMaxEventIdExceptionTransform.cs b/src/Marten/Services/EventStreamUnexpectedMaxEventIdExceptionTransform.cs index 0e2d521ac5..98405f7a44 100644 --- a/src/Marten/Services/EventStreamUnexpectedMaxEventIdExceptionTransform.cs +++ b/src/Marten/Services/EventStreamUnexpectedMaxEventIdExceptionTransform.cs @@ -9,12 +9,6 @@ namespace Marten.Services; internal class EventStreamUnexpectedMaxEventIdExceptionTransform: IExceptionTransform { - private const string ExpectedMessage = - "duplicate key value violates unique constraint \"pk_mt_events_stream_and_version\""; - - private const string ExpectedMessageForArchivedStreamParitioning = - "duplicate key value violates unique constraint \"mt_events_default_stream_id_version_is_archived_idx\""; - private const string DetailsRedactedMessage = "Detail redacted as it may contain sensitive data. " + "Specify 'Include Error Detail' in the connection string to include this information."; @@ -22,7 +16,7 @@ internal class EventStreamUnexpectedMaxEventIdExceptionTransform: IExceptionTran private const string Version = "version"; private static readonly Regex EventStreamUniqueExceptionDetailsRegex = - new(@"^Key \(stream_id, version\)=\((?.*?), (?\w+)\)"); + new(@"\(stream_id, version\)=\((?.*?), (?\w+)\)"); public bool TryTransform(Exception original, out Exception transformed) { @@ -73,6 +67,6 @@ private static bool Matches(Exception e) { return e is PostgresException pe && pe.SqlState == PostgresErrorCodes.UniqueViolation - && (pe.Message.Contains(ExpectedMessage) || pe.Message.Contains(ExpectedMessageForArchivedStreamParitioning)); + && (pe.ConstraintName == "pk_mt_events_stream_and_version" || pe.ConstraintName == "mt_events_default_stream_id_version_is_archived_idx"); } }