diff --git a/src/.editorconfig b/src/.editorconfig index 3f25e8c..dceccb2 100644 --- a/src/.editorconfig +++ b/src/.editorconfig @@ -121,3 +121,11 @@ csharp_preserve_single_line_blocks = true [*.vb] # Modifier preferences visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion +############################### +# XML and csproj # +############################### +[*.{xml,csproj}] +max_line_length = off +trim_trailing_whitespace = true +insert_final_newline = true +indent_size = 2 \ No newline at end of file diff --git a/src/Sample/Program.cs b/src/Sample/Program.cs new file mode 100644 index 0000000..9bcb7ee --- /dev/null +++ b/src/Sample/Program.cs @@ -0,0 +1,57 @@ +using System; +using System.Diagnostics; +using System.Reflection; +using Microsoft.Extensions.Configuration; +using DbUp; + +namespace FirebirdSampleApplication +{ + class Program + { + static int Main() + { + var config = GetConfig(); + string connectionString = config.GetConnectionString("SampleFirebird"); + + var upgrader = + DeployChanges.To + .FirebirdDatabase(connectionString) + .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly()) + .WithTransactionPerScript() + .LogToConsole() + .Build(); + + var result = upgrader.PerformUpgrade(); + + if (!result.Successful) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(result.Error); + Console.ResetColor(); + + WaitIfDebug(); + return -1; + } + + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("Success!"); + Console.ResetColor(); + WaitIfDebug(); + return 0; + } + + private static IConfiguration GetConfig() + { + IConfiguration config = new ConfigurationBuilder() + .AddJsonFile("appsettings.json", true, true) + .Build(); + return config; + } + + [Conditional("DEBUG")] + public static void WaitIfDebug() + { + Console.ReadLine(); + } + } +} diff --git a/src/Sample/Sample.csproj b/src/Sample/Sample.csproj new file mode 100644 index 0000000..be407ce --- /dev/null +++ b/src/Sample/Sample.csproj @@ -0,0 +1,22 @@ + + + net7.0 + Exe + false + + + + + + + + + + + + + + PreserveNewest + + + diff --git a/src/Sample/Scripts/Script0001.sql b/src/Sample/Scripts/Script0001.sql new file mode 100644 index 0000000..024d19e --- /dev/null +++ b/src/Sample/Scripts/Script0001.sql @@ -0,0 +1 @@ +CREATE TABLE Testing1 (ID integer NOT NULL) \ No newline at end of file diff --git a/src/Sample/Scripts/Script0002.sql b/src/Sample/Scripts/Script0002.sql new file mode 100644 index 0000000..b50f004 --- /dev/null +++ b/src/Sample/Scripts/Script0002.sql @@ -0,0 +1,4 @@ +CREATE TABLE Testing2 (ID integer NOT NULL) +; +CREATE TABLE Testing3 (ID integer NOT NULL) +; diff --git a/src/Sample/Scripts/Script0003.sql b/src/Sample/Scripts/Script0003.sql new file mode 100644 index 0000000..6dd7814 --- /dev/null +++ b/src/Sample/Scripts/Script0003.sql @@ -0,0 +1,4 @@ +CREATE TABLE Testing4 (ID integer NOT NULL) +; +CREATE TABLE Testing5 (ID integer NOT NULL) +; diff --git a/src/Sample/Scripts/Script0004.sql b/src/Sample/Scripts/Script0004.sql new file mode 100644 index 0000000..45bd4d5 --- /dev/null +++ b/src/Sample/Scripts/Script0004.sql @@ -0,0 +1,4 @@ +CREATE TABLE Testing6 (ID integer NOT NULL) +; +CREATE TABLE Testing7 (ID integer NOT NULL) +; diff --git a/src/Sample/appsettings.json b/src/Sample/appsettings.json new file mode 100644 index 0000000..b40a501 --- /dev/null +++ b/src/Sample/appsettings.json @@ -0,0 +1,5 @@ +{ + "ConnectionStrings": { + "SampleFirebird": "User=SOMEUSER;Password=SOMEPWD;Database=c:\\somedb.fdb;DataSource=SOMESERVERNAME;Port=SOMEPORT;Dialect=3;Charset=ISO8859_1;ServerType=0;Connection lifetime=15;Pooling=true;MinPoolSize=0;MaxPoolSize=50;" + } +} \ No newline at end of file diff --git a/src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyBasicSupport.verified.txt b/src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyBasicSupport.verified.txt new file mode 100644 index 0000000..dc21488 --- /dev/null +++ b/src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyBasicSupport.verified.txt @@ -0,0 +1,38 @@ +DB Operation: Open connection +Info: Beginning database upgrade +Info: Checking whether journal table exists.. +DB Operation: Execute scalar command: select 1 from RDB$RELATIONS where RDB$SYSTEM_FLAG = 0 and RDB$RELATION_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 1 from RDB$RELATIONS where RDB$SYSTEM_FLAG = 0 and RDB$RELATION_NAME = 'schemaversions' +DB Operation: Dispose command +Info: Creating the "schemaversions" table +DB Operation: Execute non query command: CREATE TABLE "schemaversions" +( + schemaversionsid INTEGER NOT NULL, + scriptname VARCHAR(255) NOT NULL, + applied TIMESTAMP NOT NULL, + CONSTRAINT pk_schemaversions_id PRIMARY KEY (schemaversionsid) +) +DB Operation: Dispose command +Info: The "schemaversions" table has been created +DB Operation: Execute non query command: CREATE SEQUENCE GEN_schemaversionsID +DB Operation: Dispose command +Info: The GEN_schemaversionsID generator has been created +DB Operation: Execute non query command: CREATE TRIGGER BI_schemaversionsID FOR "schemaversions" ACTIVE BEFORE INSERT POSITION 0 AS BEGIN + if (new.schemaversionsid is null or (new.schemaversionsid = 0)) then new.schemaversionsid = gen_id(GEN_schemaversionsID,1); +END; +DB Operation: Dispose command +Info: The BI_schemaversionsID trigger 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 (@scriptName, @applied) +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..c0696bf --- /dev/null +++ b/src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyJournalCreationIfNameChanged.verified.txt @@ -0,0 +1,38 @@ +DB Operation: Open connection +Info: Beginning database upgrade +Info: Checking whether journal table exists.. +DB Operation: Execute scalar command: select 1 from RDB$RELATIONS where RDB$SYSTEM_FLAG = 0 and RDB$RELATION_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 1 from RDB$RELATIONS where RDB$SYSTEM_FLAG = 0 and RDB$RELATION_NAME = 'TestSchemaVersions' +DB Operation: Dispose command +Info: Creating the "TestSchemaVersions" table +DB Operation: Execute non query command: CREATE TABLE "TestSchemaVersions" +( + schemaversionsid INTEGER NOT NULL, + scriptname VARCHAR(255) NOT NULL, + applied TIMESTAMP NOT NULL, + CONSTRAINT pk_TestSchemaVersions_id PRIMARY KEY (schemaversionsid) +) +DB Operation: Dispose command +Info: The "TestSchemaVersions" table has been created +DB Operation: Execute non query command: CREATE SEQUENCE GEN_TestSchemaVersionsID +DB Operation: Dispose command +Info: The GEN_TestSchemaVersionsID generator has been created +DB Operation: Execute non query command: CREATE TRIGGER BI_TestSchemaVersionsID FOR "TestSchemaVersions" ACTIVE BEFORE INSERT POSITION 0 AS BEGIN + if (new.schemaversionsid is null or (new.schemaversionsid = 0)) then new.schemaversionsid = gen_id(GEN_TestSchemaVersionsID,1); +END; +DB Operation: Dispose command +Info: The BI_TestSchemaVersionsID trigger 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 "TestSchemaVersions" (ScriptName, Applied) values (@scriptName, @applied) +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..1cbac5a --- /dev/null +++ b/src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyVariableSubstitutions.verified.txt @@ -0,0 +1,38 @@ +DB Operation: Open connection +Info: Beginning database upgrade +Info: Checking whether journal table exists.. +DB Operation: Execute scalar command: select 1 from RDB$RELATIONS where RDB$SYSTEM_FLAG = 0 and RDB$RELATION_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 1 from RDB$RELATIONS where RDB$SYSTEM_FLAG = 0 and RDB$RELATION_NAME = 'schemaversions' +DB Operation: Dispose command +Info: Creating the "schemaversions" table +DB Operation: Execute non query command: CREATE TABLE "schemaversions" +( + schemaversionsid INTEGER NOT NULL, + scriptname VARCHAR(255) NOT NULL, + applied TIMESTAMP NOT NULL, + CONSTRAINT pk_schemaversions_id PRIMARY KEY (schemaversionsid) +) +DB Operation: Dispose command +Info: The "schemaversions" table has been created +DB Operation: Execute non query command: CREATE SEQUENCE GEN_schemaversionsID +DB Operation: Dispose command +Info: The GEN_schemaversionsID generator has been created +DB Operation: Execute non query command: CREATE TRIGGER BI_schemaversionsID FOR "schemaversions" ACTIVE BEFORE INSERT POSITION 0 AS BEGIN + if (new.schemaversionsid is null or (new.schemaversionsid = 0)) then new.schemaversionsid = gen_id(GEN_schemaversionsID,1); +END; +DB Operation: Dispose command +Info: The BI_schemaversionsID trigger 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 (@scriptName, @applied) +DB Operation: Dispose command +Info: Upgrade successful +DB Operation: Dispose connection diff --git a/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.DotNet.verified.cs b/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.DotNet.verified.cs new file mode 100644 index 0000000..797159c --- /dev/null +++ b/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.DotNet.verified.cs @@ -0,0 +1,43 @@ +[assembly: System.CLSCompliantAttribute(true)] +[assembly: System.Runtime.InteropServices.ComVisibleAttribute(false)] + +public static class FirebirdExtensions +{ + public static DbUp.Builder.UpgradeEngineBuilder FirebirdDatabase(DbUp.Engine.Transactions.IConnectionManager connectionManager) { } + public static DbUp.Builder.UpgradeEngineBuilder FirebirdDatabase(this DbUp.Builder.SupportedDatabases supported, string connectionString) { } + public static DbUp.Builder.UpgradeEngineBuilder FirebirdDatabase(this DbUp.Builder.SupportedDatabases supported, DbUp.Engine.Transactions.IConnectionManager connectionManager) { } +} +namespace DbUp.Firebird +{ + public class FirebirdConnectionManager : DbUp.Engine.Transactions.DatabaseConnectionManager, DbUp.Engine.Transactions.IConnectionManager + { + public FirebirdConnectionManager(string connectionString) { } + protected override DbUp.Engine.Transactions.AllowedTransactionMode AllowedTransactionModes { get; } + public override System.Collections.Generic.IEnumerable SplitScriptIntoCommands(string scriptContents) { } + } + public class FirebirdObjectParser : DbUp.Support.SqlObjectParser, DbUp.Engine.ISqlObjectParser + { + public FirebirdObjectParser() { } + } + public class FirebirdPreprocessor : DbUp.Engine.IScriptPreprocessor + { + public FirebirdPreprocessor() { } + public string Process(string contents) { } + } + public class FirebirdScriptExecutor : DbUp.Support.ScriptExecutor, DbUp.Engine.IScriptExecutor + { + public FirebirdScriptExecutor(System.Func connectionManagerFactory, System.Func log, string schema, System.Func variablesEnabled, System.Collections.Generic.IEnumerable scriptPreprocessors, System.Func journal) { } + protected override bool UseTheSameTransactionForJournalTableAndScripts { get; } + protected override void ExecuteCommandsWithinExceptionHandler(int index, DbUp.Engine.SqlScript script, System.Action executeCommand) { } + protected override string GetVerifySchemaSql(string schema) { } + } + public class FirebirdTableJournal : DbUp.Support.TableJournal, DbUp.Engine.IJournal + { + public FirebirdTableJournal(System.Func connectionManager, System.Func logger, string tableName) { } + protected override string CreateSchemaTableSql(string quotedPrimaryKeyName) { } + protected override string DoesTableExistSql() { } + protected override string GetInsertJournalEntrySql(string scriptName, string applied) { } + protected override string GetJournalEntriesSql() { } + protected override void OnTableCreated(System.Func dbCommandFactory) { } + } +} 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..797159c --- /dev/null +++ b/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.Net.verified.cs @@ -0,0 +1,43 @@ +[assembly: System.CLSCompliantAttribute(true)] +[assembly: System.Runtime.InteropServices.ComVisibleAttribute(false)] + +public static class FirebirdExtensions +{ + public static DbUp.Builder.UpgradeEngineBuilder FirebirdDatabase(DbUp.Engine.Transactions.IConnectionManager connectionManager) { } + public static DbUp.Builder.UpgradeEngineBuilder FirebirdDatabase(this DbUp.Builder.SupportedDatabases supported, string connectionString) { } + public static DbUp.Builder.UpgradeEngineBuilder FirebirdDatabase(this DbUp.Builder.SupportedDatabases supported, DbUp.Engine.Transactions.IConnectionManager connectionManager) { } +} +namespace DbUp.Firebird +{ + public class FirebirdConnectionManager : DbUp.Engine.Transactions.DatabaseConnectionManager, DbUp.Engine.Transactions.IConnectionManager + { + public FirebirdConnectionManager(string connectionString) { } + protected override DbUp.Engine.Transactions.AllowedTransactionMode AllowedTransactionModes { get; } + public override System.Collections.Generic.IEnumerable SplitScriptIntoCommands(string scriptContents) { } + } + public class FirebirdObjectParser : DbUp.Support.SqlObjectParser, DbUp.Engine.ISqlObjectParser + { + public FirebirdObjectParser() { } + } + public class FirebirdPreprocessor : DbUp.Engine.IScriptPreprocessor + { + public FirebirdPreprocessor() { } + public string Process(string contents) { } + } + public class FirebirdScriptExecutor : DbUp.Support.ScriptExecutor, DbUp.Engine.IScriptExecutor + { + public FirebirdScriptExecutor(System.Func connectionManagerFactory, System.Func log, string schema, System.Func variablesEnabled, System.Collections.Generic.IEnumerable scriptPreprocessors, System.Func journal) { } + protected override bool UseTheSameTransactionForJournalTableAndScripts { get; } + protected override void ExecuteCommandsWithinExceptionHandler(int index, DbUp.Engine.SqlScript script, System.Action executeCommand) { } + protected override string GetVerifySchemaSql(string schema) { } + } + public class FirebirdTableJournal : DbUp.Support.TableJournal, DbUp.Engine.IJournal + { + public FirebirdTableJournal(System.Func connectionManager, System.Func logger, string tableName) { } + protected override string CreateSchemaTableSql(string quotedPrimaryKeyName) { } + protected override string DoesTableExistSql() { } + protected override string GetInsertJournalEntrySql(string scriptName, string applied) { } + protected override string GetJournalEntriesSql() { } + protected override void OnTableCreated(System.Func dbCommandFactory) { } + } +} diff --git a/src/Tests/DatabaseSupportTests.cs b/src/Tests/DatabaseSupportTests.cs new file mode 100644 index 0000000..e6a40ef --- /dev/null +++ b/src/Tests/DatabaseSupportTests.cs @@ -0,0 +1,23 @@ +#if !NETCORE +using DbUp.Builder; +using DbUp.Firebird; +using DbUp.Tests.Common; + +namespace DbUp.Tests.Providers.Firebird; + +public class DatabaseSupportTests : DatabaseSupportTestsBase +{ + public DatabaseSupportTests() : base() + { + } + + protected override UpgradeEngineBuilder DeployTo(SupportedDatabases to) + => to.FirebirdDatabase(""); + + protected override UpgradeEngineBuilder AddCustomNamedJournalToBuilder(UpgradeEngineBuilder builder, string schema, string tableName) + => builder.JournalTo( + (connectionManagerFactory, logFactory) + => new FirebirdTableJournal(connectionManagerFactory, logFactory, tableName) + ); +} +#endif diff --git a/src/Tests/NoPublicApiChanges.cs b/src/Tests/NoPublicApiChanges.cs new file mode 100644 index 0000000..3f3da54 --- /dev/null +++ b/src/Tests/NoPublicApiChanges.cs @@ -0,0 +1,13 @@ +#if !NETCORE +using DbUp.Tests.Common; + +namespace DbUp.Tests.Providers.Firebird; + +public class NoPublicApiChanges : NoPublicApiChangesBase +{ + public NoPublicApiChanges() + : base(typeof(FirebirdExtensions).Assembly) + { + } +} +#endif diff --git a/src/Tests/Tests.csproj b/src/Tests/Tests.csproj index eaedade..ddb89dc 100644 --- a/src/Tests/Tests.csproj +++ b/src/Tests/Tests.csproj @@ -1,30 +1,27 @@ - - net462;net8 - Tests - DbUp.Firebird.Tests - true - - enable - $(NoWarn);NETSDK1138 - latest - + + net462;net8 + Tests + DbUp.Firebird.Tests + + enable + - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + - - - - - diff --git a/src/dbup-firebird.sln b/src/dbup-firebird.sln index 70826eb..b813ad0 100644 --- a/src/dbup-firebird.sln +++ b/src/dbup-firebird.sln @@ -25,6 +25,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{77157734-01D dbup-firebird.sln.DotSettings = dbup-firebird.sln.DotSettings EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample", "Sample\Sample.csproj", "{E4279382-206F-4CB7-8B75-D8D7A501EF34}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -39,6 +41,10 @@ Global {8CE634FE-6772-408E-9340-909F6218F8F7}.Debug|Any CPU.Build.0 = Debug|Any CPU {8CE634FE-6772-408E-9340-909F6218F8F7}.Release|Any CPU.ActiveCfg = Release|Any CPU {8CE634FE-6772-408E-9340-909F6218F8F7}.Release|Any CPU.Build.0 = Release|Any CPU + {E4279382-206F-4CB7-8B75-D8D7A501EF34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E4279382-206F-4CB7-8B75-D8D7A501EF34}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E4279382-206F-4CB7-8B75-D8D7A501EF34}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E4279382-206F-4CB7-8B75-D8D7A501EF34}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {62E5FE92-E288-4E09-964D-F92AF0E49131} = {5DA19CA9-8039-46D6-B474-021943582785} diff --git a/src/dbup-firebird/FirebirdConnectionManager.cs b/src/dbup-firebird/FirebirdConnectionManager.cs new file mode 100644 index 0000000..39f51fc --- /dev/null +++ b/src/dbup-firebird/FirebirdConnectionManager.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using DbUp.Engine.Transactions; +using FirebirdSql.Data.FirebirdClient; + +namespace DbUp.Firebird +{ + /// + /// Manages Firebird database connections. + /// + public class FirebirdConnectionManager : DatabaseConnectionManager + { + /// + /// Creates a new Firebird database connection. + /// + /// The Firebird connection string. + public FirebirdConnectionManager(string connectionString) : base(new DelegateConnectionFactory(l => new FbConnection(connectionString))) + { + } + + /// + /// Splits the statements in the script using the ";" character. + /// + /// The contents of the script to split. + public override IEnumerable SplitScriptIntoCommands(string scriptContents) + { + // TODO: Possible Change - this is the PostGres version + var scriptStatements = + Regex.Split(scriptContents, "^\\s*;\\s*$", RegexOptions.IgnoreCase | RegexOptions.Multiline) + .Select(x => x.Trim()) + .Where(x => x.Length > 0) + .ToArray(); + + return scriptStatements; + } + + protected override AllowedTransactionMode AllowedTransactionModes => AllowedTransactionMode.TransactionPerScript; + } +} diff --git a/src/dbup-firebird/FirebirdExtensions.cs b/src/dbup-firebird/FirebirdExtensions.cs new file mode 100644 index 0000000..60481e5 --- /dev/null +++ b/src/dbup-firebird/FirebirdExtensions.cs @@ -0,0 +1,52 @@ +using DbUp.Builder; +using DbUp.Engine.Transactions; +using DbUp.Firebird; + +// ReSharper disable once CheckNamespace + +/// +/// Configuration extension methods for Firebird. +/// +public static class FirebirdExtensions +{ + /// + /// Creates an upgrader for Firebird databases. + /// + /// Fluent helper type. + /// Firebird database connection string. + /// + /// A builder for a database upgrader designed for Firebird databases. + /// + public static UpgradeEngineBuilder FirebirdDatabase(this SupportedDatabases supported, string connectionString) + { + return FirebirdDatabase(new FirebirdConnectionManager(connectionString)); + } + + /// + /// Creates an upgrader for Firebird databases. + /// + /// Fluent helper type. + /// The to be used during a database upgrade. + /// + /// A builder for a database upgrader designed for Firebird databases. + /// + public static UpgradeEngineBuilder FirebirdDatabase(this SupportedDatabases supported, IConnectionManager connectionManager) + => FirebirdDatabase(connectionManager); + + /// + /// Creates an upgrader for Firebird databases. + /// + /// The to be used during a database upgrade. + /// + /// A builder for a database upgrader designed for Firebird databases. + /// + public static UpgradeEngineBuilder FirebirdDatabase(IConnectionManager connectionManager) + { + var builder = new UpgradeEngineBuilder(); + builder.Configure(c => c.ConnectionManager = connectionManager); + builder.Configure(c => c.ScriptExecutor = new FirebirdScriptExecutor(() => c.ConnectionManager, () => c.Log, null, () => c.VariablesEnabled, c.ScriptPreprocessors, () => c.Journal)); + builder.Configure(c => c.Journal = new FirebirdTableJournal(() => c.ConnectionManager, () => c.Log, "schemaversions")); + builder.WithPreprocessor(new FirebirdPreprocessor()); + return builder; + } +} \ No newline at end of file diff --git a/src/dbup-firebird/FirebirdObjectParser.cs b/src/dbup-firebird/FirebirdObjectParser.cs new file mode 100644 index 0000000..42e0ba5 --- /dev/null +++ b/src/dbup-firebird/FirebirdObjectParser.cs @@ -0,0 +1,14 @@ +using DbUp.Support; + +namespace DbUp.Firebird +{ + /// + /// Parses Sql Objects and performs quoting functions + /// + public class FirebirdObjectParser : SqlObjectParser + { + public FirebirdObjectParser() : base("\"", "\"") + { + } + } +} diff --git a/src/dbup-firebird/FirebirdPreprocessor.cs b/src/dbup-firebird/FirebirdPreprocessor.cs new file mode 100644 index 0000000..48fdac6 --- /dev/null +++ b/src/dbup-firebird/FirebirdPreprocessor.cs @@ -0,0 +1,15 @@ +using DbUp.Engine; + +namespace DbUp.Firebird +{ + /// + /// This preprocessor makes adjustments to your sql to make it compatible with Firebird. + /// + public class FirebirdPreprocessor : IScriptPreprocessor + { + /// + /// Performs some preprocessing step on a Firebird script. + /// + public string Process(string contents) => contents; + } +} diff --git a/src/dbup-firebird/FirebirdScriptExecutor.cs b/src/dbup-firebird/FirebirdScriptExecutor.cs new file mode 100644 index 0000000..b336e7d --- /dev/null +++ b/src/dbup-firebird/FirebirdScriptExecutor.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using DbUp.Engine; +using DbUp.Engine.Output; +using DbUp.Engine.Transactions; +using DbUp.Support; +using FirebirdSql.Data.FirebirdClient; + +namespace DbUp.Firebird +{ + /// + /// An implementation of that executes against a Firebird database. + /// + public class FirebirdScriptExecutor : 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 FirebirdScriptExecutor(Func connectionManagerFactory, Func log, string schema, Func variablesEnabled, + IEnumerable scriptPreprocessors, Func journal) + : base(connectionManagerFactory, new FirebirdObjectParser(), log, schema, variablesEnabled, scriptPreprocessors, journal) + { + } + + protected override string GetVerifySchemaSql(string schema) + { + throw new NotSupportedException(); + } + + /// + /// We have to run the JournalTable creation and updating of the Journal table in different transactions. + /// See this: https://stackoverflow.com/questions/66537195/i-cant-run-inserts-in-firebird-2-5-table-unknown + /// + protected override bool UseTheSameTransactionForJournalTableAndScripts => false; + + protected override void ExecuteCommandsWithinExceptionHandler(int index, SqlScript script, Action executeCommand) + { + try + { + executeCommand(); + } + catch (FbException fbException) + { + Log().WriteInformation("Firebird exception has occured in script: '{0}'", script.Name); + Log().WriteError("Script block number: {0}; Firebird error code: {1}; SQLSTATE {2}; Message: {3}", index, fbException.ErrorCode, fbException.SQLSTATE, fbException.Message); + Log().WriteError(fbException.ToString()); + throw; + } + } + } +} diff --git a/src/dbup-firebird/FirebirdTableJournal.cs b/src/dbup-firebird/FirebirdTableJournal.cs new file mode 100644 index 0000000..9bcb5e2 --- /dev/null +++ b/src/dbup-firebird/FirebirdTableJournal.cs @@ -0,0 +1,90 @@ +using System; +using System.Data; +using DbUp.Engine; +using DbUp.Engine.Output; +using DbUp.Engine.Transactions; +using DbUp.Support; + +namespace DbUp.Firebird +{ + /// + /// An implementation of the interface which tracks version numbers for a + /// Firebird database using a table called SchemaVersions. + /// + public class FirebirdTableJournal : TableJournal + { + /// + /// Creates a new Firebird table journal. + /// + /// The Firebird connection manager. + /// The upgrade logger. + /// The name of the journal table. + public FirebirdTableJournal(Func connectionManager, Func logger, string tableName) + : base(connectionManager, logger, new FirebirdObjectParser(), null, tableName) + { + } + + static string CreateGeneratorSql(string tableName) + { + return $@"CREATE SEQUENCE {GeneratorName(tableName)}"; + } + + string CreateTriggerSql(string tableName) + { + return +$@"CREATE TRIGGER {TriggerName(tableName)} FOR {FqSchemaTableName} ACTIVE BEFORE INSERT POSITION 0 AS BEGIN + if (new.schemaversionsid is null or (new.schemaversionsid = 0)) then new.schemaversionsid = gen_id({GeneratorName(tableName)},1); +END;"; + } + + static string GeneratorName(string tableName) => $"GEN_{tableName}ID"; + + static string TriggerName(string tableName) => $"BI_{tableName}ID"; + + void ExecuteCommand(Func dbCommandFactory, string sql) + { + using (var command = dbCommandFactory()) + { + command.CommandText = sql; + command.CommandType = CommandType.Text; + command.ExecuteNonQuery(); + } + } + + protected override void OnTableCreated(Func dbCommandFactory) + { + var unquotedTableName = UnquoteSqlObjectName(FqSchemaTableName); + ExecuteCommand(dbCommandFactory, CreateGeneratorSql(unquotedTableName)); + Log().WriteInformation($"The {GeneratorName(unquotedTableName)} generator has been created"); + ExecuteCommand(dbCommandFactory, CreateTriggerSql(unquotedTableName)); + Log().WriteInformation($"The {TriggerName(unquotedTableName)} trigger has been created"); + } + + protected override string DoesTableExistSql() + { + return $"select 1 from RDB$RELATIONS where RDB$SYSTEM_FLAG = 0 and RDB$RELATION_NAME = '{UnquotedSchemaTableName}'"; + } + + 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 CreateSchemaTableSql(string quotedPrimaryKeyName) + { + return +$@"CREATE TABLE {FqSchemaTableName} +( + schemaversionsid INTEGER NOT NULL, + scriptname VARCHAR(255) NOT NULL, + applied TIMESTAMP NOT NULL, + CONSTRAINT pk_{UnquotedSchemaTableName}_id PRIMARY KEY (schemaversionsid) +)"; + } + } +} diff --git a/src/dbup-firebird/Properties/AssemblyInfo.cs b/src/dbup-firebird/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..8293719 --- /dev/null +++ b/src/dbup-firebird/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +using System; +using System.Runtime.InteropServices; + +[assembly: ComVisible(false)] +[assembly: CLSCompliant(true)] + diff --git a/src/dbup-firebird/dbup-firebird.csproj b/src/dbup-firebird/dbup-firebird.csproj index 80b0075..f83dc8c 100644 --- a/src/dbup-firebird/dbup-firebird.csproj +++ b/src/dbup-firebird/dbup-firebird.csproj @@ -3,35 +3,26 @@ DbUp makes it easy to deploy and upgrade SQL Server databases. This package adds Firebird support. DbUp Firebird Support - netstandard1.3;netstandard2.0;net35;net45 + DbUp Contributors + DbUp + Copyright © DbUp Contributors 2015 + net462;netstandard2.0 dbup-firebird DbUp.Firebird + dbup-firebird ../dbup.snk true https://github.com/DbUp/dbup-firebird.git - dbup_firebird - dbup-firebird dbup-icon.png - - - - $(DefineConstants);NPGSQLv2 + true - - - - - - - - - + + - + -