Skip to content

Commit

Permalink
DataParserArgs.ColumnDefaults. (#30)
Browse files Browse the repository at this point in the history
Help improvements.
  • Loading branch information
chullybun authored Dec 9, 2022
1 parent f12c1ce commit c6c75d3
Show file tree
Hide file tree
Showing 14 changed files with 252 additions and 11 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Represents the **NuGet** versions.

## v2.1.0
- *Enhancement:* Added `DataParserArgs.ColumnDefaults` so that _any_ table column(s) can be defaulted where required (where not directly specified).
- *Enhancement:* Improved help text to include the schema command and arguments.

## v2.0.0
- *Enhancement:* Added MySQL database migrations.
- *Note:* Given the extent of this and previous change a major version change is warranted (published version `v1.1.1` should be considered as deprecated as a result).
Expand Down
2 changes: 1 addition & 1 deletion Common.targets
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>2.0.0</Version>
<Version>2.1.0</Version>
<LangVersion>preview</LangVersion>
<Authors>Avanade</Authors>
<Company>Avanade</Company>
Expand Down
16 changes: 16 additions & 0 deletions src/DbEx.MySql/Console/MySqlMigrationConsole.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using DbEx.Console;
using DbEx.Migration;
using DbEx.MySql.Migration;
using Microsoft.Extensions.Logging;
using System;
using System.Reflection;

Expand Down Expand Up @@ -40,5 +41,20 @@ public sealed class MySqlMigrationConsole : MigrationConsoleBase<MySqlMigrationC

/// <inheritdoc/>
protected override DatabaseMigrationBase CreateMigrator() => new MySqlMigration(Args);

/// <inheritdoc/>
public override string AppTitle => base.AppTitle + " [MySQL]";

/// <inheritdoc/>
protected override void OnWriteHelp()
{
base.OnWriteHelp();
Logger?.LogInformation("{help}", "Script command and argument(s):");
Logger?.LogInformation("{help}", " script [default] Creates a default (empty) SQL script.");
Logger?.LogInformation("{help}", " script alter <table> Creates a SQL script to perform an ALTER TABLE.");
Logger?.LogInformation("{help}", " script create <table> Creates a SQL script to perform a CREATE TABLE.");
Logger?.LogInformation("{help}", " script refdata <table> Creates a SQL script to perform a CREATE TABLE as reference data.");
Logger?.LogInformation("{help}", string.Empty);
}
}
}
19 changes: 19 additions & 0 deletions src/DbEx.SqlServer/Console/SqlServerMigrationConsole.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using DbEx.Console;
using DbEx.Migration;
using DbEx.SqlServer.Migration;
using Microsoft.Extensions.Logging;
using System;
using System.Reflection;

Expand Down Expand Up @@ -40,5 +41,23 @@ public sealed class SqlServerMigrationConsole : MigrationConsoleBase<SqlServerMi

/// <inheritdoc/>
protected override DatabaseMigrationBase CreateMigrator() => new SqlServerMigration(Args);

/// <inheritdoc/>
public override string AppTitle => base.AppTitle + " [SQL Server]";

/// <inheritdoc/>
protected override void OnWriteHelp()
{
base.OnWriteHelp();
Logger?.LogInformation("{help}", "Script command and argument(s):");
Logger?.LogInformation("{help}", " script [default] Creates a default (empty) SQL script.");
Logger?.LogInformation("{help}", " script alter <Schema> <Table> Creates a SQL script to perform an ALTER TABLE.");
Logger?.LogInformation("{help}", " script cdc <Schema> <Table> Creates a SQL script to turn on CDC for the specified table.");
Logger?.LogInformation("{help}", " script cdcdb Creates a SQL script to turn on CDC for the database.");
Logger?.LogInformation("{help}", " script create <Schema> <Table> Creates a SQL script to perform a CREATE TABLE.");
Logger?.LogInformation("{help}", " script refdata <Schema> <Table> Creates a SQL script to perform a CREATE TABLE as reference data.");
Logger?.LogInformation("{help}", " script schema <Schema> Creates a SQL script to perform a CREATE SCHEMA.");
Logger?.LogInformation("{help}", string.Empty);
}
}
}
17 changes: 14 additions & 3 deletions src/DbEx/Console/MigrationConsoleBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public abstract class MigrationConsoleBase
private const string EntryAssemblyOnlyOptionName = "EO";
private CommandArgument<MigrationCommand>? _commandArg;
private CommandArgument? _additionalArgs;
private CommandOption? _helpOption;

/// <summary>
/// Initializes a new instance of the <see cref="MigrationConsoleBase"/> class.
Expand Down Expand Up @@ -112,9 +113,9 @@ public async Task<int> RunAsync(string[] args, CancellationToken cancellationTok

// Set up the app.
using var app = new CommandLineApplication(PhysicalConsole.Singleton) { Name = AppName, Description = AppTitle };
app.HelpOption();
_helpOption = app.HelpOption();

_commandArg = app.Argument<MigrationCommand>("command", "Database migration command.").IsRequired();
_commandArg = app.Argument<MigrationCommand>("command", "Database migration command (see https://github.com/Avanade/dbex#commands-functions).").IsRequired();
ConsoleOptions.Add(nameof(OnRamp.CodeGeneratorDbArgsBase.ConnectionString), app.Option("-cs|--connection-string", "Database connection string.", CommandOptionType.SingleValue));
ConsoleOptions.Add(nameof(OnRamp.CodeGeneratorDbArgsBase.ConnectionStringEnvironmentVariableName), app.Option("-cv|--connection-varname", "Database connection string environment variable name.", CommandOptionType.SingleValue));
ConsoleOptions.Add(nameof(MigrationArgs.SchemaOrder), app.Option("-so|--schema-order", "Database schema name (multiple can be specified in priority order).", CommandOptionType.MultipleValue));
Expand Down Expand Up @@ -199,7 +200,12 @@ public async Task<int> RunAsync(string[] args, CancellationToken cancellationTok
// Execute the command-line app.
try
{
return await app.ExecuteAsync(args, cancellationToken).ConfigureAwait(false);
var result = await app.ExecuteAsync(args, cancellationToken).ConfigureAwait(false);

if (result == 0 && _helpOption.HasValue())
OnWriteHelp();

return result;
}
catch (CommandParsingException cpex)
{
Expand Down Expand Up @@ -402,5 +408,10 @@ protected virtual void OnWriteFooter(double totalMilliseconds)
Logger?.LogInformation("{Content}", $"{AppName} Complete. [{totalMilliseconds}ms]");
Logger?.LogInformation("{Content}", string.Empty);
}

/// <summary>
/// Invoked to write additional help information to the <see cref="Logger"/>.
/// </summary>
protected virtual void OnWriteHelp() { }
}
}
46 changes: 46 additions & 0 deletions src/DbEx/Migration/Data/DataParserArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,55 @@ public class DataParserArgs
/// <remarks>The list should contain the column name and function that returns the default value (the input to the function is the corresponding row count as specified).</remarks>
public Dictionary<string, Func<int, object?>> RefDataColumnDefaults { get; } = new Dictionary<string, Func<int, object?>>();

/// <summary>
/// Adds a reference data column default to the <see cref="RefDataColumnDefaults"/>.
/// </summary>
/// <param name="column">The column name.</param>
/// <param name="default">The function that provides the default value.</param>
/// <returns>The <see cref="DataParserArgs"/> to support fluent-style method-chaining.</returns>
public DataParserArgs RefDataColumnDefault(string column, Func<int, object?> @default)
{
RefDataColumnDefaults.Add(column, @default);
return this;
}

/// <summary>
/// Gets or sets the column defaults collection.
/// </summary>
/// <remarks>The list should contain the column name and function that returns the default value (the input to the function is the corresponding row count as specified).</remarks>
public DataParserColumnDefaultCollection ColumnDefaults { get; } = new DataParserColumnDefaultCollection();

/// <summary>
/// Adds a <see cref="DataParserColumnDefault"/> to the <see cref="ColumnDefaults"/>.
/// </summary>
/// <param name="schema">The schema name; a '<c>*</c>' denotes any schema.</param>
/// <param name="table">The table name; a '<c>*</c>' denotes any table.</param>
/// <param name="column">The name of the column to be updated.</param>
/// <param name="default">The function that provides the default value.</param>
/// <returns>The <see cref="DataParserArgs"/> to support fluent-style method-chaining.</returns>
public DataParserArgs ColumnDefault(string schema, string table, string column, Func<int, object?> @default)
{
ColumnDefaults.Add(new DataParserColumnDefault(schema, table, column, @default));
return this;
}

/// <summary>
/// Gets the runtime parameters.
/// </summary>
public Dictionary<string, object?> Parameters { get; } = new Dictionary<string, object?>();

/// <summary>
/// Adds a parameter to the <see cref="Parameters"/>.
/// </summary>
/// <param name="key">The parameter key.</param>
/// <param name="value">The parameter value.</param>
/// <returns>The <see cref="DataParserArgs"/> to support fluent-style method-chaining.</returns>
public DataParserArgs Parameter(string key, object? value)
{
Parameters.Add(key, value);
return this;
}

/// <summary>
/// Gets or sets the <see cref="DbTableSchema"/> updater.
/// </summary>
Expand Down Expand Up @@ -122,6 +166,8 @@ public void CopyFrom(DataParserArgs args)
DbSchemaUpdaterAsync = args.DbSchemaUpdaterAsync;
RefDataColumnDefaults.Clear();
args.RefDataColumnDefaults.ForEach(x => RefDataColumnDefaults.Add(x.Key, x.Value));
ColumnDefaults.Clear();
args.ColumnDefaults.ForEach(ColumnDefaults.Add);
Parameters.Clear();
args.Parameters.ForEach(x => Parameters.Add(x.Key, x.Value));
}
Expand Down
47 changes: 47 additions & 0 deletions src/DbEx/Migration/Data/DataParserColumnDefault.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx

using System;

namespace DbEx.Migration.Data
{
/// <summary>
/// Provides the <see cref="DataParser"/> <see cref="DataParserArgs.ColumnDefaults"/> configuration.
/// </summary>
public class DataParserColumnDefault
{
/// <summary>
/// Initializes a new instance of the <see cref="DataParserColumnDefault"/> class.
/// </summary>
/// <param name="schema">The schema name; a '<c>*</c>' denotes any schema.</param>
/// <param name="table">The table name; a '<c>*</c>' denotes any table.</param>
/// <param name="column">The name of the column to be updated.</param>
/// <param name="default">The function that provides the default value.</param>
public DataParserColumnDefault(string schema, string table, string column, Func<int, object?> @default)
{
Schema = schema ?? throw new ArgumentNullException(nameof(schema));
Table = table ?? throw new ArgumentNullException(nameof(table));
Column = column ?? throw new ArgumentNullException(nameof(column));
Default = @default ?? throw new ArgumentNullException(nameof(@default));
}

/// <summary>
/// Gets the schema name; a '<c>*</c>' denotes any schema.
/// </summary>
public string Schema { get; }

/// <summary>
/// Gets the table name; a '<c>*</c>' denotes any table.
/// </summary>
public string Table { get; }

/// <summary>
/// Gets the column name.
/// </summary>
public string Column { get; }

/// <summary>
/// Gets the function that provides the default value.
/// </summary>
public Func<int, object?> Default { get; }
}
}
75 changes: 75 additions & 0 deletions src/DbEx/Migration/Data/DataParserColumnDefaultCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx

using DbEx.DbSchema;
using System;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;

namespace DbEx.Migration.Data
{
/// <summary>
/// Provides a <see cref="DataParserColumnDefault"/> keyed collection.
/// </summary>
public class DataParserColumnDefaultCollection : KeyedCollection<(string, string, string), DataParserColumnDefault>
{
/// <inheritdoc/>
protected override (string, string, string) GetKeyForItem(DataParserColumnDefault item) => (item.Schema, item.Table, item.Column);

/// <summary>
/// Attempts to get the <paramref name="item"/> for the specified <paramref name="schema"/>, <paramref name="table"/> and <paramref name="column"/> names.
/// </summary>
/// <param name="schema">The schema name.</param>
/// <param name="table">The table name.</param>
/// <param name="column">The column name.</param>
/// <param name="item">The corresponding <see cref="DataParserColumnDefault"/> item where found; otherwise, <c>null</c>.</param>
/// <returns><c>true</c> where found; otherwise, <c>false</c>.</returns>
/// <remarks>Attempts to match as follows:
/// <list type="number">
/// <item>Schema, table and column names match item exactly;</item>
/// <item>Schema and column names match item exactly, and the underlying default table name is configured with '<c>*</c>';</item>
/// <item>Column names match item exactly, and the underlying default schema and table names are both configured with '<c>*</c>';</item>
/// <item>Item is not found.</item>
/// </list>
/// </remarks>
public bool TryGetValue(string schema, string table, string column, [NotNullWhen(true)] out DataParserColumnDefault? item)
{
if (schema == null)
throw new ArgumentNullException(nameof(schema));

if (table == null)
throw new ArgumentNullException(nameof(table));

if (column == null)
throw new ArgumentNullException(nameof(column));

if (TryGetValue((schema, table, column), out item))
return true;

if (TryGetValue((schema, "*", column), out item))
return true;

if (TryGetValue(("*", "*", column), out item))
return true;

item = null;
return false;
}

/// <summary>
/// Get all the configured column defaults for the specified <paramref name="table"/>.
/// </summary>
/// <param name="table">The <see cref="DbTableSchema"/>.</param>
/// <returns>The configured defaults.</returns>
public DataParserColumnDefaultCollection GetDefaultsForTable(DbTableSchema table)
{
var dc = new DataParserColumnDefaultCollection();
foreach (var c in (table ?? throw new ArgumentNullException(nameof(table))).Columns)
{
if (TryGetValue(table.Schema, table.Name, c.Name, out var item))
dc.Add(item);
}

return dc;
}
}
}
12 changes: 12 additions & 0 deletions src/DbEx/Migration/Data/DataTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,14 +167,19 @@ private void AddColumn(string name)
/// </summary>
internal async Task PrepareAsync(CancellationToken cancellationToken)
{
var cds = Args.ColumnDefaults.GetDefaultsForTable(DbTable);

for (int i = 0; i < Rows.Count; i++)
{
var row = Rows[i];

// Apply the configured auditing defaults.
await AddColumnWhereNotSpecifiedAsync(row, Args.CreatedDateColumnName ?? Parser.DatabaseSchemaConfig.CreatedDateColumnName, () => Task.FromResult<object?>(Args.DateTimeNow)).ConfigureAwait(false);
await AddColumnWhereNotSpecifiedAsync(row, Args.CreatedByColumnName ?? Parser.DatabaseSchemaConfig.CreatedByColumnName, () => Task.FromResult<object?>(Args.UserName)).ConfigureAwait(false);
await AddColumnWhereNotSpecifiedAsync(row, Args.UpdatedDateColumnName ?? Parser.DatabaseSchemaConfig.UpdatedDateColumnName, () => Task.FromResult<object?>(Args.DateTimeNow)).ConfigureAwait(false);
await AddColumnWhereNotSpecifiedAsync(row, Args.UpdatedByColumnName ?? Parser.DatabaseSchemaConfig.UpdatedByColumnName, () => Task.FromResult<object?>(Args.UserName)).ConfigureAwait(false);

// Apply an reference data defaults.
if (IsRefData && Args.RefDataColumnDefaults != null)
{
foreach (var rdd in Args.RefDataColumnDefaults)
Expand All @@ -183,6 +188,7 @@ internal async Task PrepareAsync(CancellationToken cancellationToken)
}
}

// Generate the identifier where specified to do so.
if (UseIdentifierGenerator)
{
var pkc = DbTable.PrimaryKeyColumns[0];
Expand All @@ -209,6 +215,12 @@ internal async Task PrepareAsync(CancellationToken cancellationToken)
}
}
}

// Apply any configured column defaults.
foreach (var cd in cds)
{
await AddColumnWhereNotSpecifiedAsync(row, cd.Column, () => Task.FromResult(cd.Default(i + 1))).ConfigureAwait(false);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
[Phone] VARCHAR (15) NULL,
[DateOfBirth] DATE NULL,
[ContactTypeId] INT NOT NULL DEFAULT 1,
[GenderId] INT NULL
[GenderId] INT NULL,
[TenantId] NVARCHAR(50),
CONSTRAINT [FK_Test_Contact_ContactType] FOREIGN KEY ([ContactTypeId]) REFERENCES [Test].[ContactType] ([ContactTypeId])
)
5 changes: 3 additions & 2 deletions tests/DbEx.Test.Console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ internal static Task<int> Main(string[] args) => SqlServerMigrationConsole
.Create<Program>("Data Source=.;Initial Catalog=DbEx.Console;Integrated Security=True;TrustServerCertificate=true")
.Configure(c =>
{
c.Args.DataParserArgs.Parameters.Add("DefaultName", "Bazza");
c.Args.DataParserArgs.RefDataColumnDefaults.Add("SortOrder", i => i);
c.Args.AddAssembly(typeof(DbEx.Test.OutboxConsole.Program).Assembly);
c.Args.AddSchemaOrder("Test", "Outbox");
c.Args.DataParserArgs.Parameter("DefaultName", "Bazza")
.RefDataColumnDefault("SortOrder", i => i)
.ColumnDefault("*", "*", "TenantId", _ => "test-tenant");
})
.RunAsync(args);
}
Expand Down
2 changes: 1 addition & 1 deletion tests/DbEx.Test.Console/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"profiles": {
"DbEx.Test.Console": {
"commandName": "Project",
"commandLineArgs": "reset"
"commandLineArgs": "--help"
}
}
}
Loading

0 comments on commit c6c75d3

Please sign in to comment.