From ddfa5eb358f1c22364e39182488f7c6844f72a3b Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Sun, 9 Mar 2025 14:50:52 +0100 Subject: [PATCH 1/2] Fix PostgreSQL SQL --- .../UnitTests/ReadModelSqlGeneratorTests.cs | 61 +++++++++++++++++++ .../PostgresReadModelSqlGenerator.cs | 4 +- .../ReadModels/ReadModelSqlGenerator.cs | 19 +++--- 3 files changed, 74 insertions(+), 10 deletions(-) create mode 100644 Source/EventFlow.PostgreSql.Tests/UnitTests/ReadModelSqlGeneratorTests.cs diff --git a/Source/EventFlow.PostgreSql.Tests/UnitTests/ReadModelSqlGeneratorTests.cs b/Source/EventFlow.PostgreSql.Tests/UnitTests/ReadModelSqlGeneratorTests.cs new file mode 100644 index 000000000..6556537e4 --- /dev/null +++ b/Source/EventFlow.PostgreSql.Tests/UnitTests/ReadModelSqlGeneratorTests.cs @@ -0,0 +1,61 @@ +// The MIT License (MIT) +// +// Copyright (c) 2015-2025 Rasmus Mikkelsen +// https://github.com/eventflow/EventFlow +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +using System; +using EventFlow.PostgreSql.ReadModels; +using EventFlow.PostgreSql.ReadStores.Attributes; +using EventFlow.ReadStores; +using EventFlow.TestHelpers; +using FluentAssertions; +using NUnit.Framework; + +namespace EventFlow.PostgreSql.Tests.UnitTests +{ + public class ReadModelSqlGeneratorTests : TestsFor + { + [Test] + public void CreateSelectSql() + { + // Act + var sql = Sut.CreateSelectSql(); + + // Assert + sql.Should().Be("SELECT * FROM \"ReadModel-A\" WHERE \"Id\" = @EventFlowReadModelId"); + } + + [Test] + public void CreateDeleteSql() + { + // Act + var sql = Sut.CreateDeleteSql(); + + // Assert + sql.Should().Be("DELETE FROM \"ReadModel-A\" WHERE \"Id\" = @EventFlowReadModelId"); + } + + public class ReadModelA : IReadModel + { + [PostgreSqlReadModelIdentityColumn] + public Guid Id { get; set; } + } + } +} diff --git a/Source/EventFlow.PostgreSql/ReadModels/PostgresReadModelSqlGenerator.cs b/Source/EventFlow.PostgreSql/ReadModels/PostgresReadModelSqlGenerator.cs index 6b3d0b72c..d4cbcc9ac 100644 --- a/Source/EventFlow.PostgreSql/ReadModels/PostgresReadModelSqlGenerator.cs +++ b/Source/EventFlow.PostgreSql/ReadModels/PostgresReadModelSqlGenerator.cs @@ -27,8 +27,8 @@ namespace EventFlow.PostgreSql.ReadModels public class PostgresReadModelSqlGenerator : ReadModelSqlGenerator { public PostgresReadModelSqlGenerator() - : base(new ReadModelSqlGeneratorConfiguration("\"", "\"", string.Empty, string.Empty)) + : base(new ReadModelSqlGeneratorConfiguration("\"", "\"", "\"", "\"")) { } } -} \ No newline at end of file +} diff --git a/Source/EventFlow.Sql/ReadModels/ReadModelSqlGenerator.cs b/Source/EventFlow.Sql/ReadModels/ReadModelSqlGenerator.cs index c0bedd16e..bbc08d5d9 100644 --- a/Source/EventFlow.Sql/ReadModels/ReadModelSqlGenerator.cs +++ b/Source/EventFlow.Sql/ReadModels/ReadModelSqlGenerator.cs @@ -99,7 +99,7 @@ public string CreateSelectSql() where TReadModel : IReadModel } var tableName = GetTableName(); - var identityColumn = GetIdentityColumn(); + var identityColumn = GetIdentityColumn(true); sql = $"SELECT * FROM {tableName} WHERE {identityColumn} = @EventFlowReadModelId"; @@ -116,8 +116,7 @@ public string CreateDeleteSql() where TReadModel : IReadModel return sql; } - sql = - $"DELETE FROM {GetTableName()} WHERE {Configuration.ColumnQuotedIdentifierPrefix}{GetIdentityColumn()}{Configuration.ColumnQuotedIdentifierSuffix} = @EventFlowReadModelId"; + sql = $"DELETE FROM {GetTableName()} WHERE {GetIdentityColumn(true)} = @EventFlowReadModelId"; _deleteSqls[readModelType] = sql; return sql; @@ -131,7 +130,7 @@ public string CreateUpdateSql() where TReadModel : IReadModel return sql; } - var identityColumn = GetIdentityColumn(); + var identityColumn = GetIdentityColumn(true); var versionColumn = GetVersionColumn(); var versionCheck = string.IsNullOrEmpty(versionColumn) ? string.Empty @@ -145,7 +144,7 @@ public string CreateUpdateSql() where TReadModel : IReadModel var tableName = GetTableName(); - sql = $"UPDATE {tableName} SET {updateColumns} WHERE {Configuration.ColumnQuotedIdentifierPrefix}{identityColumn}{Configuration.ColumnQuotedIdentifierSuffix} = @{identityColumn} {versionCheck}"; + sql = $"UPDATE {tableName} SET {updateColumns} WHERE {identityColumn} = @{identityColumn} {versionCheck}"; _updateSqls[readModelType] = sql; @@ -164,7 +163,7 @@ protected IEnumerable GetInsertColumns() where TReadModel : protected IEnumerable GetUpdateColumns() where TReadModel : IReadModel { - var identityColumn = GetIdentityColumn(); + var identityColumn = GetIdentityColumn(false); return GetInsertColumns().Where(c => c != identityColumn); } @@ -192,9 +191,9 @@ protected virtual string GetTableName(Type readModelType) }); } - private string GetIdentityColumn() + private string GetIdentityColumn(bool quoted) { - return IdentityColumns.GetOrAdd( + var identityColumn = IdentityColumns.GetOrAdd( typeof(TReadModel), t => { @@ -203,6 +202,10 @@ private string GetIdentityColumn() pi => pi.GetCustomAttributes().Any(a => a is SqlReadModelIdentityColumnAttribute)); return propertyInfo?.Name ?? "AggregateId"; }); + + return quoted + ? $"{Configuration.TableQuotedIdentifierPrefix}{identityColumn}{Configuration.TableQuotedIdentifierSuffix}" + : identityColumn; } private string GetVersionColumn() From 0665dd27be8825d59ce27d0a8310cf9b2ea690f1 Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Sun, 9 Mar 2025 14:53:10 +0100 Subject: [PATCH 2/2] Update release notes --- RELEASE_NOTES.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 4b9b5204c..b32f67cb6 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,6 +1,9 @@ ### New in 1.2.1 (working version, not released yet) -* *Nothing yet...* +* Fix: `PostgresReadModelSqlGenerator` now correctly uses the `ColumnQuotedIdentifierSuffix` and + `ColumnQuotedIdentifierPrefix` from the `PostgresConfiguration` when generating SQL `SELECT` queries +* Fix: `PostgresReadModelSqlGenerator` now uses a default value of `"` for `ColumnQuotedIdentifierSuffix` and + `ColumnQuotedIdentifierPrefix` ### New in 1.2.0 (released 2025-03-09)