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).
///