Skip to content

Commit 7dc9f1e

Browse files
author
Erik A. Brandstadmoen
committedOct 1, 2022
Solve #245 without new command-line parameter
1 parent d67ff0e commit 7dc9f1e

12 files changed

+187
-29
lines changed
 

‎grate.unittests/Generic/GenericMigrationTables.cs

+95
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,92 @@ public async Task Migration_does_not_fail_if_table_already_exists(string tableNa
9898
Assert.DoesNotThrowAsync(() => migrator.Migrate());
9999
}
100100
}
101+
102+
[TestCase("version")]
103+
[TestCase("vErSiON")]
104+
public async Task Does_not_create_Version_table_if_it_exists_with_another_casing(string existingTable)
105+
{
106+
await CheckTableCasing("Version", existingTable, (config, name) => config.VersionTableName = name);
107+
}
108+
109+
[TestCase("scriptsrun")]
110+
[TestCase("SCRiptSrUN")]
111+
public async Task Does_not_create_ScriptsRun_table_if_it_exists_with_another_casing(string existingTable)
112+
{
113+
await CheckTableCasing("ScriptsRun", existingTable, (config, name) => config.ScriptsRunTableName = name);
114+
}
115+
116+
[TestCase("scriptsrunerrors")]
117+
[TestCase("ScripTSRunErrors")]
118+
public async Task Does_not_create_ScriptsRunErrors_table_if_it_exists_with_another_casing(string existingTable)
119+
{
120+
await CheckTableCasing("ScriptsRunErrors", existingTable, (config, name) => config.ScriptsRunErrorsTableName = name);
121+
}
122+
123+
124+
125+
private async Task CheckTableCasing(string tableName, string funnyCasing, Action<GrateConfiguration, string> setTableName)
126+
{
127+
var db = TestConfig.RandomDatabase();
128+
129+
var parent = TestConfig.CreateRandomTempDirectory();
130+
var knownFolders = FoldersConfiguration.Default();
131+
132+
// Set the version table name to be lower-case first, and run one migration.
133+
var config = Context.GetConfiguration(db, parent, knownFolders);
134+
135+
setTableName(config, funnyCasing);
136+
137+
await using (var migrator = Context.GetMigrator(config))
138+
{
139+
await migrator.Migrate();
140+
}
141+
142+
// Check that the table is indeed created with lower-case
143+
var errorCaseCountAfterFirstMigration = await TableCountIn(db, funnyCasing);
144+
var normalCountAfterFirstMigration = await TableCountIn(db, tableName);
145+
Assert.Multiple(() =>
146+
{
147+
errorCaseCountAfterFirstMigration.Should().Be(1);
148+
normalCountAfterFirstMigration.Should().Be(0);
149+
});
150+
151+
// Run migration again - make sure it does not create the table with different casing too
152+
setTableName(config, tableName);
153+
await using (var migrator = Context.GetMigrator(config))
154+
{
155+
await migrator.Migrate();
156+
}
157+
158+
var errorCaseCountAfterSecondMigration = await TableCountIn(db, funnyCasing);
159+
var normalCountAfterSecondMigration = await TableCountIn(db, tableName);
160+
Assert.Multiple(() =>
161+
{
162+
errorCaseCountAfterSecondMigration.Should().Be(1);
163+
normalCountAfterSecondMigration.Should().Be(0);
164+
});
165+
166+
}
167+
168+
private async Task<int> TableCountIn(string db, string tableName)
169+
{
170+
var schemaName = Context.DefaultConfiguration.SchemaName;
171+
var supportsSchemas = Context.DatabaseMigrator.SupportsSchemas;
172+
173+
var fullTableName = supportsSchemas ? tableName : Context.Syntax.TableWithSchema(schemaName, tableName);
174+
var tableSchema = supportsSchemas ? schemaName : db;
101175

176+
int count;
177+
string countSql = CountTableSql(tableSchema, fullTableName);
178+
179+
await using (var conn = Context.GetDbConnection(Context.ConnectionString(db)))
180+
{
181+
count = await conn.ExecuteScalarAsync<int>(countSql);
182+
}
183+
184+
return count;
185+
}
186+
102187
[Test()]
103188
public async Task Inserts_version_in_version_table()
104189
{
@@ -156,4 +241,14 @@ protected static DirectoryInfo MakeSurePathExists(DirectoryInfo? path)
156241
private static DirectoryInfo Wrap(DirectoryInfo root, string? relativePath) =>
157242
new(Path.Combine(root.ToString(), relativePath ?? ""));
158243

244+
protected virtual string CountTableSql(string schemaName, string tableName)
245+
{
246+
return $@"
247+
SELECT count(table_name) FROM information_schema.tables
248+
WHERE
249+
table_schema = '{schemaName}' AND
250+
table_name = '{tableName}'
251+
";
252+
}
253+
159254
}

‎grate.unittests/Oracle/MigrationTables.cs

+10-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,15 @@ namespace grate.unittests.Oracle;
55

66
[TestFixture]
77
[Category("Oracle")]
8-
public class MigrationTables: Generic.GenericMigrationTables
8+
public class MigrationTables : Generic.GenericMigrationTables
99
{
1010
protected override IGrateTestContext Context => GrateTestContext.Oracle;
11-
}
11+
12+
protected override string CountTableSql(string schemaName, string tableName)
13+
{
14+
return $@"
15+
SELECT COUNT(table_name) FROM user_tables
16+
WHERE
17+
lower(table_name) = '{tableName.ToLowerInvariant()}'";
18+
}
19+
}

‎grate.unittests/SqLite/MigrationTables.cs

+10-1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,13 @@ namespace grate.unittests.Sqlite;
88
public class MigrationTables: Generic.GenericMigrationTables
99
{
1010
protected override IGrateTestContext Context => GrateTestContext.Sqlite;
11-
}
11+
12+
protected override string CountTableSql(string schemaName, string tableName)
13+
{
14+
return $@"
15+
SELECT COUNT(name) FROM sqlite_master
16+
WHERE type ='table' AND
17+
name = '{tableName}';
18+
";
19+
}
20+
}

‎grate.unittests/TestInfrastructure/IGrateTestContext.cs

+11
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,17 @@ DefaultConfiguration with
5656
SqlFilesDirectory = sqlFilesDirectory
5757
};
5858

59+
public GrateConfiguration GetConfiguration(string databaseName, DirectoryInfo sqlFilesDirectory,
60+
IFoldersConfiguration knownFolders, string? env, bool runInTransaction) =>
61+
DefaultConfiguration with
62+
{
63+
ConnectionString = ConnectionString(databaseName),
64+
Folders = knownFolders,
65+
Environment = env != null ? new GrateEnvironment(env) : null,
66+
Transaction = runInTransaction,
67+
SqlFilesDirectory = sqlFilesDirectory
68+
};
69+
5970
public GrateMigrator GetMigrator(GrateConfiguration config)
6071
{
6172
var factory = Substitute.For<IFactory>();

‎grate/Configuration/GrateConfiguration.cs

+4
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ public record GrateConfiguration
2525
public string? ConnectionString { get; init; } = null;
2626

2727
public string SchemaName { get; init; } = "grate";
28+
29+
public string ScriptsRunTableName { get; set; } = "ScriptsRun";
30+
public string ScriptsRunErrorsTableName { get; set; } = "ScriptsRunErrors";
31+
public string VersionTableName { get; set; } = "Version";
2832

2933
public string? AdminConnectionString
3034
{

‎grate/Migration/AnsiSqlDatabase.cs

+47-18
Original file line numberDiff line numberDiff line change
@@ -43,26 +43,40 @@ protected AnsiSqlDatabase(ILogger logger, ISyntax syntax)
4343
.Split("=", TrimEntries | RemoveEmptyEntries).Last();
4444

4545
public abstract bool SupportsDdlTransactions { get; }
46-
protected abstract bool SupportsSchemas { get; }
46+
public abstract bool SupportsSchemas { get; }
4747
public bool SplitBatchStatements => true;
4848

4949
public string StatementSeparatorRegex => _syntax.StatementSeparatorRegex;
5050

51-
public string ScriptsRunTable => _syntax.TableWithSchema(SchemaName, "ScriptsRun");
52-
public string ScriptsRunErrorsTable => _syntax.TableWithSchema(SchemaName, "ScriptsRunErrors");
53-
public string VersionTable => _syntax.TableWithSchema(SchemaName, "Version");
51+
public string ScriptsRunTable => _syntax.TableWithSchema(SchemaName, ScriptsRunTableName);
52+
public string ScriptsRunErrorsTable => _syntax.TableWithSchema(SchemaName, ScriptsRunErrorsTableName);
53+
public string VersionTable => _syntax.TableWithSchema(SchemaName, VersionTableName);
5454

55-
public virtual Task InitializeConnections(GrateConfiguration configuration)
55+
private string ScriptsRunTableName { get; set; }
56+
private string ScriptsRunErrorsTableName { get; set; }
57+
private string VersionTableName { get; set; }
58+
59+
public virtual async Task InitializeConnections(GrateConfiguration configuration)
5660
{
5761
Logger.LogInformation("Initializing connections.");
5862

5963
ConnectionString = configuration.ConnectionString;
6064
AdminConnectionString = configuration.AdminConnectionString;
65+
6166
SchemaName = configuration.SchemaName;
67+
68+
VersionTableName = configuration.VersionTableName;
69+
ScriptsRunTableName = configuration.ScriptsRunTableName;
70+
ScriptsRunErrorsTableName = configuration.ScriptsRunErrorsTableName;
71+
6272
Config = configuration;
63-
return Task.CompletedTask;
6473
}
6574

75+
private async Task<string> ExistingOrDefault(string schemaName, string tableName) =>
76+
await ExistingTable(schemaName, tableName) ?? tableName;
77+
78+
79+
6680
private string? AdminConnectionString { get; set; }
6781
protected string? ConnectionString { get; set; }
6882

@@ -263,6 +277,9 @@ private async Task<bool> RunSchemaExists()
263277

264278
protected virtual async Task CreateScriptsRunTable()
265279
{
280+
// Update scripts run table name with the correct casing, should it differ from the standard
281+
ScriptsRunTableName = await ExistingOrDefault(SchemaName, ScriptsRunTableName);
282+
266283
string createSql = $@"
267284
CREATE TABLE {ScriptsRunTable}(
268285
{_syntax.PrimaryKeyColumn("id")},
@@ -285,6 +302,9 @@ protected virtual async Task CreateScriptsRunTable()
285302

286303
protected virtual async Task CreateScriptsRunErrorsTable()
287304
{
305+
// Update scripts run errors table name with the correct casing, should it differ from the standard
306+
ScriptsRunErrorsTableName = await ExistingOrDefault(SchemaName, ScriptsRunErrorsTableName);
307+
288308
string createSql = $@"
289309
CREATE TABLE {ScriptsRunErrorsTable}(
290310
{_syntax.PrimaryKeyColumn("id")},
@@ -307,6 +327,9 @@ protected virtual async Task CreateScriptsRunErrorsTable()
307327

308328
protected virtual async Task CreateVersionTable()
309329
{
330+
// Update version table name with the correct casing, should it differ from the standard
331+
VersionTableName = await ExistingOrDefault(SchemaName, VersionTableName);
332+
310333
string createSql = $@"
311334
CREATE TABLE {VersionTable}(
312335
{_syntax.PrimaryKeyColumn("id")},
@@ -317,6 +340,7 @@ protected virtual async Task CreateVersionTable()
317340
entered_by {_syntax.VarcharType}(50) NULL
318341
{_syntax.PrimaryKeyConstraint("Version", "id")}
319342
)";
343+
320344
if (!await VersionTableExists())
321345
{
322346
await ExecuteNonQuery(ActiveConnection, createSql, Config?.CommandTimeout);
@@ -335,21 +359,26 @@ ALTER TABLE {VersionTable}
335359
}
336360
}
337361

338-
protected async Task<bool> ScriptsRunTableExists() => await TableExists(SchemaName, "ScriptsRun");
339-
protected async Task<bool> ScriptsRunErrorsTableExists() => await TableExists(SchemaName, "ScriptsRunErrors");
340-
public async Task<bool> VersionTableExists() => await TableExists(SchemaName, "Version");
341-
protected async Task<bool> StatusColumnInVersionTableExists() => await ColumnExists(SchemaName, "Version", "status");
362+
protected async Task<bool> ScriptsRunTableExists() => (await ExistingTable(SchemaName, ScriptsRunTableName) is not null) ;
363+
protected async Task<bool> ScriptsRunErrorsTableExists() => (await ExistingTable(SchemaName, ScriptsRunErrorsTableName) is not null);
364+
public async Task<bool> VersionTableExists() => (await ExistingTable(SchemaName, VersionTableName) is not null);
365+
366+
protected async Task<bool> StatusColumnInVersionTableExists() => await ColumnExists(SchemaName, VersionTableName, "status");
342367

343-
public async Task<bool> TableExists(string schemaName, string tableName)
368+
public async Task<string?> ExistingTable(string schemaName, string tableName)
344369
{
345370
var fullTableName = SupportsSchemas ? tableName : _syntax.TableWithSchema(schemaName, tableName);
346371
var tableSchema = SupportsSchemas ? schemaName : DatabaseName;
372+
347373

348374
string existsSql = ExistsSql(tableSchema, fullTableName);
349375

350376
var res = await ExecuteScalarAsync<object>(ActiveConnection, existsSql);
351377

352-
return !DBNull.Value.Equals(res) && res is not null;
378+
var name = (!DBNull.Value.Equals(res) && res is not null) ? (string) res : null;
379+
380+
var prefix = SupportsSchemas ? string.Empty : _syntax.TableWithSchema(schemaName, string.Empty);
381+
return name?[prefix.Length..] ;
353382
}
354383

355384
private async Task<bool> ColumnExists(string schemaName, string tableName, string columnName)
@@ -366,10 +395,10 @@ private async Task<bool> ColumnExists(string schemaName, string tableName, strin
366395
protected virtual string ExistsSql(string tableSchema, string fullTableName)
367396
{
368397
return $@"
369-
SELECT * FROM information_schema.tables
398+
SELECT table_name FROM information_schema.tables
370399
WHERE
371-
table_schema = '{tableSchema}' AND
372-
table_name = '{fullTableName}'
400+
LOWER(table_schema) = LOWER('{tableSchema}') AND
401+
LOWER(table_name) = LOWER('{fullTableName}')
373402
";
374403
}
375404

@@ -378,9 +407,9 @@ protected virtual string ExistsSql(string tableSchema, string fullTableName, str
378407
return $@"
379408
SELECT * FROM information_schema.columns
380409
WHERE
381-
table_schema = '{tableSchema}' AND
382-
table_name = '{fullTableName}' AND
383-
column_name = '{columnName}'
410+
LOWER(table_schema) = LOWER('{tableSchema}') AND
411+
LOWER(table_name) = LOWER('{fullTableName}') AND
412+
LOWER(column_name) = LOWER('{columnName}')
384413
";
385414
}
386415

‎grate/Migration/IDatabase.cs

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public interface IDatabase : IAsyncDisposable
1717
public string ScriptsRunErrorsTable { get; }
1818
public string VersionTable { get; }
1919
DbConnection ActiveConnection { set; }
20+
bool SupportsSchemas { get; }
2021

2122
Task InitializeConnections(GrateConfiguration configuration);
2223
Task OpenConnection();
@@ -48,4 +49,5 @@ Task InsertScriptRun(string scriptName, string? sql, string hash, bool runOnce,
4849
void SetDefaultConnectionActive();
4950
Task<IDisposable> OpenNewActiveConnection();
5051
Task OpenActiveConnection();
52+
Task<string?> ExistingTable(string schemaName, string tableName);
5153
}

‎grate/Migration/MariaDbDatabase.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public MariaDbDatabase(ILogger<MariaDbDatabase> logger)
1515
{ }
1616

1717
public override bool SupportsDdlTransactions => false;
18-
protected override bool SupportsSchemas => false;
18+
public override bool SupportsSchemas => false;
1919
protected override DbConnection GetSqlConnection(string? connectionString) => new MySqlConnection(connectionString);
2020

2121
public override Task RestoreDatabase(string backupPath)

‎grate/Migration/OracleDatabase.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ public OracleDatabase(ILogger<OracleDatabase> logger)
2323
}
2424

2525
public override bool SupportsDdlTransactions => false;
26-
protected override bool SupportsSchemas => false;
26+
public override bool SupportsSchemas => false;
2727

2828
protected override DbConnection GetSqlConnection(string? connectionString) => new OracleConnection(connectionString);
2929

3030
protected override string ExistsSql(string tableSchema, string fullTableName) =>
3131
$@"
32-
SELECT * FROM user_tables
32+
SELECT table_name FROM user_tables
3333
WHERE
3434
lower(table_name) = '{fullTableName.ToLowerInvariant()}'
3535
";

‎grate/Migration/PostgreSqlDatabase.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ public PostgreSqlDatabase(ILogger<PostgreSqlDatabase> logger)
1313
{ }
1414

1515
public override bool SupportsDdlTransactions => true;
16-
protected override bool SupportsSchemas => true;
16+
public override bool SupportsSchemas => true;
1717
protected override DbConnection GetSqlConnection(string? connectionString) => new NpgsqlConnection(connectionString);
1818

1919
public override Task RestoreDatabase(string backupPath)
2020
{
2121
throw new System.NotImplementedException("Restoring a database from file is not currently supported for Postgresql.");
2222
}
23-
}
23+
}

‎grate/Migration/SqLiteDatabase.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@ public SqliteDatabase(ILogger<SqliteDatabase> logger)
1717
{ }
1818

1919
public override bool SupportsDdlTransactions => false;
20-
protected override bool SupportsSchemas => false;
20+
public override bool SupportsSchemas => false;
2121
protected override DbConnection GetSqlConnection(string? connectionString) => new SqliteConnection(connectionString);
2222

2323
protected override string ExistsSql(string tableSchema, string fullTableName) =>
2424
$@"
2525
SELECT name FROM sqlite_master
2626
WHERE type ='table' AND
27-
name = '{fullTableName}';
27+
LOWER(name) = LOWER('{fullTableName}');
2828
";
2929

3030
protected override string ExistsSql(string tableSchema, string fullTableName, string columnName) =>

‎grate/Migration/SqlServerDatabase.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public SqlServerDatabase(ILogger<SqlServerDatabase> logger)
1616
{ }
1717

1818
public override bool SupportsDdlTransactions => true;
19-
protected override bool SupportsSchemas => true;
19+
public override bool SupportsSchemas => true;
2020
protected override DbConnection GetSqlConnection(string? connectionString)
2121
{
2222
var conn = new SqlConnection(connectionString);

0 commit comments

Comments
 (0)