diff --git a/src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyBasicSupport.verified.txt b/src/Tests/ApprovalFiles/DatabaseSupportTests.VerifyBasicSupport.verified.txt new file mode 100644 index 0000000..c49f705 --- /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 count(name) FROM sqlite_master WHERE type = 'table' AND 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 count(name) FROM sqlite_master WHERE type = 'table' AND name = 'SchemaVersions' +DB Operation: Dispose command +Info: Creating the [SchemaVersions] table +DB Operation: Execute non query command: CREATE TABLE [SchemaVersions] ( + SchemaVersionID INTEGER CONSTRAINT [PK_SchemaVersions_Id] PRIMARY KEY AUTOINCREMENT NOT NULL, + ScriptName TEXT 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 (@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..9434571 --- /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 count(name) FROM sqlite_master WHERE type = 'table' AND 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 count(name) FROM sqlite_master WHERE type = 'table' AND name = 'TestSchemaVersions' +DB Operation: Dispose command +Info: Creating the [TestSchemaVersions] table +DB Operation: Execute non query command: CREATE TABLE [TestSchemaVersions] ( + SchemaVersionID INTEGER CONSTRAINT [PK_TestSchemaVersions_Id] PRIMARY KEY AUTOINCREMENT NOT NULL, + ScriptName TEXT NOT NULL, + Applied DATETIME NOT NULL +) +DB Operation: Dispose command +Info: The [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 [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..9bbd486 --- /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 count(name) FROM sqlite_master WHERE type = 'table' AND 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 count(name) FROM sqlite_master WHERE type = 'table' AND name = 'SchemaVersions' +DB Operation: Dispose command +Info: Creating the [SchemaVersions] table +DB Operation: Execute non query command: CREATE TABLE [SchemaVersions] ( + SchemaVersionID INTEGER CONSTRAINT [PK_SchemaVersions_Id] PRIMARY KEY AUTOINCREMENT NOT NULL, + ScriptName TEXT 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 (@scriptName, @applied) +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..f68f452 --- /dev/null +++ b/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.Net.verified.cs @@ -0,0 +1,77 @@ +[assembly: System.CLSCompliantAttribute(true)] +[assembly: System.Runtime.InteropServices.ComVisibleAttribute(false)] +[assembly: System.Runtime.InteropServices.GuidAttribute("bce32c14-27e3-4e0d-bc1f-ec9ccb128f00")] + +public static class SQLiteExtensions +{ + public static DbUp.Builder.UpgradeEngineBuilder JournalToSQLiteTable(this DbUp.Builder.UpgradeEngineBuilder builder, string table) { } + public static DbUp.Builder.UpgradeEngineBuilder SQLiteDatabase(this DbUp.Builder.SupportedDatabases supported, string connectionString) { } + public static DbUp.Builder.UpgradeEngineBuilder SQLiteDatabase(this DbUp.Builder.SupportedDatabases supported, DbUp.SQLite.Helpers.SharedConnection sharedConnection) { } +} +namespace DbUp.SQLite +{ + public class SQLiteConnectionManager : DbUp.Engine.Transactions.DatabaseConnectionManager, DbUp.Engine.Transactions.IConnectionManager + { + public SQLiteConnectionManager(string connectionString) { } + public SQLiteConnectionManager(DbUp.SQLite.Helpers.SharedConnection sharedConnection) { } + public override System.Collections.Generic.IEnumerable SplitScriptIntoCommands(string scriptContents) { } + } + public class SQLiteObjectParser : DbUp.Support.SqlObjectParser, DbUp.Engine.ISqlObjectParser + { + public SQLiteObjectParser() { } + } + public class SQLitePreprocessor : DbUp.Engine.IScriptPreprocessor + { + public SQLitePreprocessor() { } + public string Process(string contents) { } + } + public class SQLiteScriptExecutor : DbUp.Support.ScriptExecutor, DbUp.Engine.IScriptExecutor + { + public SQLiteScriptExecutor(System.Func connectionManagerFactory, System.Func log, string schema, System.Func variablesEnabled, System.Collections.Generic.IEnumerable scriptPreprocessors, System.Func journalFactory) { } + protected override void ExecuteCommandsWithinExceptionHandler(int index, DbUp.Engine.SqlScript script, System.Action executeCommand) { } + protected override string GetVerifySchemaSql(string schema) { } + } + public class SQLiteTableJournal : DbUp.Support.TableJournal, DbUp.Engine.IJournal + { + public SQLiteTableJournal(System.Func connectionManager, System.Func logger, string table) { } + protected override string CreateSchemaTableSql(string quotedPrimaryKeyName) { } + protected override string DoesTableExistSql() { } + protected override string GetInsertJournalEntrySql(string scriptName, string applied) { } + protected override string GetJournalEntriesSql() { } + } +} +namespace DbUp.SQLite.Helpers +{ + public class InMemorySQLiteDatabase : System.IDisposable + { + public InMemorySQLiteDatabase() { } + public string ConnectionString { get; set; } + public DbUp.Helpers.AdHocSqlRunner SqlRunner { get; } + public void Dispose() { } + public DbUp.Engine.Transactions.IConnectionManager GetConnectionManager() { } + } + public class SharedConnection : System.Data.IDbConnection, System.IDisposable + { + public SharedConnection(System.Data.IDbConnection dbConnection) { } + public string ConnectionString { get; set; } + public int ConnectionTimeout { get; } + public string Database { get; } + public System.Data.ConnectionState State { get; } + public System.Data.IDbTransaction BeginTransaction() { } + public System.Data.IDbTransaction BeginTransaction(System.Data.IsolationLevel il) { } + public void ChangeDatabase(string databaseName) { } + public void Close() { } + public System.Data.IDbCommand CreateCommand() { } + public void Dispose() { } + public void DoClose() { } + public void Open() { } + } + public class TemporarySQLiteDatabase : System.IDisposable + { + public TemporarySQLiteDatabase(string name) { } + public DbUp.SQLite.Helpers.SharedConnection SharedConnection { get; } + public DbUp.Helpers.AdHocSqlRunner SqlRunner { get; } + public void Create() { } + public void Dispose() { } + } +} diff --git a/src/Tests/DatabaseSupportTests.cs b/src/Tests/DatabaseSupportTests.cs new file mode 100644 index 0000000..6fd3237 --- /dev/null +++ b/src/Tests/DatabaseSupportTests.cs @@ -0,0 +1,20 @@ +using DbUp.Builder; +using DbUp.Tests.Common; + +namespace DbUp.SQLite.Tests; + +public class DatabaseSupportTests : DatabaseSupportTestsBase +{ + public DatabaseSupportTests() : base() + { + } + + protected override UpgradeEngineBuilder DeployTo(SupportedDatabases to) + => to.SQLiteDatabase(""); + + protected override UpgradeEngineBuilder AddCustomNamedJournalToBuilder(UpgradeEngineBuilder builder, string schema, string tableName) + => builder.JournalTo( + (connectionManagerFactory, logFactory) + => new SQLiteTableJournal(connectionManagerFactory, logFactory, tableName) + ); +} diff --git a/src/Tests/NoPublicApiChanges.cs b/src/Tests/NoPublicApiChanges.cs new file mode 100644 index 0000000..81e81d9 --- /dev/null +++ b/src/Tests/NoPublicApiChanges.cs @@ -0,0 +1,11 @@ +using DbUp.Tests.Common; + +namespace DbUp.SQLite.Tests; + +public class NoPublicApiChanges : NoPublicApiChangesBase +{ + public NoPublicApiChanges() + : base(typeof(SQLiteExtensions).Assembly) + { + } +} diff --git a/src/Tests/SQLiteSupportTests.cs b/src/Tests/SQLiteSupportTests.cs new file mode 100644 index 0000000..5ae3acf --- /dev/null +++ b/src/Tests/SQLiteSupportTests.cs @@ -0,0 +1,28 @@ +using System; +using System.IO; +using Mono.Data.Sqlite; +using Xunit; + +namespace DbUp.SQLite.Tests +{ + public class SQLiteSupportTests + { + static readonly string dbFilePath = Path.Combine(Environment.CurrentDirectory, "test.db"); + + [Fact] + public void CanUseSQLite() + { + var connectionString = string.Format("Data Source={0}; Version=3;", dbFilePath); + + if (!File.Exists(dbFilePath)) + { + SqliteConnection.CreateFile(dbFilePath); + } + + var upgrader = DeployChanges.To + .SQLiteDatabase(connectionString) + .WithScript("Script0001", "CREATE TABLE IF NOT EXISTS Foo (Id int)") + .Build(); + } + } +} diff --git a/src/Tests/SQLiteTableJournalTests.cs b/src/Tests/SQLiteTableJournalTests.cs new file mode 100644 index 0000000..67995d7 --- /dev/null +++ b/src/Tests/SQLiteTableJournalTests.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.Data; +using DbUp.Engine; +using DbUp.Engine.Output; +using DbUp.Engine.Transactions; +using DbUp.Tests.Common; +using Mono.Data.Sqlite; +using NSubstitute; +using Shouldly; +using Xunit; + +namespace DbUp.SQLite.Tests +{ + public class SQLiteTableJournalTests + { + [Fact] + public void dbversion_is_zero_when_journal_table_not_exist() + { + // Given + var dbConnection = Substitute.For(); + var command = Substitute.For(); + dbConnection.CreateCommand().Returns(command); + var connectionManager = Substitute.For(); + command.ExecuteScalar().Returns(x => { throw new SqliteException("table not found"); }); + var consoleUpgradeLog = new ConsoleUpgradeLog(); + var journal = new SQLiteTableJournal(() => connectionManager, () => consoleUpgradeLog, "SchemaVersions"); + + // When + var scripts = journal.GetExecutedScripts(); + + // Expect + command.DidNotReceive().ExecuteReader(); + scripts.ShouldBeEmpty(); + } + + [Fact] + public void creates_a_new_journal_table_when_not_exist() + { + // Given + var dbConnection = Substitute.For(); + var connectionManager = new TestConnectionManager(dbConnection); + connectionManager.OperationStarting(new ConsoleUpgradeLog(), new List()); + + var command = Substitute.For(); + var param1 = Substitute.For(); + var param2 = Substitute.For(); + dbConnection.CreateCommand().Returns(command); + command.CreateParameter().Returns(param1, param2); + command.ExecuteScalar().Returns(x => 0); + var consoleUpgradeLog = new ConsoleUpgradeLog(); + var journal = new SQLiteTableJournal(() => connectionManager, () => consoleUpgradeLog, "SchemaVersions"); + + // When + journal.StoreExecutedScript(new SqlScript("test", "select 1"), () => command); + + // Expect + command.Received(2).CreateParameter(); + param1.ParameterName.ShouldBe("scriptName"); + param2.ParameterName.ShouldBe("applied"); + command.Received().ExecuteNonQuery(); + } + } +} diff --git a/src/Tests/Tests.csproj b/src/Tests/Tests.csproj index 3064bc1..c5e8a26 100644 --- a/src/Tests/Tests.csproj +++ b/src/Tests/Tests.csproj @@ -1,22 +1,22 @@ - net462;net8 + net462 Tests DbUp.SQLite.Tests - enable - + all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/dbup-sqlite-mono/Helpers/InMemorySQLiteDatabase.cs b/src/dbup-sqlite-mono/Helpers/InMemorySQLiteDatabase.cs new file mode 100644 index 0000000..79309d8 --- /dev/null +++ b/src/dbup-sqlite-mono/Helpers/InMemorySQLiteDatabase.cs @@ -0,0 +1,54 @@ +using System; +using DbUp.Engine.Transactions; +using DbUp.Helpers; +using Mono.Data.Sqlite; + +namespace DbUp.SQLite.Helpers +{ + /// + /// Used to create in-memory SQLite database that is deleted at the end of a test. + /// + public class InMemorySQLiteDatabase : IDisposable + { + readonly SQLiteConnectionManager connectionManager; + readonly SqliteConnection sharedConnection; + + /// + /// Initializes a new instance of the class. + /// + public InMemorySQLiteDatabase() + { + var connectionStringBuilder = new SqliteConnectionStringBuilder() + { + DataSource = ":memory:", + Version = 3, + DefaultTimeout = 5, + JournalMode = SQLiteJournalModeEnum.Off, + UseUTF16Encoding = true + }; + ConnectionString = connectionStringBuilder.ToString(); + + connectionManager = new SQLiteConnectionManager(connectionStringBuilder.ConnectionString); + sharedConnection = new SqliteConnection(connectionStringBuilder.ConnectionString); + sharedConnection.Open(); + SqlRunner = new AdHocSqlRunner(() => sharedConnection.CreateCommand(), new SQLiteObjectParser(), null, () => true); + } + + public string ConnectionString { get; set; } + + /// + /// Gets the connection factory of in-memory database. + /// + public IConnectionManager GetConnectionManager() => connectionManager; + + /// + /// An adhoc sql runner against the in-memory database + /// + public AdHocSqlRunner SqlRunner { get; } + + /// + /// Remove the database from memory. + /// + public void Dispose() => sharedConnection.Dispose(); + } +} diff --git a/src/dbup-sqlite-mono/Helpers/SharedConnection.cs b/src/dbup-sqlite-mono/Helpers/SharedConnection.cs new file mode 100644 index 0000000..823996c --- /dev/null +++ b/src/dbup-sqlite-mono/Helpers/SharedConnection.cs @@ -0,0 +1,73 @@ +using System; +using System.Data; + +namespace DbUp.SQLite.Helpers +{ + /// + /// A database connection wrapper to manage underlying connection as a shared connection + /// during database upgrade. + /// + /// if underlying connection is already opened then it will be kept as opened and will not be closed + /// otherwise it will be opened when object is created and closed when object is disposed + /// however it will not be disposed + /// + /// + public class SharedConnection : IDbConnection + { + readonly bool connectionAlreadyOpened; + readonly IDbConnection connection; + + /// + /// Constructs a new instance + /// + public SharedConnection(IDbConnection dbConnection) + { + connection = dbConnection ?? throw new ArgumentNullException(nameof(dbConnection), "database connection is null"); + + if (connection.State == ConnectionState.Open) + connectionAlreadyOpened = true; + else + connection.Open(); + } + + public IDbTransaction BeginTransaction(IsolationLevel il) => connection.BeginTransaction(il); + + public IDbTransaction BeginTransaction() => connection.BeginTransaction(); + + public void ChangeDatabase(string databaseName) => connection.ChangeDatabase(databaseName); + + public void Close() { } // shared underlying connection is not closed + + public string ConnectionString + { + get => connection.ConnectionString; + set => connection.ConnectionString = value; + } + + public int ConnectionTimeout => connection.ConnectionTimeout; + + public IDbCommand CreateCommand() => connection.CreateCommand(); + + public string Database => connection.Database; + + public void Open() + { + if (connection.State == ConnectionState.Closed) + connection.Open(); + } + + public ConnectionState State => connection.State; + + public void Dispose() { } // shared underlying connection is not disposed + + public void DoClose() + { + // if shared underlying connection is opened by this object + // it will be closed here, otherwise the connection is not closed + if (!connectionAlreadyOpened && connection.State == ConnectionState.Open) + { + connection.Close(); + } + } + } +} diff --git a/src/dbup-sqlite-mono/Helpers/TemporarySQLiteDatabase.cs b/src/dbup-sqlite-mono/Helpers/TemporarySQLiteDatabase.cs new file mode 100644 index 0000000..9d6dffc --- /dev/null +++ b/src/dbup-sqlite-mono/Helpers/TemporarySQLiteDatabase.cs @@ -0,0 +1,79 @@ +using System; +using System.IO; +using DbUp.Helpers; + +using SQLiteConnection = Mono.Data.Sqlite.SqliteConnection; +using SQLiteConnectionStringBuilder = Mono.Data.Sqlite.SqliteConnectionStringBuilder; +using SQLiteJournalModeEnum = Mono.Data.Sqlite.SQLiteJournalModeEnum; + +namespace DbUp.SQLite.Helpers +{ + /// + /// Used to create SQLite databases that are deleted at the end of a test. + /// + public class TemporarySQLiteDatabase : IDisposable + { + readonly string dataSourcePath; + readonly SQLiteConnection sqLiteConnection; + + /// + /// Initializes a new instance of the class. + /// + /// The name. + public TemporarySQLiteDatabase(string name) + { + dataSourcePath = Path.Combine(Directory.GetCurrentDirectory(), name); + + var connectionStringBuilder = new SQLiteConnectionStringBuilder + { + DataSource = name, + Version = 3, + DefaultTimeout = 5, + JournalMode = SQLiteJournalModeEnum.Off, + UseUTF16Encoding = true + }; + + sqLiteConnection = new SQLiteConnection(connectionStringBuilder.ConnectionString); + sqLiteConnection.Open(); + SharedConnection = new SharedConnection(sqLiteConnection); + SqlRunner = new AdHocSqlRunner(() => sqLiteConnection.CreateCommand(), new SQLiteObjectParser(), null, () => true); + } + + /// + /// An adhoc sql runner against the temporary database + /// + public AdHocSqlRunner SqlRunner { get; } + + public SharedConnection SharedConnection { get; } + + /// + /// Creates the database. + /// + public void Create() + { + var filePath = new FileInfo(dataSourcePath); + if (!filePath.Exists) + { + SQLiteConnection.CreateFile(dataSourcePath); + } + } + + /// + /// Deletes the database. + /// + public void Dispose() + { + var filePath = new FileInfo(dataSourcePath); + if (!filePath.Exists) return; + SharedConnection.Dispose(); + sqLiteConnection.Dispose(); + SQLiteConnection.ClearAllPools(); + + // SQLite requires all created sql connection/command objects to be disposed + // in order to delete the database file + GC.Collect(2, GCCollectionMode.Forced); + System.Threading.Thread.Sleep(100); + File.Delete(dataSourcePath); + } + } +} diff --git a/src/dbup-sqlite-mono/Properties/AssemblyInfo.cs b/src/dbup-sqlite-mono/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..59d86d7 --- /dev/null +++ b/src/dbup-sqlite-mono/Properties/AssemblyInfo.cs @@ -0,0 +1,8 @@ +using System; +using System.Runtime.InteropServices; + +[assembly: ComVisible(false)] +[assembly: CLSCompliant(true)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("bce32c14-27e3-4e0d-bc1f-ec9ccb128f00")] diff --git a/src/dbup-sqlite-mono/SQLiteConnectionManager.cs b/src/dbup-sqlite-mono/SQLiteConnectionManager.cs new file mode 100644 index 0000000..c37d398 --- /dev/null +++ b/src/dbup-sqlite-mono/SQLiteConnectionManager.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using DbUp.Engine.Transactions; +using DbUp.SQLite.Helpers; +using Mono.Data.Sqlite; + +namespace DbUp.SQLite +{ + /// + /// Connection manager for Sql Lite + /// + public class SQLiteConnectionManager : DatabaseConnectionManager + { + /// + /// Creates new SQLite Connection Manager + /// + public SQLiteConnectionManager(string connectionString) : base(l => new SqliteConnection(connectionString)) + { + } + + /// + /// Creates new SQLite Connection Manager + /// + public SQLiteConnectionManager(SharedConnection sharedConnection) : base(l => sharedConnection) + { + } + + /// + /// Sqlite statements separator is ; (see http://www.sqlite.org/lang.html) + /// + public override IEnumerable SplitScriptIntoCommands(string scriptContents) + { + var scriptStatements = + Regex.Split(scriptContents, "^\\s*;\\s*$", RegexOptions.IgnoreCase | RegexOptions.Multiline) + .Select(x => x.Trim()) + .Where(x => x.Length > 0) + .ToArray(); + + return scriptStatements; + } + } +} diff --git a/src/dbup-sqlite-mono/SQLiteObjectParser.cs b/src/dbup-sqlite-mono/SQLiteObjectParser.cs new file mode 100644 index 0000000..71d6320 --- /dev/null +++ b/src/dbup-sqlite-mono/SQLiteObjectParser.cs @@ -0,0 +1,15 @@ +using DbUp.Support; + +namespace DbUp.SQLite +{ + /// + /// Parses Sql Objects and performs quoting functions. + /// + public class SQLiteObjectParser : SqlObjectParser + { + public SQLiteObjectParser() + : base("[", "]") + { + } + } +} diff --git a/src/dbup-sqlite-mono/SQLiteScriptExecutor.cs b/src/dbup-sqlite-mono/SQLiteScriptExecutor.cs new file mode 100644 index 0000000..c0ef016 --- /dev/null +++ b/src/dbup-sqlite-mono/SQLiteScriptExecutor.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; +using Mono.Data.Sqlite; + +namespace DbUp.SQLite +{ + /// + /// An implementation of that executes against a SQLite database. + /// + public class SQLiteScriptExecutor : 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 SQLiteScriptExecutor(Func connectionManagerFactory, Func log, string schema, Func variablesEnabled, + IEnumerable scriptPreprocessors, Func journalFactory) + : base(connectionManagerFactory, new SQLiteObjectParser(), log, schema, variablesEnabled, scriptPreprocessors, journalFactory) + { + } + + protected override string GetVerifySchemaSql(string schema) + { + throw new NotSupportedException(); + } + + protected override void ExecuteCommandsWithinExceptionHandler(int index, SqlScript script, Action executeCommand) + { + try + { + executeCommand(); + } + catch (SqliteException exception) + { + Log().WriteInformation("SQLite exception has occurred in script: '{0}'", script.Name); + Log().WriteError("Script block number: {0}; Error Code: {1}; Message: {2}", index, exception.ErrorCode, exception.Message); + Log().WriteError(exception.ToString()); + throw; + } + } + } +} diff --git a/src/dbup-sqlite-mono/SQLiteTableJournal.cs b/src/dbup-sqlite-mono/SQLiteTableJournal.cs new file mode 100644 index 0000000..a3a4714 --- /dev/null +++ b/src/dbup-sqlite-mono/SQLiteTableJournal.cs @@ -0,0 +1,47 @@ +using System; +using DbUp.Engine; +using DbUp.Engine.Output; +using DbUp.Engine.Transactions; +using DbUp.Support; + +namespace DbUp.SQLite +{ + /// + /// An implementation of the interface which tracks version numbers for a + /// SQLite database using a table called SchemaVersions. + /// + public class SQLiteTableJournal : TableJournal + { + /// + /// Initializes a new instance of the class. + /// + public SQLiteTableJournal(Func connectionManager, Func logger, string table) : + base(connectionManager, logger, new SQLiteObjectParser(), null, table) + { } + + 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} ( + SchemaVersionID INTEGER CONSTRAINT {quotedPrimaryKeyName} PRIMARY KEY AUTOINCREMENT NOT NULL, + ScriptName TEXT NOT NULL, + Applied DATETIME NOT NULL +)"; + } + + protected override string DoesTableExistSql() + { + return $"SELECT count(name) FROM sqlite_master WHERE type = 'table' AND name = '{UnquotedSchemaTableName}'"; + } + } +} diff --git a/src/dbup-sqlite-mono/SqliteExtensions.cs b/src/dbup-sqlite-mono/SqliteExtensions.cs new file mode 100644 index 0000000..b72c0f8 --- /dev/null +++ b/src/dbup-sqlite-mono/SqliteExtensions.cs @@ -0,0 +1,63 @@ +using DbUp.Builder; +using DbUp.SQLite; +using DbUp.SQLite.Helpers; + +/// +/// Configuration extension methods for SQLite (see http://www.sqlite.org/) +/// +// NOTE: DO NOT MOVE THIS TO A NAMESPACE +// Since the class just contains extension methods, we leave it in the root so that it is always discovered +// and people don't have to manually add using statements. +// ReSharper disable CheckNamespace +public static class SQLiteExtensions +// ReSharper restore CheckNamespace +{ + /// + /// Creates an upgrader for SQLite databases. + /// + /// Fluent helper type. + /// SQLite database connection string + /// + /// A builder for a database upgrader designed for SQLite databases. + /// + public static UpgradeEngineBuilder SQLiteDatabase(this SupportedDatabases supported, string connectionString) + { + var builder = new UpgradeEngineBuilder(); + builder.Configure(c => c.ConnectionManager = new SQLiteConnectionManager(connectionString)); + builder.Configure(c => c.Journal = new SQLiteTableJournal(() => c.ConnectionManager, () => c.Log, "SchemaVersions")); + builder.Configure(c => c.ScriptExecutor = new SQLiteScriptExecutor(() => c.ConnectionManager, () => c.Log, null, + () => c.VariablesEnabled, c.ScriptPreprocessors, () => c.Journal)); + builder.WithPreprocessor(new SQLitePreprocessor()); + return builder; + } + + /// + /// Creates an upgrader for SQLite databases. + /// + /// Fluent helper type. + /// SQLite database connection which you control when it is closed + /// + /// A builder for a database upgrader designed for SQLite databases. + /// + public static UpgradeEngineBuilder SQLiteDatabase(this SupportedDatabases supported, SharedConnection sharedConnection) + { + var builder = new UpgradeEngineBuilder(); + builder.Configure(c => c.ConnectionManager = new SQLiteConnectionManager(sharedConnection)); + builder.Configure(c => c.Journal = new SQLiteTableJournal(() => c.ConnectionManager, () => c.Log, "SchemaVersions")); + builder.Configure(c => c.ScriptExecutor = new SQLiteScriptExecutor(() => c.ConnectionManager, () => c.Log, null, + () => c.VariablesEnabled, c.ScriptPreprocessors, () => c.Journal)); + builder.WithPreprocessor(new SQLitePreprocessor()); + return builder; + } + + /// + /// Tracks the list of executed scripts in a custom SQLite table. + /// + /// The name of the table used to store the list of executed scripts. + /// The used to set the journal table name. + public static UpgradeEngineBuilder JournalToSQLiteTable(this UpgradeEngineBuilder builder, string table) + { + builder.Configure(c => c.Journal = new SQLiteTableJournal(() => c.ConnectionManager, () => c.Log, table)); + return builder; + } +} diff --git a/src/dbup-sqlite-mono/SqlitePreprocessor.cs b/src/dbup-sqlite-mono/SqlitePreprocessor.cs new file mode 100644 index 0000000..7efb51f --- /dev/null +++ b/src/dbup-sqlite-mono/SqlitePreprocessor.cs @@ -0,0 +1,16 @@ +using System.Text.RegularExpressions; +using DbUp.Engine; + +namespace DbUp.SQLite +{ + /// + /// This preprocessor makes adjustments to your sql to make it compatible with Sqlite + /// + public class SQLitePreprocessor : IScriptPreprocessor + { + /// + /// Performs some preprocessing step on a SQLite script + /// + public string Process(string contents) => Regex.Replace(contents, @"n?varchar\s?\(max\)", "text", RegexOptions.IgnoreCase); + } +} diff --git a/src/dbup-sqlite-mono/dbup-sqlite-mono.csproj b/src/dbup-sqlite-mono/dbup-sqlite-mono.csproj index 5bdace3..11361dd 100644 --- a/src/dbup-sqlite-mono/dbup-sqlite-mono.csproj +++ b/src/dbup-sqlite-mono/dbup-sqlite-mono.csproj @@ -1,12 +1,12 @@  - DbUp makes it easy to deploy and upgrade SQL Server databases. This package adds SQLite-mono support. - DbUp SQLite Mono Support + DbUp makes it easy to deploy and upgrade SQL Server databases. This package adds SQLite support for Mono. + DbUp SQLite Support targeting Mono DbUp Contributors DbUp Copyright © DbUp Contributors 2015 - netstandard1.3;netstandard2.0;net35;net45 + net462 dbup-sqlite-mono DbUp.SQLite dbup-sqlite-mono @@ -18,10 +18,12 @@ - - - + + + + +