From 1551475319ebe71b9db4ebc65543b63facd57f13 Mon Sep 17 00:00:00 2001 From: Tim Thompson Date: Fri, 8 Oct 2021 04:46:53 +1000 Subject: [PATCH] Feat #54: Add --baseline support (#63) --- docs/MigratingFromRoundhousE.md | 3 +- grate.unittests/CommandLineParsing.cs | 16 +++++-- .../Everytime_scripts.cs | 45 ++++++++++++++++++- grate/Commands/MigrateCommand.cs | 16 ++++--- grate/Configuration/GrateConfiguration.cs | 9 +++- grate/Infrastructure/TokenProvider.cs | 2 +- grate/Migration/DbMigrator.cs | 7 +++ 7 files changed, 84 insertions(+), 14 deletions(-) diff --git a/docs/MigratingFromRoundhousE.md b/docs/MigratingFromRoundhousE.md index 83b206cd..2e1bbd7b 100644 --- a/docs/MigratingFromRoundhousE.md +++ b/docs/MigratingFromRoundhousE.md @@ -19,7 +19,7 @@ grate is built using the new [`System.CommandLine`](https://github.com/dotnet/co - grate has a single mandatory `-cs`/`--connstring` argument for simplicity. RH's `--database`, `--server`, `--accesstoken` etc arguments are now longer allowed. - Not all previously supported tokens are available yet. For more information see the [Token Replacement docs](TokenReplacement.md). -- UserTokens have had two small changes. In keeping with the `System.CommandLine` standards the `--ut` option is now passed multiple times for multiple tokens, rather than parsing a single ';' delimited string. As a result of this change, the longer form of the option is now `--usertoken` (singular). +- UserTokens have had two small changes. In keeping with the `System.CommandLine` standards the `--ut` option is now passed multiple times for multiple tokens, rather than parsing a single ';' delimited string. Support for `;` delimited lists of tokens may be re-added in the future. - The `DropCreate` 'mode' has been merged with the `--drop` option. RH used a single-run workflow for `Normal` and `RestoreRun` modes, but needed two executions for the `DropCreate` mode. grate uses the `--drop` option like RH but **it continues with the creation and migration afterwards**! If you have a scenario for dropping a database but _not_ then running a migration, please open an issue! @@ -32,7 +32,6 @@ Rebuilding a decade old product from scratch takes time, and features have to be Expect this list to shrink over time. -- `--baseline` - `--defaultencoding` - `--isuptodate` - MSBuild Task. diff --git a/grate.unittests/CommandLineParsing.cs b/grate.unittests/CommandLineParsing.cs index 5b9f9ab3..01234d79 100644 --- a/grate.unittests/CommandLineParsing.cs +++ b/grate.unittests/CommandLineParsing.cs @@ -1,5 +1,6 @@ using System.CommandLine.Invocation; using System.CommandLine.Parsing; +using System.Linq; using System.Threading.Tasks; using FluentAssertions; using grate.Commands; @@ -217,6 +218,14 @@ public async Task WarnAndIgnoreOnOneTimeScriptChanges(string args, bool expected cfg?.WarnAndIgnoreOnOneTimeScriptChanges.Should().Be(expected); } + [TestCase("", false)] + [TestCase("--baseline", true)] + public async Task Baseline(string args, bool expected) + { + var cfg = await ParseGrateConfiguration(args); + cfg?.Baseline.Should().Be(expected); + } + [Test] public async Task WithoutTransaction_Default() { @@ -226,12 +235,13 @@ public async Task WithoutTransaction_Default() [TestCase("--silent", 0)] [TestCase("--ut=token=value", 1)] - [TestCase("--ut=token=value;abe=123", 2)] - [TestCase("--ut=token=value --usertoken=abc=123", 2)] + [TestCase("--ut=token=value --usertokens=abc=123", 2)] + //[TestCase("--usertokens=token=value;abe=123", 2)] This is a back-compat scenario we may want to add support for. public async Task UserTokens(string args, int expectedCount) { var cfg = await ParseGrateConfiguration(args); - cfg?.UserTokens?.Should().HaveCount(expectedCount); + var t = cfg?.UserTokens.Safe().ToList(); + t.Should().HaveCount(expectedCount); } [TestCase("", DatabaseType.sqlserver)] // default diff --git a/grate.unittests/Generic/Running_MigrationScripts/Everytime_scripts.cs b/grate.unittests/Generic/Running_MigrationScripts/Everytime_scripts.cs index 67ab3c88..0e8cf249 100644 --- a/grate.unittests/Generic/Running_MigrationScripts/Everytime_scripts.cs +++ b/grate.unittests/Generic/Running_MigrationScripts/Everytime_scripts.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using System.Threading.Tasks; using Dapper; using FluentAssertions; @@ -103,6 +104,48 @@ public async Task Are_recognized_by_script_name() scripts.Should().HaveCount(7); // one time script ran once, the two everytime scripts ran every time. } + [Test] + public async Task Are_not_run_in_baseline() + { + var db = TestConfig.RandomDatabase(); + + var knownFolders = KnownFolders.In(CreateRandomTempDirectory()); + var config = new GrateConfiguration + { + Baseline = true, // this is important! + CreateDatabase = true, + ConnectionString = Context.ConnectionString(db), + AdminConnectionString = Context.AdminConnectionString, + Version = "a.b.c.e", + KnownFolders = knownFolders, + AlterDatabase = true, + NonInteractive = true, + Transaction = true, + DatabaseType = Context.DatabaseType + }; + + var path = knownFolders?.Views?.Path ?? throw new Exception("Config Fail"); + + WriteSql(path, "view.sql", "create view grate as select '1' as col;"); + + await using (var migrator = Context.GetMigrator(config)) + { + await migrator.Migrate(); + } + + string sql = $"SELECT script_name FROM {Context.Syntax.TableWithSchema("grate", "ScriptsRun")}"; + + await using var conn = Context.CreateDbConnection(Context.ConnectionString(db)); + var scripts = (await conn.QueryAsync(sql)).ToArray(); + scripts.Should().HaveCount(1); //marked as run + + // but doesn't exist + Assert.ThrowsAsync(Context.DbExceptionType, async () => await conn.QueryAsync("select * from grate")); + + + + } + private void CreateEveryTimeScriptFile(MigrationsFolder? folder) { var dummySql = Context.Sql.SelectCurrentDatabase; diff --git a/grate/Commands/MigrateCommand.cs b/grate/Commands/MigrateCommand.cs index 86fddb83..4b781c98 100644 --- a/grate/Commands/MigrateCommand.cs +++ b/grate/Commands/MigrateCommand.cs @@ -34,6 +34,7 @@ public MigrateCommand(GrateMigrator mi) : base("Migrates the database") Add(WarnAndIgnoreOnScriptChange()); Add(UserTokens()); Add(DoNotStoreScriptText()); + Add(Baseline()); Add(RunAllAnyTimeScripts()); Add(DryRun()); @@ -158,18 +159,18 @@ private static Option Tokens() => private static Option WarnAndRunOnScriptChange() => new( new[] { "-w", "--warnononetimescriptchanges" }, - "WarnOnOneTimeScriptChanges - Instructs grate to execute changed one time scripts (DDL/DML in Upfolder) that have previously been run against the database instead of failing. A warning is logged for each one time script that is rerun. Defaults to false." - ); + "WarnOnOneTimeScriptChanges - Instructs grate to execute changed one time scripts(DDL / DML in Upfolder) that have previously been run against the database instead of failing. A warning is logged for each one time script that is rerun. Defaults to false." + ); private static Option WarnAndIgnoreOnScriptChange() => new( new[] { "--warnandignoreononetimescriptchanges" }, "WarnAndIgnoreOnOneTimeScriptChanges - Instructs grate to ignore and update the hash of changed one time scripts (DDL/DML in Up folder) that have previously been run against the database instead of failing. A warning is logged for each one time scripts that is rerun. Defaults to false." - ); + ); private static Option> UserTokens() => new( - new[] { "--ut", "--usertoken" }, + new[] { "--ut", "--usertokens" }, "User Tokens - Allows grate to perform token replacement on custom tokens ({{my_token}}). Set as a key=value pair, eg '--ut=my_token=myvalue'. Can be specified multiple times." ); @@ -184,8 +185,13 @@ private static Option RunAllAnyTimeScripts() => new[] { "--runallanytimescripts", "--forceanytimescripts" }, "RunAllAnyTimeScripts - This instructs grate to run any time scripts every time it is run even if they haven't changed. Defaults to false." ); - + private static Option Baseline() => + new( + new[] { "--baseline" }, + "Baseline - This instructs grate to mark the scripts as run, but not to actually run anything against the database. Use this option if you already have scripts that have been run through other means (and BEFORE you start the new ones)." + ); + private static Option DryRun() => new( new[] { "--dryrun" }, diff --git a/grate/Configuration/GrateConfiguration.cs b/grate/Configuration/GrateConfiguration.cs index b52b77f0..1ad79cc3 100644 --- a/grate/Configuration/GrateConfiguration.cs +++ b/grate/Configuration/GrateConfiguration.cs @@ -91,7 +91,7 @@ public string? AdminConnectionString public bool WarnAndIgnoreOnOneTimeScriptChanges { get; init; } //private static KnownFolders InCurrentDirectory() => KnownFolders.In(CurrentDirectory); - + /// /// The set of user-provided "key=value" pairs for use in token replacement. /// @@ -104,7 +104,12 @@ public string? AdminConnectionString /// /// If true grate will not store script text in the database to save space in small/embedded databases. /// - public bool DoNotStoreScriptsRunText { get; init; } + public bool DoNotStoreScriptsRunText { get; init; } + + /// + /// If true we mark scripts as run but don't actually run them. + /// + public bool Baseline { get; init; } /// /// If true we need to log what we would have done, but NOT run any SQL diff --git a/grate/Infrastructure/TokenProvider.cs b/grate/Infrastructure/TokenProvider.cs index ed14ca21..88330943 100644 --- a/grate/Infrastructure/TokenProvider.cs +++ b/grate/Infrastructure/TokenProvider.cs @@ -32,7 +32,7 @@ public TokenProvider(GrateConfiguration config, IDatabase db) ["AfterMigrationFolderName"] = _config.KnownFolders?.AfterMigration.ToToken(), ["AlterDatabaseFolderName"] = _config.KnownFolders?.AlterDatabase.ToToken(), - //["Baseline"] = _config.Baseline.ToString(), + ["Baseline"] = _config.Baseline.ToString(), ["BeforeMigrationFolderName"] = _config.KnownFolders?.BeforeMigration.ToToken(), ["CommandTimeout"] = _config.CommandTimeout.ToString(), ["CommandTimeoutAdmin"] = _config.AdminCommandTimeout.ToString(), diff --git a/grate/Migration/DbMigrator.cs b/grate/Migration/DbMigrator.cs index 209d2daa..9d27c79b 100644 --- a/grate/Migration/DbMigrator.cs +++ b/grate/Migration/DbMigrator.cs @@ -90,6 +90,12 @@ async Task LogAndRunSql() sql = ReplaceTokensIn(sql); } + if (Configuration.Baseline) + { + await RecordScriptInScriptsRunTable(scriptName, sql, migrationType, versionId); + return false; + } + if (await ThisScriptIsAlreadyRun(scriptName) && !IsEverytimeScript(scriptName, migrationType)) { @@ -233,6 +239,7 @@ private async Task RunTheActualSql( throw; } } + await RecordScriptInScriptsRunTable(scriptName, sql, migrationType, versionId); }