diff --git a/src/Strata.SqlTools.PostgreSql/Statements/StatementParser.cs b/src/Strata.SqlTools.PostgreSql/Statements/StatementParser.cs index 444f2be..6f5e258 100644 --- a/src/Strata.SqlTools.PostgreSql/Statements/StatementParser.cs +++ b/src/Strata.SqlTools.PostgreSql/Statements/StatementParser.cs @@ -84,23 +84,7 @@ public class StatementParser : SqlServerStatementParser /// Current position in the SQL string. /// Token and new position after the token. protected override ((TokenType type, string value, int position) token, int newPosition) HandleDoubleQuote(string sql, int position) - { - // PostgreSQL: double-quote is an identifier (like [brackets] in T-SQL) - int start = position; - position++; // Skip opening quote - var identifier = new StringBuilder(); - while (position < sql.Length && sql[position] != '"') - { - identifier.Append(sql[position]); - position++; - } - if (position < sql.Length) - { - position++; // Skip closing quote - } - - return ((TokenType.ColumnIdentifier, identifier.ToString(), start), position); - } + => HandleDoubleQuoteAsIdentifier(sql, position); /// /// Post-processes extracted clauses to handle PostgreSQL-specific LIMIT and OFFSET clauses. diff --git a/src/Strata.SqlTools.Snowflake/Breakdowns/QueryBreakdown.cs b/src/Strata.SqlTools.Snowflake/Breakdowns/QueryBreakdown.cs index fd5ba06..5eb2c9a 100644 --- a/src/Strata.SqlTools.Snowflake/Breakdowns/QueryBreakdown.cs +++ b/src/Strata.SqlTools.Snowflake/Breakdowns/QueryBreakdown.cs @@ -231,28 +231,7 @@ public class QueryBreakdown : SqlServerQueryBreakdown var visitor = isMicrosoftSql ? (IVisitor)new SqlServerCommandVisitor() : new CommandVisitor(); - var sql = expression.Accept(visitor); - - if (string.IsNullOrWhiteSpace(WhereClause.Clause)) - { - WhereClause.Clause = sql; - } - else - { - WhereClause.Clause = $"{WhereClause.Clause} {operation} {sql}"; - } - - if (!string.IsNullOrWhiteSpace(comment)) - { - if (string.IsNullOrWhiteSpace(WhereClause.Comment)) - { - WhereClause.Comment = comment; - } - else - { - WhereClause.Comment = $"{WhereClause.Comment} {comment}"; - } - } + AppendToClause(WhereClause, expression.Accept(visitor), operation, comment); } /// @@ -297,29 +276,9 @@ public class QueryBreakdown : SqlServerQueryBreakdown // Extract comments from the incoming SQL var cleanSql = parser.ExtractSqlComments(sql, out var comments); + var commentText = comments.Count > 0 ? string.Join(" ", comments) : null; - if (string.IsNullOrWhiteSpace(WhereClause.Clause)) - { - WhereClause.Clause = cleanSql.Trim(); - } - else - { - WhereClause.Clause = $"{WhereClause.Clause} {operation} {cleanSql.Trim()}"; - } - - // Merge comments - if (comments.Count > 0) - { - var newComment = string.Join(" ", comments); - if (string.IsNullOrWhiteSpace(WhereClause.Comment)) - { - WhereClause.Comment = newComment; - } - else - { - WhereClause.Comment = $"{WhereClause.Comment} {newComment}"; - } - } + AppendToClause(WhereClause, cleanSql.Trim(), operation, commentText); // Extract and add parameters from the WHERE clause using appropriate parser ExtractAndAddParametersWithParser(cleanSql, parser); @@ -476,28 +435,7 @@ public class QueryBreakdown : SqlServerQueryBreakdown var visitor = isMicrosoftSql ? (IVisitor)new SqlServerCommandVisitor() : new CommandVisitor(); - var sql = expression.Accept(visitor); - - if (string.IsNullOrWhiteSpace(HavingClause.Clause)) - { - HavingClause.Clause = sql; - } - else - { - HavingClause.Clause = $"{HavingClause.Clause} {operation} {sql}"; - } - - if (!string.IsNullOrWhiteSpace(comment)) - { - if (string.IsNullOrWhiteSpace(HavingClause.Comment)) - { - HavingClause.Comment = comment; - } - else - { - HavingClause.Comment = $"{HavingClause.Comment} {comment}"; - } - } + AppendToClause(HavingClause, expression.Accept(visitor), operation, comment); } /// diff --git a/src/Strata.SqlTools.Snowflake/Statements/StatementParser.cs b/src/Strata.SqlTools.Snowflake/Statements/StatementParser.cs index def7770..22f0467 100644 --- a/src/Strata.SqlTools.Snowflake/Statements/StatementParser.cs +++ b/src/Strata.SqlTools.Snowflake/Statements/StatementParser.cs @@ -91,23 +91,7 @@ public class StatementParser : SqlServerStatementParser /// Current position in the SQL string. /// Token and new position after the token. protected override ((TokenType type, string value, int position) token, int newPosition) HandleDoubleQuote(string sql, int position) - { - // Snowflake: double-quote is an identifier (like [brackets]) - int start = position; - position++; // Skip opening quote - var identifier = new StringBuilder(); - while (position < sql.Length && sql[position] != '"') - { - identifier.Append(sql[position]); - position++; - } - if (position < sql.Length) - { - position++; // Skip closing quote - } - - return ((TokenType.ColumnIdentifier, identifier.ToString(), start), position); - } + => HandleDoubleQuoteAsIdentifier(sql, position); /// /// Post-processes extracted clauses to handle Snowflake-specific LIMIT clause. diff --git a/src/Strata.SqlTools.SqlBreakdown/Classes/SqlBreakdownBase.cs b/src/Strata.SqlTools.SqlBreakdown/Classes/SqlBreakdownBase.cs index 4663657..241d772 100644 --- a/src/Strata.SqlTools.SqlBreakdown/Classes/SqlBreakdownBase.cs +++ b/src/Strata.SqlTools.SqlBreakdown/Classes/SqlBreakdownBase.cs @@ -53,6 +53,42 @@ public abstract class SqlBreakdownBase : ISqlBreakdown /// The SQL query string. protected abstract string GetSqlBreakdown(); + /// + /// Appends a SQL fragment to an with the given logical operation + /// (e.g. "and" / "or"), and optionally merges an associated comment. + /// If the target clause is empty, the fragment is set as the clause; otherwise it is + /// joined with the operation. + /// + /// The clause being built up (e.g. WhereClause, HavingClause). + /// The already-generated SQL fragment to append. + /// The logical operation used to join with the existing content. + /// Optional comment to merge into clause.Comment. + protected static void AppendToClause(ISqlClause clause, string sql, string operation, string? comment) + { + if (string.IsNullOrWhiteSpace(clause.Clause)) + { + clause.Clause = sql; + } + else + { + clause.Clause = $"{clause.Clause} {operation} {sql}"; + } + + if (string.IsNullOrWhiteSpace(comment)) + { + return; + } + + if (string.IsNullOrWhiteSpace(clause.Comment)) + { + clause.Comment = comment; + } + else + { + clause.Comment = $"{clause.Comment} {comment}"; + } + } + /// /// Gets a LINQ to SQL query of the specified type based on this breakdown. /// diff --git a/src/Strata.SqlTools.SqlServer/Breakdowns/QueryBreakdown.cs b/src/Strata.SqlTools.SqlServer/Breakdowns/QueryBreakdown.cs index d99f887..9d8187c 100644 --- a/src/Strata.SqlTools.SqlServer/Breakdowns/QueryBreakdown.cs +++ b/src/Strata.SqlTools.SqlServer/Breakdowns/QueryBreakdown.cs @@ -822,29 +822,9 @@ public class QueryBreakdown : SqlBreakdownBase, IQueryBreakdown // Extract comments from the incoming SQL var cleanSql = Parser.ExtractSqlComments(sql, out var comments); + var commentText = comments.Count > 0 ? string.Join(" ", comments) : null; - if (string.IsNullOrWhiteSpace(WhereClause.Clause)) - { - WhereClause.Clause = cleanSql.Trim(); - } - else - { - WhereClause.Clause = $"{WhereClause.Clause} {operation} {cleanSql.Trim()}"; - } - - // Merge comments - if (comments.Count > 0) - { - var newComment = string.Join(" ", comments); - if (string.IsNullOrWhiteSpace(WhereClause.Comment)) - { - WhereClause.Comment = newComment; - } - else - { - WhereClause.Comment = $"{WhereClause.Comment} {newComment}"; - } - } + AppendToClause(WhereClause, cleanSql.Trim(), operation, commentText); // Extract and add parameters from the WHERE clause ExtractAndAddParameters(cleanSql); @@ -901,28 +881,7 @@ public class QueryBreakdown : SqlBreakdownBase, IQueryBreakdown } var visitor = new CommandVisitor(); - var sql = expression.Accept(visitor); - - if (string.IsNullOrWhiteSpace(WhereClause.Clause)) - { - WhereClause.Clause = sql; - } - else - { - WhereClause.Clause = $"{WhereClause.Clause} {operation} {sql}"; - } - - if (!string.IsNullOrWhiteSpace(comment)) - { - if (string.IsNullOrWhiteSpace(WhereClause.Comment)) - { - WhereClause.Comment = comment; - } - else - { - WhereClause.Comment = $"{WhereClause.Comment} {comment}"; - } - } + AppendToClause(WhereClause, expression.Accept(visitor), operation, comment); } /// @@ -1013,28 +972,7 @@ public class QueryBreakdown : SqlBreakdownBase, IQueryBreakdown } var visitor = new CommandVisitor(); - var sql = expression.Accept(visitor); - - if (string.IsNullOrWhiteSpace(HavingClause.Clause)) - { - HavingClause.Clause = sql; - } - else - { - HavingClause.Clause = $"{HavingClause.Clause} {operation} {sql}"; - } - - if (!string.IsNullOrWhiteSpace(comment)) - { - if (string.IsNullOrWhiteSpace(HavingClause.Comment)) - { - HavingClause.Comment = comment; - } - else - { - HavingClause.Comment = $"{HavingClause.Comment} {comment}"; - } - } + AppendToClause(HavingClause, expression.Accept(visitor), operation, comment); } /// diff --git a/src/Strata.SqlTools.SqlServer/Statements/StatementParser.cs b/src/Strata.SqlTools.SqlServer/Statements/StatementParser.cs index 0d1551a..6b751c2 100644 --- a/src/Strata.SqlTools.SqlServer/Statements/StatementParser.cs +++ b/src/Strata.SqlTools.SqlServer/Statements/StatementParser.cs @@ -649,6 +649,32 @@ public class StatementParser return ((TokenType.String, str.ToString(), start), position); } + /// + /// Helper used by dialects (PostgreSQL, Snowflake) where double-quoted text is an + /// identifier rather than a string literal. Reads from the opening quote at + /// and returns the inner text as a + /// token. + /// + /// The SQL statement being tokenized. + /// The current position in the SQL string (must point at the opening "). + /// The identifier token and the new position past the closing ". + protected static ((TokenType type, string value, int position) token, int newPosition) HandleDoubleQuoteAsIdentifier(string sql, int position) + { + int start = position; + position++; // Skip opening quote + var identifier = new StringBuilder(); + while (position < sql.Length && sql[position] != '"') + { + identifier.Append(sql[position]); + position++; + } + if (position < sql.Length) + { + position++; // Skip closing quote + } + return ((TokenType.ColumnIdentifier, identifier.ToString(), start), position); + } + /// /// Processes a list of tokens to find SQL keywords at the top level (outside parentheses). ///