feat(etl): add ParseTableName and QUOTENAME to CommonScripts
- Add ParseTableName method to parse table names with optional schema - Supports: "Table", "dbo.Table", "[dbo].[Table]" - Returns (schema, table) tuple, defaults to "dbo" schema - Update DisableIndexes, RebuildIndexes, UpdateStatistics to: - Use QUOTENAME() for SQL injection protection - Pass schema and table as parameters via SqlScriptRunner - Support non-dbo schemas - Update CustomSql to accept optional parameters and timeout - Add comprehensive tests for ParseTableName with various formats
This commit is contained in:
@@ -5,35 +5,85 @@ namespace JdeScoping.DataSync.Etl.Scripts;
|
|||||||
|
|
||||||
public static class CommonScripts
|
public static class CommonScripts
|
||||||
{
|
{
|
||||||
public static IScriptRunner DisableIndexes(IDbConnectionFactory factory, string tableName)
|
/// <summary>
|
||||||
|
/// Parses a table name, extracting schema if present.
|
||||||
|
/// Supports: "Table", "dbo.Table", "[dbo].[Table]"
|
||||||
|
/// </summary>
|
||||||
|
public static (string Schema, string Table) ParseTableName(string tableName)
|
||||||
{
|
{
|
||||||
var sql = $@"
|
var cleaned = tableName.Replace("[", "").Replace("]", "");
|
||||||
|
var parts = cleaned.Split('.', 2);
|
||||||
|
return parts.Length == 2
|
||||||
|
? (parts[0], parts[1])
|
||||||
|
: ("dbo", parts[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IScriptRunner DisableIndexes(
|
||||||
|
IDbConnectionFactory factory,
|
||||||
|
string tableName,
|
||||||
|
int timeoutSeconds = 300)
|
||||||
|
{
|
||||||
|
var (schema, table) = ParseTableName(tableName);
|
||||||
|
|
||||||
|
var sql = @"
|
||||||
DECLARE @sql NVARCHAR(MAX) = '';
|
DECLARE @sql NVARCHAR(MAX) = '';
|
||||||
SELECT @sql = @sql + 'ALTER INDEX [' + i.name + '] ON [{tableName}] DISABLE;' + CHAR(13)
|
DECLARE @fullTableName NVARCHAR(256) = QUOTENAME(@schemaName) + '.' + QUOTENAME(@tableName);
|
||||||
|
|
||||||
|
SELECT @sql = @sql + 'ALTER INDEX ' + QUOTENAME(i.name) + ' ON ' + @fullTableName + ' DISABLE;' + CHAR(13)
|
||||||
FROM sys.indexes i
|
FROM sys.indexes i
|
||||||
INNER JOIN sys.tables t ON i.object_id = t.object_id
|
INNER JOIN sys.tables t ON i.object_id = t.object_id
|
||||||
WHERE t.name = '{tableName}'
|
INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
|
||||||
|
WHERE t.name = @tableName
|
||||||
|
AND s.name = @schemaName
|
||||||
AND i.type = 2
|
AND i.type = 2
|
||||||
AND i.is_disabled = 0;
|
AND i.is_disabled = 0;
|
||||||
|
|
||||||
IF LEN(@sql) > 0 EXEC sp_executesql @sql;";
|
IF LEN(@sql) > 0 EXEC sp_executesql @sql;";
|
||||||
|
|
||||||
return new SqlScriptRunner(factory, sql, $"DisableIndexes:{tableName}", timeoutSeconds: 300);
|
return new SqlScriptRunner(factory, sql, $"DisableIndexes:{schema}.{table}",
|
||||||
|
parameters: new { tableName = table, schemaName = schema },
|
||||||
|
timeoutSeconds: timeoutSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IScriptRunner RebuildIndexes(IDbConnectionFactory factory, string tableName)
|
public static IScriptRunner RebuildIndexes(
|
||||||
|
IDbConnectionFactory factory,
|
||||||
|
string tableName,
|
||||||
|
int timeoutSeconds = 3600)
|
||||||
{
|
{
|
||||||
var sql = $"ALTER INDEX ALL ON [{tableName}] REBUILD WITH (FILLFACTOR = 95)";
|
var (schema, table) = ParseTableName(tableName);
|
||||||
return new SqlScriptRunner(factory, sql, $"RebuildIndexes:{tableName}", timeoutSeconds: 3600);
|
|
||||||
|
var sql = @"
|
||||||
|
DECLARE @sql NVARCHAR(256) = 'ALTER INDEX ALL ON ' + QUOTENAME(@schemaName) + '.' + QUOTENAME(@tableName) + ' REBUILD WITH (FILLFACTOR = 95)';
|
||||||
|
EXEC sp_executesql @sql;";
|
||||||
|
|
||||||
|
return new SqlScriptRunner(factory, sql, $"RebuildIndexes:{schema}.{table}",
|
||||||
|
parameters: new { tableName = table, schemaName = schema },
|
||||||
|
timeoutSeconds: timeoutSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IScriptRunner UpdateStatistics(IDbConnectionFactory factory, string tableName)
|
public static IScriptRunner UpdateStatistics(
|
||||||
|
IDbConnectionFactory factory,
|
||||||
|
string tableName,
|
||||||
|
int timeoutSeconds = 600)
|
||||||
{
|
{
|
||||||
var sql = $"UPDATE STATISTICS [{tableName}]";
|
var (schema, table) = ParseTableName(tableName);
|
||||||
return new SqlScriptRunner(factory, sql, $"UpdateStats:{tableName}", timeoutSeconds: 600);
|
|
||||||
|
var sql = @"
|
||||||
|
DECLARE @sql NVARCHAR(256) = 'UPDATE STATISTICS ' + QUOTENAME(@schemaName) + '.' + QUOTENAME(@tableName);
|
||||||
|
EXEC sp_executesql @sql;";
|
||||||
|
|
||||||
|
return new SqlScriptRunner(factory, sql, $"UpdateStats:{schema}.{table}",
|
||||||
|
parameters: new { tableName = table, schemaName = schema },
|
||||||
|
timeoutSeconds: timeoutSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IScriptRunner CustomSql(IDbConnectionFactory factory, string sql, string name)
|
public static IScriptRunner CustomSql(
|
||||||
|
IDbConnectionFactory factory,
|
||||||
|
string sql,
|
||||||
|
string name,
|
||||||
|
object? parameters = null,
|
||||||
|
int timeoutSeconds = 30)
|
||||||
{
|
{
|
||||||
return new SqlScriptRunner(factory, sql, name);
|
return new SqlScriptRunner(factory, sql, name, parameters, timeoutSeconds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,25 +8,71 @@ public class CommonScriptsTests
|
|||||||
{
|
{
|
||||||
private readonly IDbConnectionFactory _factory = Substitute.For<IDbConnectionFactory>();
|
private readonly IDbConnectionFactory _factory = Substitute.For<IDbConnectionFactory>();
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("WorkOrder", "dbo", "WorkOrder")]
|
||||||
|
[InlineData("dbo.WorkOrder", "dbo", "WorkOrder")]
|
||||||
|
[InlineData("[dbo].[WorkOrder]", "dbo", "WorkOrder")]
|
||||||
|
[InlineData("Config.Settings", "Config", "Settings")]
|
||||||
|
[InlineData("[Config].[Settings]", "Config", "Settings")]
|
||||||
|
[InlineData("myschema.MyTable", "myschema", "MyTable")]
|
||||||
|
[InlineData("[other].[table.with.dots]", "other", "table.with.dots")]
|
||||||
|
public void ParseTableName_VariousFormats_ParsesCorrectly(string input, string expectedSchema, string expectedTable)
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var (schema, table) = CommonScripts.ParseTableName(input);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedSchema, schema);
|
||||||
|
Assert.Equal(expectedTable, table);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void DisableIndexes_ReturnsRunnerWithCorrectName()
|
public void DisableIndexes_ReturnsRunnerWithCorrectName()
|
||||||
{
|
{
|
||||||
var runner = CommonScripts.DisableIndexes(_factory, "WorkOrder");
|
var runner = CommonScripts.DisableIndexes(_factory, "WorkOrder");
|
||||||
Assert.Equal("DisableIndexes:WorkOrder", runner.ScriptName);
|
Assert.Equal("DisableIndexes:dbo.WorkOrder", runner.ScriptName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DisableIndexes_WithSchema_ReturnsRunnerWithCorrectName()
|
||||||
|
{
|
||||||
|
var runner = CommonScripts.DisableIndexes(_factory, "Config.Settings");
|
||||||
|
Assert.Equal("DisableIndexes:Config.Settings", runner.ScriptName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DisableIndexes_WithBracketedSchema_ReturnsRunnerWithCorrectName()
|
||||||
|
{
|
||||||
|
var runner = CommonScripts.DisableIndexes(_factory, "[dbo].[WorkOrder]");
|
||||||
|
Assert.Equal("DisableIndexes:dbo.WorkOrder", runner.ScriptName);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void RebuildIndexes_ReturnsRunnerWithCorrectName()
|
public void RebuildIndexes_ReturnsRunnerWithCorrectName()
|
||||||
{
|
{
|
||||||
var runner = CommonScripts.RebuildIndexes(_factory, "WorkOrder");
|
var runner = CommonScripts.RebuildIndexes(_factory, "WorkOrder");
|
||||||
Assert.Equal("RebuildIndexes:WorkOrder", runner.ScriptName);
|
Assert.Equal("RebuildIndexes:dbo.WorkOrder", runner.ScriptName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RebuildIndexes_WithSchema_ReturnsRunnerWithCorrectName()
|
||||||
|
{
|
||||||
|
var runner = CommonScripts.RebuildIndexes(_factory, "Config.Settings");
|
||||||
|
Assert.Equal("RebuildIndexes:Config.Settings", runner.ScriptName);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void UpdateStatistics_ReturnsRunnerWithCorrectName()
|
public void UpdateStatistics_ReturnsRunnerWithCorrectName()
|
||||||
{
|
{
|
||||||
var runner = CommonScripts.UpdateStatistics(_factory, "WorkOrder");
|
var runner = CommonScripts.UpdateStatistics(_factory, "WorkOrder");
|
||||||
Assert.Equal("UpdateStats:WorkOrder", runner.ScriptName);
|
Assert.Equal("UpdateStats:dbo.WorkOrder", runner.ScriptName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void UpdateStatistics_WithSchema_ReturnsRunnerWithCorrectName()
|
||||||
|
{
|
||||||
|
var runner = CommonScripts.UpdateStatistics(_factory, "Config.Settings");
|
||||||
|
Assert.Equal("UpdateStats:Config.Settings", runner.ScriptName);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -35,4 +81,21 @@ public class CommonScriptsTests
|
|||||||
var runner = CommonScripts.CustomSql(_factory, "SELECT 1", "MyCustomScript");
|
var runner = CommonScripts.CustomSql(_factory, "SELECT 1", "MyCustomScript");
|
||||||
Assert.Equal("MyCustomScript", runner.ScriptName);
|
Assert.Equal("MyCustomScript", runner.ScriptName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CustomSql_WithParameters_ReturnsRunner()
|
||||||
|
{
|
||||||
|
var parameters = new { value = 42 };
|
||||||
|
var runner = CommonScripts.CustomSql(_factory, "SELECT @value", "ParameterizedScript", parameters);
|
||||||
|
Assert.Equal("ParameterizedScript", runner.ScriptName);
|
||||||
|
Assert.NotNull(runner);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CustomSql_WithTimeout_ReturnsRunner()
|
||||||
|
{
|
||||||
|
var runner = CommonScripts.CustomSql(_factory, "SELECT 1", "TimeoutScript", timeoutSeconds: 120);
|
||||||
|
Assert.Equal("TimeoutScript", runner.ScriptName);
|
||||||
|
Assert.NotNull(runner);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user