From edffdf1b3528eda30d000a414214e4aede6af35d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crocquesel?= <88554524+scrocquesel@users.noreply.github.com> Date: Tue, 7 May 2024 22:40:36 +0200 Subject: [PATCH 01/12] Add NpgsqlDataSource support to PostgresqlConnectionManager --- src/dbup-postgresql/PostgresqlConnectionManager.cs | 11 +++++++++++ src/dbup-postgresql/Properties/AssemblyInfo.cs | 6 ++++++ src/dbup-postgresql/dbup-postgresql.csproj | 4 +++- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/dbup-postgresql/PostgresqlConnectionManager.cs b/src/dbup-postgresql/PostgresqlConnectionManager.cs index 8a10b3d..e5e56e7 100644 --- a/src/dbup-postgresql/PostgresqlConnectionManager.cs +++ b/src/dbup-postgresql/PostgresqlConnectionManager.cs @@ -39,6 +39,17 @@ public PostgresqlConnectionManager(string connectionString, X509Certificate2 cer { } +#if (NETSTANDARD2_0_OR_GREATER || NET462_OR_GREATER) + /// + /// Creates a new PostgreSQL database connection with a NpgsqlDatasource + /// + /// The PostgreSQL NpgsqlDataSource. + public PostgresqlConnectionManager(NpgsqlDataSource datasource) + : base(new DelegateConnectionFactory(l => datasource.CreateConnection())) + { + } +#endif + /// /// Splits the statements in the script using the ";" character. /// diff --git a/src/dbup-postgresql/Properties/AssemblyInfo.cs b/src/dbup-postgresql/Properties/AssemblyInfo.cs index 67961a0..4a03b1c 100644 --- a/src/dbup-postgresql/Properties/AssemblyInfo.cs +++ b/src/dbup-postgresql/Properties/AssemblyInfo.cs @@ -2,7 +2,13 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] + +#if (NETSTANDARD2_0_OR_GREATER || NET462_OR_GREATER) +// Npgsql is not CLS compliant and PostgresqlConnectionManager exposes types like NpgsqlDataSource +[assembly: CLSCompliant(false)] +#else [assembly: CLSCompliant(true)] +#endif // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("5ddc04cc-0bd3-421e-9ae4-9fd0e4f4ef04")] diff --git a/src/dbup-postgresql/dbup-postgresql.csproj b/src/dbup-postgresql/dbup-postgresql.csproj index ef3496a..e7ff7e2 100644 --- a/src/dbup-postgresql/dbup-postgresql.csproj +++ b/src/dbup-postgresql/dbup-postgresql.csproj @@ -19,7 +19,9 @@ - + + + From d97055c6b0f95cb5ee35ecddcb74109239a80e54 Mon Sep 17 00:00:00 2001 From: Robert Wagner Date: Sat, 20 Jul 2024 11:54:35 +1000 Subject: [PATCH 02/12] Used new GHA workflows --- .github/workflows/main.yml | 73 ++------------------------- .github/workflows/publish-release.yml | 12 +++++ .github/workflows/test-report.yml | 12 +++++ 3 files changed, 28 insertions(+), 69 deletions(-) create mode 100644 .github/workflows/publish-release.yml create mode 100644 .github/workflows/test-report.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 18198a1..26798d9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,77 +2,12 @@ name: CI on: push: + branches: + - '**' # Ignores pushes of tags pull_request: workflow_dispatch: jobs: build: - runs-on: windows-latest # Use Ubuntu in v5.0 - - env: - DOTNET_NOLOGO: true - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true # Avoid pre-populating the NuGet package cache - - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 # all - - - name: Setup .NET 2.0 # Remove in v5.0 - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 2.0.x - - - name: Setup .NET 8.0 - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 8.0.x - - - name: Install GitVersion - uses: gittools/actions/gitversion/setup@v0 - with: - versionSpec: '5.x' - - - name: Run GitVersion - id: gitversion - uses: gittools/actions/gitversion/execute@v0 - - - name: Display SemVer - run: | - echo "SemVer: $env:GitVersion_SemVer" - - - name: Add DbUp NuGet Source - run: dotnet nuget add source --name DbUp --username DbUp --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text https://nuget.pkg.github.com/DbUp/index.json - - - name: Restore - run: dotnet restore - working-directory: src - - - name: Build - run: dotnet build -c Release --no-restore /p:Version=$env:GitVersion_SemVer - working-directory: src - - - name: Test - run: dotnet test --no-build -c Release --logger trx --logger "console;verbosity=detailed" --results-directory ../artifacts - working-directory: src - - - name: Pack - run: dotnet pack --no-build -c Release -o ../artifacts /p:Version=$env:GitVersion_SemVer - working-directory: src - - - name: Push NuGet packages to GitHub Packages ⬆️ - working-directory: artifacts - run: dotnet nuget push *.nupkg --api-key ${{ secrets.GITHUB_TOKEN }} --source "https://nuget.pkg.github.com/DbUp/index.json" - - - name: Push NuGet packages to NuGet ⬆️ - if: ${{ steps.gitversion.outputs.preReleaseLabel == '' }} - working-directory: artifacts - run: dotnet nuget push *.nupkg --api-key ${{ secrets.NUGET_APIKEY }} --source https://api.nuget.org/v3/index.json - - - name: Test Report 🧪 - uses: dorny/test-reporter@v1 - if: ${{ always() }} - with: - name: Tests - path: artifacts/*.trx - reporter: dotnet-trx + name: Build + uses: DbUp/Universe/.github/workflows/build.yml@main diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 0000000..eb5e03f --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,12 @@ +name: Publish DbUp Packages to NuGet + +on: + release: + types: [ published ] + workflow_dispatch: + +jobs: + publish: + name: Publish Package + uses: DbUp/Universe/.github/workflows/publish-release.yml@main + secrets: inherit diff --git a/.github/workflows/test-report.yml b/.github/workflows/test-report.yml new file mode 100644 index 0000000..e8c9f8f --- /dev/null +++ b/.github/workflows/test-report.yml @@ -0,0 +1,12 @@ +name: Test Report +run-name: Generate Test Report for run `${{ github.event.workflow_run.run_number }}` branch `${{ github.event.workflow_run.head_branch }}` + +on: + workflow_run: + workflows: ["CI", "build"] + types: [completed] + +jobs: + report: + name: Test Report 🧪 + uses: DbUp/Universe/.github/workflows/test-report.yml@main \ No newline at end of file From e149f1c7047f09eb615e8c753fd86f3ef425e555 Mon Sep 17 00:00:00 2001 From: Robert Wagner Date: Wed, 24 Jul 2024 15:39:07 +1000 Subject: [PATCH 03/12] Updated to dbup-core 6.0 beta, targeted netstandard2 and update Npgsql reference --- src/Directory.Build.props | 14 ------ ...portTests.VerifyBasicSupport.approved.txt} | 4 +- ...JournalCreationIfNameChanged.approved.txt} | 4 +- ....VerifyVariableSubstitutions.approved.txt} | 4 +- .../NoPublicApiChanges.Run.Net.verified.cs | 49 ------------------- ....cs => NoPublicApiChanges.Run.approved.cs} | 0 src/Tests/PostgresTableJournalTests.cs | 15 +++--- src/Tests/Tests.csproj | 12 ++--- src/dbup-postgresql.sln.DotSettings | 3 ++ src/dbup-postgresql/PostgresqlExtensions.cs | 17 ++----- .../PostgresqlScriptExecutor.cs | 6 +-- src/dbup-postgresql/dbup-postgresql.csproj | 13 +++-- 12 files changed, 41 insertions(+), 100 deletions(-) rename src/Tests/ApprovalFiles/{DatabaseSupportTests.VerifyBasicSupport.verified.txt => DatabaseSupportTests.VerifyBasicSupport.approved.txt} (92%) rename src/Tests/ApprovalFiles/{DatabaseSupportTests.VerifyJournalCreationIfNameChanged.verified.txt => DatabaseSupportTests.VerifyJournalCreationIfNameChanged.approved.txt} (93%) rename src/Tests/ApprovalFiles/{DatabaseSupportTests.VerifyVariableSubstitutions.verified.txt => DatabaseSupportTests.VerifyVariableSubstitutions.approved.txt} (92%) delete mode 100644 src/Tests/ApprovalFiles/NoPublicApiChanges.Run.Net.verified.cs rename src/Tests/ApprovalFiles/{NoPublicApiChanges.Run.DotNet.verified.cs => NoPublicApiChanges.Run.approved.cs} (100%) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 99db373..370efc5 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -10,18 +10,4 @@ latest true - - - - - true - - - true - - - embedded - - - diff --git a/src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyBasicSupport.verified.txt b/src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyBasicSupport.approved.txt similarity index 92% rename from src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyBasicSupport.verified.txt rename to src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyBasicSupport.approved.txt index 36671a2..ac69b5d 100644 --- a/src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyBasicSupport.verified.txt +++ b/src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyBasicSupport.approved.txt @@ -1,11 +1,11 @@ DB Operation: Open connection Info: Beginning database upgrade -Info: Checking whether journal table exists.. +Info: Checking whether journal table exists DB Operation: Execute scalar command: select 1 from INFORMATION_SCHEMA.TABLES where TABLE_NAME = 'schemaversions' DB Operation: Dispose command Info: Journal table does not exist Info: Executing Database Server script 'Script0001.sql' -Info: Checking whether journal table exists.. +Info: Checking whether journal table exists DB Operation: Execute scalar command: select 1 from INFORMATION_SCHEMA.TABLES where TABLE_NAME = 'schemaversions' DB Operation: Dispose command Info: Creating the "schemaversions" table diff --git a/src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyJournalCreationIfNameChanged.verified.txt b/src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyJournalCreationIfNameChanged.approved.txt similarity index 93% rename from src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyJournalCreationIfNameChanged.verified.txt rename to src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyJournalCreationIfNameChanged.approved.txt index 0a54bf4..46bee6c 100644 --- a/src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyJournalCreationIfNameChanged.verified.txt +++ b/src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyJournalCreationIfNameChanged.approved.txt @@ -1,11 +1,11 @@ DB Operation: Open connection Info: Beginning database upgrade -Info: Checking whether journal table exists.. +Info: Checking whether journal table exists DB Operation: Execute scalar command: select 1 from INFORMATION_SCHEMA.TABLES where TABLE_NAME = 'TestSchemaVersions' and TABLE_SCHEMA = 'test' DB Operation: Dispose command Info: Journal table does not exist Info: Executing Database Server script 'Script0001.sql' -Info: Checking whether journal table exists.. +Info: Checking whether journal table exists DB Operation: Execute scalar command: select 1 from INFORMATION_SCHEMA.TABLES where TABLE_NAME = 'TestSchemaVersions' and TABLE_SCHEMA = 'test' DB Operation: Dispose command Info: Creating the "test"."TestSchemaVersions" table diff --git a/src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyVariableSubstitutions.verified.txt b/src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyVariableSubstitutions.approved.txt similarity index 92% rename from src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyVariableSubstitutions.verified.txt rename to src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyVariableSubstitutions.approved.txt index 496fbb6..3321b7a 100644 --- a/src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyVariableSubstitutions.verified.txt +++ b/src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyVariableSubstitutions.approved.txt @@ -1,11 +1,11 @@ DB Operation: Open connection Info: Beginning database upgrade -Info: Checking whether journal table exists.. +Info: Checking whether journal table exists DB Operation: Execute scalar command: select 1 from INFORMATION_SCHEMA.TABLES where TABLE_NAME = 'schemaversions' DB Operation: Dispose command Info: Journal table does not exist Info: Executing Database Server script 'Script0001.sql' -Info: Checking whether journal table exists.. +Info: Checking whether journal table exists DB Operation: Execute scalar command: select 1 from INFORMATION_SCHEMA.TABLES where TABLE_NAME = 'schemaversions' DB Operation: Dispose command Info: Creating the "schemaversions" table diff --git a/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.Net.verified.cs b/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.Net.verified.cs deleted file mode 100644 index 165f98e..0000000 --- a/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.Net.verified.cs +++ /dev/null @@ -1,49 +0,0 @@ -[assembly: System.CLSCompliantAttribute(true)] -[assembly: System.Runtime.InteropServices.ComVisibleAttribute(false)] -[assembly: System.Runtime.InteropServices.GuidAttribute("5ddc04cc-0bd3-421e-9ae4-9fd0e4f4ef04")] - -public static class PostgresqlExtensions -{ - public static DbUp.Builder.UpgradeEngineBuilder JournalToPostgresqlTable(this DbUp.Builder.UpgradeEngineBuilder builder, string schema, string table) { } - public static DbUp.Builder.UpgradeEngineBuilder PostgresqlDatabase(DbUp.Engine.Transactions.IConnectionManager connectionManager) { } - public static DbUp.Builder.UpgradeEngineBuilder PostgresqlDatabase(this DbUp.Builder.SupportedDatabases supported, string connectionString) { } - public static DbUp.Builder.UpgradeEngineBuilder PostgresqlDatabase(this DbUp.Builder.SupportedDatabases supported, DbUp.Engine.Transactions.IConnectionManager connectionManager) { } - public static DbUp.Builder.UpgradeEngineBuilder PostgresqlDatabase(DbUp.Engine.Transactions.IConnectionManager connectionManager, string schema) { } - public static DbUp.Builder.UpgradeEngineBuilder PostgresqlDatabase(this DbUp.Builder.SupportedDatabases supported, string connectionString, string schema) { } - public static DbUp.Builder.UpgradeEngineBuilder PostgresqlDatabase(this DbUp.Builder.SupportedDatabases supported, string connectionString, string schema, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) { } - public static void PostgresqlDatabase(this DbUp.SupportedDatabasesForEnsureDatabase supported, string connectionString) { } - public static void PostgresqlDatabase(this DbUp.SupportedDatabasesForEnsureDatabase supported, string connectionString, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) { } - public static void PostgresqlDatabase(this DbUp.SupportedDatabasesForEnsureDatabase supported, string connectionString, DbUp.Engine.Output.IUpgradeLog logger) { } -} -namespace DbUp.Postgresql -{ - public class PostgresqlConnectionManager : DbUp.Engine.Transactions.DatabaseConnectionManager, DbUp.Engine.Transactions.IConnectionManager - { - public PostgresqlConnectionManager(string connectionString) { } - public PostgresqlConnectionManager(string connectionString, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) { } - public override System.Collections.Generic.IEnumerable SplitScriptIntoCommands(string scriptContents) { } - } - public class PostgresqlObjectParser : DbUp.Support.SqlObjectParser, DbUp.Engine.ISqlObjectParser - { - public PostgresqlObjectParser() { } - } - public class PostgresqlPreprocessor : DbUp.Engine.IScriptPreprocessor - { - public PostgresqlPreprocessor() { } - public string Process(string contents) { } - } - public class PostgresqlScriptExecutor : DbUp.Support.ScriptExecutor, DbUp.Engine.IScriptExecutor - { - public PostgresqlScriptExecutor(System.Func connectionManagerFactory, System.Func log, string schema, System.Func variablesEnabled, System.Collections.Generic.IEnumerable scriptPreprocessors, System.Func journalFactory) { } - protected override void ExecuteCommandsWithinExceptionHandler(int index, DbUp.Engine.SqlScript script, System.Action executeCommand) { } - protected override string GetVerifySchemaSql(string schema) { } - } - public class PostgresqlTableJournal : DbUp.Support.TableJournal, DbUp.Engine.IJournal - { - public PostgresqlTableJournal(System.Func connectionManager, System.Func logger, string schema, string tableName) { } - protected override string CreateSchemaTableSql(string quotedPrimaryKeyName) { } - protected override string GetInsertJournalEntrySql(string scriptName, string applied) { } - protected override System.Data.IDbCommand GetInsertScriptCommand(System.Func dbCommandFactory, DbUp.Engine.SqlScript script) { } - protected override string GetJournalEntriesSql() { } - } -} diff --git a/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.DotNet.verified.cs b/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.approved.cs similarity index 100% rename from src/Tests/ApprovalFiles/NoPublicApiChanges.Run.DotNet.verified.cs rename to src/Tests/ApprovalFiles/NoPublicApiChanges.Run.approved.cs diff --git a/src/Tests/PostgresTableJournalTests.cs b/src/Tests/PostgresTableJournalTests.cs index ab727ba..fceb301 100644 --- a/src/Tests/PostgresTableJournalTests.cs +++ b/src/Tests/PostgresTableJournalTests.cs @@ -11,13 +11,13 @@ namespace DbUp.Postgresql.Tests; public class PostgresTableJournalTests : IDisposable { - [Fact] + [Fact] public void uses_positional_parameters_when_sql_rewriting_disabled() { AppContext.SetSwitch("Npgsql.EnableSqlRewriting", false); var dbConnection = Substitute.For(); - var connectionManager = new TestConnectionManager(dbConnection, true); + var connectionManager = new TestConnectionManager(dbConnection); var command = Substitute.For(); var param1 = Substitute.For(); var param2 = Substitute.For(); @@ -25,7 +25,8 @@ public void uses_positional_parameters_when_sql_rewriting_disabled() command.CreateParameter().Returns(param1, param2); command.ExecuteScalar().Returns(x => 0); var consoleUpgradeLog = new ConsoleUpgradeLog(); - var journal = new PostgresqlTableJournal(() => connectionManager, () => consoleUpgradeLog, "public", "SchemaVersions"); + var journal = new PostgresqlTableJournal(() => connectionManager, () => consoleUpgradeLog, "public", + "SchemaVersions"); // Act journal.StoreExecutedScript(new SqlScript("test", "select 1"), () => command); @@ -45,7 +46,7 @@ public void uses_named_parameters_when_sql_rewriting_enabled() AppContext.SetSwitch("Npgsql.EnableSqlRewriting", true); var dbConnection = Substitute.For(); - var connectionManager = new TestConnectionManager(dbConnection, true); + var connectionManager = new TestConnectionManager(dbConnection); var command = Substitute.For(); var param1 = Substitute.For(); var param2 = Substitute.For(); @@ -53,7 +54,8 @@ public void uses_named_parameters_when_sql_rewriting_enabled() command.CreateParameter().Returns(param1, param2); command.ExecuteScalar().Returns(x => 0); var consoleUpgradeLog = new ConsoleUpgradeLog(); - var journal = new PostgresqlTableJournal(() => connectionManager, () => consoleUpgradeLog, "public", "SchemaVersions"); + var journal = new PostgresqlTableJournal(() => connectionManager, () => consoleUpgradeLog, "public", + "SchemaVersions"); // Act journal.StoreExecutedScript(new SqlScript("test", "select 1"), () => command); @@ -62,7 +64,8 @@ public void uses_named_parameters_when_sql_rewriting_enabled() command.Received(2).CreateParameter(); param1.ParameterName.ShouldBe("scriptName"); param2.ParameterName.ShouldBe("applied"); - command.CommandText.ShouldBe("""insert into "public"."SchemaVersions" (ScriptName, Applied) values (@scriptName, @applied)"""); + command.CommandText.ShouldBe( + """insert into "public"."SchemaVersions" (ScriptName, Applied) values (@scriptName, @applied)"""); command.Received().ExecuteNonQuery(); } diff --git a/src/Tests/Tests.csproj b/src/Tests/Tests.csproj index 2cbc38f..664dede 100644 --- a/src/Tests/Tests.csproj +++ b/src/Tests/Tests.csproj @@ -1,19 +1,19 @@ - net462;net8 + net8 Tests DbUp.Postgresql.Tests - + enable enable - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/dbup-postgresql.sln.DotSettings b/src/dbup-postgresql.sln.DotSettings index 2c78c0a..d3334fb 100644 --- a/src/dbup-postgresql.sln.DotSettings +++ b/src/dbup-postgresql.sln.DotSettings @@ -5,5 +5,8 @@ SQ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> + True <data><IncludeFilters /><ExcludeFilters /></data> <data /> \ No newline at end of file diff --git a/src/dbup-postgresql/PostgresqlExtensions.cs b/src/dbup-postgresql/PostgresqlExtensions.cs index 8ff517a..fd885df 100644 --- a/src/dbup-postgresql/PostgresqlExtensions.cs +++ b/src/dbup-postgresql/PostgresqlExtensions.cs @@ -153,7 +153,7 @@ private static void PostgresqlDatabase(this SupportedDatabasesForEnsureDatabase logMasterConnectionStringBuilder.Password = "******"; } - logger.WriteInformation("Master ConnectionString => {0}", logMasterConnectionStringBuilder.ConnectionString); + logger.LogInformation("Master ConnectionString => {0}", logMasterConnectionStringBuilder.ConnectionString); using (var connection = new NpgsqlConnection(masterConnectionStringBuilder.ConnectionString)) { @@ -164,11 +164,8 @@ private static void PostgresqlDatabase(this SupportedDatabasesForEnsureDatabase } connection.Open(); - var sqlCommandText = string.Format - ( - @"SELECT case WHEN oid IS NOT NULL THEN 1 ELSE 0 end FROM pg_database WHERE datname = '{0}' limit 1;", - databaseName - ); + var sqlCommandText = + $@"SELECT case WHEN oid IS NOT NULL THEN 1 ELSE 0 end FROM pg_database WHERE datname = '{databaseName}' limit 1;"; // check to see if the database already exists.. using (var command = new NpgsqlCommand(sqlCommandText, connection) @@ -185,11 +182,7 @@ private static void PostgresqlDatabase(this SupportedDatabasesForEnsureDatabase } } - sqlCommandText = string.Format - ( - "create database \"{0}\";", - databaseName - ); + sqlCommandText = $"create database \"{databaseName}\";"; // Create the database... using (var command = new NpgsqlCommand(sqlCommandText, connection) @@ -200,7 +193,7 @@ private static void PostgresqlDatabase(this SupportedDatabasesForEnsureDatabase command.ExecuteNonQuery(); } - logger.WriteInformation(@"Created database {0}", databaseName); + logger.LogInformation(@"Created database {0}", databaseName); } } diff --git a/src/dbup-postgresql/PostgresqlScriptExecutor.cs b/src/dbup-postgresql/PostgresqlScriptExecutor.cs index c8f58d3..e99555c 100644 --- a/src/dbup-postgresql/PostgresqlScriptExecutor.cs +++ b/src/dbup-postgresql/PostgresqlScriptExecutor.cs @@ -42,9 +42,9 @@ protected override void ExecuteCommandsWithinExceptionHandler(int index, SqlScri catch (PostgresException exception) #endif { - Log().WriteInformation("Npgsql exception has occurred in script: '{0}'", script.Name); - Log().WriteError("Script block number: {0}; Block line {1}; Position: {2}; Message: {3}", index, exception.Line, exception.Position, exception.Message); - Log().WriteError(exception.ToString()); + Log().LogInformation("Npgsql exception has occurred in script: '{0}'", script.Name); + Log().LogError("Script block number: {0}; Block line {1}; Position: {2}; Message: {3}", index, exception.Line, exception.Position, exception.Message); + Log().LogError(exception.ToString()); throw; } } diff --git a/src/dbup-postgresql/dbup-postgresql.csproj b/src/dbup-postgresql/dbup-postgresql.csproj index ef3496a..990526b 100644 --- a/src/dbup-postgresql/dbup-postgresql.csproj +++ b/src/dbup-postgresql/dbup-postgresql.csproj @@ -6,7 +6,7 @@ DbUp Contributors DbUp Copyright © DbUp Contributors 2015 - netstandard1.3;netstandard2.0;net462 + netstandard2.0 dbup-postgresql DbUp.Postgresql dbup-postgresql @@ -16,10 +16,15 @@ dbup-icon.png + + true + true + embedded + + - - - + + From 2a1919092b02a4ad300308b4664fed45a5330ba7 Mon Sep 17 00:00:00 2001 From: Robert Wagner Date: Fri, 26 Jul 2024 09:22:48 +1000 Subject: [PATCH 04/12] Bump to the next beta of dbup-core --- src/Tests/Tests.csproj | 2 +- src/dbup-postgresql/dbup-postgresql.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tests/Tests.csproj b/src/Tests/Tests.csproj index 664dede..492abe6 100644 --- a/src/Tests/Tests.csproj +++ b/src/Tests/Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/dbup-postgresql/dbup-postgresql.csproj b/src/dbup-postgresql/dbup-postgresql.csproj index 990526b..1d48f85 100644 --- a/src/dbup-postgresql/dbup-postgresql.csproj +++ b/src/dbup-postgresql/dbup-postgresql.csproj @@ -23,7 +23,7 @@ - + From e785713d3e8773cbc4ddcec7b912b6e8be7f7fb8 Mon Sep 17 00:00:00 2001 From: Robert Wagner Date: Fri, 26 Jul 2024 15:33:02 +1000 Subject: [PATCH 05/12] Merge fix --- src/Tests/ApprovalFiles/NoPublicApiChanges.Run.approved.cs | 3 ++- src/dbup-postgresql/Properties/AssemblyInfo.cs | 6 ------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.approved.cs b/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.approved.cs index 165f98e..1e5eab5 100644 --- a/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.approved.cs +++ b/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.approved.cs @@ -1,4 +1,4 @@ -[assembly: System.CLSCompliantAttribute(true)] +[assembly: System.CLSCompliantAttribute(false)] [assembly: System.Runtime.InteropServices.ComVisibleAttribute(false)] [assembly: System.Runtime.InteropServices.GuidAttribute("5ddc04cc-0bd3-421e-9ae4-9fd0e4f4ef04")] @@ -20,6 +20,7 @@ namespace DbUp.Postgresql public class PostgresqlConnectionManager : DbUp.Engine.Transactions.DatabaseConnectionManager, DbUp.Engine.Transactions.IConnectionManager { public PostgresqlConnectionManager(string connectionString) { } + public PostgresqlConnectionManager(Npgsql.NpgsqlDataSource datasource) { } public PostgresqlConnectionManager(string connectionString, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) { } public override System.Collections.Generic.IEnumerable SplitScriptIntoCommands(string scriptContents) { } } diff --git a/src/dbup-postgresql/Properties/AssemblyInfo.cs b/src/dbup-postgresql/Properties/AssemblyInfo.cs index 4a03b1c..7083ac6 100644 --- a/src/dbup-postgresql/Properties/AssemblyInfo.cs +++ b/src/dbup-postgresql/Properties/AssemblyInfo.cs @@ -2,13 +2,7 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] - -#if (NETSTANDARD2_0_OR_GREATER || NET462_OR_GREATER) // Npgsql is not CLS compliant and PostgresqlConnectionManager exposes types like NpgsqlDataSource [assembly: CLSCompliant(false)] -#else -[assembly: CLSCompliant(true)] -#endif - // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("5ddc04cc-0bd3-421e-9ae4-9fd0e4f4ef04")] From 20cb366d99863a0e78b5138ffdd3269c2661c2d4 Mon Sep 17 00:00:00 2001 From: Robert Wagner Date: Fri, 26 Jul 2024 15:37:40 +1000 Subject: [PATCH 06/12] Removed stray #If --- src/Tests/DatabaseSupportTests.cs | 5 +---- src/Tests/NoPublicApiChanges.cs | 4 +--- src/dbup-postgresql/PostgresqlScriptExecutor.cs | 4 ---- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/Tests/DatabaseSupportTests.cs b/src/Tests/DatabaseSupportTests.cs index a8943d2..5b4741c 100644 --- a/src/Tests/DatabaseSupportTests.cs +++ b/src/Tests/DatabaseSupportTests.cs @@ -1,6 +1,4 @@ -#if !NETCORE -using System; -using DbUp.Builder; +using DbUp.Builder; using DbUp.Tests.Common; namespace DbUp.Postgresql.Tests; @@ -21,4 +19,3 @@ protected override UpgradeEngineBuilder AddCustomNamedJournalToBuilder(UpgradeEn => new PostgresqlTableJournal(connectionManagerFactory, logFactory, schema, tableName) ); } -#endif diff --git a/src/Tests/NoPublicApiChanges.cs b/src/Tests/NoPublicApiChanges.cs index 2f8d1b7..8cdd442 100644 --- a/src/Tests/NoPublicApiChanges.cs +++ b/src/Tests/NoPublicApiChanges.cs @@ -1,5 +1,4 @@ -#if !NETCORE -using DbUp.Tests.Common; +using DbUp.Tests.Common; namespace DbUp.Postgresql.Tests; @@ -10,4 +9,3 @@ public NoPublicApiChanges() { } } -#endif diff --git a/src/dbup-postgresql/PostgresqlScriptExecutor.cs b/src/dbup-postgresql/PostgresqlScriptExecutor.cs index e99555c..ce361a9 100644 --- a/src/dbup-postgresql/PostgresqlScriptExecutor.cs +++ b/src/dbup-postgresql/PostgresqlScriptExecutor.cs @@ -36,11 +36,7 @@ protected override void ExecuteCommandsWithinExceptionHandler(int index, SqlScri { executeCommand(); } -#if NPGSQLv2 - catch (NpgsqlException exception) -#else catch (PostgresException exception) -#endif { Log().LogInformation("Npgsql exception has occurred in script: '{0}'", script.Name); Log().LogError("Script block number: {0}; Block line {1}; Position: {2}; Message: {3}", index, exception.Line, exception.Position, exception.Message); From 80c9a504a40181055e3430d7857a16155be789be Mon Sep 17 00:00:00 2001 From: Campbell Harding-Deason Date: Tue, 10 Dec 2024 19:42:06 +1300 Subject: [PATCH 07/12] fix: #680 PostgreSQL statements split implemented (#19) * fix: #680 PostgreSQL statements split implemented Co-authored-by: shokurov * Updated approval file --------- Co-authored-by: shokurov Co-authored-by: Robert Wagner --- .../NoPublicApiChanges.Run.approved.cs | 1 + src/Tests/PostgresqlQueryParserTests.cs | 76 ++++ .../PostgresqlConnectionManager.cs | 10 +- src/dbup-postgresql/PostgresqlQueryParser.cs | 364 ++++++++++++++++++ 4 files changed, 448 insertions(+), 3 deletions(-) create mode 100644 src/Tests/PostgresqlQueryParserTests.cs create mode 100644 src/dbup-postgresql/PostgresqlQueryParser.cs diff --git a/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.approved.cs b/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.approved.cs index 7ca65ec..12ef4f6 100644 --- a/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.approved.cs +++ b/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.approved.cs @@ -25,6 +25,7 @@ public PostgresqlConnectionManager(string connectionString) { } public PostgresqlConnectionManager(Npgsql.NpgsqlDataSource datasource) { } public PostgresqlConnectionManager(string connectionString, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) { } public PostgresqlConnectionManager(string connectionString, DbUp.Postgresql.PostgresqlConnectionOptions connectionOptions) { } + public bool StandardConformingStrings { get; set; } public override System.Collections.Generic.IEnumerable SplitScriptIntoCommands(string scriptContents) { } } public class PostgresqlConnectionOptions diff --git a/src/Tests/PostgresqlQueryParserTests.cs b/src/Tests/PostgresqlQueryParserTests.cs new file mode 100644 index 0000000..bd61b9c --- /dev/null +++ b/src/Tests/PostgresqlQueryParserTests.cs @@ -0,0 +1,76 @@ +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace DbUp.Postgresql.Tests; + +public class PostgresqlQueryParserTests +{ + [Theory] + [InlineData("SELECT 1\n;\nSELECT 2", 2, "SELECT 1", "SELECT 2")] + [InlineData(";;SELECT 1", 1, "SELECT 1")] + [InlineData("SELECT 1;", 1, "SELECT 1")] + [InlineData("", 0)] + [InlineData("CREATE OR REPLACE RULE test AS ON UPDATE TO test DO (SELECT 1; SELECT 1)", + 1, + "CREATE OR REPLACE RULE test AS ON UPDATE TO test DO (SELECT 1; SELECT 1)")] + [InlineData("CREATE OR REPLACE RULE test AS ON UPDATE TO test DO (SELECT 1); SELECT 2", + 2, + "CREATE OR REPLACE RULE test AS ON UPDATE TO test DO (SELECT 1)", "SELECT 2")] + [InlineData("SELECT 1 /* block comment; */", 1, "SELECT 1 /* block comment; */")] + [InlineData( + """ + SELECT 1; + -- Line comment; with semicolon + SELECT 2; + """, 2, + "SELECT 1", + """ + -- Line comment; with semicolon + SELECT 2 + """)] + [InlineData("SELECT 'string with; semicolon'", 1, "SELECT 'string with; semicolon'")] + [InlineData("SELECT 'string with'' quote and; semicolon'", 1, "SELECT 'string with'' quote and; semicolon'")] + [InlineData(""" + CREATE FUNCTION TXT() + LANGUAGE PLPGSQL AS + $BODY$ + BEGIN + SELECT 'string with'' quote and; semicolon'; + END + $BODY$ + """, 1)] + [InlineData("SELECT 1 as \"QUOTED;IDENT\"", 1)] + [InlineData("SELECT E'\\041'; SELECT '1'", 2, "SELECT E'\\041'", "SELECT '1'")] + [InlineData(""" + SELECT 'some' + 'text'; + SELECT '1' + """, 2)] + public void split_into_statements(string sql, int statementCount, params string[] expected) + { + var results = ParseCommand(sql); + Assert.Equal(statementCount, results.Count); + if (expected.Length > 0) + Assert.Equal(expected, results); + } + + [Fact] + public void split_into_statements_non_sql_standard() + { + const string sql = "SELECT 'string with\\' quote and; semicolon'"; + var results = ParseCommand(sql, false); + Assert.Single(results); + Assert.Equal(sql, results[0]); + } + + private List ParseCommand(string sql) + => ParseCommand(sql, true); + + private static List ParseCommand(string sql, bool standardConformingStrings) + { + var manager = new PostgresqlConnectionManager("") { StandardConformingStrings = standardConformingStrings }; + var commands = manager.SplitScriptIntoCommands(sql); + return commands.ToList(); + } +} diff --git a/src/dbup-postgresql/PostgresqlConnectionManager.cs b/src/dbup-postgresql/PostgresqlConnectionManager.cs index b78742b..6896e90 100644 --- a/src/dbup-postgresql/PostgresqlConnectionManager.cs +++ b/src/dbup-postgresql/PostgresqlConnectionManager.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Linq; using System.Security.Cryptography.X509Certificates; -using System.Text.RegularExpressions; using DbUp.Engine.Transactions; using Npgsql; @@ -12,6 +11,11 @@ namespace DbUp.Postgresql /// public class PostgresqlConnectionManager : DatabaseConnectionManager { + /// + /// Disallow single quotes to be escaped with a backslash (\') + /// + public bool StandardConformingStrings { get; set; } = true; + /// /// Creates a new PostgreSQL database connection. /// @@ -47,7 +51,7 @@ public PostgresqlConnectionManager(string connectionString, PostgresqlConnection return databaseConnection; } - )) + )) { } @@ -67,7 +71,7 @@ public PostgresqlConnectionManager(NpgsqlDataSource datasource) public override IEnumerable SplitScriptIntoCommands(string scriptContents) { var scriptStatements = - Regex.Split(scriptContents, "^\\s*;\\s*$", RegexOptions.IgnoreCase | RegexOptions.Multiline) + PostgresqlQueryParser.ParseRawQuery(scriptContents, StandardConformingStrings) .Select(x => x.Trim()) .Where(x => x.Length > 0) .ToArray(); diff --git a/src/dbup-postgresql/PostgresqlQueryParser.cs b/src/dbup-postgresql/PostgresqlQueryParser.cs new file mode 100644 index 0000000..de47bd0 --- /dev/null +++ b/src/dbup-postgresql/PostgresqlQueryParser.cs @@ -0,0 +1,364 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace DbUp.Postgresql; + +internal static class PostgresqlQueryParser +{ + public static IReadOnlyCollection ParseRawQuery( + string sql, + bool standardConformingStrings = true) + { + List result = new(); + StringBuilder currentStatementBuilder = new(); + + currentStatementBuilder.Clear(); + + var currCharOfs = 0; + var end = sql.Length; + var ch = '\0'; + int dollarTagStart; + int dollarTagEnd; + var currTokenBeg = 0; + var blockCommentLevel = 0; + var parenthesisLevel = 0; + + None: + if (currCharOfs >= end) + goto Finish; + var lastChar = ch; + ch = sql[currCharOfs++]; + NoneContinue: + while (true) + { + switch (ch) + { + case '/': + goto BlockCommentBegin; + case '-': + goto LineCommentBegin; + case '\'': + if (standardConformingStrings) + goto Quoted; + goto Escaped; + case '$': + if (!IsIdentifier(lastChar)) + goto DollarQuotedStart; + break; + case '"': + goto Quoted; + case ';': + if (parenthesisLevel == 0) + goto SemiColon; + break; + case '(': + parenthesisLevel++; + break; + case ')': + parenthesisLevel--; + break; + case 'e': + case 'E': + if (!IsLetter(lastChar)) + goto EscapedStart; + break; + } + + if (currCharOfs >= end) + goto Finish; + + lastChar = ch; + ch = sql[currCharOfs++]; + } + + Quoted: + Debug.Assert(ch is '\'' or '"'); + while (currCharOfs < end && sql[currCharOfs] != ch) + { + currCharOfs++; + } + + if (currCharOfs < end) + { + currCharOfs++; + ch = '\0'; + goto None; + } + + goto Finish; + + EscapedStart: + if (currCharOfs < end) + { + lastChar = ch; + ch = sql[currCharOfs++]; + if (ch == '\'') + goto Escaped; + goto NoneContinue; + } + + goto Finish; + + Escaped: + while (currCharOfs < end) + { + ch = sql[currCharOfs++]; + switch (ch) + { + case '\'': + goto MaybeConcatenatedEscaped; + case '\\': + { + if (currCharOfs >= end) + goto Finish; + currCharOfs++; + break; + } + } + } + + goto Finish; + + MaybeConcatenatedEscaped: + while (currCharOfs < end) + { + ch = sql[currCharOfs++]; + switch (ch) + { + case '\r': + case '\n': + goto MaybeConcatenatedEscaped2; + case ' ': + case '\t': + case '\f': + continue; + default: + lastChar = '\0'; + goto NoneContinue; + } + } + + goto Finish; + + MaybeConcatenatedEscaped2: + while (currCharOfs < end) + { + ch = sql[currCharOfs++]; + switch (ch) + { + case '\'': + goto Escaped; + case '-': + { + if (currCharOfs >= end) + goto Finish; + ch = sql[currCharOfs++]; + if (ch == '-') + goto MaybeConcatenatedEscapeAfterComment; + lastChar = '\0'; + goto NoneContinue; + } + case ' ': + case '\t': + case '\n': + case '\r': + case '\f': + continue; + default: + lastChar = '\0'; + goto NoneContinue; + } + } + + goto Finish; + + MaybeConcatenatedEscapeAfterComment: + while (currCharOfs < end) + { + ch = sql[currCharOfs++]; + if (ch is '\r' or '\n') + goto MaybeConcatenatedEscaped2; + } + + goto Finish; + + DollarQuotedStart: + if (currCharOfs < end) + { + ch = sql[currCharOfs]; + if (ch == '$') + { + // Empty tag + dollarTagStart = dollarTagEnd = currCharOfs; + goto DollarQuoted; + } + + if (IsIdentifierStart(ch)) + { + dollarTagStart = currCharOfs; + currCharOfs++; + goto DollarQuotedInFirstDelim; + } + + lastChar = '$'; + currCharOfs++; + goto NoneContinue; + } + + goto Finish; + + DollarQuotedInFirstDelim: + while (currCharOfs < end) + { + lastChar = ch; + ch = sql[currCharOfs++]; + if (ch == '$') + { + dollarTagEnd = currCharOfs - 1; + goto DollarQuoted; + } + + if (!IsDollarTagIdentifier(ch)) + goto NoneContinue; + } + + goto Finish; + + DollarQuoted: + var tag = sql.Substring(dollarTagStart - 1, dollarTagEnd - dollarTagStart + 2); + var pos = sql.IndexOf(tag, dollarTagEnd + 1, StringComparison.Ordinal); + if (pos == -1) + { + goto Finish; + } + + pos += dollarTagEnd + 1; // If the substring is found adjust the position to be relative to the entire string + currCharOfs = pos + dollarTagEnd - dollarTagStart + 2; + ch = '\0'; + goto None; + + LineCommentBegin: + if (currCharOfs < end) + { + ch = sql[currCharOfs++]; + if (ch == '-') + goto LineComment; + lastChar = '\0'; + goto NoneContinue; + } + + goto Finish; + + LineComment: + while (currCharOfs < end) + { + ch = sql[currCharOfs++]; + if (ch is '\r' or '\n') + goto None; + } + + goto Finish; + + BlockCommentBegin: + while (currCharOfs < end) + { + ch = sql[currCharOfs++]; + switch (ch) + { + case '*': + blockCommentLevel++; + goto BlockComment; + case '/': + continue; + } + + if (blockCommentLevel > 0) + goto BlockComment; + lastChar = '\0'; + goto NoneContinue; + } + + goto Finish; + + BlockComment: + while (currCharOfs < end) + { + ch = sql[currCharOfs++]; + switch (ch) + { + case '*': + goto BlockCommentEnd; + case '/': + goto BlockCommentBegin; + } + } + + goto Finish; + + BlockCommentEnd: + while (currCharOfs < end) + { + ch = sql[currCharOfs++]; + if (ch == '/') + { + if (--blockCommentLevel > 0) + goto BlockComment; + goto None; + } + + if (ch != '*') + goto BlockComment; + } + + goto Finish; + + SemiColon: + currentStatementBuilder.Append(sql, currTokenBeg, currCharOfs - currTokenBeg - 1); + result.Add(currentStatementBuilder.ToString()); + while (currCharOfs < end) + { + ch = sql[currCharOfs]; + if (char.IsWhiteSpace(ch)) + { + currCharOfs++; + continue; + } + + // TODO: Handle end of line comment? Although psql doesn't seem to handle them... + currentStatementBuilder.Clear(); + + currTokenBeg = currCharOfs; + goto None; + } + + return result; + + Finish: + currentStatementBuilder.Append(sql, currTokenBeg, end - currTokenBeg); + result.Add(currentStatementBuilder.ToString()); + return result; + } + + // Is ASCII letter comparison optimization https://github.com/dotnet/runtime/blob/60cfaec2e6cffeb9a006bec4b8908ffcf71ac5b4/src/libraries/System.Private.CoreLib/src/System/Char.cs#L236 + + private static bool IsLetter(char ch) + // [a-zA-Z] + => (uint)((ch | 0x20) - 'a') <= ('z' - 'a'); + + private static bool IsIdentifierStart(char ch) + // [a-zA-Z_\x80-\xFF] + => (uint)((ch | 0x20) - 'a') <= ('z' - 'a') || ch == '_' || (uint)(ch - 128) <= 127u; + + private static bool IsDollarTagIdentifier(char ch) + // [a-zA-Z0-9_\x80-\xFF] + => (uint)((ch | 0x20) - 'a') <= ('z' - 'a') || (uint)(ch - '0') <= ('9' - '0') || ch == '_' || + (uint)(ch - 128) <= 127u; + + private static bool IsIdentifier(char ch) + // [a-zA-Z0-9_$\x80-\xFF] + => (uint)((ch | 0x20) - 'a') <= ('z' - 'a') || (uint)(ch - '0') <= ('9' - '0') || ch == '_' || ch == '$' || + (uint)(ch - 128) <= 127u; +} From 283ad5eeaf9e90bc7d3cb4f281dbdc3a19b3ee1a Mon Sep 17 00:00:00 2001 From: Robert Wagner Date: Tue, 10 Dec 2024 16:45:33 +1000 Subject: [PATCH 08/12] Update of libs --- src/Tests/Tests.csproj | 8 ++++---- src/dbup-postgresql/dbup-postgresql.csproj | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Tests/Tests.csproj b/src/Tests/Tests.csproj index 492abe6..83295fe 100644 --- a/src/Tests/Tests.csproj +++ b/src/Tests/Tests.csproj @@ -10,14 +10,14 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/dbup-postgresql/dbup-postgresql.csproj b/src/dbup-postgresql/dbup-postgresql.csproj index 1d48f85..8996e15 100644 --- a/src/dbup-postgresql/dbup-postgresql.csproj +++ b/src/dbup-postgresql/dbup-postgresql.csproj @@ -23,7 +23,7 @@ - + From 49956ad557970788184ddd15b5de9e5d24eab2b5 Mon Sep 17 00:00:00 2001 From: Robert Wagner Date: Tue, 10 Dec 2024 16:50:20 +1000 Subject: [PATCH 09/12] File scoped namespaces and primary ctors --- .../PostgresqlConnectionManager.cs | 125 +++++++++--------- src/dbup-postgresql/PostgresqlObjectParser.cs | 18 +-- src/dbup-postgresql/PostgresqlPreprocessor.cs | 19 ++- .../PostgresqlScriptExecutor.cs | 61 +++++---- src/dbup-postgresql/PostgresqlTableJournal.cs | 93 +++++++------ 5 files changed, 153 insertions(+), 163 deletions(-) diff --git a/src/dbup-postgresql/PostgresqlConnectionManager.cs b/src/dbup-postgresql/PostgresqlConnectionManager.cs index 6896e90..b30bea3 100644 --- a/src/dbup-postgresql/PostgresqlConnectionManager.cs +++ b/src/dbup-postgresql/PostgresqlConnectionManager.cs @@ -4,79 +4,78 @@ using DbUp.Engine.Transactions; using Npgsql; -namespace DbUp.Postgresql +namespace DbUp.Postgresql; + +/// +/// Manages PostgreSQL database connections. +/// +public class PostgresqlConnectionManager : DatabaseConnectionManager { /// - /// Manages PostgreSQL database connections. + /// Disallow single quotes to be escaped with a backslash (\') + /// + public bool StandardConformingStrings { get; set; } = true; + + /// + /// Creates a new PostgreSQL database connection. /// - public class PostgresqlConnectionManager : DatabaseConnectionManager + /// The PostgreSQL connection string. + public PostgresqlConnectionManager(string connectionString) + : base(new DelegateConnectionFactory(l => new NpgsqlConnection(connectionString))) { - /// - /// Disallow single quotes to be escaped with a backslash (\') - /// - public bool StandardConformingStrings { get; set; } = true; + } - /// - /// Creates a new PostgreSQL database connection. - /// - /// The PostgreSQL connection string. - public PostgresqlConnectionManager(string connectionString) - : base(new DelegateConnectionFactory(l => new NpgsqlConnection(connectionString))) + /// + /// Creates a new PostgreSQL database connection with a certificate. + /// + /// The PostgreSQL connection string. + /// Certificate for securing connection. + public PostgresqlConnectionManager(string connectionString, X509Certificate2 certificate) + : this(connectionString, new PostgresqlConnectionOptions { - } + ClientCertificate = certificate + }) + { + } - /// - /// Creates a new PostgreSQL database connection with a certificate. - /// - /// The PostgreSQL connection string. - /// Certificate for securing connection. - public PostgresqlConnectionManager(string connectionString, X509Certificate2 certificate) - : this(connectionString, new PostgresqlConnectionOptions + /// + /// Create a new PostgreSQL database connection + /// + /// The PostgreSQL connection string. + /// Custom options to apply on the created connection + public PostgresqlConnectionManager(string connectionString, PostgresqlConnectionOptions connectionOptions) + : base(new DelegateConnectionFactory(l => { - ClientCertificate = certificate - }) - { - } - - /// - /// Create a new PostgreSQL database connection - /// - /// The PostgreSQL connection string. - /// Custom options to apply on the created connection - public PostgresqlConnectionManager(string connectionString, PostgresqlConnectionOptions connectionOptions) - : base(new DelegateConnectionFactory(l => - { - NpgsqlConnection databaseConnection = new NpgsqlConnection(connectionString); - databaseConnection.ApplyConnectionOptions(connectionOptions); + NpgsqlConnection databaseConnection = new NpgsqlConnection(connectionString); + databaseConnection.ApplyConnectionOptions(connectionOptions); - return databaseConnection; - } - )) - { - } + return databaseConnection; + } + )) + { + } - /// - /// Creates a new PostgreSQL database connection with a NpgsqlDatasource - /// - /// The PostgreSQL NpgsqlDataSource. - public PostgresqlConnectionManager(NpgsqlDataSource datasource) - : base(new DelegateConnectionFactory(l => datasource.CreateConnection())) - { - } + /// + /// Creates a new PostgreSQL database connection with a NpgsqlDatasource + /// + /// The PostgreSQL NpgsqlDataSource. + public PostgresqlConnectionManager(NpgsqlDataSource datasource) + : base(new DelegateConnectionFactory(l => datasource.CreateConnection())) + { + } - /// - /// Splits the statements in the script using the ";" character. - /// - /// The contents of the script to split. - public override IEnumerable SplitScriptIntoCommands(string scriptContents) - { - var scriptStatements = - PostgresqlQueryParser.ParseRawQuery(scriptContents, StandardConformingStrings) - .Select(x => x.Trim()) - .Where(x => x.Length > 0) - .ToArray(); + /// + /// Splits the statements in the script using the ";" character. + /// + /// The contents of the script to split. + public override IEnumerable SplitScriptIntoCommands(string scriptContents) + { + var scriptStatements = + PostgresqlQueryParser.ParseRawQuery(scriptContents, StandardConformingStrings) + .Select(x => x.Trim()) + .Where(x => x.Length > 0) + .ToArray(); - return scriptStatements; - } + return scriptStatements; } -} +} \ No newline at end of file diff --git a/src/dbup-postgresql/PostgresqlObjectParser.cs b/src/dbup-postgresql/PostgresqlObjectParser.cs index 3d7fb89..7e5cc44 100644 --- a/src/dbup-postgresql/PostgresqlObjectParser.cs +++ b/src/dbup-postgresql/PostgresqlObjectParser.cs @@ -1,14 +1,8 @@ using DbUp.Support; -namespace DbUp.Postgresql -{ - /// - /// Parses Sql Objects and performs quoting functions. - /// - public class PostgresqlObjectParser : SqlObjectParser - { - public PostgresqlObjectParser() : base("\"", "\"") - { - } - } -} +namespace DbUp.Postgresql; + +/// +/// Parses Sql Objects and performs quoting functions. +/// +public class PostgresqlObjectParser() : SqlObjectParser("\"", "\""); diff --git a/src/dbup-postgresql/PostgresqlPreprocessor.cs b/src/dbup-postgresql/PostgresqlPreprocessor.cs index 33a9dc5..43b7539 100644 --- a/src/dbup-postgresql/PostgresqlPreprocessor.cs +++ b/src/dbup-postgresql/PostgresqlPreprocessor.cs @@ -1,15 +1,14 @@ using DbUp.Engine; -namespace DbUp.Postgresql +namespace DbUp.Postgresql; + +/// +/// This preprocessor makes adjustments to your sql to make it compatible with PostgreSQL. +/// +public class PostgresqlPreprocessor : IScriptPreprocessor { /// - /// This preprocessor makes adjustments to your sql to make it compatible with PostgreSQL. + /// Performs some preprocessing step on a PostgreSQL script. /// - public class PostgresqlPreprocessor : IScriptPreprocessor - { - /// - /// Performs some preprocessing step on a PostgreSQL script. - /// - public string Process(string contents) => contents; - } -} + public string Process(string contents) => contents; +} \ No newline at end of file diff --git a/src/dbup-postgresql/PostgresqlScriptExecutor.cs b/src/dbup-postgresql/PostgresqlScriptExecutor.cs index ce361a9..78d4766 100644 --- a/src/dbup-postgresql/PostgresqlScriptExecutor.cs +++ b/src/dbup-postgresql/PostgresqlScriptExecutor.cs @@ -6,43 +6,42 @@ using DbUp.Support; using Npgsql; -namespace DbUp.Postgresql +namespace DbUp.Postgresql; + +/// +/// An implementation of that executes against a PostgreSQL database. +/// +public class PostgresqlScriptExecutor : ScriptExecutor { /// - /// An implementation of that executes against a PostgreSQL database. + /// Initializes an instance of the class. /// - public class PostgresqlScriptExecutor : ScriptExecutor + /// + /// The logging mechanism. + /// The schema that contains the table. + /// Function that returns true if variables should be replaced, false otherwise. + /// Script Preprocessors in addition to variable substitution + /// Database journal + public PostgresqlScriptExecutor(Func connectionManagerFactory, Func log, string schema, Func variablesEnabled, + IEnumerable scriptPreprocessors, Func journalFactory) + : base(connectionManagerFactory, new PostgresqlObjectParser(), log, schema, variablesEnabled, scriptPreprocessors, journalFactory) { - /// - /// Initializes an instance of the class. - /// - /// - /// The logging mechanism. - /// The schema that contains the table. - /// Function that returns true if variables should be replaced, false otherwise. - /// Script Preprocessors in addition to variable substitution - /// Database journal - public PostgresqlScriptExecutor(Func connectionManagerFactory, Func log, string schema, Func variablesEnabled, - IEnumerable scriptPreprocessors, Func journalFactory) - : base(connectionManagerFactory, new PostgresqlObjectParser(), log, schema, variablesEnabled, scriptPreprocessors, journalFactory) - { - } + } - protected override string GetVerifySchemaSql(string schema) => $"CREATE SCHEMA IF NOT EXISTS {schema}"; + protected override string GetVerifySchemaSql(string schema) => $"CREATE SCHEMA IF NOT EXISTS {schema}"; - protected override void ExecuteCommandsWithinExceptionHandler(int index, SqlScript script, Action executeCommand) + protected override void ExecuteCommandsWithinExceptionHandler(int index, SqlScript script, Action executeCommand) + { + try + { + executeCommand(); + } + catch (PostgresException exception) { - try - { - executeCommand(); - } - catch (PostgresException exception) - { - Log().LogInformation("Npgsql exception has occurred in script: '{0}'", script.Name); - Log().LogError("Script block number: {0}; Block line {1}; Position: {2}; Message: {3}", index, exception.Line, exception.Position, exception.Message); - Log().LogError(exception.ToString()); - throw; - } + Log().LogInformation("Npgsql exception has occurred in script: '{0}'", script.Name); + Log().LogError("Script block number: {0}; Block line {1}; Position: {2}; Message: {3}", index, exception.Line, exception.Position, exception.Message); + Log().LogError(exception.ToString()); + throw; } } -} +} \ No newline at end of file diff --git a/src/dbup-postgresql/PostgresqlTableJournal.cs b/src/dbup-postgresql/PostgresqlTableJournal.cs index cf23212..fc35160 100644 --- a/src/dbup-postgresql/PostgresqlTableJournal.cs +++ b/src/dbup-postgresql/PostgresqlTableJournal.cs @@ -5,70 +5,69 @@ using DbUp.Engine.Transactions; using DbUp.Support; -namespace DbUp.Postgresql +namespace DbUp.Postgresql; + +/// +/// An implementation of the interface which tracks version numbers for a +/// PostgreSQL database using a table called SchemaVersions. +/// +public class PostgresqlTableJournal : TableJournal { /// - /// An implementation of the interface which tracks version numbers for a - /// PostgreSQL database using a table called SchemaVersions. + /// Creates a new PostgreSQL table journal. /// - public class PostgresqlTableJournal : TableJournal + /// The PostgreSQL connection manager. + /// The upgrade logger. + /// The name of the schema the journal is stored in. + /// The name of the journal table. + public PostgresqlTableJournal(Func connectionManager, Func logger, string schema, string tableName) + : base(connectionManager, logger, new PostgresqlObjectParser(), schema, tableName) { - /// - /// Creates a new PostgreSQL table journal. - /// - /// The PostgreSQL connection manager. - /// The upgrade logger. - /// The name of the schema the journal is stored in. - /// The name of the journal table. - public PostgresqlTableJournal(Func connectionManager, Func logger, string schema, string tableName) - : base(connectionManager, logger, new PostgresqlObjectParser(), schema, tableName) - { - } + } - protected override IDbCommand GetInsertScriptCommand(Func dbCommandFactory, SqlScript script) - { - // EnableSqlRewriting is enabled by default, and needs to be explicitly disabled - bool enableSqlRewriting = !AppContext.TryGetSwitch("Npgsql.EnableSqlRewriting", out bool enabled) || enabled; + protected override IDbCommand GetInsertScriptCommand(Func dbCommandFactory, SqlScript script) + { + // EnableSqlRewriting is enabled by default, and needs to be explicitly disabled + bool enableSqlRewriting = !AppContext.TryGetSwitch("Npgsql.EnableSqlRewriting", out bool enabled) || enabled; - if (enableSqlRewriting) - return base.GetInsertScriptCommand(dbCommandFactory, script); + if (enableSqlRewriting) + return base.GetInsertScriptCommand(dbCommandFactory, script); - // Use positional parameters instead of named parameters - var command = dbCommandFactory(); + // Use positional parameters instead of named parameters + var command = dbCommandFactory(); - var scriptNameParam = command.CreateParameter(); - scriptNameParam.Value = script.Name; - command.Parameters.Add(scriptNameParam); + var scriptNameParam = command.CreateParameter(); + scriptNameParam.Value = script.Name; + command.Parameters.Add(scriptNameParam); - var appliedParam = command.CreateParameter(); - appliedParam.Value = DateTime.Now; - command.Parameters.Add(appliedParam); + var appliedParam = command.CreateParameter(); + appliedParam.Value = DateTime.Now; + command.Parameters.Add(appliedParam); - command.CommandText = GetInsertJournalEntrySql("$1", "$2"); - command.CommandType = CommandType.Text; - return command; - } + command.CommandText = GetInsertJournalEntrySql("$1", "$2"); + command.CommandType = CommandType.Text; + return command; + } - protected override string GetInsertJournalEntrySql(string scriptName, string applied) - { - return $"insert into {FqSchemaTableName} (ScriptName, Applied) values ({scriptName}, {applied})"; - } + protected override string GetInsertJournalEntrySql(string scriptName, string applied) + { + return $"insert into {FqSchemaTableName} (ScriptName, Applied) values ({scriptName}, {applied})"; + } - protected override string GetJournalEntriesSql() - { - return $"select ScriptName from {FqSchemaTableName} order by ScriptName"; - } + protected override string GetJournalEntriesSql() + { + return $"select ScriptName from {FqSchemaTableName} order by ScriptName"; + } - protected override string CreateSchemaTableSql(string quotedPrimaryKeyName) - { - return -$@"CREATE TABLE {FqSchemaTableName} + protected override string CreateSchemaTableSql(string quotedPrimaryKeyName) + { + return + $@"CREATE TABLE {FqSchemaTableName} ( schemaversionsid serial NOT NULL, scriptname character varying(255) NOT NULL, applied timestamp without time zone NOT NULL, CONSTRAINT {quotedPrimaryKeyName} PRIMARY KEY (schemaversionsid) )"; - } } -} +} \ No newline at end of file From 881ecdc79b17a5ddd4eb23b30e823d0058b1744e Mon Sep 17 00:00:00 2001 From: Robert Wagner Date: Tue, 17 Dec 2024 08:57:47 +1000 Subject: [PATCH 10/12] Target `net8` so we can use the latest version of Npgsql (#24) * Target net8 so we can use the latest version of Npgsql * Used new APIs --- src/dbup-postgresql/PostgresqlExtensions.cs | 15 +++++++-------- src/dbup-postgresql/dbup-postgresql.csproj | 4 ++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/dbup-postgresql/PostgresqlExtensions.cs b/src/dbup-postgresql/PostgresqlExtensions.cs index 00bbe66..8e8448c 100644 --- a/src/dbup-postgresql/PostgresqlExtensions.cs +++ b/src/dbup-postgresql/PostgresqlExtensions.cs @@ -247,14 +247,13 @@ public static UpgradeEngineBuilder JournalToPostgresqlTable(this UpgradeEngineBu internal static void ApplyConnectionOptions(this NpgsqlConnection connection, PostgresqlConnectionOptions connectionOptions) { - if (connectionOptions?.ClientCertificate != null) + connection.SslClientAuthenticationOptionsCallback = options => { - connection.ProvideClientCertificatesCallback += - certs => certs.Add(connectionOptions.ClientCertificate); - } - if (connectionOptions?.UserCertificateValidationCallback != null) - { - connection.UserCertificateValidationCallback = connectionOptions.UserCertificateValidationCallback; - } + if (connectionOptions?.ClientCertificate != null) + options.ClientCertificates = new X509Certificate2Collection(connectionOptions.ClientCertificate); + + if (connectionOptions?.UserCertificateValidationCallback != null) + options.RemoteCertificateValidationCallback = connectionOptions.UserCertificateValidationCallback; + }; } } diff --git a/src/dbup-postgresql/dbup-postgresql.csproj b/src/dbup-postgresql/dbup-postgresql.csproj index 8996e15..f121447 100644 --- a/src/dbup-postgresql/dbup-postgresql.csproj +++ b/src/dbup-postgresql/dbup-postgresql.csproj @@ -6,7 +6,7 @@ DbUp Contributors DbUp Copyright © DbUp Contributors 2015 - netstandard2.0 + net8.0 dbup-postgresql DbUp.Postgresql dbup-postgresql @@ -24,7 +24,7 @@ - + From 5fb1a06b4e63661c8e9dd806f4dd308e7bcb7ad8 Mon Sep 17 00:00:00 2001 From: Robert Wagner Date: Tue, 17 Dec 2024 08:58:07 +1000 Subject: [PATCH 11/12] Added Npgsql licence information (#25) --- src/dbup-postgresql/Npgsql-LICENSE | 17 +++++++++++++++++ src/dbup-postgresql/PostgresqlQueryParser.cs | 6 +++++- 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 src/dbup-postgresql/Npgsql-LICENSE diff --git a/src/dbup-postgresql/Npgsql-LICENSE b/src/dbup-postgresql/Npgsql-LICENSE new file mode 100644 index 0000000..b102b0e --- /dev/null +++ b/src/dbup-postgresql/Npgsql-LICENSE @@ -0,0 +1,17 @@ +Copyright (c) 2002-2021, Npgsql + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose, without fee, and without a written agreement +is hereby granted, provided that the above copyright notice and this +paragraph and the following two paragraphs appear in all copies. + +IN NO EVENT SHALL NPGSQL BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, +SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, +ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF +Npgsql HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +NPGSQL SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND Npgsql +HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, +OR MODIFICATIONS. diff --git a/src/dbup-postgresql/PostgresqlQueryParser.cs b/src/dbup-postgresql/PostgresqlQueryParser.cs index de47bd0..1c2ade5 100644 --- a/src/dbup-postgresql/PostgresqlQueryParser.cs +++ b/src/dbup-postgresql/PostgresqlQueryParser.cs @@ -1,4 +1,8 @@ -#nullable enable +// This code based on original code from Npgsql +// https://github.com/npgsql/npgsql/blob/main/src/Npgsql/SqlQueryParser.cs +// which is BSD license: https://github.com/npgsql/npgsql/blob/main/LICENSE + +#nullable enable using System; using System.Collections.Generic; using System.Diagnostics; From 56d8911cfa404d63ed7ba01ae30e0198dc300e71 Mon Sep 17 00:00:00 2001 From: nvierge-addactis <151941872+nvierge-addactis@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:41:52 +0100 Subject: [PATCH 12/12] fix(#26): avoid NullReferenceException when creating database (#27) * fix(#26): avoid NullReferenceException when creating database * Updated approval file --------- Co-authored-by: Robert Wagner --- .../ApprovalFiles/NoPublicApiChanges.Run.approved.cs | 2 ++ src/dbup-postgresql/PostgresqlExtensions.cs | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.approved.cs b/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.approved.cs index 12ef4f6..00d5ce0 100644 --- a/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.approved.cs +++ b/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.approved.cs @@ -16,6 +16,8 @@ public static void PostgresqlDatabase(this DbUp.SupportedDatabasesForEnsureDatab public static void PostgresqlDatabase(this DbUp.SupportedDatabasesForEnsureDatabase supported, string connectionString, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) { } public static void PostgresqlDatabase(this DbUp.SupportedDatabasesForEnsureDatabase supported, string connectionString, DbUp.Postgresql.PostgresqlConnectionOptions connectionOptions) { } public static void PostgresqlDatabase(this DbUp.SupportedDatabasesForEnsureDatabase supported, string connectionString, DbUp.Engine.Output.IUpgradeLog logger) { } + public static void PostgresqlDatabase(this DbUp.SupportedDatabasesForEnsureDatabase supported, string connectionString, DbUp.Engine.Output.IUpgradeLog logger, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) { } + public static void PostgresqlDatabase(this DbUp.SupportedDatabasesForEnsureDatabase supported, string connectionString, DbUp.Engine.Output.IUpgradeLog logger, DbUp.Postgresql.PostgresqlConnectionOptions connectionOptions) { } } namespace DbUp.Postgresql { diff --git a/src/dbup-postgresql/PostgresqlExtensions.cs b/src/dbup-postgresql/PostgresqlExtensions.cs index 8e8448c..3dda4b4 100644 --- a/src/dbup-postgresql/PostgresqlExtensions.cs +++ b/src/dbup-postgresql/PostgresqlExtensions.cs @@ -147,10 +147,10 @@ public static void PostgresqlDatabase(this SupportedDatabasesForEnsureDatabase s /// public static void PostgresqlDatabase(this SupportedDatabasesForEnsureDatabase supported, string connectionString, IUpgradeLog logger) { - PostgresqlDatabase(supported, connectionString, logger, (PostgresqlConnectionOptions)null); + PostgresqlDatabase(supported, connectionString, logger, new PostgresqlConnectionOptions()); } - - private static void PostgresqlDatabase(this SupportedDatabasesForEnsureDatabase supported, string connectionString, IUpgradeLog logger, X509Certificate2 certificate) + + public static void PostgresqlDatabase(this SupportedDatabasesForEnsureDatabase supported, string connectionString, IUpgradeLog logger, X509Certificate2 certificate) { var options = new PostgresqlConnectionOptions { @@ -159,7 +159,7 @@ private static void PostgresqlDatabase(this SupportedDatabasesForEnsureDatabase PostgresqlDatabase(supported, connectionString, logger, options); } - private static void PostgresqlDatabase( + public static void PostgresqlDatabase( this SupportedDatabasesForEnsureDatabase supported, string connectionString, IUpgradeLog logger,