refactor(dedup): share TryParse prelude across breakdown families (Cluster D)

The standard `TryParse(...)` prelude — null/empty check, parser
construction, comment-preserving normalize, statement-prefix regex
validation, setup/finish clause extraction — was copy-pasted in
**eight** breakdown classes across the SqlServer and Snowflake
dialects. Sonar flagged it as a six-way duplicate cluster on the
shorter (~17-line) common block, and as additional pairwise
duplicates on the longer (~30-line) version.

Introduces `Strata.SqlTools.Statements.SqlServer.ParsePreparation`
with a single `TryRunPrelude(sql, parser, prefixRegex,
prefixDescription, out ...)` method. Each `TryParse` now calls it
once and proceeds straight to dialect-specific match logic.

Touched callers:
- `SqlServer.InsertBreakdown`, `SqlServer.DeleteBreakdown`,
  `SqlServer.UpdateBreakdown`, `SqlServer.ProcedureBreakdown`
- `Snowflake.InsertBreakdown`, `Snowflake.DeleteBreakdown`,
  `Snowflake.UpdateBreakdown`, `Snowflake.ProcedureBreakdown`

The Microsoft-SQL fallback path in the Snowflake breakdowns (which
delegates to the SqlServer breakdown's TryParse before the prelude
even runs) is preserved unchanged.

`ParsePreparation` is `public` because it sits in the SqlServer
assembly and is consumed cross-assembly by Snowflake/PostgreSql.
This is a new public type but it's deliberately a thin scaffold —
external consumers should still be calling the breakdown
classes' own `TryParse` methods.

All 1180 tests stay green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Thom Lamb
2026-05-27 16:33:43 -05:00
co-authored by Claude Opus 4.7
parent 4b5348c53a
commit c121dfa611
9 changed files with 93 additions and 165 deletions
@@ -152,31 +152,14 @@ public class UpdateBreakdown : SqlBreakdownBase
try
{
if (string.IsNullOrWhiteSpace(sql))
{
errorMessage = "SQL statement cannot be null or empty.";
return false;
}
var parser = new StatementParser();
sql = parser.NormalizeSqlPreservingComments(sql);
// Check if it's an UPDATE statement
var sqlTrimmed = sql.TrimStart();
if (!Regex.IsMatch(sqlTrimmed, @"^\s*UPDATE\b",
RegexOptions.IgnoreCase, Strata.SqlTools.SqlBreakdown.Utilities.RegexDefaults.MatchTimeout))
if (!ParsePreparation.TryRunPrelude(
sql, parser, @"^\s*UPDATE\b", "UPDATE",
out sql, out var setupClauses, out var finishClauses, out errorMessage))
{
errorMessage = "SQL statement must start with UPDATE.";
return false;
}
// Extract setup and finish clauses
var setupClauses = new List<string>();
sql = parser.ExtractSetupClauses(sql, setupClauses);
var finishClauses = new ArrayList();
sql = parser.ExtractFinishClauses(sql, finishClauses);
// Parse UPDATE statement - handle both with and without FROM clause
// Pattern: UPDATE table SET column=value [FROM table] [WHERE condition]
var updateMatch = Regex.Match(sql,