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.Functions.Conditional;
|
||||||
using Strata.SqlTools.SqlBreakdown.Expressions.Literals;
|
using Strata.SqlTools.SqlBreakdown.Expressions.Literals;
|
||||||
using Strata.SqlTools.SqlBreakdown.Interfaces.Core;
|
using Strata.SqlTools.SqlBreakdown.Interfaces.Core;
|
||||||
|
using static Strata.SqlTools.Markdown.Internal.MarkdownTextHelpers;
|
||||||
|
|
||||||
namespace Strata.SqlTools.Markdown.Expressions;
|
namespace Strata.SqlTools.Markdown.Expressions;
|
||||||
|
|
||||||
@@ -360,8 +361,6 @@ public class ExpressionGenerator : IVisitor<string>
|
|||||||
.Replace("]", "]");
|
.Replace("]", "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string TruncateText(string text, int maxLength)
|
|
||||||
=> Internal.MarkdownTextHelpers.TruncateText(text, maxLength);
|
|
||||||
|
|
||||||
#region IVisitor Implementation
|
#region IVisitor Implementation
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Strata.SqlTools.Breakdowns.SqlServer;
|
using Strata.SqlTools.Breakdowns.SqlServer;
|
||||||
using Strata.SqlTools.SqlBreakdown.Interfaces;
|
using Strata.SqlTools.SqlBreakdown.Interfaces;
|
||||||
|
using static Strata.SqlTools.Markdown.Internal.MarkdownTextHelpers;
|
||||||
|
|
||||||
namespace Strata.SqlTools.Markdown.SqlServer;
|
namespace Strata.SqlTools.Markdown.SqlServer;
|
||||||
|
|
||||||
@@ -191,7 +192,5 @@ public static class QueryBreakdownGenerator
|
|||||||
.Replace(">", ">");
|
.Replace(">", ">");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string TruncateText(string text, int maxLength)
|
|
||||||
=> Internal.MarkdownTextHelpers.TruncateText(text, maxLength);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Strata.SqlTools.SqlBreakdown.Interfaces;
|
using Strata.SqlTools.SqlBreakdown.Interfaces;
|
||||||
|
using static Strata.SqlTools.Markdown.Internal.MarkdownTextHelpers;
|
||||||
|
|
||||||
namespace Strata.SqlTools.Markdown.SqlServer;
|
namespace Strata.SqlTools.Markdown.SqlServer;
|
||||||
|
|
||||||
@@ -112,9 +113,6 @@ public static class SqlStatementGenerator
|
|||||||
.Replace("\r", "");
|
.Replace("\r", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string TruncateText(string text, int maxLength)
|
|
||||||
=> Internal.MarkdownTextHelpers.TruncateText(text, maxLength);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Cleans table name for use in Mermaid diagrams.
|
/// Cleans table name for use in Mermaid diagrams.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -36,14 +36,8 @@ public class QueryBreakdown : SqlServerQueryBreakdown
|
|||||||
public QueryBreakdown(string selectClause, string fromClause, bool isMicrosoftSql = false) : base()
|
public QueryBreakdown(string selectClause, string fromClause, bool isMicrosoftSql = false) : base()
|
||||||
{
|
{
|
||||||
var parser = isMicrosoftSql ? Parser : PostgreSqlParserInstance;
|
var parser = isMicrosoftSql ? Parser : PostgreSqlParserInstance;
|
||||||
|
parser.PopulateClauseWithComments(selectClause, SelectClause);
|
||||||
var cleanSelect = parser.ExtractSqlComments(selectClause, out var selectComments);
|
parser.PopulateClauseWithComments(fromClause, FromClause);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -104,76 +104,39 @@ public class StatementReader : SqlServerStatementReader
|
|||||||
|
|
||||||
if (CurrentCharacter == '=')
|
if (CurrentCharacter == '=')
|
||||||
{
|
{
|
||||||
// Handle => operator (used in PostgreSQL for hstore and other operations)
|
// =, => (PostgreSQL hstore + other operations)
|
||||||
MovePosition();
|
MovePosition();
|
||||||
if (CurrentCharacter == '>')
|
if (TryMatchTwoCharOperator('>', "=>")) return true;
|
||||||
{
|
|
||||||
MovePosition();
|
|
||||||
_currentToken = new Token(TokenType.Operator, "=>");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Single = is handled as regular operator
|
|
||||||
_currentToken = new Token(TokenType.Operator, "=");
|
_currentToken = new Token(TokenType.Operator, "=");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CurrentCharacter == '|')
|
if (CurrentCharacter == '|')
|
||||||
{
|
{
|
||||||
// Handle || concatenation operator
|
// |, ||
|
||||||
MovePosition();
|
MovePosition();
|
||||||
if (CurrentCharacter == '|')
|
if (TryMatchTwoCharOperator('|', "||")) return true;
|
||||||
{
|
|
||||||
MovePosition();
|
|
||||||
_currentToken = new Token(TokenType.Operator, "||");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Single | is also an operator
|
|
||||||
_currentToken = new Token(TokenType.Operator, "|");
|
_currentToken = new Token(TokenType.Operator, "|");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CurrentCharacter == '<')
|
if (CurrentCharacter == '<')
|
||||||
{
|
{
|
||||||
// Handle <, <=, <>, << operators
|
// <, <=, <>, <<
|
||||||
MovePosition();
|
MovePosition();
|
||||||
if (CurrentCharacter == '=')
|
if (TryMatchTwoCharOperator('=', "<=")) return true;
|
||||||
{
|
if (TryMatchTwoCharOperator('>', "<>")) return true;
|
||||||
MovePosition();
|
if (TryMatchTwoCharOperator('<', "<<")) return true;
|
||||||
_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;
|
|
||||||
}
|
|
||||||
_currentToken = new Token(TokenType.Operator, "<");
|
_currentToken = new Token(TokenType.Operator, "<");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CurrentCharacter == '>')
|
if (CurrentCharacter == '>')
|
||||||
{
|
{
|
||||||
// Handle >, >=, >> operators
|
// >, >=, >>
|
||||||
MovePosition();
|
MovePosition();
|
||||||
if (CurrentCharacter == '=')
|
if (TryMatchTwoCharOperator('=', ">=")) return true;
|
||||||
{
|
if (TryMatchTwoCharOperator('>', ">>")) return true;
|
||||||
MovePosition();
|
|
||||||
_currentToken = new Token(TokenType.Operator, ">=");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (CurrentCharacter == '>')
|
|
||||||
{
|
|
||||||
MovePosition();
|
|
||||||
_currentToken = new Token(TokenType.Operator, ">>");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
_currentToken = new Token(TokenType.Operator, ">");
|
_currentToken = new Token(TokenType.Operator, ">");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -244,6 +207,23 @@ public class StatementReader : SqlServerStatementReader
|
|||||||
|
|
||||||
return stringValue.ToString();
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse INSERT statement using regex
|
if (!Strata.SqlTools.Statements.SqlServer.ParsePreparation.TryMatchInsertSql(
|
||||||
var insertMatch = System.Text.RegularExpressions.Regex.Match(sql,
|
sql, out var tableName, out var columnsClause, out var valuesClause, out errorMessage))
|
||||||
@"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)
|
|
||||||
{
|
{
|
||||||
errorMessage = "Could not parse INSERT statement. Expected format: INSERT INTO table (columns) VALUES (values)";
|
|
||||||
return false;
|
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)
|
result = new InsertBreakdown(tableName, columnsClause, valuesClause, isMicrosoftSql: false)
|
||||||
{
|
{
|
||||||
SetupClauses = setupClauses,
|
SetupClauses = setupClauses,
|
||||||
|
|||||||
@@ -39,14 +39,8 @@ public class QueryBreakdown : SqlServerQueryBreakdown
|
|||||||
public QueryBreakdown(string selectClause, string fromClause, bool isMicrosoftSql = false) : base()
|
public QueryBreakdown(string selectClause, string fromClause, bool isMicrosoftSql = false) : base()
|
||||||
{
|
{
|
||||||
var parser = isMicrosoftSql ? Parser : SnowflakeParserInstance;
|
var parser = isMicrosoftSql ? Parser : SnowflakeParserInstance;
|
||||||
|
parser.PopulateClauseWithComments(selectClause, SelectClause);
|
||||||
var cleanSelect = parser.ExtractSqlComments(selectClause, out var selectComments);
|
parser.PopulateClauseWithComments(fromClause, FromClause);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -44,35 +44,8 @@ public class UpdateBreakdown : SqlServerUpdateBreakdown
|
|||||||
WhereClause.Comment = whereComments.Count > 0 ? string.Join(" ", whereComments) : null;
|
WhereClause.Comment = whereComments.Count > 0 ? string.Join(" ", whereComments) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
// GetSqlBreakdown() inherited from SqlServer.UpdateBreakdown — Snowflake's UPDATE syntax
|
||||||
/// Gets the SQL breakdown as a string for Snowflake.
|
// (including the optional FROM clause) is identical at the formatter level, so no override needed.
|
||||||
/// </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();
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Parse Methods
|
#region Parse Methods
|
||||||
|
|
||||||
|
|||||||
@@ -152,22 +152,12 @@ public class InsertBreakdown : SqlBreakdownBase
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse INSERT statement using regex
|
if (!Strata.SqlTools.Statements.SqlServer.ParsePreparation.TryMatchInsertSql(
|
||||||
// Pattern: INSERT INTO table (columns) VALUES (values)
|
sql, out var tableName, out var columnsClause, out var valuesClause, out errorMessage))
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
errorMessage = "Could not parse INSERT statement. Expected format: INSERT INTO table (columns) VALUES (values)";
|
|
||||||
return false;
|
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)
|
result = new InsertBreakdown(tableName, columnsClause, valuesClause)
|
||||||
{
|
{
|
||||||
SetupClauses = setupClauses,
|
SetupClauses = setupClauses,
|
||||||
|
|||||||
@@ -66,4 +66,37 @@ public static class ParsePreparation
|
|||||||
|
|
||||||
return true;
|
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="sql">The SQL statement containing comments.</param>
|
||||||
/// <param name="comments">The extracted comments as a list of strings.</param>
|
/// <param name="comments">The extracted comments as a list of strings.</param>
|
||||||
/// <returns>The SQL statement with comments removed.</returns>
|
/// <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 S3776 // Cognitive Complexity of methods should not be too high
|
||||||
#pragma warning disable S127 // "for" loop stop conditions should be invariant
|
#pragma warning disable S127 // "for" loop stop conditions should be invariant
|
||||||
public virtual string ExtractSqlComments(string sql, out List<string> comments)
|
public virtual string ExtractSqlComments(string sql, out List<string> comments)
|
||||||
|
|||||||
Reference in New Issue
Block a user