using System.Collections; using System.Text; using System.Text.RegularExpressions; using Strata.SqlTools.SqlBreakdown.Classes; using Strata.SqlTools.Statements.SqlServer; namespace Strata.SqlTools.Breakdowns.SqlServer; /// /// Represents an UPDATE SQL statement breakdown with SET, FROM, and WHERE clauses for SQL Server. /// public class UpdateBreakdown : SqlBreakdownBase { protected readonly StatementParser Parser; /// /// Initializes a new instance of the class. /// public UpdateBreakdown() { Parser = new StatementParser(); TableName = new SqlClause(); SetClause = new SqlClause(); FromClause = new SqlClause(); WhereClause = new SqlClause(); } /// /// Initializes a new instance of the class. /// /// The table name. /// The SET clause. /// The WHERE clause. public UpdateBreakdown(string tableName, string setClause, string whereClause) : this() { var cleanTable = Parser.ExtractSqlComments(tableName, out var tableComments); TableName.Clause = cleanTable.Trim(); TableName.Comment = tableComments.Count > 0 ? string.Join(" ", tableComments) : null; var cleanSet = Parser.ExtractSqlComments(setClause, out var setComments); SetClause.Clause = cleanSet.Trim(); SetClause.Comment = setComments.Count > 0 ? string.Join(" ", setComments) : null; var cleanWhere = Parser.ExtractSqlComments(whereClause, out var whereComments); WhereClause.Clause = cleanWhere.Trim(); WhereClause.Comment = whereComments.Count > 0 ? string.Join(" ", whereComments) : null; } /// /// Gets or sets the SET clause. /// public SqlClause SetClause { get; set; } /// /// Gets or sets the table name. /// public SqlClause TableName { get; set; } /// /// Gets a value indicating whether a FROM clause is being used. /// public bool IsUsingFromClause => !string.IsNullOrWhiteSpace(FromClause.Clause); /// /// Gets or sets the FROM clause (for UPDATE with JOIN). /// public SqlClause FromClause { get; set; } /// /// Gets a value indicating whether a WHERE clause is being used. /// public bool IsUsingWhereClause => !string.IsNullOrWhiteSpace(WhereClause.Clause); /// /// Gets or sets the WHERE clause. /// public SqlClause WhereClause { get; set; } /// /// Gets the SQL breakdown as a string. /// /// The UPDATE SQL statement. protected override string GetSqlBreakdown() { var sb = new StringBuilder(); sb.AppendLine("UPDATE "); sb.AppendLine($" {TableName.Clause}"); sb.AppendLine("SET "); sb.AppendLine($" {SetClause.Clause}"); if (IsUsingFromClause) { sb.AppendLine("FROM "); sb.AppendLine($" {FromClause.Clause}"); } if (IsUsingWhereClause) { sb.AppendLine("WHERE "); sb.AppendLine($" {WhereClause.Clause}"); } return sb.ToString(); } #region Parse Methods /// /// Parses an UPDATE SQL statement into an UpdateBreakdown object. /// /// The UPDATE SQL statement to parse. /// An UpdateBreakdown object representing the parsed statement. /// Thrown when sql is null or empty. /// Thrown when the SQL statement cannot be parsed. public static UpdateBreakdown Parse(string sql) { if (string.IsNullOrWhiteSpace(sql)) { throw new ArgumentNullException(nameof(sql), "SQL statement cannot be null or empty."); } if (!TryParse(sql, out var result, out var error)) { throw new FormatException($"Failed to parse UPDATE statement: {error}"); } return result; } /// /// Attempts to parse an UPDATE SQL statement into an UpdateBreakdown object. /// /// The UPDATE SQL statement to parse. /// When this method returns, contains the parsed UpdateBreakdown if successful, or null if parsing failed. /// true if the SQL was successfully parsed; otherwise, false. public static bool TryParse(string sql, out UpdateBreakdown result) => TryParse(sql, out result, out _); /// /// Attempts to parse an UPDATE SQL statement into an UpdateBreakdown object. /// /// The UPDATE SQL statement to parse. /// When this method returns, contains the parsed UpdateBreakdown if successful, or null if parsing failed. /// When this method returns false, contains a message describing why parsing failed. /// true if the SQL was successfully parsed; otherwise, false. public static bool TryParse(string sql, out UpdateBreakdown result, out string errorMessage) { result = null!; errorMessage = null!; try { var parser = new StatementParser(); if (!ParsePreparation.TryRunPrelude( sql, parser, @"^\s*UPDATE\b", "UPDATE", out sql, out var setupClauses, out var finishClauses, out errorMessage)) { return false; } // 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, @"UPDATE\s+([^\s]+)\s+SET\s+(.*?)(?:\s+FROM\s+(.*?))?(?:\s+WHERE\s+(.*))?$", RegexOptions.IgnoreCase | RegexOptions.Singleline, Strata.SqlTools.SqlBreakdown.Utilities.RegexDefaults.MatchTimeout); if (!updateMatch.Success) { errorMessage = "Could not parse UPDATE statement. Expected format: UPDATE table SET column=value [FROM table] [WHERE condition]"; return false; } var tableName = updateMatch.Groups[1].Value.Trim(); var setClause = updateMatch.Groups[2].Value.Trim(); var fromClause = updateMatch.Groups.Count > 3 ? updateMatch.Groups[3].Value.Trim() : string.Empty; var whereClause = updateMatch.Groups.Count > 4 ? updateMatch.Groups[4].Value.Trim() : string.Empty; result = new UpdateBreakdown(tableName, setClause, whereClause) { SetupClauses = setupClauses, FinishClauses = finishClauses }; if (!string.IsNullOrWhiteSpace(fromClause)) { var cleanFrom = parser.ExtractSqlComments(fromClause, out var fromComments); result.FromClause.Clause = cleanFrom.Trim(); result.FromClause.Comment = fromComments.Count > 0 ? string.Join(" ", fromComments) : null; } return true; } catch (Exception ex) { errorMessage = $"Unexpected error during parsing: {ex.Message}"; return false; } } #endregion }