From ca060ac4000880fb20f8d91a855027f8dc906a16 Mon Sep 17 00:00:00 2001 From: Eric Sibly Date: Fri, 11 Oct 2024 15:50:56 -0700 Subject: [PATCH 1/2] v2.7.0 - *Enhancement:* Require a means to add an explicitly named resource-based script outside of the automatic convention-based discovery; see new `MigrationArgs.AddScript`. - *Enhancement:* Moving the [_Beef_](https://github.com/Avanade/beef)-based standardized SQL Server scripts (functions and stored procedures) to _DbEx_ to enable greater usage. New `MigrationArgs.IncludeExtendedSchemaScripts` extension method will add (leverages new `MigrationArgs.AddScript`). - *Enhancement:* Added PostgreSQL equivalent standardized SQL Server scripts (functions and stored procedures). - *Enhancement:* Added command-line option `-dso|--drop-schema-objects` to set `MigrationArgs.DropSchemaObjects` directly from the console. --- CHANGELOG.md | 8 ++- Common.targets | 2 +- src/DbEx.MySql/DbEx.MySql.csproj | 2 +- .../Console/MigrationArgsExtensions.cs | 37 +++++++++++++ src/DbEx.Postgres/DbEx.Postgres.csproj | 14 ++++- .../Functions/fn_get_tenant_id.sql | 19 +++++++ .../Functions/fn_get_timestamp.sql | 23 ++++++++ .../Functions/fn_get_user_id.sql | 19 +++++++ .../Functions/fn_get_username.sql | 22 ++++++++ .../sp_set_session_context.sql | 28 ++++++++++ .../sp_throw_authorization_exception.sql | 15 +++++ .../sp_throw_business_exception.sql | 15 +++++ .../sp_throw_concurrency_exception.sql | 15 +++++ .../sp_throw_conflict_exception.sql | 15 +++++ .../sp_throw_duplicate_exception.sql | 15 +++++ .../sp_throw_not_found_exception.sql | 15 +++++ .../sp_throw_validation_exception.sql | 15 +++++ .../Console/MigrationArgsExtensions.cs | 38 +++++++++++++ src/DbEx.SqlServer/DbEx.SqlServer.csproj | 14 ++++- .../Functions/fnGetTenantId.sql | 21 +++++++ .../Functions/fnGetTimestamp.sql | 25 +++++++++ .../ExtendedSchema/Functions/fnGetUserId.sql | 21 +++++++ .../Functions/fnGetUsername.sql | 25 +++++++++ .../Stored Procedures/spSetSessionContext.sql | 29 ++++++++++ .../spThrowAuthorizationException.sql | 9 +++ .../spThrowBusinessException.sql | 9 +++ .../spThrowConcurrencyException.sql | 9 +++ .../spThrowConflictException.sql | 9 +++ .../spThrowDuplicateException.sql | 9 +++ .../spThrowNotFoundException.sql | 9 +++ .../spThrowValidationException.sql | 9 +++ src/DbEx/Console/MigrationConsoleBase.cs | 7 +++ src/DbEx/DatabaseSchemaConfig.cs | 4 ++ src/DbEx/DbEx.csproj | 4 +- src/DbEx/Migration/DatabaseMigrationBase.cs | 38 +++++++++++-- src/DbEx/Migration/MigrationArgsBase.cs | 29 +++++++++- src/DbEx/Migration/MigrationArgsBaseT.cs | 24 ++++++++ tests/DbEx.Test.Console/Program.cs | 1 + .../DbEx.Test.Console/Resources/Table_sql.hb | 2 +- tests/DbEx.Test/DatabaseSchemaTest.cs | 1 + tests/DbEx.Test/DbEx.Test.csproj | 4 +- tests/DbEx.Test/MySqlMigrationTest.cs | 1 + tests/DbEx.Test/PostgresMigrationTest.cs | 55 ++++++++++++++++++- tests/DbEx.Test/SqlServerMigrationTest.cs | 49 ++++++++++++++++- tests/DbEx.Test/SqlServerOutboxTest.cs | 1 + 45 files changed, 714 insertions(+), 21 deletions(-) create mode 100644 src/DbEx.Postgres/Console/MigrationArgsExtensions.cs create mode 100644 src/DbEx.Postgres/Resources/ExtendedSchema/Functions/fn_get_tenant_id.sql create mode 100644 src/DbEx.Postgres/Resources/ExtendedSchema/Functions/fn_get_timestamp.sql create mode 100644 src/DbEx.Postgres/Resources/ExtendedSchema/Functions/fn_get_user_id.sql create mode 100644 src/DbEx.Postgres/Resources/ExtendedSchema/Functions/fn_get_username.sql create mode 100644 src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_set_session_context.sql create mode 100644 src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_throw_authorization_exception.sql create mode 100644 src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_throw_business_exception.sql create mode 100644 src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_throw_concurrency_exception.sql create mode 100644 src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_throw_conflict_exception.sql create mode 100644 src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_throw_duplicate_exception.sql create mode 100644 src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_throw_not_found_exception.sql create mode 100644 src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_throw_validation_exception.sql create mode 100644 src/DbEx.SqlServer/Console/MigrationArgsExtensions.cs create mode 100644 src/DbEx.SqlServer/Resources/ExtendedSchema/Functions/fnGetTenantId.sql create mode 100644 src/DbEx.SqlServer/Resources/ExtendedSchema/Functions/fnGetTimestamp.sql create mode 100644 src/DbEx.SqlServer/Resources/ExtendedSchema/Functions/fnGetUserId.sql create mode 100644 src/DbEx.SqlServer/Resources/ExtendedSchema/Functions/fnGetUsername.sql create mode 100644 src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spSetSessionContext.sql create mode 100644 src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spThrowAuthorizationException.sql create mode 100644 src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spThrowBusinessException.sql create mode 100644 src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spThrowConcurrencyException.sql create mode 100644 src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spThrowConflictException.sql create mode 100644 src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spThrowDuplicateException.sql create mode 100644 src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spThrowNotFoundException.sql create mode 100644 src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spThrowValidationException.sql diff --git a/CHANGELOG.md b/CHANGELOG.md index e880439..486e770 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,14 @@ Represents the **NuGet** versions. +## v2.7.0 +- *Enhancement:* Require a means to add an explicitly named resource-based script outside of the automatic convention-based discovery; see new `MigrationArgs.AddScript`. +- *Enhancement:* Moving the [_Beef_](https://github.com/Avanade/beef)-based standardized SQL Server scripts (functions and stored procedures) to _DbEx_ to enable greater usage. New `MigrationArgs.IncludeExtendedSchemaScripts` extension method will add (leverages new `MigrationArgs.AddScript`). +- *Enhancement:* Added PostgreSQL equivalent standardized SQL Server scripts (functions and stored procedures). +- *Enhancement:* Added command-line option `-dso|--drop-schema-objects` to set `MigrationArgs.DropSchemaObjects` directly from the console. + ## v2.6.1 -- *Fixed:* Added `MigrationCommand.CreateMigrateAndCodeGen`. This can be useful in development scenarios where the `CodeGen` phase results in a new migration script that needs to be applied before any corresponding `Schema` operations are performed; in this case, a secondary +- *Fixed:* Added `MigrationCommand.CreateMigrateAndCodeGen`. This can be useful in development scenarios where the `CodeGen` phase results in a new migration script that needs to be applied before any corresponding `Schema` operations are performed; in this case, a secondary migration will be required. ## v2.6.0 - *Enhancement:* Added a `DbColumnSchema.SqlType2` that does _not_ include nullability. diff --git a/Common.targets b/Common.targets index 57a9865..fc00e24 100644 --- a/Common.targets +++ b/Common.targets @@ -1,6 +1,6 @@ - 2.6.1 + 2.7.0 preview Avanade Avanade diff --git a/src/DbEx.MySql/DbEx.MySql.csproj b/src/DbEx.MySql/DbEx.MySql.csproj index 44c66ec..b83449d 100644 --- a/src/DbEx.MySql/DbEx.MySql.csproj +++ b/src/DbEx.MySql/DbEx.MySql.csproj @@ -41,7 +41,7 @@ - + diff --git a/src/DbEx.Postgres/Console/MigrationArgsExtensions.cs b/src/DbEx.Postgres/Console/MigrationArgsExtensions.cs new file mode 100644 index 0000000..5be52c0 --- /dev/null +++ b/src/DbEx.Postgres/Console/MigrationArgsExtensions.cs @@ -0,0 +1,37 @@ +// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx + +using DbEx.Migration; +using System.Linq; + +namespace DbEx.Postgres.Console +{ + /// + /// Provides extension methods for . + /// + public static class MigrationArgsExtensions + { + /// + /// Include the Postgres extended Schema scripts (stored procedures and functions) from . + /// + /// The . + /// The to support fluent-style method-chaining. + public static MigrationArgs IncludeExtendedSchemaScripts(this MigrationArgs args) + { + AddExtendedSchemaScripts(args); + return args; + } + + /// + /// Include the Postgres extended Schema scripts (stored procedures and functions) from . + /// + /// The . + /// The to support fluent-style method-chaining. + public static void AddExtendedSchemaScripts(TArgs args) where TArgs : MigrationArgsBase + { + foreach (var rn in typeof(MigrationArgsExtensions).Assembly.GetManifestResourceNames().Where(x => x.StartsWith("DbEx.Postgres.Resources.ExtendedSchema.") && x.EndsWith(".sql"))) + { + args.AddScript(MigrationCommand.Schema, typeof(MigrationArgsExtensions).Assembly, rn); + } + } + } +} \ No newline at end of file diff --git a/src/DbEx.Postgres/DbEx.Postgres.csproj b/src/DbEx.Postgres/DbEx.Postgres.csproj index 3d1c0f8..b8e5569 100644 --- a/src/DbEx.Postgres/DbEx.Postgres.csproj +++ b/src/DbEx.Postgres/DbEx.Postgres.csproj @@ -31,6 +31,18 @@ + + + + + + + + + + + + @@ -42,7 +54,7 @@ - + diff --git a/src/DbEx.Postgres/Resources/ExtendedSchema/Functions/fn_get_tenant_id.sql b/src/DbEx.Postgres/Resources/ExtendedSchema/Functions/fn_get_tenant_id.sql new file mode 100644 index 0000000..b48d29c --- /dev/null +++ b/src/DbEx.Postgres/Resources/ExtendedSchema/Functions/fn_get_tenant_id.sql @@ -0,0 +1,19 @@ +-- Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/Beef + +CREATE OR REPLACE FUNCTION fn_get_tenant_id( + "Override" TEXT = NULL +) +RETURNS TEXT +LANGUAGE plpgsql +AS $$ +DECLARE "TenantId" TEXT; +BEGIN + IF "Override" IS NULL THEN + "TenantId" := current_setting('Session.TenantId', true); + ELSE + "TenantId" := "Override"; + END IF; + + RETURN "TenantId"; +END +$$; \ No newline at end of file diff --git a/src/DbEx.Postgres/Resources/ExtendedSchema/Functions/fn_get_timestamp.sql b/src/DbEx.Postgres/Resources/ExtendedSchema/Functions/fn_get_timestamp.sql new file mode 100644 index 0000000..008bdad --- /dev/null +++ b/src/DbEx.Postgres/Resources/ExtendedSchema/Functions/fn_get_timestamp.sql @@ -0,0 +1,23 @@ +-- Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/Beef + +CREATE OR REPLACE FUNCTION fn_get_timestamp( + "Override" TIMESTAMP WITH TIME ZONE = NULL +) +RETURNS TIMESTAMP WITH TIME ZONE +LANGUAGE plpgsql +AS $$ +DECLARE "Timestamp" TIMESTAMP WITH TIME ZONE; +BEGIN + "Timestamp" := CURRENT_TIMESTAMP; + IF "Override" IS NULL THEN + "Timestamp" := to_timestamp(current_setting('Session.Timestamp', true), 'YYYY-MM-DD"T"HH24:MI:SS.FF6'); + IF "Timestamp" IS NULL THEN + "Timestamp" := CURRENT_TIMESTAMP; + END IF; + ELSE + "Timestamp" := "Override"; + END IF; + + RETURN "Timestamp"; +END +$$; \ No newline at end of file diff --git a/src/DbEx.Postgres/Resources/ExtendedSchema/Functions/fn_get_user_id.sql b/src/DbEx.Postgres/Resources/ExtendedSchema/Functions/fn_get_user_id.sql new file mode 100644 index 0000000..cd1eacf --- /dev/null +++ b/src/DbEx.Postgres/Resources/ExtendedSchema/Functions/fn_get_user_id.sql @@ -0,0 +1,19 @@ +-- Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/Beef + +CREATE OR REPLACE FUNCTION fn_get_user_id( + "Override" TEXT = NULL +) +RETURNS TEXT +LANGUAGE plpgsql +AS $$ +DECLARE "UserId" TEXT; +BEGIN + IF "Override" IS NULL THEN + "UserId" := current_setting('Session.UserId', true); + ELSE + "UserId" := "Override"; + END IF; + + RETURN "UserId"; +END +$$; \ No newline at end of file diff --git a/src/DbEx.Postgres/Resources/ExtendedSchema/Functions/fn_get_username.sql b/src/DbEx.Postgres/Resources/ExtendedSchema/Functions/fn_get_username.sql new file mode 100644 index 0000000..27b3d2c --- /dev/null +++ b/src/DbEx.Postgres/Resources/ExtendedSchema/Functions/fn_get_username.sql @@ -0,0 +1,22 @@ +-- Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/Beef + +CREATE OR REPLACE FUNCTION fn_get_username( + "Override" TEXT = NULL +) +RETURNS TEXT +LANGUAGE plpgsql +AS $$ +DECLARE "Username" TEXT; +BEGIN + IF "Override" IS NULL THEN + "Username" := current_setting('Session.Username', true); + IF "Username" IS NULL THEN + "Username" := current_user; + END IF; + ELSE + "Username" := "Override"; + END IF; + + RETURN "Username"; +END +$$; \ No newline at end of file diff --git a/src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_set_session_context.sql b/src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_set_session_context.sql new file mode 100644 index 0000000..9f38be4 --- /dev/null +++ b/src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_set_session_context.sql @@ -0,0 +1,28 @@ +-- Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx + +CREATE OR REPLACE PROCEDURE sp_set_session_context( + "Timestamp" TIMESTAMP WITH TIME ZONE = NULL, + "Username" TEXT = NULL, + "TenantId" TEXT = NULL, + "UserId" TEXT = NULL +) +LANGUAGE plpgsql +AS $$ +BEGIN + IF "Timestamp" IS NOT NULL THEN + PERFORM set_config('Session.Timestamp', to_char("Timestamp", 'YYYY-MM-DD"T"HH24:MI:SS.FF6'), false); + END IF; + + IF "Username" IS NOT NULL THEN + PERFORM set_config('Session.Username', "Username", false); + END IF; + + IF "TenantId" IS NOT NULL THEN + PERFORM set_config('Session.TenantId', "TenantId", false); + END IF; + + IF "UserId" IS NOT NULL THEN + PERFORM set_config('Session.UserId', "UserId", false); + END IF; +END +$$; \ No newline at end of file diff --git a/src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_throw_authorization_exception.sql b/src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_throw_authorization_exception.sql new file mode 100644 index 0000000..ac4ee96 --- /dev/null +++ b/src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_throw_authorization_exception.sql @@ -0,0 +1,15 @@ +-- Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx + +CREATE OR REPLACE PROCEDURE sp_throw_authorization_exception( + "message" TEXT = NULL +) +LANGUAGE plpgsql +AS $$ +BEGIN + IF "message" IS NULL THEN + "message" := ''; + END IF; + + RAISE USING MESSAGE = "message", ERRCODE = '56003'; +END +$$; \ No newline at end of file diff --git a/src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_throw_business_exception.sql b/src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_throw_business_exception.sql new file mode 100644 index 0000000..b7500ad --- /dev/null +++ b/src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_throw_business_exception.sql @@ -0,0 +1,15 @@ +-- Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx + +CREATE OR REPLACE PROCEDURE sp_throw_business_exception( + "message" TEXT = NULL +) +LANGUAGE plpgsql +AS $$ +BEGIN + IF "message" IS NULL THEN + "message" := ''; + END IF; + + RAISE USING MESSAGE = "message", ERRCODE = '56002'; +END +$$; \ No newline at end of file diff --git a/src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_throw_concurrency_exception.sql b/src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_throw_concurrency_exception.sql new file mode 100644 index 0000000..72b1d7f --- /dev/null +++ b/src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_throw_concurrency_exception.sql @@ -0,0 +1,15 @@ +-- Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx + +CREATE OR REPLACE PROCEDURE sp_throw_concurrency_exception( + "message" TEXT = NULL +) +LANGUAGE plpgsql +AS $$ +BEGIN + IF "message" IS NULL THEN + "message" := ''; + END IF; + + RAISE USING MESSAGE = "message", ERRCODE = '56004'; +END +$$; \ No newline at end of file diff --git a/src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_throw_conflict_exception.sql b/src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_throw_conflict_exception.sql new file mode 100644 index 0000000..b2ec66f --- /dev/null +++ b/src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_throw_conflict_exception.sql @@ -0,0 +1,15 @@ +-- Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx + +CREATE OR REPLACE PROCEDURE sp_throw_conflict_exception( + "message" TEXT = NULL +) +LANGUAGE plpgsql +AS $$ +BEGIN + IF "message" IS NULL THEN + "message" := ''; + END IF; + + RAISE USING MESSAGE = "message", ERRCODE = '56006'; +END +$$; \ No newline at end of file diff --git a/src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_throw_duplicate_exception.sql b/src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_throw_duplicate_exception.sql new file mode 100644 index 0000000..7f39495 --- /dev/null +++ b/src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_throw_duplicate_exception.sql @@ -0,0 +1,15 @@ +-- Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx + +CREATE OR REPLACE PROCEDURE sp_throw_duplicate_exception( + "message" TEXT = NULL +) +LANGUAGE plpgsql +AS $$ +BEGIN + IF "message" IS NULL THEN + "message" := ''; + END IF; + + RAISE USING MESSAGE = "message", ERRCODE = '56007'; +END +$$; \ No newline at end of file diff --git a/src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_throw_not_found_exception.sql b/src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_throw_not_found_exception.sql new file mode 100644 index 0000000..ba43c6b --- /dev/null +++ b/src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_throw_not_found_exception.sql @@ -0,0 +1,15 @@ +-- Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx + +CREATE OR REPLACE PROCEDURE sp_throw_not_found_exception( + "message" TEXT = NULL +) +LANGUAGE plpgsql +AS $$ +BEGIN + IF "message" IS NULL THEN + "message" := ''; + END IF; + + RAISE USING MESSAGE = "message", ERRCODE = '56005'; +END +$$; \ No newline at end of file diff --git a/src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_throw_validation_exception.sql b/src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_throw_validation_exception.sql new file mode 100644 index 0000000..d93b6ed --- /dev/null +++ b/src/DbEx.Postgres/Resources/ExtendedSchema/Stored Procedures/sp_throw_validation_exception.sql @@ -0,0 +1,15 @@ +-- Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx + +CREATE OR REPLACE PROCEDURE sp_throw_validation_exception( + "message" TEXT = NULL +) +LANGUAGE plpgsql +AS $$ +BEGIN + IF "message" IS NULL THEN + "message" := ''; + END IF; + + RAISE USING MESSAGE = "message", ERRCODE = '56001'; +END +$$; \ No newline at end of file diff --git a/src/DbEx.SqlServer/Console/MigrationArgsExtensions.cs b/src/DbEx.SqlServer/Console/MigrationArgsExtensions.cs new file mode 100644 index 0000000..32a6515 --- /dev/null +++ b/src/DbEx.SqlServer/Console/MigrationArgsExtensions.cs @@ -0,0 +1,38 @@ +// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx + +using DbEx.Migration; +using System; +using System.Linq; + +namespace DbEx.SqlServer.Console +{ + /// + /// Provides extension methods for . + /// + public static class MigrationArgsExtensions + { + /// + /// Include the SQL Server extended Schema scripts (stored procedures and functions) from . + /// + /// The . + /// The to support fluent-style method-chaining. + public static MigrationArgs IncludeExtendedSchemaScripts(this MigrationArgs args) + { + AddExtendedSchemaScripts(args); + return args; + } + + /// + /// Adds the SQL Server extended Schema scripts (stored procedures and functions) from . + /// + /// The . + /// The . + public static void AddExtendedSchemaScripts(TArgs args) where TArgs : MigrationArgsBase + { + foreach (var rn in typeof(MigrationArgsExtensions).Assembly.GetManifestResourceNames().Where(x => x.StartsWith("DbEx.SqlServer.Resources.ExtendedSchema.") && x.EndsWith(".sql"))) + { + args.AddScript(MigrationCommand.Schema, typeof(MigrationArgsExtensions).Assembly, rn); + } + } + } +} \ No newline at end of file diff --git a/src/DbEx.SqlServer/DbEx.SqlServer.csproj b/src/DbEx.SqlServer/DbEx.SqlServer.csproj index b32acc3..9f36fb7 100644 --- a/src/DbEx.SqlServer/DbEx.SqlServer.csproj +++ b/src/DbEx.SqlServer/DbEx.SqlServer.csproj @@ -23,6 +23,18 @@ + + + + + + + + + + + + @@ -32,7 +44,7 @@ - + diff --git a/src/DbEx.SqlServer/Resources/ExtendedSchema/Functions/fnGetTenantId.sql b/src/DbEx.SqlServer/Resources/ExtendedSchema/Functions/fnGetTenantId.sql new file mode 100644 index 0000000..c1759b3 --- /dev/null +++ b/src/DbEx.SqlServer/Resources/ExtendedSchema/Functions/fnGetTenantId.sql @@ -0,0 +1,21 @@ +-- Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/Beef + +CREATE OR ALTER FUNCTION [dbo].[fnGetTenantId] +( + @Override as NVARCHAR(1024) = null +) +RETURNS NVARCHAR(1024) +AS +BEGIN + DECLARE @TenantId NVARCHAR(1024) + IF @Override IS NULL + BEGIN + SET @TenantId = CONVERT(NVARCHAR(1024), SESSION_CONTEXT(N'TenantId')); + END + ELSE + BEGIN + SET @TenantId = @Override + END + + RETURN @TenantId +END \ No newline at end of file diff --git a/src/DbEx.SqlServer/Resources/ExtendedSchema/Functions/fnGetTimestamp.sql b/src/DbEx.SqlServer/Resources/ExtendedSchema/Functions/fnGetTimestamp.sql new file mode 100644 index 0000000..2441c69 --- /dev/null +++ b/src/DbEx.SqlServer/Resources/ExtendedSchema/Functions/fnGetTimestamp.sql @@ -0,0 +1,25 @@ +-- Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/Beef + +CREATE OR ALTER FUNCTION [dbo].[fnGetTimestamp] +( + @Override as datetime = null +) +RETURNS datetime +AS +BEGIN + DECLARE @Timestamp datetime + IF @Override IS NULL + BEGIN + SET @Timestamp = CONVERT(datetime, SESSION_CONTEXT(N'Timestamp')); + IF @Timestamp IS NULL + BEGIN + SET @Timestamp = SYSUTCDATETIME() + END + END + ELSE + BEGIN + SET @Timestamp = @Override + END + + RETURN @Timestamp +END \ No newline at end of file diff --git a/src/DbEx.SqlServer/Resources/ExtendedSchema/Functions/fnGetUserId.sql b/src/DbEx.SqlServer/Resources/ExtendedSchema/Functions/fnGetUserId.sql new file mode 100644 index 0000000..6a8e2c7 --- /dev/null +++ b/src/DbEx.SqlServer/Resources/ExtendedSchema/Functions/fnGetUserId.sql @@ -0,0 +1,21 @@ +-- Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/Beef + +CREATE OR ALTER FUNCTION [dbo].[fnGetUserId] +( + @Override as NVARCHAR(1024) = null +) +RETURNS NVARCHAR(1024) +AS +BEGIN + DECLARE @UserId NVARCHAR(1024) + IF @Override IS NULL + BEGIN + SET @UserId = CONVERT(NVARCHAR(1024), SESSION_CONTEXT(N'UserId')); + END + ELSE + BEGIN + SET @UserId = @Override + END + + RETURN @UserId +END \ No newline at end of file diff --git a/src/DbEx.SqlServer/Resources/ExtendedSchema/Functions/fnGetUsername.sql b/src/DbEx.SqlServer/Resources/ExtendedSchema/Functions/fnGetUsername.sql new file mode 100644 index 0000000..f231c87 --- /dev/null +++ b/src/DbEx.SqlServer/Resources/ExtendedSchema/Functions/fnGetUsername.sql @@ -0,0 +1,25 @@ +-- Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/Beef + +CREATE OR ALTER FUNCTION [dbo].[fnGetUsername] +( + @Override AS NVARCHAR(1024) = null +) +RETURNS NVARCHAR(1024) +AS +BEGIN + DECLARE @Username NVARCHAR(1024) + IF @Override IS NULL + BEGIN + SET @Username = CONVERT(NVARCHAR(1024), SESSION_CONTEXT(N'Username')); + IF @Username IS NULL + BEGIN + SET @Username = SYSTEM_USER + END + END + ELSE + BEGIN + SET @Username = @Override + END + + RETURN @Username +END \ No newline at end of file diff --git a/src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spSetSessionContext.sql b/src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spSetSessionContext.sql new file mode 100644 index 0000000..318c215 --- /dev/null +++ b/src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spSetSessionContext.sql @@ -0,0 +1,29 @@ +-- Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx + +CREATE OR ALTER PROCEDURE [dbo].[spSetSessionContext] + @Timestamp DATETIME2 = null, + @Username NVARCHAR(1024) = null, + @TenantId NVARCHAR(1024) = null, + @UserId NVARCHAR(1024) = null +AS +BEGIN + IF @Timestamp IS NOT NULL + BEGIN + EXEC sp_set_session_context 'Timestamp', @Timestamp, @read_only = 1; + END + + IF @Username IS NOT NULL + BEGIN + EXEC sp_set_session_context 'Username', @Username, @read_only = 1; + END + + IF @TenantId IS NOT NULL + BEGIN + EXEC sp_set_session_context 'TenantId', @TenantId, @read_only = 1; + END + + IF @UserId IS NOT NULL + BEGIN + EXEC sp_set_session_context 'UserId', @UserId, @read_only = 1; + END +END \ No newline at end of file diff --git a/src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spThrowAuthorizationException.sql b/src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spThrowAuthorizationException.sql new file mode 100644 index 0000000..8c8c2f7 --- /dev/null +++ b/src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spThrowAuthorizationException.sql @@ -0,0 +1,9 @@ +-- Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx + +CREATE OR ALTER PROCEDURE [dbo].[spThrowAuthorizationException] + @Message NVARCHAR(2048) = NULL +AS +BEGIN + SET NOCOUNT ON; + THROW 56003, @Message, 1 +END \ No newline at end of file diff --git a/src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spThrowBusinessException.sql b/src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spThrowBusinessException.sql new file mode 100644 index 0000000..71d80ab --- /dev/null +++ b/src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spThrowBusinessException.sql @@ -0,0 +1,9 @@ +-- Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/Beef + +CREATE OR ALTER PROCEDURE [dbo].[spThrowBusinessException] + @Message NVARCHAR(2048) = NULL +AS +BEGIN + SET NOCOUNT ON; + THROW 56002, @Message, 1 +END \ No newline at end of file diff --git a/src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spThrowConcurrencyException.sql b/src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spThrowConcurrencyException.sql new file mode 100644 index 0000000..3f7c880 --- /dev/null +++ b/src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spThrowConcurrencyException.sql @@ -0,0 +1,9 @@ +-- Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/Beef + +CREATE OR ALTER PROCEDURE [dbo].[spThrowConcurrencyException] + @Message NVARCHAR(2048) = NULL +AS +BEGIN + SET NOCOUNT ON; + THROW 56004, @Message, 1 +END \ No newline at end of file diff --git a/src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spThrowConflictException.sql b/src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spThrowConflictException.sql new file mode 100644 index 0000000..57477a2 --- /dev/null +++ b/src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spThrowConflictException.sql @@ -0,0 +1,9 @@ +-- Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/Beef + +CREATE OR ALTER PROCEDURE [dbo].[spThrowConflictException] + @Message NVARCHAR(2048) = NULL +AS +BEGIN + SET NOCOUNT ON; + THROW 56006, @Message, 1 +END \ No newline at end of file diff --git a/src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spThrowDuplicateException.sql b/src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spThrowDuplicateException.sql new file mode 100644 index 0000000..3fa7852 --- /dev/null +++ b/src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spThrowDuplicateException.sql @@ -0,0 +1,9 @@ +-- Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/Beef + +CREATE OR ALTER PROCEDURE [dbo].[spThrowDuplicateException] + @Message NVARCHAR(2048) = NULL +AS +BEGIN + SET NOCOUNT ON; + THROW 56007, @Message, 1 +END \ No newline at end of file diff --git a/src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spThrowNotFoundException.sql b/src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spThrowNotFoundException.sql new file mode 100644 index 0000000..3cf33e1 --- /dev/null +++ b/src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spThrowNotFoundException.sql @@ -0,0 +1,9 @@ +-- Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/Beef + +CREATE OR ALTER PROCEDURE [dbo].[spThrowNotFoundException] + @Message NVARCHAR(2048) = NULL +AS +BEGIN + SET NOCOUNT ON; + THROW 56005, @Message, 1 +END \ No newline at end of file diff --git a/src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spThrowValidationException.sql b/src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spThrowValidationException.sql new file mode 100644 index 0000000..7c29d7a --- /dev/null +++ b/src/DbEx.SqlServer/Resources/ExtendedSchema/Stored Procedures/spThrowValidationException.sql @@ -0,0 +1,9 @@ +-- Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/Beef + +CREATE OR ALTER PROCEDURE [dbo].[spThrowValidationException] + @Message NVARCHAR(2048) = NULL +AS +BEGIN + SET NOCOUNT ON; + THROW 56001, @Message, 1 +END \ No newline at end of file diff --git a/src/DbEx/Console/MigrationConsoleBase.cs b/src/DbEx/Console/MigrationConsoleBase.cs index 2e1dc90..fc121ef 100644 --- a/src/DbEx/Console/MigrationConsoleBase.cs +++ b/src/DbEx/Console/MigrationConsoleBase.cs @@ -31,6 +31,7 @@ public abstract class MigrationConsoleBase private static readonly string[] memberNames = ["args"]; private const string EntryAssemblyOnlyOptionName = "entry-assembly-only"; private const string AcceptPromptsOptionName = "accept-prompts"; + private const string DropSchemaObjectsName = "drop-schema-objects"; private CommandArgument? _commandArg; private CommandArgument? _additionalArgs; private CommandOption? _helpOption; @@ -126,6 +127,7 @@ public async Task RunAsync(string[] args, CancellationToken cancellationTok ConsoleOptions.Add(nameof(MigrationArgsBase.Assemblies), app.Option("-a|--assembly", "Assembly containing embedded resources (multiple can be specified in probing order).", CommandOptionType.MultipleValue)); ConsoleOptions.Add(nameof(MigrationArgsBase.Parameters), app.Option("-p|--param", "Parameter expressed as a 'Name=Value' pair (multiple can be specified).", CommandOptionType.MultipleValue)); ConsoleOptions.Add(EntryAssemblyOnlyOptionName, app.Option("-eo|--entry-assembly-only", "Use the entry assembly only (ignore all other assemblies).", CommandOptionType.NoValue)); + ConsoleOptions.Add(DropSchemaObjectsName, app.Option("-dso|--drop-schema-objects", "Drop all known schema objects before applying; bypasses automatic skip where all scripts are replacements.", CommandOptionType.NoValue)); ConsoleOptions.Add(AcceptPromptsOptionName, app.Option("--accept-prompts", "Accept prompts; command should _not_ stop and wait for user confirmation (DROP or RESET commands).", CommandOptionType.NoValue)); _additionalArgs = app.Argument("args", "Additional arguments; 'Script' arguments (first being the script name) -or- 'Execute' (each a SQL statement to invoke).", multipleValues: true); @@ -212,6 +214,11 @@ public async Task RunAsync(string[] args, CancellationToken cancellationTok } } + // Handle drop schema objects. + var dso = GetCommandOption(DropSchemaObjectsName); + if (dso is not null && dso.HasValue()) + Args.DropSchemaObjects = true; + return res; }); diff --git a/src/DbEx/DatabaseSchemaConfig.cs b/src/DbEx/DatabaseSchemaConfig.cs index 7317aa0..75aad56 100644 --- a/src/DbEx/DatabaseSchemaConfig.cs +++ b/src/DbEx/DatabaseSchemaConfig.cs @@ -122,6 +122,10 @@ public virtual void PrepareMigrationArgs() Migration.Args.IsDeletedColumnName ??= IsDeletedColumnName; Migration.Args.RefDataCodeColumnName ??= RefDataCodeColumnName; Migration.Args.RefDataTextColumnName ??= RefDataTextColumnName; + + // Where the database has a default schema then this should be ordered first where not already set. + if (!string.IsNullOrEmpty(DefaultSchema) && !Migration.Args.SchemaOrder.Contains(DefaultSchema)) + Migration.Args.SchemaOrder.Insert(0, DefaultSchema); } /// diff --git a/src/DbEx/DbEx.csproj b/src/DbEx/DbEx.csproj index 2fd30b0..db4c99c 100644 --- a/src/DbEx/DbEx.csproj +++ b/src/DbEx/DbEx.csproj @@ -20,8 +20,8 @@ - - + + diff --git a/src/DbEx/Migration/DatabaseMigrationBase.cs b/src/DbEx/Migration/DatabaseMigrationBase.cs index b60e977..852da3e 100644 --- a/src/DbEx/Migration/DatabaseMigrationBase.cs +++ b/src/DbEx/Migration/DatabaseMigrationBase.cs @@ -467,6 +467,21 @@ private async Task DatabaseMigrateAsync(CancellationToken cancellationToke { Logger.LogInformation("{Content}", $" Probing for embedded resources: {string.Join(", ", GetNamespacesWithSuffix($"{MigrationsNamespace}.*.sql"))}"); + // Function to add the script in a consistent manner. + void AddScript(List scripts, Assembly assembly, string name) + { + // A name should be unique; always use the first version. + if (scripts.Any(x => x.Name == name)) + return; + + // Determine run order and add script to list. + var order = name.EndsWith(".pre.deploy.sql", StringComparison.InvariantCultureIgnoreCase) ? 1 : + name.EndsWith(".post.deploy.sql", StringComparison.InvariantCultureIgnoreCase) ? 3 : 2; + + scripts.Add(new DatabaseMigrationScript(this, assembly, name) { GroupOrder = order, RunAlways = order != 2 }); + }; + + // Get all the resources and their included scripts from the assemblies. var scripts = new List(); foreach (var ass in Args.ProbeAssemblies) { @@ -476,14 +491,16 @@ private async Task DatabaseMigrateAsync(CancellationToken cancellationToke if (name.EndsWith($".{OnDatabaseCreateName}.sql", StringComparison.InvariantCultureIgnoreCase)) continue; - // Determine run order and add script to list. - var order = name.EndsWith(".pre.deploy.sql", StringComparison.InvariantCultureIgnoreCase) ? 1 : - name.EndsWith(".post.deploy.sql", StringComparison.InvariantCultureIgnoreCase) ? 3 : 2; - - scripts.Add(new DatabaseMigrationScript(this, ass.Assembly, name) { GroupOrder = order, RunAlways = order != 2 }); + AddScript(scripts, ass.Assembly, name); } } + // Include any explicitly named scripts. + foreach (var s in Args.Scripts.Where(x => x.Command == MigrationCommand.Migrate)) + { + AddScript(scripts, s.Assembly, s.Name); + } + if (scripts.Count == 0) { Logger.LogInformation("{Content}", NothingFoundText); @@ -552,6 +569,15 @@ private async Task DatabaseSchemaAsync(CancellationToken cancellationToken } } + // Include any explicitly named scripts. + foreach (var ss in Args.Scripts.Where(x => x.Command == MigrationCommand.Schema)) + { + if (scripts.Any(x => x.Name == ss.Name)) + continue; + + scripts.Add(new DatabaseMigrationScript(this, ss.Assembly, ss.Name)); + } + // Make sure there is work to be done. if (scripts.Count == 0) { @@ -613,7 +639,7 @@ protected virtual async Task DatabaseSchemaAsync(List x.SchemaOrder).ThenBy(x => x.TypeOrder).ThenBy(x => x.Schema).ThenBy(x => x.Name)) { var migrationScript = sor.MigrationScript; diff --git a/src/DbEx/Migration/MigrationArgsBase.cs b/src/DbEx/Migration/MigrationArgsBase.cs index 7a80254..0f0d644 100644 --- a/src/DbEx/Migration/MigrationArgsBase.cs +++ b/src/DbEx/Migration/MigrationArgsBase.cs @@ -1,5 +1,7 @@ // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx +global using ExplicitMigrationScript = (DbEx.MigrationCommand Command, System.Reflection.Assembly Assembly, string Name); + using CoreEx; using CoreEx.Entities; using CoreEx.RefData; @@ -19,6 +21,7 @@ namespace DbEx.Migration public abstract class MigrationArgsBase : OnRamp.CodeGeneratorDbArgsBase { private readonly List _assemblies = [new MigrationAssemblyArgs(typeof(MigrationArgsBase).Assembly)]; + private readonly HashSet _scripts = []; /// /// Gets the name. @@ -55,6 +58,11 @@ public abstract class MigrationArgsBase : OnRamp.CodeGeneratorDbArgsBase /// public IEnumerable ProbeAssemblies => Assemblies.Reverse(); + /// + /// Gets the list of explicitly named resource-based scripts to be included (executed) as per the specified (phase). + /// + public IEnumerable Scripts => _scripts; + /// /// Gets the runtime parameters. /// @@ -168,7 +176,7 @@ public abstract class MigrationArgsBase : OnRamp.CodeGeneratorDbArgsBase public bool AcceptPrompts { get; set; } /// - /// Indicates whether to drop all the known schema objects before creating them. + /// Indicates whether to drop all the known schema objects before creating/replacing them. /// public bool DropSchemaObjects { get; set; } @@ -283,6 +291,8 @@ protected void CopyFrom(MigrationArgsBase args) MigrationCommand = args.MigrationCommand; _assemblies.Clear(); _assemblies.AddRange(args.Assemblies); + _scripts.Clear(); + args.Scripts.ForEach(x => _scripts.Add(x)); Parameters.Clear(); args.Parameters.ForEach(x => Parameters.Add(x.Key, x.Value)); Logger = args.Logger; @@ -329,5 +339,22 @@ protected void CopyFrom(MigrationArgsBase args) return dict; } + + /// + /// Adds a being an explicitly named resource-based script to be included (executed) as per the specified (phase). + /// + /// The (phase) where the script should be executed. + /// The where the script resource resides. + /// The corresponding resource name within the . + /// The must be a single value; currently only and are supported. This represents the phase in which the script will be + /// included for execution. + public void AddScript(MigrationCommand command, Assembly assembly, string name) + { + if (command != MigrationCommand.Migrate && command != MigrationCommand.Schema) + throw new ArgumentException($"The migration script command '{command}' is not supported; currently only '{MigrationCommand.Migrate}' and '{MigrationCommand.Schema}' are supported.", nameof(command)); + + _ = assembly.ThrowIfNull().GetManifestResourceInfo(name.ThrowIfNullOrEmpty()) ?? throw new ArgumentException($"The migration script resource '{name}' does not exist within the assembly '{assembly.FullName}'.", nameof(name)); + _scripts.Add((command, assembly, name)); + } } } \ No newline at end of file diff --git a/src/DbEx/Migration/MigrationArgsBaseT.cs b/src/DbEx/Migration/MigrationArgsBaseT.cs index 414f25d..715a15a 100644 --- a/src/DbEx/Migration/MigrationArgsBaseT.cs +++ b/src/DbEx/Migration/MigrationArgsBaseT.cs @@ -80,5 +80,29 @@ public TSelf AddSchemaOrder(params string[] schemas) SchemaOrder.AddRange(schemas); return (TSelf)this; } + + /// + /// Adds a being an explicitly named resource-based script to be included (executed) as per the specified (phase). + /// + /// The (phase) where the script should be executed. + /// The where the script resource resides. + /// The corresponding resource name within the . + /// The must be a single value; currently only and are supported. This represents the phase in which the script will be + /// included for execution. + public new TSelf AddScript(MigrationCommand command, Assembly assembly, string name) + { + base.AddScript(command, assembly, name); + return (TSelf)this; + } + + /// + /// Adds a being an explicitly named resource-based script to be included (executed) as per the specified (phase). + /// + /// The to use to infer the underlying where the script resource resides. + /// The (phase) where the script should be executed. + /// The corresponding resource name within the . + /// The must be a single value; currently only and are supported. This represents the phase in which the script will be + /// included for execution. + public TSelf AddScript(MigrationCommand command, string name) => AddScript(command, typeof(TAssembly).Assembly, name); } } \ No newline at end of file diff --git a/tests/DbEx.Test.Console/Program.cs b/tests/DbEx.Test.Console/Program.cs index ba441f8..67fcc71 100644 --- a/tests/DbEx.Test.Console/Program.cs +++ b/tests/DbEx.Test.Console/Program.cs @@ -11,6 +11,7 @@ internal static Task Main(string[] args) => SqlServerMigrationConsole { c.Args.AddAssembly("Data", "Data2"); c.Args.AddSchemaOrder("Test", "Outbox"); + c.Args.IncludeExtendedSchemaScripts(); c.Args.DataParserArgs.Parameter("DefaultName", "Bazza") .RefDataColumnDefault("SortOrder", i => i) .ColumnDefault("*", "*", "TenantId", _ => "test-tenant") diff --git a/tests/DbEx.Test.Console/Resources/Table_sql.hb b/tests/DbEx.Test.Console/Resources/Table_sql.hb index c3505dc..cd861aa 100644 --- a/tests/DbEx.Test.Console/Resources/Table_sql.hb +++ b/tests/DbEx.Test.Console/Resources/Table_sql.hb @@ -1,4 +1,4 @@ -{{! Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/Beef }} +{{! Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx }} {{! PARAM:Param1=Schema }} {{! PARAM:Param2=Table }} {{! FILENAME:create-[lookup Parameters 'Param1']-[lookup Parameters 'Param2']-table }} diff --git a/tests/DbEx.Test/DatabaseSchemaTest.cs b/tests/DbEx.Test/DatabaseSchemaTest.cs index 30d8e4c..8e20208 100644 --- a/tests/DbEx.Test/DatabaseSchemaTest.cs +++ b/tests/DbEx.Test/DatabaseSchemaTest.cs @@ -12,6 +12,7 @@ using NUnit.Framework; using System.Linq; using System.Threading.Tasks; +using Assert = NUnit.Framework.Legacy.ClassicAssert; namespace DbEx.Test { diff --git a/tests/DbEx.Test/DbEx.Test.csproj b/tests/DbEx.Test/DbEx.Test.csproj index 1ef4de5..e305a42 100644 --- a/tests/DbEx.Test/DbEx.Test.csproj +++ b/tests/DbEx.Test/DbEx.Test.csproj @@ -11,8 +11,8 @@ - - + + all diff --git a/tests/DbEx.Test/MySqlMigrationTest.cs b/tests/DbEx.Test/MySqlMigrationTest.cs index 49ddda5..229833b 100644 --- a/tests/DbEx.Test/MySqlMigrationTest.cs +++ b/tests/DbEx.Test/MySqlMigrationTest.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.Configuration; using NUnit.Framework; using System.Threading.Tasks; +using Assert = NUnit.Framework.Legacy.ClassicAssert; namespace DbEx.Test { diff --git a/tests/DbEx.Test/PostgresMigrationTest.cs b/tests/DbEx.Test/PostgresMigrationTest.cs index 74794b6..f1cc6ef 100644 --- a/tests/DbEx.Test/PostgresMigrationTest.cs +++ b/tests/DbEx.Test/PostgresMigrationTest.cs @@ -1,9 +1,15 @@ -using DbEx.Migration; +using CoreEx; +using CoreEx.Database; +using CoreEx.Database.Postgres; +using DbEx.Migration; +using DbEx.Postgres.Console; using DbEx.Postgres.Migration; using DbEx.Test.PostgresConsole; using Microsoft.Extensions.Configuration; using NUnit.Framework; +using System; using System.Threading.Tasks; +using Assert = NUnit.Framework.Legacy.ClassicAssert; namespace DbEx.Test { @@ -16,7 +22,7 @@ public async Task A120_MigrateAll() { var cs = UnitTest.GetConfig("DbEx_").GetConnectionString("PostgresDb"); var l = UnitTest.GetLogger(); - var a = new MigrationArgs(MigrationCommand.DropAndAll, cs) { Logger = l }.AddAssembly(); + var a = new MigrationArgs(MigrationCommand.DropAndAll, cs) { Logger = l }.AddAssembly().IncludeExtendedSchemaScripts(); using var m = new PostgresMigration(a); var r = await m.MigrateAsync().ConfigureAwait(false); Assert.IsTrue(r); @@ -40,5 +46,50 @@ public async Task A120_MigrateReset() r = await m2.MigrateAsync().ConfigureAwait(false); Assert.IsTrue(r); } + + [Test] + public async Task B110_Throw_Exceptions() + { + await A120_MigrateAll(); + + var cs = UnitTest.GetConfig("DbEx_").GetConnectionString("PostgresDb"); + using var db = new PostgresDatabase(() => new Npgsql.NpgsqlConnection(cs)); + + Assert.ThrowsAsync(() => db.StoredProcedure("sp_throw_authorization_exception").Param("message", null).NonQueryAsync()); + Assert.ThrowsAsync(() => db.StoredProcedure("sp_throw_business_exception").Param("message", null).NonQueryAsync()); + Assert.ThrowsAsync(() => db.StoredProcedure("sp_throw_concurrency_exception").Param("message", null).NonQueryAsync()); + Assert.ThrowsAsync(() => db.StoredProcedure("sp_throw_conflict_exception").Param("message", null).NonQueryAsync()); + Assert.ThrowsAsync(() => db.StoredProcedure("sp_throw_duplicate_exception").Param("message", null).NonQueryAsync()); + Assert.ThrowsAsync(() => db.StoredProcedure("sp_throw_not_found_exception").Param("message", null).NonQueryAsync()); + Assert.ThrowsAsync(() => db.StoredProcedure("sp_throw_validation_exception").Param("message", null).NonQueryAsync()); + var vex = Assert.ThrowsAsync(() => db.StoredProcedure("sp_throw_validation_exception").Param("message", "On no!").NonQueryAsync()); + Assert.AreEqual("On no!", vex.Message); + } + + [Test] + public async Task B120_Set_Session_Context() + { + await A120_MigrateAll(); + + var cs = UnitTest.GetConfig("DbEx_").GetConnectionString("PostgresDb"); + using var db = new PostgresDatabase(() => new Npgsql.NpgsqlConnection(cs)); + + var now = DateTime.UtcNow; + var ts = new DateTime(2024, 09, 30, 23, 45, 08, 123, DateTimeKind.Utc); + + await db.SetPostgresSessionContextAsync("bob@gmail.com", ts, "banana", "bob2"); + + Assert.That(await db.SqlStatement("select fn_get_timestamp()").ScalarAsync(), Is.EqualTo(ts)); + Assert.That(await db.SqlStatement("select fn_get_username()").ScalarAsync(), Is.EqualTo("bob@gmail.com")); + Assert.That(await db.SqlStatement("select fn_get_tenant_id()").ScalarAsync(), Is.EqualTo("banana")); + Assert.That(await db.SqlStatement("select fn_get_user_id()").ScalarAsync(), Is.EqualTo("bob2")); + + // Make sure the session context doesn't leak between connections. + using var db2 = new PostgresDatabase(() => new Npgsql.NpgsqlConnection(cs)); + Assert.That(await db2.SqlStatement("select fn_get_timestamp()").ScalarAsync(), Is.GreaterThanOrEqualTo(now)); + Assert.That(await db2.SqlStatement("select fn_get_username()").ScalarAsync(), Is.Not.Null.And.Not.EqualTo("bob@gmail.com")); + Assert.That(await db2.SqlStatement("select fn_get_tenant_id()").ScalarAsync(), Is.Null); + Assert.That(await db2.SqlStatement("select fn_get_user_id()").ScalarAsync(), Is.Null); + } } } \ No newline at end of file diff --git a/tests/DbEx.Test/SqlServerMigrationTest.cs b/tests/DbEx.Test/SqlServerMigrationTest.cs index 3f3f292..3024c17 100644 --- a/tests/DbEx.Test/SqlServerMigrationTest.cs +++ b/tests/DbEx.Test/SqlServerMigrationTest.cs @@ -1,7 +1,10 @@ -using CoreEx.Database; +using CoreEx; +using CoreEx.Database; +using CoreEx.Database.Postgres; using CoreEx.Database.SqlServer; using DbEx.Migration; using DbEx.Migration.Data; +using DbEx.SqlServer.Console; using DbEx.SqlServer.Migration; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Configuration; @@ -10,6 +13,7 @@ using System; using System.Linq; using System.Threading.Tasks; +using Assert = NUnit.Framework.Legacy.ClassicAssert; namespace DbEx.Test { @@ -169,7 +173,7 @@ public async Task A130_MigrateAll_Console() { var cs = UnitTest.GetConfig("DbEx_").GetConnectionString("ConsoleDb"); var l = UnitTest.GetLogger(); - var a = new MigrationArgs(MigrationCommand.DropAndAll, cs) { Logger = l }.AddAssembly(typeof(Console.Program)); + var a = new MigrationArgs(MigrationCommand.DropAndAll, cs) { Logger = l }.AddAssembly(typeof(Console.Program)).IncludeExtendedSchemaScripts(); using var m = new SqlServerMigration(a); m.Args.DataParserArgs.Parameters.Add("DefaultName", "Bazza"); @@ -185,6 +189,47 @@ public async Task A130_MigrateAll_Console() return (cs, l, m); } + [Test] + public async Task C110_Throw_Exceptions() + { + var (cs, l, m) = await CreateConsoleDb().ConfigureAwait(false); + using var db = new SqlServerDatabase(() => new SqlConnection(cs)); + + Assert.ThrowsAsync(() => db.StoredProcedure("spThrowAuthorizationException").Param("message", null).NonQueryAsync()); + Assert.ThrowsAsync(() => db.StoredProcedure("spThrowBusinessException").Param("message", null).NonQueryAsync()); + Assert.ThrowsAsync(() => db.StoredProcedure("spThrowConcurrencyException").Param("message", null).NonQueryAsync()); + Assert.ThrowsAsync(() => db.StoredProcedure("spThrowConflictException").Param("message", null).NonQueryAsync()); + Assert.ThrowsAsync(() => db.StoredProcedure("spThrowDuplicateException").Param("message", null).NonQueryAsync()); + Assert.ThrowsAsync(() => db.StoredProcedure("spThrowNotFoundException").Param("message", null).NonQueryAsync()); + Assert.ThrowsAsync(() => db.StoredProcedure("spThrowValidationException").Param("message", null).NonQueryAsync()); + var vex = Assert.ThrowsAsync(() => db.StoredProcedure("spThrowValidationException").Param("message", "On no!").NonQueryAsync()); + Assert.AreEqual("On no!", vex.Message); + } + + [Test] + public async Task C120_Set_Session_Context() + { + var (cs, l, m) = await CreateConsoleDb().ConfigureAwait(false); + using var db = new SqlServerDatabase(() => new SqlConnection(cs)); + + var now = DateTime.UtcNow; + var ts = new DateTime(2024, 09, 30, 23, 45, 08, 123, DateTimeKind.Utc); + + await db.SetSqlSessionContextAsync("bob@gmail.com", ts, "banana", "bob2"); + + Assert.That(await db.SqlStatement("select dbo.fnGetTimestamp(null)").ScalarAsync(), Is.EqualTo(ts)); + Assert.That(await db.SqlStatement("select dbo.fnGetUsername(null)").ScalarAsync(), Is.EqualTo("bob@gmail.com")); + Assert.That(await db.SqlStatement("select dbo.fnGetTenantId(null)").ScalarAsync(), Is.EqualTo("banana")); + Assert.That(await db.SqlStatement("select dbo.fnGetUserId(null)").ScalarAsync(), Is.EqualTo("bob2")); + + // Make sure the session context doesn't leak between connections. + using var db2 = new SqlServerDatabase(() => new SqlConnection(cs)); + Assert.That(await db2.SqlStatement("select dbo.fnGetTimestamp(null)").ScalarAsync(), Is.GreaterThanOrEqualTo(now)); + Assert.That(await db2.SqlStatement("select dbo.fnGetUsername(null)").ScalarAsync(), Is.Not.Null.And.Not.EqualTo("bob@gmail.com")); + Assert.That(await db2.SqlStatement("select dbo.fnGetTenantId(null)").ScalarAsync(), Is.Null); + Assert.That(await db2.SqlStatement("select dbo.fnGetUserId(null)").ScalarAsync(), Is.Null); + } + [Test] public async Task A140_Reset_None() { diff --git a/tests/DbEx.Test/SqlServerOutboxTest.cs b/tests/DbEx.Test/SqlServerOutboxTest.cs index 834f496..836de35 100644 --- a/tests/DbEx.Test/SqlServerOutboxTest.cs +++ b/tests/DbEx.Test/SqlServerOutboxTest.cs @@ -11,6 +11,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Assert = NUnit.Framework.Legacy.ClassicAssert; namespace DbEx.Test { From 2029f4445077f53480a943db76317d51fbe1ad27 Mon Sep 17 00:00:00 2001 From: Eric Sibly Date: Fri, 11 Oct 2024 16:10:16 -0700 Subject: [PATCH 2/2] Fix test error. --- src/DbEx/DatabaseSchemaConfig.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DbEx/DatabaseSchemaConfig.cs b/src/DbEx/DatabaseSchemaConfig.cs index 75aad56..50b1e69 100644 --- a/src/DbEx/DatabaseSchemaConfig.cs +++ b/src/DbEx/DatabaseSchemaConfig.cs @@ -124,7 +124,7 @@ public virtual void PrepareMigrationArgs() Migration.Args.RefDataTextColumnName ??= RefDataTextColumnName; // Where the database has a default schema then this should be ordered first where not already set. - if (!string.IsNullOrEmpty(DefaultSchema) && !Migration.Args.SchemaOrder.Contains(DefaultSchema)) + if (SupportsSchema && !string.IsNullOrEmpty(DefaultSchema) && !Migration.Args.SchemaOrder.Contains(DefaultSchema)) Migration.Args.SchemaOrder.Insert(0, DefaultSchema); }