From 62b9f1f42c25d1f2b7895515eb97312cd4b9e5b4 Mon Sep 17 00:00:00 2001 From: Robert Wagner Date: Wed, 24 Jan 2024 14:16:33 +1000 Subject: [PATCH] Copy from main project --- .github/workflows/main.yml | 4 +- ...pportTests.VerifyBasicSupport.verified.txt | 28 +++++++++ ...yJournalCreationIfNameChanged.verified.txt | 28 +++++++++ ...s.VerifyVariableSubstitutions.verified.txt | 28 +++++++++ .../NoPublicApiChanges.Run.Net.verified.cs | 38 ++++++++++++ src/Tests/DatabaseSupportTests.cs | 20 +++++++ src/Tests/NoPublicApiChanges.cs | 11 ++++ src/Tests/Tests.csproj | 2 +- .../SqlAnywhereConnectionManager.cs | 29 ++++++++++ src/dbup-sqlanywhere/SqlAnywhereExtensions.cs | 50 ++++++++++++++++ .../SqlAnywhereObjectParser.cs | 47 +++++++++++++++ .../SqlAnywhereScriptExecutor.cs | 51 ++++++++++++++++ .../SqlAnywhereSqlPreprocessor.cs | 15 +++++ .../SqlAnywhereTableJournal.cs | 58 +++++++++++++++++++ src/dbup-sqlanywhere/dbup-sqlanywhere.csproj | 8 +-- 15 files changed, 410 insertions(+), 7 deletions(-) create mode 100644 src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyBasicSupport.verified.txt create mode 100644 src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyJournalCreationIfNameChanged.verified.txt create mode 100644 src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyVariableSubstitutions.verified.txt create mode 100644 src/Tests/ApprovalFiles/NoPublicApiChanges.Run.Net.verified.cs create mode 100644 src/Tests/DatabaseSupportTests.cs create mode 100644 src/Tests/NoPublicApiChanges.cs create mode 100644 src/dbup-sqlanywhere/SqlAnywhereConnectionManager.cs create mode 100644 src/dbup-sqlanywhere/SqlAnywhereExtensions.cs create mode 100644 src/dbup-sqlanywhere/SqlAnywhereObjectParser.cs create mode 100644 src/dbup-sqlanywhere/SqlAnywhereScriptExecutor.cs create mode 100644 src/dbup-sqlanywhere/SqlAnywhereSqlPreprocessor.cs create mode 100644 src/dbup-sqlanywhere/SqlAnywhereTableJournal.cs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 92f3e92..18198a1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,8 +1,8 @@ name: CI on: - #push: - #pull_request: + push: + pull_request: workflow_dispatch: jobs: diff --git a/src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyBasicSupport.verified.txt b/src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyBasicSupport.verified.txt new file mode 100644 index 0000000..b2f7c2d --- /dev/null +++ b/src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyBasicSupport.verified.txt @@ -0,0 +1,28 @@ +DB Operation: Open connection +Info: Beginning database upgrade +Info: Checking whether journal table exists.. +DB Operation: Execute scalar command: select cast(1 as Int) from systab t where t.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.. +DB Operation: Execute scalar command: select cast(1 as Int) from systab t where t.table_name = 'SCHEMAVERSIONS' +DB Operation: Dispose command +Info: Creating the [schemaversions] table +DB Operation: Execute non query command: create table [schemaversions] ( + [Id] int identity not null constraint [PK_schemaversions_Id] primary key, + [ScriptName] nvarchar(255) not null, + [Applied] datetime not null + ) +DB Operation: Dispose command +Info: The [schemaversions] table has been created +DB Operation: Execute non query command: script1contents +DB Operation: Dispose command +DB Operation: Create parameter +Info: DB Operation: Add parameter to command: scriptName=Script0001.sql +DB Operation: Create parameter +Info: DB Operation: Add parameter to command: applied= +DB Operation: Execute non query command: insert into [schemaversions] (ScriptName, Applied) values (? ,?) +DB Operation: Dispose command +Info: Upgrade successful +DB Operation: Dispose connection diff --git a/src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyJournalCreationIfNameChanged.verified.txt b/src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyJournalCreationIfNameChanged.verified.txt new file mode 100644 index 0000000..42ff5ef --- /dev/null +++ b/src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyJournalCreationIfNameChanged.verified.txt @@ -0,0 +1,28 @@ +DB Operation: Open connection +Info: Beginning database upgrade +Info: Checking whether journal table exists.. +DB Operation: Execute scalar command: select cast(1 as Int) from systab t where t.table_name = 'TESTSCHEMAVERSIONS' +DB Operation: Dispose command +Info: Journal table does not exist +Info: Executing Database Server script 'Script0001.sql' +Info: Checking whether journal table exists.. +DB Operation: Execute scalar command: select cast(1 as Int) from systab t where t.table_name = 'TESTSCHEMAVERSIONS' +DB Operation: Dispose command +Info: Creating the [test].[TestSchemaVersions] table +DB Operation: Execute non query command: create table [test].[TestSchemaVersions] ( + [Id] int identity not null constraint [PK_TestSchemaVersions_Id] primary key, + [ScriptName] nvarchar(255) not null, + [Applied] datetime not null + ) +DB Operation: Dispose command +Info: The [test].[TestSchemaVersions] table has been created +DB Operation: Execute non query command: script1contents +DB Operation: Dispose command +DB Operation: Create parameter +Info: DB Operation: Add parameter to command: scriptName=Script0001.sql +DB Operation: Create parameter +Info: DB Operation: Add parameter to command: applied= +DB Operation: Execute non query command: insert into [test].[TestSchemaVersions] (ScriptName, Applied) values (? ,?) +DB Operation: Dispose command +Info: Upgrade successful +DB Operation: Dispose connection diff --git a/src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyVariableSubstitutions.verified.txt b/src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyVariableSubstitutions.verified.txt new file mode 100644 index 0000000..0ca7c02 --- /dev/null +++ b/src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyVariableSubstitutions.verified.txt @@ -0,0 +1,28 @@ +DB Operation: Open connection +Info: Beginning database upgrade +Info: Checking whether journal table exists.. +DB Operation: Execute scalar command: select cast(1 as Int) from systab t where t.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.. +DB Operation: Execute scalar command: select cast(1 as Int) from systab t where t.table_name = 'SCHEMAVERSIONS' +DB Operation: Dispose command +Info: Creating the [schemaversions] table +DB Operation: Execute non query command: create table [schemaversions] ( + [Id] int identity not null constraint [PK_schemaversions_Id] primary key, + [ScriptName] nvarchar(255) not null, + [Applied] datetime not null + ) +DB Operation: Dispose command +Info: The [schemaversions] table has been created +DB Operation: Execute non query command: print SubstitutedValue +DB Operation: Dispose command +DB Operation: Create parameter +Info: DB Operation: Add parameter to command: scriptName=Script0001.sql +DB Operation: Create parameter +Info: DB Operation: Add parameter to command: applied= +DB Operation: Execute non query command: insert into [schemaversions] (ScriptName, Applied) values (? ,?) +DB Operation: Dispose command +Info: Upgrade successful +DB Operation: Dispose connection diff --git a/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.Net.verified.cs b/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.Net.verified.cs new file mode 100644 index 0000000..f8573fb --- /dev/null +++ b/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.Net.verified.cs @@ -0,0 +1,38 @@ + +public static class SqlAnywhereExtensions +{ + public static DbUp.Builder.UpgradeEngineBuilder SqlAnywhereDatabase(this DbUp.Builder.SupportedDatabases supported, string connectionString) { } +} +namespace DbUp.SqlAnywhere +{ + public class SqlAnywhereConnectionManager : DbUp.Engine.Transactions.DatabaseConnectionManager, DbUp.Engine.Transactions.IConnectionManager + { + public SqlAnywhereConnectionManager(string connectionString) { } + public override System.Collections.Generic.IEnumerable SplitScriptIntoCommands(string scriptContents) { } + } + public class SqlAnywhereObjectParser : DbUp.Support.SqlObjectParser, DbUp.Engine.ISqlObjectParser + { + public SqlAnywhereObjectParser() { } + public override string QuoteIdentifier(string objectName, DbUp.Support.ObjectNameOptions objectNameOptions) { } + } + public class SqlAnywhereScriptExecutor : DbUp.Support.ScriptExecutor, DbUp.Engine.IScriptExecutor + { + public SqlAnywhereScriptExecutor(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 excuteCommand) { } + protected override string GetVerifySchemaSql(string schema) { } + } + public class SqlAnywhereSqlPreprocessor : DbUp.Engine.IScriptPreprocessor + { + public SqlAnywhereSqlPreprocessor() { } + public string Process(string contents) { } + } + public class SqlAnywhereTableJournal : DbUp.Support.TableJournal, DbUp.Engine.IJournal + { + public static System.Globalization.CultureInfo English; + public SqlAnywhereTableJournal(System.Func connectionManager, System.Func logger, string schema, string table) { } + protected override string CreateSchemaTableSql(string quotedPrimaryKeyName) { } + protected override string DoesTableExistSql() { } + protected override string GetInsertJournalEntrySql(string scriptName, string applied) { } + protected override string GetJournalEntriesSql() { } + } +} diff --git a/src/Tests/DatabaseSupportTests.cs b/src/Tests/DatabaseSupportTests.cs new file mode 100644 index 0000000..b52c050 --- /dev/null +++ b/src/Tests/DatabaseSupportTests.cs @@ -0,0 +1,20 @@ +using DbUp.Builder; +using DbUp.Tests.Common; + +namespace DbUp.SqlAnywhere.Tests; + +public class DatabaseSupportTests : DatabaseSupportTestsBase +{ + public DatabaseSupportTests() : base() + { + } + + protected override UpgradeEngineBuilder DeployTo(SupportedDatabases to) + => to.SqlAnywhereDatabase(""); + + protected override UpgradeEngineBuilder AddCustomNamedJournalToBuilder(UpgradeEngineBuilder builder, string schema, string tableName) + => builder.JournalTo( + (connectionManagerFactory, logFactory) + => new SqlAnywhereTableJournal(connectionManagerFactory, logFactory, schema, tableName) + ); +} diff --git a/src/Tests/NoPublicApiChanges.cs b/src/Tests/NoPublicApiChanges.cs new file mode 100644 index 0000000..d3bdb57 --- /dev/null +++ b/src/Tests/NoPublicApiChanges.cs @@ -0,0 +1,11 @@ +using DbUp.Tests.Common; + +namespace DbUp.SqlAnywhere.Tests; + +public class NoPublicApiChanges : NoPublicApiChangesBase +{ + public NoPublicApiChanges() + : base(typeof(SqlAnywhereExtensions).Assembly) + { + } +} diff --git a/src/Tests/Tests.csproj b/src/Tests/Tests.csproj index b039934..8110871 100644 --- a/src/Tests/Tests.csproj +++ b/src/Tests/Tests.csproj @@ -1,7 +1,7 @@ - net462 + net462 Tests DbUp.SqlAnywhere.Tests diff --git a/src/dbup-sqlanywhere/SqlAnywhereConnectionManager.cs b/src/dbup-sqlanywhere/SqlAnywhereConnectionManager.cs new file mode 100644 index 0000000..fe45e99 --- /dev/null +++ b/src/dbup-sqlanywhere/SqlAnywhereConnectionManager.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using DbUp.Engine.Transactions; +using Sap.Data.SQLAnywhere; + +namespace DbUp.SqlAnywhere +{ + public class SqlAnywhereConnectionManager : DatabaseConnectionManager + { + /// + /// Split text on ; or GO and ignore those between Being and End statements + /// + static readonly Regex splitOnCommaOrGoRegEx = new Regex(@"\s*(?:(? new SAConnection(connectionString)) + { + } + + public override IEnumerable SplitScriptIntoCommands(string scriptContents) + { + var stringSeparators = new string[] { "GO" }; + var parts = scriptContents.Split(stringSeparators, StringSplitOptions.RemoveEmptyEntries).ToList(); + return parts.Select(x => x.Trim()).Where(x => x.Length > 0).ToArray(); + } + } +} \ No newline at end of file diff --git a/src/dbup-sqlanywhere/SqlAnywhereExtensions.cs b/src/dbup-sqlanywhere/SqlAnywhereExtensions.cs new file mode 100644 index 0000000..e3055b5 --- /dev/null +++ b/src/dbup-sqlanywhere/SqlAnywhereExtensions.cs @@ -0,0 +1,50 @@ +using DbUp.Builder; +using DbUp.Engine.Transactions; +using DbUp.SqlAnywhere; + +/// +/// Configuration extension methods for SQL Anywhere. +/// +// ReSharper disable once CheckNamespace +public static class SqlAnywhereExtensions +{ + /// + /// Creates an upgrader for Sql Anywhere databases. + /// + /// Fluent helper type. + /// Sql Anywhere database connection string. + /// + /// A builder for a database upgrader designed for Sql Anywhere databases. + /// + public static UpgradeEngineBuilder SqlAnywhereDatabase(this SupportedDatabases supported, string connectionString) + { + var builder = new UpgradeEngineBuilder(); + builder.Configure(c => c.ConnectionManager = new SqlAnywhereConnectionManager(connectionString)); + builder.Configure(c => c.ScriptExecutor = new SqlAnywhereScriptExecutor(() => c.ConnectionManager, () => c.Log, null, () => c.VariablesEnabled, c.ScriptPreprocessors, () => c.Journal)); + builder.Configure(c => c.Journal = new SqlAnywhereTableJournal(() => c.ConnectionManager, () => c.Log, null, "schemaversions")); + return builder; + } + + /// + /// Creates an upgrader for SQL Server databases. + /// + /// The to be used during a database + /// upgrade. See for an example implementation + /// The SqlAnywhere schema name to use. Defaults to ''. + /// + /// A builder for a database upgrader designed for SqlAnywhere databases. + /// + static UpgradeEngineBuilder SqlAnywhereDatabase(IConnectionManager connectionManager, string schema) + { + var builder = new UpgradeEngineBuilder(); + builder.Configure(c => c.ConnectionManager = connectionManager); + builder.Configure(c => c.ScriptExecutor = new SqlAnywhereScriptExecutor(() => c.ConnectionManager, () => c.Log, schema, () => c.VariablesEnabled, c.ScriptPreprocessors, () => c.Journal)); + builder.Configure(c => c.Journal = new SqlAnywhereTableJournal(() => c.ConnectionManager, () => c.Log, schema, "SchemaVersions")); + return builder; + } + + + + + +} diff --git a/src/dbup-sqlanywhere/SqlAnywhereObjectParser.cs b/src/dbup-sqlanywhere/SqlAnywhereObjectParser.cs new file mode 100644 index 0000000..587342c --- /dev/null +++ b/src/dbup-sqlanywhere/SqlAnywhereObjectParser.cs @@ -0,0 +1,47 @@ +using System; +using System.Linq; +using DbUp.Support; + +namespace DbUp.SqlAnywhere +{ + /// + /// Parses Sql Objects and performs quoting functions + /// + public class SqlAnywhereObjectParser : SqlObjectParser + { + public SqlAnywhereObjectParser() : base("[", "]") + { + } + + /// + /// Quotes the SqlAnywhere identifier to allow Special characters in the object name. + /// This function implements System.Data.SqlClient.SqlCommandBuilder.QuoteIdentifier() with an additional + /// validation which is missing from the SqlCommandBuilder version. + /// + /// Name of the object to quote. + /// The settings which indicate if the whitespace should be dropped or not. + /// The quoted object name + public override string QuoteIdentifier(string objectName, ObjectNameOptions objectNameOptions) + { + if (string.IsNullOrEmpty(objectName)) + throw new ArgumentNullException(); + + + if (ObjectNameOptions.Trim == objectNameOptions) + objectName = objectName.Trim(); + + + const int SqlSysnameLength = 128; + if (objectName.Length > SqlSysnameLength) + throw new ArgumentOutOfRangeException(@"objectName", "A SqlAnywhere object name is maximum 128 characters long"); + + + // The ] in the string need to be doubled up so it means we always need an un-even number of ] + if (objectName.StartsWith("[") && objectName.EndsWith("]") && objectName.Count(x => x == ']') % 2 == 1) + return objectName; + + + return string.Concat("[", objectName.Replace("]", "]]"), "]"); + } + } +} diff --git a/src/dbup-sqlanywhere/SqlAnywhereScriptExecutor.cs b/src/dbup-sqlanywhere/SqlAnywhereScriptExecutor.cs new file mode 100644 index 0000000..b378d86 --- /dev/null +++ b/src/dbup-sqlanywhere/SqlAnywhereScriptExecutor.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using DbUp.Engine; +using DbUp.Engine.Output; +using DbUp.Engine.Transactions; +using DbUp.Support; + +namespace DbUp.SqlAnywhere +{ + /// + /// A standard implementation of the ScriptExecutor that executes against a SqlAnywhere + /// database. + /// + public class SqlAnywhereScriptExecutor : ScriptExecutor + { + /// + /// 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 SqlAnywhereScriptExecutor(Func connectionManagerFactory, Func log, string schema, Func variablesEnabled, + IEnumerable scriptPreprocessors, Func journalFactory) + : base(connectionManagerFactory, new SqlAnywhereObjectParser(), log, schema, variablesEnabled, scriptPreprocessors, journalFactory) + { + } + + protected override string GetVerifySchemaSql(string schema) + { + throw new NotSupportedException(); + } + + protected override void ExecuteCommandsWithinExceptionHandler(int index, SqlScript script, Action excuteCommand) + { + try + { + excuteCommand(); + } + catch (Sap.Data.SQLAnywhere.SAException sqlException) + { + Log().WriteInformation("SQLAnywhere exception has occured in script: '{0}'", script.Name); + Log().WriteError("Script block number: {0}; Message: {1}", index, sqlException.Message); + Log().WriteError(sqlException.ToString()); + throw; + } + } + } +} diff --git a/src/dbup-sqlanywhere/SqlAnywhereSqlPreprocessor.cs b/src/dbup-sqlanywhere/SqlAnywhereSqlPreprocessor.cs new file mode 100644 index 0000000..a287408 --- /dev/null +++ b/src/dbup-sqlanywhere/SqlAnywhereSqlPreprocessor.cs @@ -0,0 +1,15 @@ +using DbUp.Engine; + +namespace DbUp.SqlAnywhere +{ + /// + /// This preprocessor makes adjustments to your sql to make it compatible with Sql Anywhere. + /// + public class SqlAnywhereSqlPreprocessor : IScriptPreprocessor + { + public string Process(string contents) + { + return contents; + } + } +} \ No newline at end of file diff --git a/src/dbup-sqlanywhere/SqlAnywhereTableJournal.cs b/src/dbup-sqlanywhere/SqlAnywhereTableJournal.cs new file mode 100644 index 0000000..b284860 --- /dev/null +++ b/src/dbup-sqlanywhere/SqlAnywhereTableJournal.cs @@ -0,0 +1,58 @@ +using System; +using System.Globalization; +using DbUp.Engine.Output; +using DbUp.Engine.Transactions; +using DbUp.Support; + +namespace DbUp.SqlAnywhere +{ + /// + /// An implementation of the interface which tracks version numbers for a + /// Sql Anywhere database using a table called SchemaVersions. + /// + public class SqlAnywhereTableJournal : TableJournal + { + /// + /// Initializes a new instance of the class. + /// + /// The connection manager. + /// The log. + /// The schema that contains the table. + /// The table name. + /// + /// var journal = new TableJournal("Server=server;Database=database;Trusted_Connection=True", "dba", "MyVersionTable"); + /// + public SqlAnywhereTableJournal(Func connectionManager, Func logger, string schema, string table) + : base(connectionManager, logger, new SqlAnywhereObjectParser(), schema, table) + { + } + + public static CultureInfo English = new CultureInfo("en-UK", false); + + protected override string GetInsertJournalEntrySql(string @scriptName, string @applied) + { + return $"insert into {FqSchemaTableName} (ScriptName, Applied) values (? ,?)"; + } + + protected override string GetJournalEntriesSql() + { + return $"select [ScriptName] from {FqSchemaTableName} order by [ScriptName]"; + } + + protected override string CreateSchemaTableSql(string quotedPrimaryKeyName) + { + return + $@"create table {FqSchemaTableName} ( + [Id] int identity not null constraint {quotedPrimaryKeyName} primary key, + [ScriptName] nvarchar(255) not null, + [Applied] datetime not null + )"; + } + + protected override string DoesTableExistSql() + { + var unquotedSchemaTableName = UnquotedSchemaTableName.ToUpper(English); + return $"select cast(1 as Int) from systab t where t.table_name = '{unquotedSchemaTableName}'"; + } + } +} \ No newline at end of file diff --git a/src/dbup-sqlanywhere/dbup-sqlanywhere.csproj b/src/dbup-sqlanywhere/dbup-sqlanywhere.csproj index bfcabf5..6071e68 100644 --- a/src/dbup-sqlanywhere/dbup-sqlanywhere.csproj +++ b/src/dbup-sqlanywhere/dbup-sqlanywhere.csproj @@ -1,12 +1,12 @@ - + DbUp makes it easy to deploy and upgrade SQL Server databases. This package adds SqlAnywhere support. DbUp SqlAnywhere Support DbUp Contributors DbUp - Copyright © DbUp Contributors 2015 - net462 + Copyright � DbUp Contributors 2015 + net462 dbup-sqlanywhere DbUp.SqlAnywhere dbup-sqlanywhere @@ -18,8 +18,8 @@ + -