Merge pull request 'refactor(dedup): second pass at src/ duplication — clear 10 of 13 remaining blocks' (#21) from chore/sonarqube-src-dedup-pass2 into main
SonarQube Analysis / sonarqube (push) Successful in 4m32s
SonarQube Analysis / sonarqube (push) Successful in 4m32s
Reviewed-on: #21
This commit was merged in pull request #21.
This commit is contained in:
@@ -10,6 +10,7 @@ using Strata.SqlTools.SqlBreakdown.Expressions.Functions.Aggregate;
|
||||
using Strata.SqlTools.SqlBreakdown.Expressions.Functions.Conditional;
|
||||
using Strata.SqlTools.SqlBreakdown.Expressions.Literals;
|
||||
using Strata.SqlTools.SqlBreakdown.Interfaces.Core;
|
||||
using static Strata.SqlTools.Markdown.Internal.MarkdownTextHelpers;
|
||||
|
||||
namespace Strata.SqlTools.Markdown.Expressions;
|
||||
|
||||
@@ -360,8 +361,6 @@ public class ExpressionGenerator : IVisitor<string>
|
||||
.Replace("]", "]");
|
||||
}
|
||||
|
||||
private static string TruncateText(string text, int maxLength)
|
||||
=> Internal.MarkdownTextHelpers.TruncateText(text, maxLength);
|
||||
|
||||
#region IVisitor Implementation
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Text;
|
||||
using Strata.SqlTools.Breakdowns.SqlServer;
|
||||
using Strata.SqlTools.SqlBreakdown.Interfaces;
|
||||
using static Strata.SqlTools.Markdown.Internal.MarkdownTextHelpers;
|
||||
|
||||
namespace Strata.SqlTools.Markdown.SqlServer;
|
||||
|
||||
@@ -191,7 +192,5 @@ public static class QueryBreakdownGenerator
|
||||
.Replace(">", ">");
|
||||
}
|
||||
|
||||
private static string TruncateText(string text, int maxLength)
|
||||
=> Internal.MarkdownTextHelpers.TruncateText(text, maxLength);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Text;
|
||||
using Strata.SqlTools.SqlBreakdown.Interfaces;
|
||||
using static Strata.SqlTools.Markdown.Internal.MarkdownTextHelpers;
|
||||
|
||||
namespace Strata.SqlTools.Markdown.SqlServer;
|
||||
|
||||
@@ -112,9 +113,6 @@ public static class SqlStatementGenerator
|
||||
.Replace("\r", "");
|
||||
}
|
||||
|
||||
private static string TruncateText(string text, int maxLength)
|
||||
=> Internal.MarkdownTextHelpers.TruncateText(text, maxLength);
|
||||
|
||||
/// <summary>
|
||||
/// Cleans table name for use in Mermaid diagrams.
|
||||
/// </summary>
|
||||
|
||||
@@ -36,14 +36,8 @@ public class QueryBreakdown : SqlServerQueryBreakdown
|
||||
public QueryBreakdown(string selectClause, string fromClause, bool isMicrosoftSql = false) : base()
|
||||
{
|
||||
var parser = isMicrosoftSql ? Parser : PostgreSqlParserInstance;
|
||||
|
||||
var cleanSelect = parser.ExtractSqlComments(selectClause, out var selectComments);
|
||||
SelectClause.Clause = cleanSelect.Trim();
|
||||
SelectClause.Comment = selectComments.Count > 0 ? string.Join(" ", selectComments) : null;
|
||||
|
||||
var cleanFrom = parser.ExtractSqlComments(fromClause, out var fromComments);
|
||||
FromClause.Clause = cleanFrom.Trim();
|
||||
FromClause.Comment = fromComments.Count > 0 ? string.Join(" ", fromComments) : null;
|
||||
parser.PopulateClauseWithComments(selectClause, SelectClause);
|
||||
parser.PopulateClauseWithComments(fromClause, FromClause);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -104,76 +104,39 @@ public class StatementReader : SqlServerStatementReader
|
||||
|
||||
if (CurrentCharacter == '=')
|
||||
{
|
||||
// Handle => operator (used in PostgreSQL for hstore and other operations)
|
||||
// =, => (PostgreSQL hstore + other operations)
|
||||
MovePosition();
|
||||
if (CurrentCharacter == '>')
|
||||
{
|
||||
MovePosition();
|
||||
_currentToken = new Token(TokenType.Operator, "=>");
|
||||
return true;
|
||||
}
|
||||
// Single = is handled as regular operator
|
||||
if (TryMatchTwoCharOperator('>', "=>")) return true;
|
||||
_currentToken = new Token(TokenType.Operator, "=");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (CurrentCharacter == '|')
|
||||
{
|
||||
// Handle || concatenation operator
|
||||
// |, ||
|
||||
MovePosition();
|
||||
if (CurrentCharacter == '|')
|
||||
{
|
||||
MovePosition();
|
||||
_currentToken = new Token(TokenType.Operator, "||");
|
||||
return true;
|
||||
}
|
||||
// Single | is also an operator
|
||||
if (TryMatchTwoCharOperator('|', "||")) return true;
|
||||
_currentToken = new Token(TokenType.Operator, "|");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (CurrentCharacter == '<')
|
||||
{
|
||||
// Handle <, <=, <>, << operators
|
||||
// <, <=, <>, <<
|
||||
MovePosition();
|
||||
if (CurrentCharacter == '=')
|
||||
{
|
||||
MovePosition();
|
||||
_currentToken = new Token(TokenType.Operator, "<=");
|
||||
return true;
|
||||
}
|
||||
if (CurrentCharacter == '>')
|
||||
{
|
||||
MovePosition();
|
||||
_currentToken = new Token(TokenType.Operator, "<>");
|
||||
return true;
|
||||
}
|
||||
if (CurrentCharacter == '<')
|
||||
{
|
||||
MovePosition();
|
||||
_currentToken = new Token(TokenType.Operator, "<<");
|
||||
return true;
|
||||
}
|
||||
if (TryMatchTwoCharOperator('=', "<=")) return true;
|
||||
if (TryMatchTwoCharOperator('>', "<>")) return true;
|
||||
if (TryMatchTwoCharOperator('<', "<<")) return true;
|
||||
_currentToken = new Token(TokenType.Operator, "<");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (CurrentCharacter == '>')
|
||||
{
|
||||
// Handle >, >=, >> operators
|
||||
// >, >=, >>
|
||||
MovePosition();
|
||||
if (CurrentCharacter == '=')
|
||||
{
|
||||
MovePosition();
|
||||
_currentToken = new Token(TokenType.Operator, ">=");
|
||||
return true;
|
||||
}
|
||||
if (CurrentCharacter == '>')
|
||||
{
|
||||
MovePosition();
|
||||
_currentToken = new Token(TokenType.Operator, ">>");
|
||||
return true;
|
||||
}
|
||||
if (TryMatchTwoCharOperator('=', ">=")) return true;
|
||||
if (TryMatchTwoCharOperator('>', ">>")) return true;
|
||||
_currentToken = new Token(TokenType.Operator, ">");
|
||||
return true;
|
||||
}
|
||||
@@ -244,6 +207,23 @@ public class StatementReader : SqlServerStatementReader
|
||||
|
||||
return stringValue.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the position is currently sitting on <paramref name="nextChar"/>, advances past it,
|
||||
/// emits <paramref name="twoCharOperator"/> as the current Operator token, and returns
|
||||
/// <c>true</c>. Otherwise leaves position untouched and returns <c>false</c>. Used by the
|
||||
/// multi-character operator dispatch (e.g. <c><</c>/<c><=</c>/<c><></c>/<c><<</c>).
|
||||
/// </summary>
|
||||
private bool TryMatchTwoCharOperator(char nextChar, string twoCharOperator)
|
||||
{
|
||||
if (CurrentCharacter != nextChar)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
MovePosition();
|
||||
_currentToken = new Token(TokenType.Operator, twoCharOperator);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -143,21 +143,12 @@ public class InsertBreakdown : SqlServerInsertBreakdown
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse INSERT statement using regex
|
||||
var insertMatch = System.Text.RegularExpressions.Regex.Match(sql,
|
||||
@"INSERT\s+INTO\s+([^\(\s]+)\s*\(([^\)]*)\)\s*VALUES\s*\(([^\)]*)\)",
|
||||
System.Text.RegularExpressions.RegexOptions.IgnoreCase | System.Text.RegularExpressions.RegexOptions.Singleline, Strata.SqlTools.SqlBreakdown.Utilities.RegexDefaults.MatchTimeout);
|
||||
|
||||
if (!insertMatch.Success)
|
||||
if (!Strata.SqlTools.Statements.SqlServer.ParsePreparation.TryMatchInsertSql(
|
||||
sql, out var tableName, out var columnsClause, out var valuesClause, out errorMessage))
|
||||
{
|
||||
errorMessage = "Could not parse INSERT statement. Expected format: INSERT INTO table (columns) VALUES (values)";
|
||||
return false;
|
||||
}
|
||||
|
||||
var tableName = insertMatch.Groups[1].Value.Trim();
|
||||
var columnsClause = insertMatch.Groups[2].Value.Trim();
|
||||
var valuesClause = insertMatch.Groups[3].Value.Trim();
|
||||
|
||||
result = new InsertBreakdown(tableName, columnsClause, valuesClause, isMicrosoftSql: false)
|
||||
{
|
||||
SetupClauses = setupClauses,
|
||||
|
||||
@@ -39,14 +39,8 @@ public class QueryBreakdown : SqlServerQueryBreakdown
|
||||
public QueryBreakdown(string selectClause, string fromClause, bool isMicrosoftSql = false) : base()
|
||||
{
|
||||
var parser = isMicrosoftSql ? Parser : SnowflakeParserInstance;
|
||||
|
||||
var cleanSelect = parser.ExtractSqlComments(selectClause, out var selectComments);
|
||||
SelectClause.Clause = cleanSelect.Trim();
|
||||
SelectClause.Comment = selectComments.Count > 0 ? string.Join(" ", selectComments) : null;
|
||||
|
||||
var cleanFrom = parser.ExtractSqlComments(fromClause, out var fromComments);
|
||||
FromClause.Clause = cleanFrom.Trim();
|
||||
FromClause.Comment = fromComments.Count > 0 ? string.Join(" ", fromComments) : null;
|
||||
parser.PopulateClauseWithComments(selectClause, SelectClause);
|
||||
parser.PopulateClauseWithComments(fromClause, FromClause);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -44,35 +44,8 @@ public class UpdateBreakdown : SqlServerUpdateBreakdown
|
||||
WhereClause.Comment = whereComments.Count > 0 ? string.Join(" ", whereComments) : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the SQL breakdown as a string for Snowflake.
|
||||
/// </summary>
|
||||
/// <returns>The UPDATE SQL statement.</returns>
|
||||
protected override string GetSqlBreakdown()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine("UPDATE ");
|
||||
sb.AppendLine($" {TableName.Clause}");
|
||||
|
||||
sb.AppendLine("SET ");
|
||||
sb.AppendLine($" {SetClause.Clause}");
|
||||
|
||||
if (IsUsingFromClause)
|
||||
{
|
||||
// Snowflake supports FROM clause in UPDATE
|
||||
sb.AppendLine("FROM ");
|
||||
sb.AppendLine($" {FromClause.Clause}");
|
||||
}
|
||||
|
||||
if (IsUsingWhereClause)
|
||||
{
|
||||
sb.AppendLine("WHERE ");
|
||||
sb.AppendLine($" {WhereClause.Clause}");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
// GetSqlBreakdown() inherited from SqlServer.UpdateBreakdown — Snowflake's UPDATE syntax
|
||||
// (including the optional FROM clause) is identical at the formatter level, so no override needed.
|
||||
|
||||
#region Parse Methods
|
||||
|
||||
|
||||
@@ -152,22 +152,12 @@ public class InsertBreakdown : SqlBreakdownBase
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse INSERT statement using regex
|
||||
// Pattern: INSERT INTO table (columns) VALUES (values)
|
||||
var insertMatch = System.Text.RegularExpressions.Regex.Match(sql,
|
||||
@"INSERT\s+INTO\s+([^\(\s]+)\s*\(([^\)]*)\)\s*VALUES\s*\(([^\)]*)\)",
|
||||
System.Text.RegularExpressions.RegexOptions.IgnoreCase | System.Text.RegularExpressions.RegexOptions.Singleline, Strata.SqlTools.SqlBreakdown.Utilities.RegexDefaults.MatchTimeout);
|
||||
|
||||
if (!insertMatch.Success)
|
||||
if (!Strata.SqlTools.Statements.SqlServer.ParsePreparation.TryMatchInsertSql(
|
||||
sql, out var tableName, out var columnsClause, out var valuesClause, out errorMessage))
|
||||
{
|
||||
errorMessage = "Could not parse INSERT statement. Expected format: INSERT INTO table (columns) VALUES (values)";
|
||||
return false;
|
||||
}
|
||||
|
||||
var tableName = insertMatch.Groups[1].Value.Trim();
|
||||
var columnsClause = insertMatch.Groups[2].Value.Trim();
|
||||
var valuesClause = insertMatch.Groups[3].Value.Trim();
|
||||
|
||||
result = new InsertBreakdown(tableName, columnsClause, valuesClause)
|
||||
{
|
||||
SetupClauses = setupClauses,
|
||||
|
||||
@@ -66,4 +66,37 @@ public static class ParsePreparation
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the shared INSERT-statement match used by SqlServer / Snowflake (and any future
|
||||
/// dialect that accepts the same <c>INSERT INTO table (cols) VALUES (vals)</c> grammar).
|
||||
/// </summary>
|
||||
/// <param name="sql">The SQL passed through <see cref="TryRunPrelude"/>.</param>
|
||||
/// <param name="tableName">On success, the matched table name (trimmed).</param>
|
||||
/// <param name="columnsClause">On success, the matched column list (trimmed).</param>
|
||||
/// <param name="valuesClause">On success, the matched values list (trimmed).</param>
|
||||
/// <param name="errorMessage">On failure, a human-readable parse-error message.</param>
|
||||
/// <returns><c>true</c> if the regex matched and the three groups are populated; <c>false</c> otherwise.</returns>
|
||||
public static bool TryMatchInsertSql(string sql, out string tableName, out string columnsClause, out string valuesClause, out string errorMessage)
|
||||
{
|
||||
tableName = null!;
|
||||
columnsClause = null!;
|
||||
valuesClause = null!;
|
||||
errorMessage = null!;
|
||||
|
||||
var insertMatch = Regex.Match(sql,
|
||||
@"INSERT\s+INTO\s+([^\(\s]+)\s*\(([^\)]*)\)\s*VALUES\s*\(([^\)]*)\)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Singleline, RegexDefaults.MatchTimeout);
|
||||
|
||||
if (!insertMatch.Success)
|
||||
{
|
||||
errorMessage = "Could not parse INSERT statement. Expected format: INSERT INTO table (columns) VALUES (values)";
|
||||
return false;
|
||||
}
|
||||
|
||||
tableName = insertMatch.Groups[1].Value.Trim();
|
||||
columnsClause = insertMatch.Groups[2].Value.Trim();
|
||||
valuesClause = insertMatch.Groups[3].Value.Trim();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +140,22 @@ public class StatementParser
|
||||
/// <param name="sql">The SQL statement containing comments.</param>
|
||||
/// <param name="comments">The extracted comments as a list of strings.</param>
|
||||
/// <returns>The SQL statement with comments removed.</returns>
|
||||
/// <summary>
|
||||
/// Runs <see cref="ExtractSqlComments"/> on <paramref name="rawText"/> and assigns the
|
||||
/// cleaned text to <paramref name="target"/>'s <see cref="ISqlClause.Clause"/> (trimmed)
|
||||
/// and the merged comments to its <see cref="ISqlClause.Comment"/>. Helper for
|
||||
/// dialect-specific <c>QueryBreakdown</c> constructors that need to ingest
|
||||
/// comment-bearing SQL fragments.
|
||||
/// </summary>
|
||||
/// <param name="rawText">The SQL fragment to clean.</param>
|
||||
/// <param name="target">The clause to populate.</param>
|
||||
public void PopulateClauseWithComments(string rawText, ISqlClause target)
|
||||
{
|
||||
var clean = ExtractSqlComments(rawText, out var comments);
|
||||
target.Clause = clean.Trim();
|
||||
target.Comment = comments.Count > 0 ? string.Join(" ", comments) : null;
|
||||
}
|
||||
|
||||
#pragma warning disable S3776 // Cognitive Complexity of methods should not be too high
|
||||
#pragma warning disable S127 // "for" loop stop conditions should be invariant
|
||||
public virtual string ExtractSqlComments(string sql, out List<string> comments)
|
||||
|
||||
Reference in New Issue
Block a user