Skip to content

Commit

Permalink
Fix audit logging. (#43)
Browse files Browse the repository at this point in the history
Fix codegen argument constraint.
  • Loading branch information
chullybun committed Aug 25, 2023
1 parent a1aff1f commit 7ff5790
Show file tree
Hide file tree
Showing 20 changed files with 196 additions and 57 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.3.9
- *Fixed:* The YAML-based `MigrationCommand.Data` logic previously set the `CreatedBy`, `CreatedDate`, `UpdatedBy` and `UpdatedDate` (or specified equivalent) regardless of whether the data was being inserted or merged (insert/update). This has been corrected such that the appropriate values are only set for the specific type of operation being performed; i.e. `Created*`-only or `Updated*`-only.
- *Fixed:* Enabled additional command-line arguments to be passed for `MigrationCommand.CodeGen` to enable inherited usage flexibility. Removed `MigrationArgsBase.ScriptName` and `MigrationArgsBase.ScriptArguments` and included within existing `MigrationArgsBase.Parameters` as singular dictionary of key/value pairs (simplification).

## v2.3.8
- *Fixed:* `SqlServerMigration` has been fixed to handle column size of `MAX` correctly.

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.3.8</Version>
<Version>2.3.9</Version>
<LangVersion>preview</LangVersion>
<Authors>Avanade</Authors>
<Company>Avanade</Company>
Expand Down
5 changes: 5 additions & 0 deletions src/DbEx.MySql/MySqlSchemaConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ public override void PrepareDataParserArgs(DataParserArgs dataParserArgs)
dataParserArgs.RefDataColumnDefaults.TryAdd("is_active", _ => true);
dataParserArgs.RefDataColumnDefaults.TryAdd("sort_order", i => i);
}

dataParserArgs.CreatedByColumnName ??= CreatedByColumnName;
dataParserArgs.CreatedDateColumnName ??= CreatedDateColumnName;
dataParserArgs.UpdatedByColumnName ??= UpdatedByColumnName;
dataParserArgs.UpdatedDateColumnName ??= UpdatedDateColumnName;
}

/// <inheritdoc/>
Expand Down
4 changes: 2 additions & 2 deletions src/DbEx.MySql/Resources/DatabaseData_sql.hbs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{{! Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx }}
{{#if IsMerge}}
{{#each Rows}}
INSERT INTO `{{Table.Name}}` ({{#each Columns}}`{{Name}}`{{#unless @last}}, {{/unless}}{{/each}}) VALUES ({{#each Columns}}{{#if UseForeignKeyQueryForId}}(SELECT `{{DbColumn.ForeignColumn}}` FROM `{{DbColumn.ForeignTable}}` WHERE `{{DbColumn.ForeignRefDataCodeColumn}}` = {{{SqlValue}}} LIMIT 1){{else}}{{{SqlValue}}}{{/if}}{{#unless @last}}, {{/unless}}{{/each}}) ON DUPLICATE KEY UPDATE {{#each Table.MergeUpdateColumns}}`{{Name}}` = VALUES(`{{Name}}`){{#unless @last}}, {{/unless}}{{/each}};
INSERT INTO `{{Table.Name}}` ({{#each MergeInsertColumns}}`{{Name}}`{{#unless @last}}, {{/unless}}{{/each}}) VALUES ({{#each MergeInsertColumns}}{{#if UseForeignKeyQueryForId}}(SELECT `{{DbColumn.ForeignColumn}}` FROM `{{DbColumn.ForeignTable}}` WHERE `{{DbColumn.ForeignRefDataCodeColumn}}` = {{{SqlValue}}} LIMIT 1){{else}}{{{SqlValue}}}{{/if}}{{#unless @last}}, {{/unless}}{{/each}}) ON DUPLICATE KEY UPDATE {{#each MergeUpdateColumns}}`{{Name}}` = {{{SqlValue}}}{{#unless @last}}, {{/unless}}{{/each}};
{{/each}}
SELECT {{Rows.Count}}; -- Total rows upserted
{{else}}
{{#each Rows}}
INSERT INTO `{{Table.Name}}` ({{#each Columns}}`{{Name}}`{{#unless @last}}, {{/unless}}{{/each}}) VALUES ({{#each Columns}}{{#if UseForeignKeyQueryForId}}(SELECT `{{DbColumn.ForeignColumn}}` FROM `{{DbColumn.ForeignTable}}` WHERE `{{DbColumn.ForeignRefDataCodeColumn}}` = {{{SqlValue}}} LIMIT 1){{else}}{{{SqlValue}}}{{/if}}{{#unless @last}}, {{/unless}}{{/each}});
INSERT INTO `{{Table.Name}}` ({{#each InsertColumns}}`{{Name}}`{{#unless @last}}, {{/unless}}{{/each}}) VALUES ({{#each InsertColumns}}{{#if UseForeignKeyQueryForId}}(SELECT `{{DbColumn.ForeignColumn}}` FROM `{{DbColumn.ForeignTable}}` WHERE `{{DbColumn.ForeignRefDataCodeColumn}}` = {{{SqlValue}}} LIMIT 1){{else}}{{{SqlValue}}}{{/if}}{{#unless @last}}, {{/unless}}{{/each}});
{{/each}}
SELECT {{Rows.Count}}; -- Total rows inserted
{{/if}}
6 changes: 3 additions & 3 deletions src/DbEx.SqlServer/Resources/DatabaseData_sql.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ MERGE INTO [{{Schema}}].[{{Name}}] WITH (HOLDLOCK) as [t]
SELECT {{#each MergeMatchColumns}}[t].[{{Name}}]{{#unless @last}}, {{/unless}}{{/each}})
THEN UPDATE SET {{#each MergeUpdateColumns}}[t].[{{Name}}] = [s].[{{Name}}]{{#unless @last}}, {{/unless}}{{/each}}
WHEN NOT MATCHED BY TARGET
THEN INSERT ({{#each Columns}}[{{Name}}]{{#unless @last}}, {{/unless}}{{/each}})
VALUES ({{#each Columns}}[s].[{{Name}}]{{#unless @last}}, {{/unless}}{{/each}});
THEN INSERT ({{#each MergeInsertColumns}}[{{Name}}]{{#unless @last}}, {{/unless}}{{/each}})
VALUES ({{#each MergeInsertColumns}}[s].[{{Name}}]{{#unless @last}}, {{/unless}}{{/each}});

SELECT @@ROWCOUNT
DROP TABLE #temp
{{else}}
{{#each Rows}}
INSERT INTO [{{Table.Schema}}].[{{Table.Name}}] ({{#each Columns}}[{{Name}}]{{#unless @last}}, {{/unless}}{{/each}}) VALUES ({{#each Columns}}{{#if UseForeignKeyQueryForId}}(SELECT TOP 1 [{{DbColumn.ForeignColumn}}] FROM [{{DbColumn.ForeignSchema}}].[{{DbColumn.ForeignTable}}] WHERE [{{DbColumn.ForeignRefDataCodeColumn}}] = {{{SqlValue}}}){{else}}{{{SqlValue}}}{{/if}}{{#unless @last}}, {{/unless}}{{/each}})
INSERT INTO [{{Table.Schema}}].[{{Table.Name}}] ({{#each InsertColumns}}[{{Name}}]{{#unless @last}}, {{/unless}}{{/each}}) VALUES ({{#each InsertColumns}}{{#if UseForeignKeyQueryForId}}(SELECT TOP 1 [{{DbColumn.ForeignColumn}}] FROM [{{DbColumn.ForeignSchema}}].[{{DbColumn.ForeignTable}}] WHERE [{{DbColumn.ForeignRefDataCodeColumn}}] = {{{SqlValue}}}){{else}}{{{SqlValue}}}{{/if}}{{#unless @last}}, {{/unless}}{{/each}})
{{/each}}
SELECT {{Rows.Count}} -- Total rows inserted
{{/if}}
5 changes: 5 additions & 0 deletions src/DbEx.SqlServer/SqlServerSchemaConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ public override void PrepareDataParserArgs(DataParserArgs dataParserArgs)
dataParserArgs.RefDataColumnDefaults.TryAdd("IsActive", _ => true);
dataParserArgs.RefDataColumnDefaults.TryAdd("SortOrder", i => i);
}

dataParserArgs.CreatedByColumnName ??= CreatedByColumnName;
dataParserArgs.CreatedDateColumnName ??= CreatedDateColumnName;
dataParserArgs.UpdatedByColumnName ??= UpdatedByColumnName;
dataParserArgs.UpdatedDateColumnName ??= UpdatedDateColumnName;
}

/// <inheritdoc/>
Expand Down
18 changes: 6 additions & 12 deletions src/DbEx/Console/MigrationConsoleBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ namespace DbEx.Console
/// <summary>
/// Base console that facilitates the <see cref="DatabaseMigrationBase"/> by managing the standard console command-line arguments/options.
/// </summary>
/// <remarks>The standard console command-line arguments/options can be controlled via the constructor using the <see cref="SupportedOptions"/> flags. Additional capabilities can be added by inherting and overridding the
/// <see cref="OnBeforeExecute(CommandLineApplication)"/>, <see cref="OnValidation(ValidationContext)"/> and <see cref="OnMigrateAsync"/>. Changes to the console output can be achieved by overridding
/// <remarks>The standard console command-line arguments/options can be controlled via the constructor using the <see cref="SupportedOptions"/> flags. Additional capabilities can be added by inheriting and overriding the
/// <see cref="OnBeforeExecute(CommandLineApplication)"/>, <see cref="OnValidation(ValidationContext)"/> and <see cref="OnMigrateAsync"/>. Changes to the console output can be achieved by overriding
/// <see cref="OnWriteMasthead"/>, <see cref="OnWriteHeader"/>, <see cref="OnWriteArgs(DatabaseMigrationBase)"/> and <see cref="OnWriteFooter(double)"/>.
/// <para>The underlying command line parsing is provided by <see href="https://natemcmaster.github.io/CommandLineUtils/"/>.</para></remarks>
public abstract class MigrationConsoleBase
Expand Down Expand Up @@ -160,20 +160,14 @@ public async Task<int> RunAsync(string[] args, CancellationToken cancellationTok
if (vr != ValidationResult.Success)
return vr;
if (_additionalArgs.Values.Count > 0 && !(Args.MigrationCommand.HasFlag(MigrationCommand.Script) || Args.MigrationCommand.HasFlag(MigrationCommand.Execute)))
return new ValidationResult($"Additional arguments can only be specified when the command is '{nameof(MigrationCommand.Script)}' or '{nameof(MigrationCommand.Execute)}'.", new string[] { "args" });
if (_additionalArgs.Values.Count > 0 && !(Args.MigrationCommand.HasFlag(MigrationCommand.CodeGen) || Args.MigrationCommand.HasFlag(MigrationCommand.Script) || Args.MigrationCommand.HasFlag(MigrationCommand.Execute)))
return new ValidationResult($"Additional arguments can only be specified when the command is '{nameof(MigrationCommand.CodeGen)}', '{nameof(MigrationCommand.Script)}' or '{nameof(MigrationCommand.Execute)}'.", new string[] { "args" });
if (Args.MigrationCommand.HasFlag(MigrationCommand.Script))
if (Args.MigrationCommand.HasFlag(MigrationCommand.CodeGen) || Args.MigrationCommand.HasFlag(MigrationCommand.Script))
{
for (int i = 0; i < _additionalArgs.Values.Count; i++)
{
if (i == 0)
Args.ScriptName = _additionalArgs.Values[i];
else
{
Args.ScriptArguments ??= new Dictionary<string, string?>();
Args.ScriptArguments.Add($"Param{i}", _additionalArgs.Values[i]);
}
Args.Parameters.Add($"Param{i}", _additionalArgs.Values[i]);
}
}
Expand Down
11 changes: 11 additions & 0 deletions src/DbEx/DatabaseExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ public static async Task<List<DbTableSchema>> SelectSchemaAsync(this IDatabase d

// Get all the tables and their columns.
using var sr = DatabaseMigrationBase.GetRequiredResourcesStreamReader("SelectTableAndColumns.sql", new Assembly[] { typeof(DatabaseExtensions).Assembly });
#if NET7_0_OR_GREATER
await database.SqlStatement(await sr.ReadToEndAsync(cancellationToken).ConfigureAwait(false)).SelectQueryAsync(dr =>
#else
await database.SqlStatement(await sr.ReadToEndAsync().ConfigureAwait(false)).SelectQueryAsync(dr =>
#endif
{
if (!databaseSchemaConfig.SupportsSchema && dr.GetValue<string>("TABLE_SCHEMA") != databaseSchemaConfig.DatabaseName)
return 0;
Expand All @@ -52,6 +56,9 @@ public static async Task<List<DbTableSchema>> SelectSchemaAsync(this IDatabase d
tables.Add(table = dt);
var dc = databaseSchemaConfig.CreateColumnFromInformationSchema(table, dr);
dc.IsCreatedAudit = dc.Name == (dataParserArgs?.CreatedByColumnName ?? databaseSchemaConfig.CreatedByColumnName) || dc.Name == (dataParserArgs?.CreatedDateColumnName ?? databaseSchemaConfig.CreatedDateColumnName);
dc.IsUpdatedAudit = dc.Name == (dataParserArgs?.UpdatedByColumnName ?? databaseSchemaConfig.UpdatedByColumnName) || dc.Name == (dataParserArgs?.UpdatedDateColumnName ?? databaseSchemaConfig.UpdatedDateColumnName);
table.Columns.Add(dc);
return 0;
}, cancellationToken).ConfigureAwait(false);
Expand All @@ -70,7 +77,11 @@ public static async Task<List<DbTableSchema>> SelectSchemaAsync(this IDatabase d

// Configure all the single column primary and unique constraints.
using var sr2 = DatabaseMigrationBase.GetRequiredResourcesStreamReader("SelectTablePrimaryKey.sql", new Assembly[] { typeof(DatabaseExtensions).Assembly });
#if NET7_0_OR_GREATER
var pks = await database.SqlStatement(await sr2.ReadToEndAsync(cancellationToken).ConfigureAwait(false)).SelectQueryAsync(dr => new
#else
var pks = await database.SqlStatement(await sr2.ReadToEndAsync().ConfigureAwait(false)).SelectQueryAsync(dr => new
#endif
{
ConstraintName = dr.GetValue<string>("CONSTRAINT_NAME"),
TableSchema = dr.GetValue<string>("TABLE_SCHEMA"),
Expand Down
8 changes: 4 additions & 4 deletions src/DbEx/DatabaseSchemaConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ protected DatabaseSchemaConfig(string databaseName, bool supportsSchema = true)
/// Opportunity to load additional `InformationSchema` related data that is specific to the database.
/// </summary>
/// <param name="database">The <see cref="IDatabase"/>.</param>
/// <param name="tables">The <see cref="DbTableSchema"/> list to load addtional data into.</param>
/// <param name="tables">The <see cref="DbTableSchema"/> list to load additional data into.</param>
/// <param name="dataParserArgs">The <see cref="DataParserArgs"/>.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
public virtual Task LoadAdditionalInformationSchema(IDatabase database, List<DbTableSchema> tables, DataParserArgs? dataParserArgs, CancellationToken cancellationToken) => Task.CompletedTask;
Expand Down Expand Up @@ -143,21 +143,21 @@ protected DatabaseSchemaConfig(string databaseName, bool supportsSchema = true)
public abstract string ToFormattedSqlStatementValue(DataParserArgs dataParserArgs, object? value);

/// <summary>
/// Inidicates whether the <paramref name="dbType"/> is considered an integer.
/// Indicates whether the <paramref name="dbType"/> is considered an integer.
/// </summary>
/// <param name="dbType">The database type.</param>
/// <returns><c>true</c> indicates it is; otherwise, <c>false</c>.</returns>
public abstract bool IsDbTypeInteger(string? dbType);

/// <summary>
/// Inidicates whether the <paramref name="dbType"/> is considered a decimal.
/// Indicates whether the <paramref name="dbType"/> is considered a decimal.
/// </summary>
/// <param name="dbType">The database type.</param>
/// <returns><c>true</c> indicates it is; otherwise, <c>false</c>.</returns>
public abstract bool IsDbTypeDecimal(string? dbType);

/// <summary>
/// Inidicates whether the <paramref name="dbType"/> is considered a string.
/// Indicates whether the <paramref name="dbType"/> is considered a string.
/// </summary>
/// <param name="dbType">The database type.</param>
/// <returns><c>true</c> indicates it is; otherwise, <c>false</c>.</returns>
Expand Down
26 changes: 23 additions & 3 deletions src/DbEx/DbSchema/DbColumnSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace DbEx.DbSchema
public class DbColumnSchema
{
private string? _dotNetType;
private string? _dotNetName;
private string? _sqlType;

/// <summary>
Expand Down Expand Up @@ -127,22 +128,38 @@ public DbColumnSchema(DbTableSchema dbTable, string name, string type)
/// </summary>
public string? ForeignRefDataCodeColumn { get; set; }

/// <summary>
/// Indicates whether the column is a created audit column; i.e. name is <c>CreatedDate</c> or <c>CreatedBy</c>.
/// </summary>
public bool IsCreatedAudit { get; set; }

/// <summary>
/// Indicates whether the column is an updated audit column; i.e. name is <c>UpdatedDate</c> or <c>UpdatedBy</c>.
/// </summary>
public bool IsUpdatedAudit { get; set; }

/// <summary>
/// Gets the corresponding .NET <see cref="System.Type"/> name.
/// </summary>
public string DotNetType => _dotNetType ?? DbTable?.Config.ToDotNetTypeName(this) ?? throw new InvalidOperationException($"The {nameof(DbTable)} must be set before the {nameof(DotNetType)} property can be accessed.");
public string DotNetType => _dotNetType ??= DbTable?.Config.ToDotNetTypeName(this) ?? throw new InvalidOperationException($"The {nameof(DbTable)} must be set before the {nameof(DotNetType)} property can be accessed.");

/// <summary>
/// Gets the corresponding .NET name.
/// </summary>
public string DotNetName => _dotNetName ??= DbTableSchema.CreateDotNetName(Name);

/// <summary>
/// Gets the fully defined SQL type.
/// </summary>
public string SqlType => _sqlType ?? DbTable?.Config.ToFormattedSqlType(this) ?? throw new InvalidOperationException($"The {nameof(DbTable)} must be set before the {nameof(SqlType)} property can be accessed.");
public string SqlType => _sqlType ??= DbTable?.Config.ToFormattedSqlType(this) ?? throw new InvalidOperationException($"The {nameof(DbTable)} must be set before the {nameof(SqlType)} property can be accessed.");

/// <summary>
/// Prepares the schema by updating the calcuated properties: <see cref="DotNetType"/> and <see cref="SqlType"/>.
/// Prepares the schema by updating the calculated properties: <see cref="DotNetType"/>, <see cref="DotNetName"/> and <see cref="SqlType"/>.
/// </summary>
public void Prepare()
{
_dotNetType = DbTable.Config.ToDotNetTypeName(this);
_dotNetName = DbTableSchema.CreateDotNetName(Name);
_sqlType = DbTable.Config.ToFormattedSqlType(this);
}

Expand Down Expand Up @@ -179,7 +196,10 @@ public void CopyFrom(DbColumnSchema column)
ForeignColumn = column.ForeignColumn;
IsForeignRefData = column.IsForeignRefData;
ForeignRefDataCodeColumn = column.ForeignRefDataCodeColumn;
IsCreatedAudit = column.IsCreatedAudit;
IsUpdatedAudit = column.IsUpdatedAudit;
_dotNetType = column._dotNetType;
_dotNetName = column._dotNetName;
_sqlType = column._sqlType;
}
}
Expand Down
44 changes: 44 additions & 0 deletions src/DbEx/DbSchema/DbTableSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace DbEx.DbSchema
Expand All @@ -17,6 +18,9 @@ namespace DbEx.DbSchema
[DebuggerDisplay("{QualifiedName}")]
public class DbTableSchema
{
private string? _dotNetName;
private string? _pluralName;

/// <summary>
/// The <see cref="Regex"/> expression pattern for splitting strings into words.
/// </summary>
Expand All @@ -37,6 +41,36 @@ public static string CreateAlias(string name)
return new string(s.Replace(" ", " ").Replace("_", " ").Replace("-", " ").Split(' ').Where(x => !string.IsNullOrEmpty(x)).Select(x => x[..1].ToLower(System.Globalization.CultureInfo.InvariantCulture).ToCharArray()[0]).ToArray());
}

/// <summary>
/// Create a .NET friendly name.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>The .NET friendly name.</returns>
public static string CreateDotNetName(string name)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentNullException(nameof(name));

var sb = new StringBuilder();
name.Split(new char[] { '_', '-' }, StringSplitOptions.RemoveEmptyEntries).ForEach(part => sb.Append(StringConverter.ToPascalCase(part)));
return sb.ToString();
}

/// <summary>
/// Create a plural from the name.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>The pluralized name.</returns>
public static string CreatePluralName(string name)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentNullException(nameof(name));

var words = Regex.Split(name, WordSplitPattern).Where(x => !string.IsNullOrEmpty(x)).ToList();
words[^1] = StringConverter.ToPlural(words[^1]);
return string.Join(string.Empty, words);
}

/// <summary>
/// Initializes a new instance of the <see cref="DbTableSchema"/> class.
/// </summary>
Expand All @@ -62,6 +96,16 @@ public DbTableSchema(DatabaseSchemaConfig config, string schema, string name)
/// </summary>
public string Name { get; }

/// <summary>
/// Gets the table name in .NET friendly form.
/// </summary>
public string DotNetName => _dotNetName ??= CreateDotNetName(Name);

/// <summary>
/// Gets the <see cref="DotNetName"/> in plural form.
/// </summary>
public string PluralName => _pluralName ??= CreatePluralName(DotNetName);

/// <summary>
/// Gets the schema name.
/// </summary>
Expand Down
Loading

0 comments on commit 7ff5790

Please sign in to comment.