diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a888ad..f52bdd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ Represents the **NuGet** versions. +## v3.2.0 +- *Enhancement:* The `MigrationCommand.Execute` has been enhanced to support the execution of YAML/JSON data files. Raw SQL must _now_ be prefixed by `>` to explicitly differentiate from a file path. +- *Enhancement:* The `MigrationCommand.Inspect` has been enhanced to also report whether each column has been identified as a JSON column (i.e. `DbColumnSchema.IsJson`). + ## v3.1.2 - *Fixed:* Where a `DbException` occurs executing a command, only the exception message is now logged; not the full stack trace as this is misleading (experience improvement). - *Fixed:* Prior to executing a command that requires the database, its existence will be checked first and an appropriate error emitted where not found (experience improvement). diff --git a/Common.targets b/Common.targets index fdfb43b..edd22f3 100644 --- a/Common.targets +++ b/Common.targets @@ -1,6 +1,6 @@ - 3.1.2 + 3.2.0 preview Avanade Avanade diff --git a/README.md b/README.md index abf356f..3fde8fb 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ Command | Description `ResetAndAll` | Performs `Reset` and `All` (designed primarily for testing). `ResetAndData` | Performs `Reset` and `Data` (designed primarily for testing). `ResetAndDatabase` | Performs `Reset` and `Database` (designed primarily for testing). -`Execute` | Executes the SQL statement(s) passed as additional arguments. +`Execute` | Executes the SQL statement(s) passed as additional arguments (each being a file-path or raw SQL statement). `Script` | Creates a new [`migration`](#Migrate) script file using the defined naming convention. [`Inspect`](#Inspect) | Inspects one or more existing database tables and outputs the inferred schema (columns, types, nullability, defaults, primary key, identity, computed and unique flags) as markdown to the console. @@ -210,8 +210,7 @@ Arguments: command Database migration command (see https://github.com/Avanade/dbex#commands-functions). Allowed values are: None, Drop, Create, Migrate, CodeGen, Schema, Deploy, Reset, Data, DeployWithData, Database, DropAndDatabase, All, DropAndAll, ResetAndData, ResetAndDatabase, ResetAndAll, Execute, Script, Inspect. - args Additional arguments; 'Script' arguments (first being the script name) -or- 'Execute' (each a SQL statement to invoke) -or- 'Inspect' (schema followed by one or more table names). - + args Additional arguments; 'Script' arguments (first being the script name) -or- 'Execute' (each being a file-path or raw SQL statement) -or- 'Inspect' (schema plus one or more table names). Options: -?|-h|--help Show help information. -cs|--connection-string Database connection string. @@ -285,13 +284,16 @@ dotnet run script cdc Foo Bar #### Execute command -The execute command allows one or more SQL Statements, and/or Script files, to be executed directly against the database. This is intended for enabling commands to be executed only. No response other than success or failure will be acknowledged; as such this is not intended for performing queries. +The execute command allows one or more SQL Statements, and/or Script files, to be executed directly against the database. This is intended for enabling commands to be executed only. No response other than success or failure will be acknowledged; as such this is not intended for performing queries. A raw SQL statement must be prefixed with a `>` character, otherwise it will be treated as a file path to a SQL script file. The SQL statement(s) or script file(s) must be specified in the order they are to be executed. + +Additionally, YAML and JSON data seeding files can be specified to be executed directly against the database. Examples as follows. ``` -dotnet run execute "create schema [Xyz] authorization [dbo]" +dotnet run execute "> create schema [Xyz] authorization [dbo]" dotnet run execute ./schema/createscehma.sql +dotnet run execute ./data/data.yaml ./data/other.json ```
diff --git a/src/DbEx.MySql/MySqlSchemaConfig.cs b/src/DbEx.MySql/MySqlSchemaConfig.cs index 2e48c60..646173b 100644 --- a/src/DbEx.MySql/MySqlSchemaConfig.cs +++ b/src/DbEx.MySql/MySqlSchemaConfig.cs @@ -84,7 +84,7 @@ public override DbColumnSchema CreateColumnFromInformationSchema(DbTableSchema t IsDotNetTimeOnly = RemovePrecisionFromDataType(dt).Equals("TIME", StringComparison.OrdinalIgnoreCase) }; - c.IsJsonContent = c.Type.Equals("JSON", StringComparison.OrdinalIgnoreCase) || (c.DotNetName == "string" && c.Name.EndsWith(JsonColumnNameSuffix, StringComparison.Ordinal)); + c.IsJsonContent = c.Type.Equals("JSON", StringComparison.OrdinalIgnoreCase) || (c.DotNetType == "string" && c.Name.EndsWith(JsonColumnNameSuffix, StringComparison.Ordinal)); if (c.IsJsonContent && c.Name.EndsWith(JsonColumnNameSuffix, StringComparison.Ordinal)) c.DotNetCleanedName = DbTableSchema.CreateDotNetName(c.Name[..^JsonColumnNameSuffix.Length]); diff --git a/src/DbEx.Postgres/PostgresSchemaConfig.cs b/src/DbEx.Postgres/PostgresSchemaConfig.cs index 6e52464..6e4101e 100644 --- a/src/DbEx.Postgres/PostgresSchemaConfig.cs +++ b/src/DbEx.Postgres/PostgresSchemaConfig.cs @@ -83,7 +83,7 @@ public override DbColumnSchema CreateColumnFromInformationSchema(DbTableSchema t IsDotNetTimeOnly = RemovePrecisionFromDataType(dr.GetValue("DATA_TYPE")!).Equals("TIME WITHOUT TIME ZONE", StringComparison.OrdinalIgnoreCase) }; - c.IsJsonContent = c.Type.Equals("JSON", StringComparison.OrdinalIgnoreCase) || (c.DotNetName == "string" && c.Name.EndsWith(JsonColumnNameSuffix, StringComparison.Ordinal)); + c.IsJsonContent = c.Type.Equals("JSON", StringComparison.OrdinalIgnoreCase) || (c.DotNetType == "string" && c.Name.EndsWith(JsonColumnNameSuffix, StringComparison.Ordinal)); if (c.IsJsonContent && c.Name.EndsWith(JsonColumnNameSuffix, StringComparison.Ordinal)) c.DotNetCleanedName = DbTableSchema.CreateDotNetName(c.Name[..^JsonColumnNameSuffix.Length]); diff --git a/src/DbEx.SqlServer/SqlServerSchemaConfig.cs b/src/DbEx.SqlServer/SqlServerSchemaConfig.cs index 36636d5..6028264 100644 --- a/src/DbEx.SqlServer/SqlServerSchemaConfig.cs +++ b/src/DbEx.SqlServer/SqlServerSchemaConfig.cs @@ -80,7 +80,8 @@ public override DbColumnSchema CreateColumnFromInformationSchema(DbTableSchema t IsDotNetTimeOnly = RemovePrecisionFromDataType(dr.GetValue("DATA_TYPE")!).Equals("TIME", StringComparison.OrdinalIgnoreCase), }; - if (c.IsJsonContent = c.DotNetName == "string" && c.Name.EndsWith(JsonColumnNameSuffix, StringComparison.Ordinal)) + c.IsJsonContent = c.Type.Equals("JSON", StringComparison.OrdinalIgnoreCase) || (c.DotNetType == "string" && c.Name.EndsWith(JsonColumnNameSuffix, StringComparison.Ordinal)); + if (c.IsJsonContent && c.Name.EndsWith(JsonColumnNameSuffix, StringComparison.Ordinal)) c.DotNetCleanedName = DbTableSchema.CreateDotNetName(c.Name[..^JsonColumnNameSuffix.Length]); return c; @@ -196,7 +197,7 @@ public override string ToDotNetTypeName(DbColumnSchema schema) return dbType.ToUpperInvariant() switch { - "NCHAR" or "CHAR" or "NVARCHAR" or "VARCHAR" or "TEXT" or "NTEXT" => "string", + "NCHAR" or "CHAR" or "NVARCHAR" or "VARCHAR" or "TEXT" or "NTEXT" or "JSON" => "string", "DECIMAL" or "MONEY" or "NUMERIC" or "SMALLMONEY" => "decimal", "DATETIME" or "DATETIME2" or "SMALLDATETIME" => "DateTime", "DATETIMEOFFSET" => "DateTimeOffset", diff --git a/src/DbEx/Console/MigrationConsoleBase.cs b/src/DbEx/Console/MigrationConsoleBase.cs index b92709e..6c75c5e 100644 --- a/src/DbEx/Console/MigrationConsoleBase.cs +++ b/src/DbEx/Console/MigrationConsoleBase.cs @@ -107,7 +107,7 @@ public async Task RunAsync(string[] args, CancellationToken cancellationTok ConsoleOptions.Add(DropSchemaObjectsName, app.Option("-dso|--drop-schema-objects", "Drop all known schema objects before applying; bypasses automatic skip where all scripts are replacements.", CommandOptionType.NoValue)); ConsoleOptions.Add(AcceptPromptsOptionName, app.Option("--accept-prompts", "Accept prompts; command should _not_ stop and wait for user confirmation (DROP or RESET commands).", CommandOptionType.NoValue)); ConsoleOptions.Add(ExpectNoChangesName, app.Option("--expect-no-changes", "Indicates to expect no changes during code-generation (i.e. result in error on change).", CommandOptionType.NoValue)); - _additionalArgs = app.Argument("args", "Additional arguments; 'Script' arguments (first being the script name) -or- 'Execute' (each a SQL statement to invoke).", multipleValues: true); + _additionalArgs = app.Argument("args", "Additional arguments; 'Script' arguments (first being the script name) -or- 'Execute' (each being a file-path or raw SQL statement) -or- 'Inspect' (schema where applicable plus one or more table names).", multipleValues: true); OnBeforeExecute(app); diff --git a/src/DbEx/Migration/DatabaseMigrationBase.cs b/src/DbEx/Migration/DatabaseMigrationBase.cs index 68b17ac..029f168 100644 --- a/src/DbEx/Migration/DatabaseMigrationBase.cs +++ b/src/DbEx/Migration/DatabaseMigrationBase.cs @@ -175,6 +175,9 @@ public virtual async Task MigrateAsync(CancellationToken cancellationToken if (!await CommandExecuteAsync(MigrationCommand.Drop, "DATABASE DROP: Checking database existence and dropping where found...", DatabaseDropAsync, null, cancellationToken).ConfigureAwait(false)) return false; + if (Args.MigrationCommand == MigrationCommand.Drop) + return true; // Where only dropping the database, then exit here to avoid errant database exists check coming up. + // Database create. if (!await CommandExecuteAsync(MigrationCommand.Create, "DATABASE CREATE: Checking database existence and creating where not found...", DatabaseCreateAsync, null, cancellationToken).ConfigureAwait(false)) return false; @@ -333,7 +336,7 @@ protected async Task CommandExecuteAsync(string title, Func ExecuteScriptsAsync(IEnumerable CreateScriptInternalAsync(string? name, IDictionary ExecuteSqlStatementsAsync(string[]? statements, CancellationToken cancellationToken = default) { PreExecutionInitialization(); - return await CommandExecuteAsync("DATABASE EXECUTE: Executes the SQL statement(s)...", async ct => await ExecuteSqlStatementsInternalAsync(statements, ct).ConfigureAwait(false), null, cancellationToken).ConfigureAwait(false); + return await CommandExecuteAsync($"DATABASE EXECUTE: Executing the {statements?.Length ?? 0} statement(s)...", async ct => await ExecuteSqlStatementsInternalAsync(statements, ct).ConfigureAwait(false), null, cancellationToken).ConfigureAwait(false); } /// - /// Executes the raw SQL statements. + /// Executes the SQL statements (files or raw SQL). /// private async Task ExecuteSqlStatementsInternalAsync(string[]? statements, CancellationToken cancellationToken) { @@ -1049,18 +1057,82 @@ private async Task ExecuteSqlStatementsInternalAsync(string[]? statements, if (statements.Length >= 1000) throw new ArgumentException("A maximum of 999 SQL statements may be executed at one-time.", nameof(statements)); - var sn = $"{DateTime.UtcNow.ToString("yyyyMMdd-HHmmss", System.Globalization.CultureInfo.InvariantCulture)}-console-execute-"; - - var scripts = new List(); for (int i = 0; i < statements.Length; i++) { - if (File.Exists(statements[i])) - scripts.Add(new DatabaseMigrationScript(this, new FileInfo(statements[i]), statements[i])); + Logger.LogInformation("{Content}", string.Empty); + + if (i > 0) + { + Logger.LogInformation("{Content}", $" {new string('-', 78)}"); + Logger.LogInformation("{Content}", string.Empty); + } + + if (!await ExecuteSqlStatementInternalAsync(statements[i], i, cancellationToken).ConfigureAwait(false)) + return false; + } + + return true; + } + + /// + /// Executes an individual SQL statement (file or raw SQL). + /// + private async Task ExecuteSqlStatementInternalAsync(string statement, int index, CancellationToken cancellationToken) + { + // If the statement is not a file, assume it is a raw SQL statement and create a temporary script to execute it. + if (!File.Exists(statement)) + { + if (string.IsNullOrWhiteSpace(statement)) + { + Logger.LogWarning("{Content}", $"** Statement is empty; skipping... [#{index + 1}]"); + return true; + } + + if (statement.StartsWith('>')) + statement = statement[1..]; else - scripts.Add(new DatabaseMigrationScript(this, statements[i], $"{sn}{i + 1:000}.{SchemaConfig.ScriptSuffix}")); + { + Logger.LogError("{Content}", $"Error: Statement is not a file (does not exist) or does not start with '>' (raw SQL). [#{index + 1}]"); + return false; + } + + if (string.IsNullOrWhiteSpace(statement)) + { + Logger.LogWarning("{Content}", $"** Statement is empty; skipping... [#{index + 1}]"); + return true; + } + + Logger.LogInformation("{Content}", $"** Executing: Raw SQL [#{index + 1}]..."); + var script = new DatabaseMigrationScript(this, statement, $"{DateTime.UtcNow.ToString("yyyyMMdd-HHmmss", System.Globalization.CultureInfo.InvariantCulture)}-console-execute.{index + 1:000}.{SchemaConfig.ScriptSuffix}"); + await ExecuteScriptAsync(script, cancellationToken).ConfigureAwait(false); + return true; + } + + var fi = new FileInfo(statement); + + // If the statement is a YAML or JSON file, parse it and execute the data insert/merge. + if (statement.EndsWith(".yaml", StringComparison.InvariantCultureIgnoreCase) || statement.EndsWith(".yml", StringComparison.InvariantCultureIgnoreCase)) + { + Logger.LogInformation("{Content}", $"** Parsing and executing: {fi.FullName} [#{index + 1}]..."); + using var sr = new StreamReader(statement); + var schema = await Database.SelectSchemaAsync(this, cancellationToken).ConfigureAwait(false); + var tables = await new DataParser(this, schema).ParseYamlAsync(sr, cancellationToken).ConfigureAwait(false); + return await DatabaseDataAsync(tables, cancellationToken).ConfigureAwait(false); } - return await ExecuteScriptsAsync(scripts, false, cancellationToken).ConfigureAwait(false); + if (statement.EndsWith(".json", StringComparison.InvariantCultureIgnoreCase) || statement.EndsWith(".jsn", StringComparison.InvariantCultureIgnoreCase)) + { + Logger.LogInformation("{Content}", $"** Parsing and executing: {fi.FullName} [#{index + 1}]..."); + using var sr = new StreamReader(statement); + var tables = await new DataParser(this, await Database.SelectSchemaAsync(this, cancellationToken).ConfigureAwait(false)).ParseJsonAsync(sr, cancellationToken).ConfigureAwait(false); + return await DatabaseDataAsync(tables, cancellationToken).ConfigureAwait(false); + } + + // Assume the statement is a SQL file, create a script to execute it. + Logger.LogInformation("{Content}", $"** Executing: {fi.FullName} [#{index + 1}] ..."); + var sqlScript = new DatabaseMigrationScript(this, new FileInfo(statement), Path.GetFileName(statement)); + await ExecuteScriptAsync(sqlScript, cancellationToken).ConfigureAwait(false); + return true; } /// @@ -1148,7 +1220,8 @@ private void InspectTableMarkdown(string? schema, string name, DbSchema.DbTableS c.IsPrimaryKey ? "Yes" : "No", c.IsIdentity ? "Yes" : "No", c.IsComputed ? "Yes" : "No", - c.IsUnique ? "Yes" : "No" + c.IsUnique ? "Yes" : "No", + c.IsJsonContent ? "Yes" : "No" ]); } @@ -1160,12 +1233,13 @@ private void InspectTableMarkdown(string? schema, string name, DbSchema.DbTableS var identityMaxLength = Math.Max("Identity".Length, columns.Max(x => x[5].Length)); var computedMaxLength = Math.Max("Computed".Length, columns.Max(x => x[6].Length)); var uniqueMaxLength = Math.Max("Unique".Length, columns.Max(x => x[7].Length)); + var jsonMaxLength = Math.Max("JSON".Length, columns.Max(x => x[8].Length)); - Logger.LogInformation("{Content}", $"| Column{new string(' ', columnMaxLength - "Column".Length)} | Type{new string(' ', typeMaxLength - "Type".Length)} | Null{new string(' ', isNullMaxLength - "Null".Length)} | Default{new string(' ', defaultMaxLength - "Default".Length)} | PK{new string(' ', pkMaxLength - "PK".Length)} | Identity{new string(' ', identityMaxLength - "Identity".Length)} | Computed{new string(' ', computedMaxLength - "Computed".Length)} | Unique{new string(' ', uniqueMaxLength - "Unique".Length)} |"); - Logger.LogInformation("{Content}", $"|-{"".PadRight(columnMaxLength, '-')}-|-{"".PadRight(typeMaxLength, '-')}-|-{"".PadRight(isNullMaxLength, '-')}-|-{"".PadRight(defaultMaxLength, '-')}-|-{"".PadRight(pkMaxLength, '-')}-|-{"".PadRight(identityMaxLength, '-')}-|-{"".PadRight(computedMaxLength, '-')}-|-{"".PadRight(uniqueMaxLength, '-')}-|"); + Logger.LogInformation("{Content}", $"| Column{new string(' ', columnMaxLength - "Column".Length)} | Type{new string(' ', typeMaxLength - "Type".Length)} | Null{new string(' ', isNullMaxLength - "Null".Length)} | Default{new string(' ', defaultMaxLength - "Default".Length)} | PK{new string(' ', pkMaxLength - "PK".Length)} | Identity{new string(' ', identityMaxLength - "Identity".Length)} | Computed{new string(' ', computedMaxLength - "Computed".Length)} | Unique{new string(' ', uniqueMaxLength - "Unique".Length)} | JSON{new string(' ', jsonMaxLength - "JSON".Length)} |"); + Logger.LogInformation("{Content}", $"|-{"".PadRight(columnMaxLength, '-')}-|-{"".PadRight(typeMaxLength, '-')}-|-{"".PadRight(isNullMaxLength, '-')}-|-{"".PadRight(defaultMaxLength, '-')}-|-{"".PadRight(pkMaxLength, '-')}-|-{"".PadRight(identityMaxLength, '-')}-|-{"".PadRight(computedMaxLength, '-')}-|-{"".PadRight(uniqueMaxLength, '-')}-|-{"".PadRight(jsonMaxLength, '-')}-|"); foreach (var column in columns) { - Logger.LogInformation("{Content}", $"| {column[0].PadRight(columnMaxLength)} | {column[1].PadRight(typeMaxLength)} | {column[2].PadRight(isNullMaxLength)} | {column[3].PadRight(defaultMaxLength)} | {column[4].PadRight(pkMaxLength)} | {column[5].PadRight(identityMaxLength)} | {column[6].PadRight(computedMaxLength)} | {column[7].PadRight(uniqueMaxLength)} |"); + Logger.LogInformation("{Content}", $"| {column[0].PadRight(columnMaxLength)} | {column[1].PadRight(typeMaxLength)} | {column[2].PadRight(isNullMaxLength)} | {column[3].PadRight(defaultMaxLength)} | {column[4].PadRight(pkMaxLength)} | {column[5].PadRight(identityMaxLength)} | {column[6].PadRight(computedMaxLength)} | {column[7].PadRight(uniqueMaxLength)} | {column[8].PadRight(jsonMaxLength)} |"); } Logger.LogInformation("{Content}", string.Empty); diff --git a/tests/DbEx.Test/Data.yaml b/tests/DbEx.Test/Data.yaml new file mode 100644 index 0000000..60620bb --- /dev/null +++ b/tests/DbEx.Test/Data.yaml @@ -0,0 +1,4 @@ +Test: +- Contact: + - { ContactId: 101, ContactType: E, Gender: M, Name: Bob, DateOfBirth: 2001-10-22, Addresses: [ { ContactAddressId: 1010, Street: "1 Main Street" } ] } + - { ContactId: 102, ContactType: I, Name: ^jane_name, Phone: 1234, Addresses: [ { ContactAddressId: 2020, ContactId: 102, Street: "1 Main Street" } ] } \ No newline at end of file diff --git a/tests/DbEx.Test/DbEx.Test.csproj b/tests/DbEx.Test/DbEx.Test.csproj index b4a5329..db36787 100644 --- a/tests/DbEx.Test/DbEx.Test.csproj +++ b/tests/DbEx.Test/DbEx.Test.csproj @@ -49,6 +49,9 @@ Always + + PreserveNewest + diff --git a/tests/DbEx.Test/Resources/MySqlInspect.md b/tests/DbEx.Test/Resources/MySqlInspect.md index 57ed833..19f00e3 100644 --- a/tests/DbEx.Test/Resources/MySqlInspect.md +++ b/tests/DbEx.Test/Resources/MySqlInspect.md @@ -16,15 +16,15 @@ This command is intended to be used as a quick-and-easy way to inspect the infer ### Columns -| Column | Type | Null | Default | PK | Identity | Computed | Unique | -|------------|--------------|------|---------|-----|----------|----------|--------| -| gender_id | INT | No | | Yes | Yes | No | No | -| code | VARCHAR(50) | No | | No | No | No | Yes | -| text | VARCHAR(256) | No | | No | No | No | No | -| created_by | VARCHAR(50) | Yes | | No | No | No | No | -| created_on | DATETIME | Yes | | No | No | No | No | -| updated_by | VARCHAR(50) | Yes | | No | No | No | No | -| updated_on | DATETIME | Yes | | No | No | No | No | +| Column | Type | Null | Default | PK | Identity | Computed | Unique | JSON | +|------------|--------------|------|---------|-----|----------|----------|--------|------| +| gender_id | INT | No | | Yes | Yes | No | No | No | +| code | VARCHAR(50) | No | | No | No | No | Yes | No | +| text | VARCHAR(256) | No | | No | No | No | No | No | +| created_by | VARCHAR(50) | Yes | | No | No | No | No | No | +| created_on | DATETIME | Yes | | No | No | No | No | No | +| updated_by | VARCHAR(50) | Yes | | No | No | No | No | No | +| updated_on | DATETIME | Yes | | No | No | No | No | No | ## CONTACT - Exists: Yes @@ -36,18 +36,18 @@ This command is intended to be used as a quick-and-easy way to inspect the infer ### Columns -| Column | Type | Null | Default | PK | Identity | Computed | Unique | -|-------------------|--------------|------|---------|-----|----------|----------|--------| -| contact_id | INT | No | | Yes | Yes | No | No | -| name | VARCHAR(200) | No | | No | No | No | No | -| phone | VARCHAR(15) | Yes | | No | No | No | No | -| date_of_birth | DATE | Yes | | No | No | No | No | -| contact_type_id | INT | No | 1 | No | No | No | No | -| gender_id | INT | Yes | | No | No | No | No | -| notes | TEXT | Yes | | No | No | No | No | -| created_by | VARCHAR(50) | Yes | | No | No | No | No | -| created_on | DATETIME | Yes | | No | No | No | No | -| updated_by | VARCHAR(50) | Yes | | No | No | No | No | -| updated_on | DATETIME | Yes | | No | No | No | No | -| contact_type_code | VARCHAR(50) | Yes | | No | No | No | No | +| Column | Type | Null | Default | PK | Identity | Computed | Unique | JSON | +|-------------------|--------------|------|---------|-----|----------|----------|--------|------| +| contact_id | INT | No | | Yes | Yes | No | No | No | +| name | VARCHAR(200) | No | | No | No | No | No | No | +| phone | VARCHAR(15) | Yes | | No | No | No | No | No | +| date_of_birth | DATE | Yes | | No | No | No | No | No | +| contact_type_id | INT | No | 1 | No | No | No | No | No | +| gender_id | INT | Yes | | No | No | No | No | No | +| notes | TEXT | Yes | | No | No | No | No | No | +| created_by | VARCHAR(50) | Yes | | No | No | No | No | No | +| created_on | DATETIME | Yes | | No | No | No | No | No | +| updated_by | VARCHAR(50) | Yes | | No | No | No | No | No | +| updated_on | DATETIME | Yes | | No | No | No | No | No | +| contact_type_code | VARCHAR(50) | Yes | | No | No | No | No | No | diff --git a/tests/DbEx.Test/Resources/PostgresInspect.md b/tests/DbEx.Test/Resources/PostgresInspect.md index fa84722..3a49d18 100644 --- a/tests/DbEx.Test/Resources/PostgresInspect.md +++ b/tests/DbEx.Test/Resources/PostgresInspect.md @@ -16,16 +16,16 @@ This command is intended to be used as a quick-and-easy way to inspect the infer ### Columns -| Column | Type | Null | Default | PK | Identity | Computed | Unique | -|------------|--------------------------|------|---------|-----|----------|----------|--------| -| gender_id | INTEGER | No | | Yes | Yes | No | No | -| code | CHARACTER VARYING(50) | No | | No | No | No | Yes | -| text | CHARACTER VARYING(256) | No | | No | No | No | No | -| created_by | CHARACTER VARYING(50) | Yes | | No | No | No | No | -| created_on | TIMESTAMP WITH TIME ZONE | Yes | | No | No | No | No | -| updated_by | CHARACTER VARYING(50) | Yes | | No | No | No | No | -| updated_on | TIMESTAMP WITH TIME ZONE | Yes | | No | No | No | No | -| xmin | XID | No | | No | No | Yes | No | +| Column | Type | Null | Default | PK | Identity | Computed | Unique | JSON | +|------------|--------------------------|------|---------|-----|----------|----------|--------|------| +| gender_id | INTEGER | No | | Yes | Yes | No | No | No | +| code | CHARACTER VARYING(50) | No | | No | No | No | Yes | No | +| text | CHARACTER VARYING(256) | No | | No | No | No | No | No | +| created_by | CHARACTER VARYING(50) | Yes | | No | No | No | No | No | +| created_on | TIMESTAMP WITH TIME ZONE | Yes | | No | No | No | No | No | +| updated_by | CHARACTER VARYING(50) | Yes | | No | No | No | No | No | +| updated_on | TIMESTAMP WITH TIME ZONE | Yes | | No | No | No | No | No | +| xmin | XID | No | | No | No | Yes | No | No | ## PUBLIC.CONTACT - Exists: Yes @@ -37,19 +37,19 @@ This command is intended to be used as a quick-and-easy way to inspect the infer ### Columns -| Column | Type | Null | Default | PK | Identity | Computed | Unique | -|-------------------|--------------------------|------|---------|-----|----------|----------|--------| -| contact_id | INTEGER | No | | Yes | Yes | No | No | -| name | CHARACTER VARYING(200) | No | | No | No | No | No | -| phone | CHARACTER VARYING(15) | Yes | | No | No | No | No | -| date_of_birth | DATE | Yes | | No | No | No | No | -| contact_type_id | INTEGER | No | 1 | No | No | No | No | -| gender_id | INTEGER | Yes | | No | No | No | No | -| notes | TEXT | Yes | | No | No | No | No | -| created_by | CHARACTER VARYING(50) | Yes | | No | No | No | No | -| created_on | TIMESTAMP WITH TIME ZONE | Yes | | No | No | No | No | -| updated_by | CHARACTER VARYING(50) | Yes | | No | No | No | No | -| updated_on | TIMESTAMP WITH TIME ZONE | Yes | | No | No | No | No | -| contact_type_code | CHARACTER VARYING(50) | Yes | | No | No | No | No | -| xmin | XID | No | | No | No | Yes | No | +| Column | Type | Null | Default | PK | Identity | Computed | Unique | JSON | +|-------------------|--------------------------|------|---------|-----|----------|----------|--------|------| +| contact_id | INTEGER | No | | Yes | Yes | No | No | No | +| name | CHARACTER VARYING(200) | No | | No | No | No | No | No | +| phone | CHARACTER VARYING(15) | Yes | | No | No | No | No | No | +| date_of_birth | DATE | Yes | | No | No | No | No | No | +| contact_type_id | INTEGER | No | 1 | No | No | No | No | No | +| gender_id | INTEGER | Yes | | No | No | No | No | No | +| notes | TEXT | Yes | | No | No | No | No | No | +| created_by | CHARACTER VARYING(50) | Yes | | No | No | No | No | No | +| created_on | TIMESTAMP WITH TIME ZONE | Yes | | No | No | No | No | No | +| updated_by | CHARACTER VARYING(50) | Yes | | No | No | No | No | No | +| updated_on | TIMESTAMP WITH TIME ZONE | Yes | | No | No | No | No | No | +| contact_type_code | CHARACTER VARYING(50) | Yes | | No | No | No | No | No | +| xmin | XID | No | | No | No | Yes | No | No | diff --git a/tests/DbEx.Test/Resources/SqlServerInspect.md b/tests/DbEx.Test/Resources/SqlServerInspect.md index d2e4d30..330c53f 100644 --- a/tests/DbEx.Test/Resources/SqlServerInspect.md +++ b/tests/DbEx.Test/Resources/SqlServerInspect.md @@ -16,15 +16,15 @@ This command is intended to be used as a quick-and-easy way to inspect the infer ### Columns -| Column | Type | Null | Default | PK | Identity | Computed | Unique | -|-----------|----------------|------|---------|-----|----------|----------|--------| -| GenderId | INT | No | | Yes | Yes | No | No | -| Code | NVARCHAR(50) | No | | No | No | No | Yes | -| Text | VARCHAR(256) | No | | No | No | No | No | -| CreatedBy | NVARCHAR(250) | Yes | | No | No | No | No | -| CreatedOn | DATETIMEOFFSET | Yes | | No | No | No | No | -| UpdatedBy | NVARCHAR(250) | Yes | | No | No | No | No | -| UpdatedOn | DATETIMEOFFSET | Yes | | No | No | No | No | +| Column | Type | Null | Default | PK | Identity | Computed | Unique | JSON | +|-----------|----------------|------|---------|-----|----------|----------|--------|------| +| GenderId | INT | No | | Yes | Yes | No | No | No | +| Code | NVARCHAR(50) | No | | No | No | No | Yes | No | +| Text | VARCHAR(256) | No | | No | No | No | No | No | +| CreatedBy | NVARCHAR(250) | Yes | | No | No | No | No | No | +| CreatedOn | DATETIMEOFFSET | Yes | | No | No | No | No | No | +| UpdatedBy | NVARCHAR(250) | Yes | | No | No | No | No | No | +| UpdatedOn | DATETIMEOFFSET | Yes | | No | No | No | No | No | ## TEST.CONTACT - Exists: Yes @@ -36,15 +36,15 @@ This command is intended to be used as a quick-and-easy way to inspect the infer ### Columns -| Column | Type | Null | Default | PK | Identity | Computed | Unique | -|-----------------|---------------|------|---------|-----|----------|----------|--------| -| ContactId | INT | No | | Yes | No | No | No | -| Name | NVARCHAR(200) | No | | No | No | No | No | -| Phone | VARCHAR(15) | Yes | | No | No | No | No | -| DateOfBirth | DATE | Yes | | No | No | No | No | -| ContactTypeId | INT | No | ((1)) | No | No | No | No | -| GenderId | INT | Yes | | No | No | No | No | -| TenantId | NVARCHAR(50) | Yes | | No | No | No | No | -| Notes | NVARCHAR(MAX) | Yes | | No | No | No | No | -| ContactTypeCode | NVARCHAR(50) | Yes | | No | No | No | No | +| Column | Type | Null | Default | PK | Identity | Computed | Unique | JSON | +|-----------------|---------------|------|---------|-----|----------|----------|--------|------| +| ContactId | INT | No | | Yes | No | No | No | No | +| Name | NVARCHAR(200) | No | | No | No | No | No | No | +| Phone | VARCHAR(15) | Yes | | No | No | No | No | No | +| DateOfBirth | DATE | Yes | | No | No | No | No | No | +| ContactTypeId | INT | No | ((1)) | No | No | No | No | No | +| GenderId | INT | Yes | | No | No | No | No | No | +| TenantId | NVARCHAR(50) | Yes | | No | No | No | No | No | +| Notes | NVARCHAR(MAX) | Yes | | No | No | No | No | No | +| ContactTypeCode | NVARCHAR(50) | Yes | | No | No | No | No | No | diff --git a/tests/DbEx.Test/SqlServerMigrationTest.cs b/tests/DbEx.Test/SqlServerMigrationTest.cs index 9cce8e0..86e4b89 100644 --- a/tests/DbEx.Test/SqlServerMigrationTest.cs +++ b/tests/DbEx.Test/SqlServerMigrationTest.cs @@ -172,12 +172,7 @@ public async Task A130_MigrateAll_Console() var a = new MigrationArgs(MigrationCommand.DropAndAll, cs) { Logger = l }.AddAssembly(typeof(Console.Program)).IncludeExtendedSchemaScripts(); using var m = new SqlServerMigration(a); - m.Args.DataParserArgs.Parameters.Add("DefaultName", "Bazza"); - m.Args.DataParserArgs.Parameters.Add("jane_name", "Jane"); - m.Args.DataParserArgs.RefDataColumnDefaults.Add("SortOrder", i => i); - m.Args.DataParserArgs.ColumnDefaults.Add(new DataParserColumnDefault("*", "*", "TenantId", _ => "test-tenant")); - m.Args.DataParserArgs.TableNameMappings.Add("XTest", "XContactType", "Test", "ContactType", new() { { "XNumber", "Number" } }) - .Add("Test", "Addresses", "Test", "ContactAddress"); + ConfigureMigrationArgs(m); var r = await m.MigrateAsync().ConfigureAwait(false); @@ -186,6 +181,16 @@ public async Task A130_MigrateAll_Console() return (cs, l, m); } + private static void ConfigureMigrationArgs(SqlServerMigration m) + { + m.Args.DataParserArgs.Parameters.Add("DefaultName", "Bazza"); + m.Args.DataParserArgs.Parameters.Add("jane_name", "Jane"); + m.Args.DataParserArgs.RefDataColumnDefaults.Add("SortOrder", i => i); + m.Args.DataParserArgs.ColumnDefaults.Add(new DataParserColumnDefault("*", "*", "TenantId", _ => "test-tenant")); + m.Args.DataParserArgs.TableNameMappings.Add("XTest", "XContactType", "Test", "ContactType", new() { { "XNumber", "Number" } }) + .Add("Test", "Addresses", "Test", "ContactAddress"); + } + [Test] public async Task C110_Throw_Exceptions() { @@ -280,7 +285,7 @@ public async Task B100_Execute_Console_Success() var a = new MigrationArgs(MigrationCommand.Execute, c.cs) { Logger = c.l }.AddAssembly(typeof(Console.Program).Assembly); using var m = new SqlServerMigration(a); - var r = await m.ExecuteSqlStatementsAsync(["SELECT * FROM Test.Contact"]).ConfigureAwait(false); + var r = await m.ExecuteSqlStatementsAsync([">SELECT * FROM Test.Contact"]).ConfigureAwait(false); Assert.IsTrue(r); } @@ -291,7 +296,7 @@ public async Task B110_Execute_Console_Error() var a = new MigrationArgs(MigrationCommand.Execute, c.cs) { Logger = c.l }.AddAssembly(typeof(Console.Program).Assembly); using var m = new SqlServerMigration(a); - var r = await m.ExecuteSqlStatementsAsync(["SELECT * FROM Test.Contact", "SELECT BANANAS"]).ConfigureAwait(false); + var r = await m.ExecuteSqlStatementsAsync([">SELECT * FROM Test.Contact", ">SELECT BANANAS"]).ConfigureAwait(false); Assert.IsFalse(r); } @@ -302,7 +307,7 @@ public async Task B120_Execute_Console_Batch_Error() var a = new MigrationArgs(MigrationCommand.Execute, c.cs) { Logger = c.l }.AddAssembly(typeof(Console.Program).Assembly); using var m = new SqlServerMigration(a); - var r = await m.ExecuteSqlStatementsAsync([@"SELECT * FROM Test.ContactBad; /* end */ GO; SELECT * FROM Test.Contact -- comment"]).ConfigureAwait(false); + var r = await m.ExecuteSqlStatementsAsync([@">SELECT * FROM Test.ContactBad; /* end */ GO; SELECT * FROM Test.Contact -- comment"]).ConfigureAwait(false); Assert.IsFalse(r); } @@ -313,13 +318,25 @@ public async Task B130_Execute_Console_Batch_Success() var a = new MigrationArgs(MigrationCommand.Execute, c.cs) { Logger = c.l }.AddAssembly(typeof(Console.Program).Assembly); using var m = new SqlServerMigration(a); - var r = await m.ExecuteSqlStatementsAsync([ @"SELECT * FROM Test.Contact; + var r = await m.ExecuteSqlStatementsAsync([ @"> SELECT * FROM Test.Contact; /* end */ GO SELECT * FROM Test.Contact -- comment" ]).ConfigureAwait(false); Assert.IsTrue(r); } + [Test] + public async Task B140_Execute_Console_YAML_File() + { + var c = await CreateConsoleDb().ConfigureAwait(false); + var a = new MigrationArgs(MigrationCommand.Execute, c.cs) { Logger = c.l }.AddAssembly(typeof(Console.Program).Assembly); + using var m = new SqlServerMigration(a); + ConfigureMigrationArgs(m); + + var r = await m.ExecuteSqlStatementsAsync(["Data.yaml"]).ConfigureAwait(false); + Assert.IsTrue(r); + } + [Test] public async Task SqlServerSchemaScript_SchemaAndObject() {