Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug #536: Run After Create Database scripts are run even if database isn't created from scratch by grate #565

Merged
merged 2 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -265,12 +265,15 @@ jobs:
fail-fast: false
matrix:
database:
- name: "SqlServer 2022"
# - name: "SqlServer 2022"
# type: SqlServer
# image: "mcr.microsoft.com/mssql/server:2022-latest"
- name: "SqlServer 2022 (Ubuntu 22.04)"
type: SqlServer
image: "mcr.microsoft.com/mssql/server:2022-latest"
image: "mcr.microsoft.com/mssql/server:2022-preview-ubuntu-22.04"
- name: "SqlServer 2019"
type: SqlServer
image: "mcr.microsoft.com/mssql/server:2019-latest"
image: "mcr.microsoft.com/mssql/server:2019-CU26-ubuntu-20.04"
- name: "SqlServer 2017"
type: SqlServer
image: "mcr.microsoft.com/mssql/server:2017-latest"
Expand Down
33 changes: 17 additions & 16 deletions src/grate.core/Configuration/FoldersConfiguration.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using grate.Migration;
using static grate.Configuration.KnownFolderKeys;
using static grate.Configuration.MigrationType;

namespace grate.Configuration;
Expand Down Expand Up @@ -35,23 +36,23 @@ public static IFoldersConfiguration Default(IKnownFolderNames? folderNames = nul

var foldersConfiguration = new FoldersConfiguration()
{
{ KnownFolderKeys.BeforeMigration, new MigrationsFolder("BeforeMigration", folderNames.BeforeMigration, EveryTime, TransactionHandling: TransactionHandling.Autonomous) },
{ KnownFolderKeys.AlterDatabase , new MigrationsFolder("AlterDatabase", folderNames.AlterDatabase, AnyTime, ConnectionType.Admin, TransactionHandling.Autonomous) },
{ KnownFolderKeys.RunAfterCreateDatabase, new MigrationsFolder("Run After Create Database", folderNames.RunAfterCreateDatabase, AnyTime) },
{ KnownFolderKeys.RunBeforeUp, new MigrationsFolder("Run Before Update", folderNames.RunBeforeUp, AnyTime) },
{ KnownFolderKeys.Up, new MigrationsFolder("Update", folderNames.Up, Once) },
{ KnownFolderKeys.RunFirstAfterUp, new MigrationsFolder("Run First After Update", folderNames.RunFirstAfterUp, AnyTime) },
{ KnownFolderKeys.Functions, new MigrationsFolder("Functions", folderNames.Functions, AnyTime) },
{ KnownFolderKeys.Views, new MigrationsFolder("Views", folderNames.Views, AnyTime) },
{ KnownFolderKeys.Sprocs, new MigrationsFolder("Stored Procedures", folderNames.Sprocs, AnyTime) },
{ KnownFolderKeys.Triggers, new MigrationsFolder("Triggers", folderNames.Triggers, AnyTime) },
{ KnownFolderKeys.Indexes, new MigrationsFolder("Indexes", folderNames.Indexes, AnyTime) },
{ KnownFolderKeys.RunAfterOtherAnyTimeScripts, new MigrationsFolder("Run after Other Anytime Scripts", folderNames.RunAfterOtherAnyTimeScripts, AnyTime) },
{ KnownFolderKeys.Permissions, new MigrationsFolder("Permissions", folderNames.Permissions, EveryTime, TransactionHandling: TransactionHandling.Autonomous) },
{ KnownFolderKeys.AfterMigration, new MigrationsFolder("AfterMigration", folderNames.AfterMigration, EveryTime, TransactionHandling: TransactionHandling.Autonomous) },
{ BeforeMigration, new MigrationsFolder(BeforeMigration, "BeforeMigration", folderNames.BeforeMigration, EveryTime, TransactionHandling: TransactionHandling.Autonomous) },
{ AlterDatabase , new MigrationsFolder(AlterDatabase, "AlterDatabase", folderNames.AlterDatabase, AnyTime, ConnectionType.Admin, TransactionHandling.Autonomous) },
{ RunAfterCreateDatabase, new MigrationsFolder(RunAfterCreateDatabase, "Run After Create Database", folderNames.RunAfterCreateDatabase, AnyTime) },
{ RunBeforeUp, new MigrationsFolder(RunBeforeUp, "Run Before Update", folderNames.RunBeforeUp, AnyTime) },
{ Up, new MigrationsFolder(Up, "Update", folderNames.Up, Once) },
{ RunFirstAfterUp, new MigrationsFolder(RunFirstAfterUp, "Run First After Update", folderNames.RunFirstAfterUp, AnyTime) },
{ Functions, new MigrationsFolder(Functions, "Functions", folderNames.Functions, AnyTime) },
{ Views, new MigrationsFolder(Views, "Views", folderNames.Views, AnyTime) },
{ Sprocs, new MigrationsFolder(Sprocs, "Stored Procedures", folderNames.Sprocs, AnyTime) },
{ Triggers, new MigrationsFolder(Triggers, "Triggers", folderNames.Triggers, AnyTime) },
{ Indexes, new MigrationsFolder(Indexes, "Indexes", folderNames.Indexes, AnyTime) },
{ RunAfterOtherAnyTimeScripts, new MigrationsFolder(RunAfterOtherAnyTimeScripts, "Run after Other Anytime Scripts", folderNames.RunAfterOtherAnyTimeScripts, AnyTime) },
{ Permissions, new MigrationsFolder(Permissions, "Permissions", folderNames.Permissions, EveryTime, TransactionHandling: TransactionHandling.Autonomous) },
{ AfterMigration, new MigrationsFolder(AfterMigration, "AfterMigration", folderNames.AfterMigration, EveryTime, TransactionHandling: TransactionHandling.Autonomous) },
};
foldersConfiguration.CreateDatabase = new MigrationsFolder("CreateDatabase", folderNames.CreateDatabase, AnyTime, ConnectionType.Admin, TransactionHandling.Autonomous);
foldersConfiguration.DropDatabase = new MigrationsFolder("DropDatabase", folderNames.DropDatabase, AnyTime, ConnectionType.Admin, TransactionHandling.Autonomous);
foldersConfiguration.CreateDatabase = new MigrationsFolder(KnownFolderKeys.CreateDatabase, "CreateDatabase", folderNames.CreateDatabase, AnyTime, ConnectionType.Admin, TransactionHandling.Autonomous);
foldersConfiguration.DropDatabase = new MigrationsFolder(KnownFolderKeys.DropDatabase, "DropDatabase", folderNames.DropDatabase, AnyTime, ConnectionType.Admin, TransactionHandling.Autonomous);

return foldersConfiguration;
}
Expand Down
7 changes: 4 additions & 3 deletions src/grate.core/Configuration/KnownFolderKeys.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ internal static class KnownFolderKeys
public const string RunAfterOtherAnyTimeScripts = nameof(RunAfterOtherAnyTimeScripts);
public const string Permissions = nameof(Permissions);
public const string AfterMigration = nameof(AfterMigration);
public const string DropDatabase = nameof(DropDatabase);

public static readonly IEnumerable<string> Keys = new[]
{
public static readonly IEnumerable<string> Keys =
[
CreateDatabase, BeforeMigration, AlterDatabase, RunAfterCreateDatabase, RunBeforeUp, Up, RunFirstAfterUp, Functions, Views,
Sprocs, Triggers, Indexes, RunAfterOtherAnyTimeScripts, Permissions, AfterMigration
};
];
}
14 changes: 12 additions & 2 deletions src/grate.core/Configuration/MigrationsFolder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ namespace grate.Configuration;
/// <param name="TransactionHandling">Whether to roll back this folder if something fails, or run these
/// scripts in a separate, autonomous transactions, which makes them run no matter if other stuff errors.</param>
public record MigrationsFolder(
string Key,
string Name,
string Path,
MigrationType Type = MigrationType.Once,
Expand All @@ -21,12 +22,21 @@ public record MigrationsFolder(
{
public MigrationsFolder(
string name,
string path,
MigrationType type = MigrationType.Once,
ConnectionType connectionType = ConnectionType.Default,
TransactionHandling transactionHandling = TransactionHandling.Default)
: this(name, name, type, connectionType, transactionHandling)
: this(name, name, path, type, connectionType, transactionHandling)
{ }

public MigrationsFolder(
string name,
MigrationType type = MigrationType.Once,
ConnectionType connectionType = ConnectionType.Default,
TransactionHandling transactionHandling = TransactionHandling.Default)
: this(name, name, name, type, connectionType, transactionHandling)
{ }

public override string ToString() =>
$"{Name}=path:{Path},type:{Type},connectionType:{ConnectionType},transactionHandling:{TransactionHandling}";
$"{Key}=name:{Name},path:{Path},type:{Type},connectionType:{ConnectionType},transactionHandling:{TransactionHandling}";
}
2 changes: 1 addition & 1 deletion src/grate.core/Migration/GrateMigrator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ public async Task Migrate()

// This is an ugly "if" run on every script, to check one special folder which has conditions.
// If possible, we should find a 'cleaner' way to do this.
if (nameof(KnownFolderNames.RunAfterCreateDatabase).Equals(folder?.Name) && !databaseCreated)
if (KnownFolderKeys.RunAfterCreateDatabase.Equals(folder?.Key) && !databaseCreated)
{
continue;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using MariaDB.TestInfrastructure;
using TestCommon.TestInfrastructure;

namespace MariaDB.Running_MigrationScripts;

[Collection(nameof(MariaDbGrateTestContext))]
// ReSharper disable once InconsistentNaming
// ReSharper disable once UnusedType.Global
public class Run_After_Create_Database_scripts(MariaDbGrateTestContext testContext, ITestOutputHelper testOutput)
: TestCommon.Generic.Running_MigrationScripts.Run_After_Create_Database_scripts(testContext, testOutput);
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Oracle.TestInfrastructure;
using TestCommon.TestInfrastructure;

namespace Oracle.Running_MigrationScripts;

[Collection(nameof(OracleGrateTestContext))]
// ReSharper disable once InconsistentNaming
// ReSharper disable once UnusedType.Global
public class Run_After_Create_Database_scripts(OracleGrateTestContext testContext, ITestOutputHelper testOutput)
: TestCommon.Generic.Running_MigrationScripts.Run_After_Create_Database_scripts(testContext, testOutput);
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Dapper;
using FluentAssertions;
using grate.Configuration;
using PostgreSQL.TestInfrastructure;
using TestCommon.TestInfrastructure;
using static grate.Configuration.KnownFolderKeys;

namespace PostgreSQL.Running_MigrationScripts;

[Collection(nameof(PostgreSqlGrateTestContext))]
// ReSharper disable once InconsistentNaming
// ReSharper disable once UnusedType.Global
public class Run_After_Create_Database_scripts(PostgreSqlGrateTestContext testContext, ITestOutputHelper testOutput)
: TestCommon.Generic.Running_MigrationScripts.Run_After_Create_Database_scripts(testContext, testOutput);
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using SqlServer.TestInfrastructure;
using TestCommon.TestInfrastructure;

namespace SqlServer.Running_MigrationScripts;

[Collection(nameof(SqlServerGrateTestContext))]
// ReSharper disable once InconsistentNaming
// ReSharper disable once UnusedType.Global
public class Run_After_Create_Database_scripts(SqlServerGrateTestContext testContext, ITestOutputHelper testOutput)
: TestCommon.Generic.Running_MigrationScripts.Run_After_Create_Database_scripts(testContext, testOutput);
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public record SqlServerTestContainerDatabase(
: TestContainerDatabase(GrateTestConfig)
{
// Run with linux/amd86 on ARM architectures too, the docker emulation is good enough
public override string DockerImage => GrateTestConfig.DockerImage ?? "mcr.microsoft.com/mssql/server:2022-latest";
public override string DockerImage => GrateTestConfig.DockerImage ?? "mcr.microsoft.com/mssql/server:2022-preview-ubuntu-22.04";
protected override int InternalPort => MsSqlBuilder.MsSqlPort;
protected override string NetworkAlias => "sqlserver-test-container";

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using SqlServerCaseSensitive.TestInfrastructure;
using TestCommon.TestInfrastructure;

namespace SqlServerCaseSensitive.Running_MigrationScripts;

[Collection(nameof(SqlServerGrateTestContext))]
// ReSharper disable once InconsistentNaming
// ReSharper disable once UnusedType.Global
public class Run_After_Create_Database_scripts(SqlServerGrateTestContext testContext, ITestOutputHelper testOutput)
: TestCommon.Generic.Running_MigrationScripts.Run_After_Create_Database_scripts(testContext, testOutput);
38 changes: 7 additions & 31 deletions unittests/Sqlite/Database.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Microsoft.Data.Sqlite;
using Sqlite.TestInfrastructure;
using Sqlite.TestInfrastructure;
using TestCommon.TestInfrastructure;
using static TestCommon.TestInfrastructure.DatabaseHelpers;

namespace Sqlite;

Expand All @@ -10,37 +10,13 @@ namespace Sqlite;
public class Database(SqliteGrateTestContext testContext, ITestOutputHelper testOutput)
: TestCommon.Generic.GenericDatabase(testContext, testOutput)
{

protected override async Task CreateDatabaseFromConnectionString(string db, string connectionString)
{
await using var conn = new SqliteConnection(connectionString);
conn.Open();
await using var cmd = conn.CreateCommand();

// Create a table to actually create the .sqlite file
var sql = "CREATE TABLE dummy(name VARCHAR(1))";
cmd.CommandText = sql;
await cmd.ExecuteNonQueryAsync();

// Remove the table to avoid polluting the database with dummy tables :)
sql = "DROP TABLE dummy";
cmd.CommandText = sql;
await cmd.ExecuteNonQueryAsync();
}

protected override async Task<IEnumerable<string>> GetDatabases()
{
var builder = new SqliteConnectionStringBuilder(this.Context.AdminConnectionString);
var root = Path.GetDirectoryName(builder.DataSource) ?? Directory.CreateTempSubdirectory().ToString() ;
var dbFiles = Directory.EnumerateFiles(root, "*.db");
IEnumerable<string> dbNames = dbFiles
.Select(Path.GetFileNameWithoutExtension)
.Where(name => name is not null)
.Cast<string>();

return await ValueTask.FromResult(dbNames);
}
protected override Task CreateDatabaseFromConnectionString(string db, string connectionString)
=> CreateSqliteDatabaseFromConnectionString(connectionString);

protected override Task<IEnumerable<string>> GetDatabases()
=> Context.GetSqliteDatabases();

[Fact(Skip = "SQLite does not support custom database creation script")]
public override Task Is_created_with_custom_script_if_custom_create_database_folder_exists() =>
Task.CompletedTask;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Sqlite.TestInfrastructure;
using TestCommon.TestInfrastructure;
using static TestCommon.TestInfrastructure.DatabaseHelpers;

namespace Sqlite.Running_MigrationScripts;

[Collection(nameof(SqliteTestDatabase))]
// ReSharper disable once InconsistentNaming
// ReSharper disable once UnusedType.Global
public class Run_After_Create_Database_scripts(SqliteGrateTestContext testContext, ITestOutputHelper testOutput)
: TestCommon.Generic.Running_MigrationScripts.Run_After_Create_Database_scripts(testContext, testOutput)
{
[Fact(Skip = "Sqlite does not support creating databases using grate")]
public override Task Are_run_if_the_database_is_created_from_scratch() => Task.CompletedTask;

protected override Task<IEnumerable<string>> GetDatabases() => Context.GetSqliteDatabases();

protected override Task CreateDatabaseFromConnectionString(string db, string connectionString)
=> CreateSqliteDatabaseFromConnectionString(connectionString);
}
90 changes: 3 additions & 87 deletions unittests/TestCommon/Generic/GenericDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,94 +157,10 @@ public async Task Does_not_need_admin_connection_if_database_already_exists(stri

protected Task CreateDatabase(string db) => CreateDatabaseFromConnectionString(db, Context.ConnectionString(db));

protected virtual async Task CreateDatabaseFromConnectionString(string db, string connectionString)
{
var uid = TestConfig.Username(connectionString);
var pwd = TestConfig.Password(connectionString);

using (new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled))
{
for (var i = 0; i < 5; i++)
{
try
{
using var conn = Context.CreateAdminDbConnection();

string? commandText = null;
try
{
commandText = Context.Syntax.CreateDatabase(db, pwd);
await conn.ExecuteAsync(commandText);
}
catch (DbException dbe)
{
TestOutput.WriteLine("Got error when creating database: " + dbe.Message);
TestOutput.WriteLine("database: " + db);
TestOutput.WriteLine("admin connection string: " + conn.ConnectionString);
TestOutput.WriteLine("user connection string: " + connectionString);
TestOutput.WriteLine("commandText: " + commandText);
}

string? createUserSql = null;
try
{
createUserSql = Context.Sql.CreateUser(db, uid, pwd);
if (createUserSql is not null)
{
await conn.ExecuteAsync(createUserSql);
}
}
catch (DbException dbe)
{
TestOutput.WriteLine("Got error when creating user: " + dbe.Message);
TestOutput.WriteLine("Error creating user: " + uid + " for database: " + db);
TestOutput.WriteLine("admin connection string: " + conn.ConnectionString);
TestOutput.WriteLine("user connection string: " + connectionString);
TestOutput.WriteLine("createUserSql: " + createUserSql);
}

var grantAccessSql = Context.Sql.GrantAccess(db, uid);
if (grantAccessSql is not null)
{
await conn.ExecuteAsync(grantAccessSql);
}

break;
}
catch (DbException dbe)
{
TestOutput.WriteLine($"Got error in loop, iteration: {i}: {dbe.Message}");
}

await Task.Delay(1000);
}
}
}
protected virtual Task CreateDatabaseFromConnectionString(string db, string connectionString) =>
Context.CreateDatabaseFromConnectionString(db, connectionString, TestOutput);

protected virtual async Task<IEnumerable<string>> GetDatabases()
{
IEnumerable<string> databases = Enumerable.Empty<string>();
string sql = Context.Syntax.ListDatabases;

using (new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled))
{
for (var i = 0; i < 5; i++)
{
using var conn = Context.CreateAdminDbConnection();
try
{
databases = (await conn.QueryAsync<string>(sql)).ToArray();
break;
}
catch (DbException dbe)
{
TestOutput.WriteLine("Got error when listing databases: " + dbe.Message);
TestOutput.WriteLine("admin connection string: " + conn.ConnectionString);
}
}
}
return databases.ToArray();
}
protected virtual Task<IEnumerable<string>> GetDatabases() => Context.GetDatabases(TestOutput);

protected virtual bool ThrowOnMissingDatabase => true;

Expand Down
Loading
Loading