Merge pull request 'fix: resolving more duplicate lines issue' (#8) from fix/sonarqube-duplicate-code-lines-2 into main
SonarQube Analysis / sonarqube (push) Successful in 3m57s
SonarQube Analysis / sonarqube (push) Successful in 3m57s
Reviewed-on: #8
This commit was merged in pull request #8.
This commit is contained in:
@@ -13,89 +13,21 @@ namespace Strata.SqlTools.Breakdowns.Snowflake;
|
||||
/// time travel, snowflake-specific parameters (:parameter and @parameter syntax),
|
||||
/// and proper batch handling.
|
||||
/// </remarks>
|
||||
public class QueryBreakdownCollection : SqlBreakdownCollection
|
||||
public class QueryBreakdownCollection : SqlServer.QueryBreakdownCollectionBase<QueryBreakdown>
|
||||
{
|
||||
private readonly List<QueryBreakdown> _queryBreakdowns;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="QueryBreakdownCollection"/> class for Snowflake.
|
||||
/// </summary>
|
||||
public QueryBreakdownCollection() : base()
|
||||
{
|
||||
_queryBreakdowns = new List<QueryBreakdown>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="QueryBreakdownCollection"/> class with initial query breakdowns.
|
||||
/// </summary>
|
||||
/// <param name="queryBreakdowns">The initial collection of query breakdowns.</param>
|
||||
public QueryBreakdownCollection(IEnumerable<QueryBreakdown> queryBreakdowns)
|
||||
: base(queryBreakdowns?.Cast<ISqlBreakdown>() ?? Enumerable.Empty<ISqlBreakdown>())
|
||||
public QueryBreakdownCollection(IEnumerable<QueryBreakdown> queryBreakdowns) : base(queryBreakdowns)
|
||||
{
|
||||
_queryBreakdowns = new List<QueryBreakdown>(queryBreakdowns ?? Enumerable.Empty<QueryBreakdown>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of QueryBreakdown objects.
|
||||
/// </summary>
|
||||
public IReadOnlyList<QueryBreakdown> QueryBreakdowns => _queryBreakdowns.AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a QueryBreakdown to the collection.
|
||||
/// </summary>
|
||||
/// <param name="queryBreakdown">The query breakdown to add.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when queryBreakdown is null.</exception>
|
||||
public void Add(QueryBreakdown queryBreakdown)
|
||||
{
|
||||
if (queryBreakdown == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(queryBreakdown));
|
||||
}
|
||||
|
||||
_queryBreakdowns.Add(queryBreakdown);
|
||||
base.Add(queryBreakdown);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds multiple QueryBreakdowns to the collection.
|
||||
/// </summary>
|
||||
/// <param name="queryBreakdowns">The query breakdowns to add.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when queryBreakdowns is null.</exception>
|
||||
public void AddRange(IEnumerable<QueryBreakdown> queryBreakdowns)
|
||||
{
|
||||
if (queryBreakdowns == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(queryBreakdowns));
|
||||
}
|
||||
|
||||
foreach (var breakdown in queryBreakdowns)
|
||||
{
|
||||
Add(breakdown);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a QueryBreakdown from the collection.
|
||||
/// </summary>
|
||||
/// <param name="queryBreakdown">The query breakdown to remove.</param>
|
||||
/// <returns>True if removed; otherwise, false.</returns>
|
||||
public bool Remove(QueryBreakdown queryBreakdown)
|
||||
{
|
||||
var removed = _queryBreakdowns.Remove(queryBreakdown);
|
||||
if (removed)
|
||||
{
|
||||
base.Remove(queryBreakdown);
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all query breakdowns from the collection.
|
||||
/// </summary>
|
||||
public new void Clear()
|
||||
{
|
||||
_queryBreakdowns.Clear();
|
||||
base.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -122,11 +54,11 @@ public class QueryBreakdownCollection : SqlBreakdownCollection
|
||||
}
|
||||
|
||||
// Add all queries with semicolon separators
|
||||
if (_queryBreakdowns.Count > 0)
|
||||
if (QueryBreakdownList.Count > 0)
|
||||
{
|
||||
for (int i = 0; i < _queryBreakdowns.Count; i++)
|
||||
for (int i = 0; i < QueryBreakdownList.Count; i++)
|
||||
{
|
||||
var query = _queryBreakdowns[i];
|
||||
var query = QueryBreakdownList[i];
|
||||
var sql = query.GetSql(includeSetupFinish);
|
||||
|
||||
// Ensure proper termination
|
||||
@@ -139,7 +71,7 @@ public class QueryBreakdownCollection : SqlBreakdownCollection
|
||||
}
|
||||
|
||||
// Add spacing between statements
|
||||
if (i < _queryBreakdowns.Count - 1)
|
||||
if (i < QueryBreakdownList.Count - 1)
|
||||
{
|
||||
sb.AppendLine();
|
||||
sb.AppendLine();
|
||||
@@ -161,7 +93,7 @@ public class QueryBreakdownCollection : SqlBreakdownCollection
|
||||
/// <returns>Query breakdowns that reference stages.</returns>
|
||||
public IEnumerable<QueryBreakdown> WhereUseStageReference(string? stageName = null)
|
||||
{
|
||||
return _queryBreakdowns.Where(q =>
|
||||
return QueryBreakdownList.Where(q =>
|
||||
{
|
||||
var sql = q.GetSql();
|
||||
|
||||
@@ -193,7 +125,7 @@ public class QueryBreakdownCollection : SqlBreakdownCollection
|
||||
/// <returns>Query breakdowns that use JSON functions or colon notation.</returns>
|
||||
public IEnumerable<QueryBreakdown> WhereUseSemiStructuredData()
|
||||
{
|
||||
return _queryBreakdowns.Where(q =>
|
||||
return QueryBreakdownList.Where(q =>
|
||||
{
|
||||
var sql = q.GetSql().ToUpperInvariant();
|
||||
|
||||
@@ -223,7 +155,7 @@ public class QueryBreakdownCollection : SqlBreakdownCollection
|
||||
// Normalize parameter name (remove : or @)
|
||||
var cleanName = parameterName.TrimStart(':', '@');
|
||||
|
||||
return _queryBreakdowns.Where(q =>
|
||||
return QueryBreakdownList.Where(q =>
|
||||
{
|
||||
var sql = q.GetSql();
|
||||
return sql.Contains($":{cleanName}", StringComparison.OrdinalIgnoreCase) ||
|
||||
@@ -240,7 +172,7 @@ public class QueryBreakdownCollection : SqlBreakdownCollection
|
||||
/// <returns>Query breakdowns using time travel syntax.</returns>
|
||||
public IEnumerable<QueryBreakdown> WhereUseTimeTravelFeature()
|
||||
{
|
||||
return _queryBreakdowns.Where(q =>
|
||||
return QueryBreakdownList.Where(q =>
|
||||
{
|
||||
var sql = q.GetSql().ToUpperInvariant();
|
||||
|
||||
@@ -256,7 +188,7 @@ public class QueryBreakdownCollection : SqlBreakdownCollection
|
||||
/// <returns>Query breakdowns using Snowflake-specific functions.</returns>
|
||||
public IEnumerable<QueryBreakdown> WhereUseSnowflakeFunctions()
|
||||
{
|
||||
return _queryBreakdowns.Where(q =>
|
||||
return QueryBreakdownList.Where(q =>
|
||||
{
|
||||
var sql = q.GetSql().ToUpperInvariant();
|
||||
|
||||
@@ -279,7 +211,7 @@ public class QueryBreakdownCollection : SqlBreakdownCollection
|
||||
/// <returns>Query breakdowns using temporary tables.</returns>
|
||||
public IEnumerable<QueryBreakdown> WhereUseTemporaryTables()
|
||||
{
|
||||
return _queryBreakdowns.Where(q =>
|
||||
return QueryBreakdownList.Where(q =>
|
||||
{
|
||||
var sql = q.GetSql().ToUpperInvariant();
|
||||
|
||||
@@ -296,7 +228,7 @@ public class QueryBreakdownCollection : SqlBreakdownCollection
|
||||
/// <returns>Query breakdowns using external data sources.</returns>
|
||||
public IEnumerable<QueryBreakdown> WhereUseExternalData()
|
||||
{
|
||||
return _queryBreakdowns.Where(q =>
|
||||
return QueryBreakdownList.Where(q =>
|
||||
{
|
||||
var sql = q.GetSql().ToUpperInvariant();
|
||||
|
||||
@@ -306,85 +238,13 @@ public class QueryBreakdownCollection : SqlBreakdownCollection
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters queries by the SELECT clause content using Snowflake's format.
|
||||
/// </summary>
|
||||
/// <param name="selectContains">The text to find in the SELECT clause.</param>
|
||||
/// <returns>Filtered query breakdowns.</returns>
|
||||
public IEnumerable<QueryBreakdown> WhereSelectContains(string selectContains)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(selectContains))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(selectContains));
|
||||
}
|
||||
|
||||
return _queryBreakdowns.Where(q =>
|
||||
q.SelectClause?.Clause?.Contains(selectContains, StringComparison.OrdinalIgnoreCase) ?? false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters queries that reference specific tables or schemas.
|
||||
/// </summary>
|
||||
/// <param name="tableNameContains">The table name or schema pattern to find.</param>
|
||||
/// <returns>Filtered query breakdowns.</returns>
|
||||
public IEnumerable<QueryBreakdown> WhereTableContains(string tableNameContains)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(tableNameContains))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tableNameContains));
|
||||
}
|
||||
|
||||
return _queryBreakdowns.Where(q =>
|
||||
q.FromClause?.Clause?.Contains(tableNameContains, StringComparison.OrdinalIgnoreCase) ?? false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters queries that have WHERE clauses.
|
||||
/// </summary>
|
||||
/// <returns>Query breakdowns with WHERE clauses.</returns>
|
||||
public IEnumerable<QueryBreakdown> WhereHaveWhereClause()
|
||||
{
|
||||
return _queryBreakdowns.Where(q =>
|
||||
!string.IsNullOrWhiteSpace(q.WhereClause?.Clause));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters queries without WHERE clauses (potentially risky for full table scans).
|
||||
/// </summary>
|
||||
/// <returns>Query breakdowns without WHERE clauses.</returns>
|
||||
public IEnumerable<QueryBreakdown> WhereHaveNoWhereClause()
|
||||
{
|
||||
return _queryBreakdowns.Where(q =>
|
||||
string.IsNullOrWhiteSpace(q.WhereClause?.Clause));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters queries that have GROUP BY clauses.
|
||||
/// </summary>
|
||||
/// <returns>Query breakdowns with GROUP BY clauses.</returns>
|
||||
public IEnumerable<QueryBreakdown> WhereHaveGroupByClause()
|
||||
{
|
||||
return _queryBreakdowns.Where(q =>
|
||||
!string.IsNullOrWhiteSpace(q.GroupByClause?.Clause));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters queries that have ORDER BY clauses.
|
||||
/// </summary>
|
||||
/// <returns>Query breakdowns with ORDER BY clauses.</returns>
|
||||
public IEnumerable<QueryBreakdown> WhereHaveOrderByClause()
|
||||
{
|
||||
return _queryBreakdowns.Where(q =>
|
||||
!string.IsNullOrWhiteSpace(q.OrderByClause?.Clause));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a comprehensive analysis of all queries in the collection.
|
||||
/// </summary>
|
||||
/// <returns>Analysis summary for each query.</returns>
|
||||
public IEnumerable<SnowflakeQueryAnalysis> AnalyzeQueries()
|
||||
{
|
||||
return _queryBreakdowns.Select((q, index) => new SnowflakeQueryAnalysis
|
||||
return QueryBreakdownList.Select((q, index) => new SnowflakeQueryAnalysis
|
||||
{
|
||||
Index = index,
|
||||
HasSelectClause = !string.IsNullOrWhiteSpace(q.SelectClause?.Clause),
|
||||
@@ -469,13 +329,6 @@ public class QueryBreakdownCollection : SqlBreakdownCollection
|
||||
sql.Contains("COPY INTO @");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronizes parameter values across all queries in the collection.
|
||||
/// Ensures that if a parameter with the same name exists in multiple queries, they all have the same value.
|
||||
/// </summary>
|
||||
public void SynchronizeParameters()
|
||||
=> SqlServer.QueryCollectionAnalysisHelper.SynchronizeParameters(_queryBreakdowns);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a parameter with a specific value to all queries in the collection.
|
||||
/// </summary>
|
||||
@@ -488,19 +341,12 @@ public class QueryBreakdownCollection : SqlBreakdownCollection
|
||||
throw new ArgumentException("Parameter name cannot be null or empty.", nameof(parameterName));
|
||||
}
|
||||
|
||||
foreach (var query in _queryBreakdowns)
|
||||
foreach (var query in QueryBreakdownList)
|
||||
{
|
||||
query.Parameters[parameterName] = value!;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all unique parameters from all queries in the collection as a combined dictionary.
|
||||
/// </summary>
|
||||
/// <returns>A dictionary containing all unique parameters across all queries.</returns>
|
||||
protected Dictionary<string, object?> GetCombinedParameterDictionary()
|
||||
=> SqlServer.QueryCollectionAnalysisHelper.GetCombinedParameters(_queryBreakdowns);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a formatted string representation of all unique parameters with Snowflake-specific syntax.
|
||||
/// </summary>
|
||||
@@ -551,7 +397,7 @@ public class QueryBreakdownCollection : SqlBreakdownCollection
|
||||
/// <returns>Parameter usage information.</returns>
|
||||
public IEnumerable<ParameterUsageReport> GetParameterUsageReport()
|
||||
{
|
||||
return SqlServer.QueryCollectionAnalysisHelper.GetParameterUsage(_queryBreakdowns)
|
||||
return SqlServer.QueryCollectionAnalysisHelper.GetParameterUsage(QueryBreakdownList)
|
||||
.Select(usage => new ParameterUsageReport
|
||||
{
|
||||
ParameterName = usage.Name,
|
||||
@@ -561,25 +407,6 @@ public class QueryBreakdownCollection : SqlBreakdownCollection
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total number of columns selected across all queries.
|
||||
/// </summary>
|
||||
/// <returns>Total column count.</returns>
|
||||
public int GetTotalSelectedColumns()
|
||||
=> SqlServer.QueryCollectionAnalysisHelper.GetTotalSelectedColumns(_queryBreakdowns);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all unique table names referenced across all queries.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This provides a quick overview of which tables are being queried.
|
||||
/// Note: This is a best-effort extraction and may not capture all table references,
|
||||
/// especially in complex subqueries or with aliasing.
|
||||
/// </remarks>
|
||||
/// <returns>List of unique table names.</returns>
|
||||
public IEnumerable<string> GetUniqueTableReferences()
|
||||
=> SqlServer.QueryCollectionAnalysisHelper.GetUniqueTableReferences(_queryBreakdowns);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a summary of all queries including their types and basic composition.
|
||||
/// </summary>
|
||||
@@ -589,7 +416,7 @@ public class QueryBreakdownCollection : SqlBreakdownCollection
|
||||
var stageQueries = WhereUseStageReference().ToHashSet();
|
||||
var semiStructuredQueries = WhereUseSemiStructuredData().ToHashSet();
|
||||
|
||||
return _queryBreakdowns.Select((q, index) => new SnowflakeQueryAnalysis
|
||||
return QueryBreakdownList.Select((q, index) => new SnowflakeQueryAnalysis
|
||||
{
|
||||
Index = index,
|
||||
HasSelectClause = !string.IsNullOrWhiteSpace(q.SelectClause?.Clause),
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Text;
|
||||
using Strata.SqlTools.SqlBreakdown.Classes;
|
||||
using Strata.SqlTools.SqlBreakdown.Interfaces;
|
||||
|
||||
namespace Strata.SqlTools.Breakdowns.SqlServer;
|
||||
|
||||
@@ -11,88 +9,21 @@ namespace Strata.SqlTools.Breakdowns.SqlServer;
|
||||
/// This class extends SqlBreakdownCollection with SQL Server-specific functionality,
|
||||
/// including support for T-SQL features like batches (GO), temporary tables, stored procedures, and CTEs.
|
||||
/// </remarks>
|
||||
public class QueryBreakdownCollection : SqlBreakdownCollection
|
||||
public class QueryBreakdownCollection : QueryBreakdownCollectionBase<QueryBreakdown>
|
||||
{
|
||||
private readonly List<QueryBreakdown> _queryBreakdowns;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="QueryBreakdownCollection"/> class.
|
||||
/// </summary>
|
||||
public QueryBreakdownCollection() : base()
|
||||
{
|
||||
_queryBreakdowns = new List<QueryBreakdown>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="QueryBreakdownCollection"/> class with initial query breakdowns.
|
||||
/// </summary>
|
||||
/// <param name="queryBreakdowns">The initial collection of query breakdowns.</param>
|
||||
public QueryBreakdownCollection(IEnumerable<QueryBreakdown> queryBreakdowns) : base(queryBreakdowns?.Cast<ISqlBreakdown>() ?? Enumerable.Empty<ISqlBreakdown>())
|
||||
public QueryBreakdownCollection(IEnumerable<QueryBreakdown> queryBreakdowns) : base(queryBreakdowns)
|
||||
{
|
||||
_queryBreakdowns = new List<QueryBreakdown>(queryBreakdowns ?? Enumerable.Empty<QueryBreakdown>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of QueryBreakdown objects.
|
||||
/// </summary>
|
||||
public IReadOnlyList<QueryBreakdown> QueryBreakdowns => _queryBreakdowns.AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a QueryBreakdown to the collection.
|
||||
/// </summary>
|
||||
/// <param name="queryBreakdown">The query breakdown to add.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when queryBreakdown is null.</exception>
|
||||
public void Add(QueryBreakdown queryBreakdown)
|
||||
{
|
||||
if (queryBreakdown == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(queryBreakdown));
|
||||
}
|
||||
|
||||
_queryBreakdowns.Add(queryBreakdown);
|
||||
base.Add(queryBreakdown);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds multiple QueryBreakdowns to the collection.
|
||||
/// </summary>
|
||||
/// <param name="queryBreakdowns">The query breakdowns to add.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when queryBreakdowns is null.</exception>
|
||||
public void AddRange(IEnumerable<QueryBreakdown> queryBreakdowns)
|
||||
{
|
||||
if (queryBreakdowns == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(queryBreakdowns));
|
||||
}
|
||||
|
||||
foreach (var breakdown in queryBreakdowns)
|
||||
{
|
||||
Add(breakdown);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a QueryBreakdown from the collection.
|
||||
/// </summary>
|
||||
/// <param name="queryBreakdown">The query breakdown to remove.</param>
|
||||
/// <returns>True if removed; otherwise, false.</returns>
|
||||
public bool Remove(QueryBreakdown queryBreakdown)
|
||||
{
|
||||
var removed = _queryBreakdowns.Remove(queryBreakdown);
|
||||
if (removed)
|
||||
{
|
||||
base.Remove(queryBreakdown);
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all query breakdowns from the collection.
|
||||
/// </summary>
|
||||
public new void Clear()
|
||||
{
|
||||
_queryBreakdowns.Clear();
|
||||
base.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -116,15 +47,15 @@ public class QueryBreakdownCollection : SqlBreakdownCollection
|
||||
}
|
||||
|
||||
// Add all queries with GO separators
|
||||
if (_queryBreakdowns.Count > 0)
|
||||
if (QueryBreakdownList.Count > 0)
|
||||
{
|
||||
for (int i = 0; i < _queryBreakdowns.Count; i++)
|
||||
for (int i = 0; i < QueryBreakdownList.Count; i++)
|
||||
{
|
||||
var query = _queryBreakdowns[i];
|
||||
var query = QueryBreakdownList[i];
|
||||
sb.Append(query.GetSql(includeSetupFinish));
|
||||
|
||||
// Add GO separator between queries (not after last)
|
||||
if (i < _queryBreakdowns.Count - 1)
|
||||
if (i < QueryBreakdownList.Count - 1)
|
||||
{
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("GO");
|
||||
@@ -143,88 +74,13 @@ public class QueryBreakdownCollection : SqlBreakdownCollection
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters query breakdowns where the SELECT clause contains specific text.
|
||||
/// </summary>
|
||||
/// <param name="selectContains">The text to find in the SELECT clause.</param>
|
||||
/// <returns>Filtered query breakdowns.</returns>
|
||||
public IEnumerable<QueryBreakdown> WhereSelectContains(string selectContains)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(selectContains))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(selectContains));
|
||||
}
|
||||
|
||||
return _queryBreakdowns.Where(q =>
|
||||
q.SelectClause?.Clause?.Contains(selectContains, StringComparison.OrdinalIgnoreCase) ?? false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters query breakdowns where the FROM clause contains specific text.
|
||||
/// </summary>
|
||||
/// <param name="tableNameContains">The table name or pattern to find.</param>
|
||||
/// <returns>Filtered query breakdowns.</returns>
|
||||
public IEnumerable<QueryBreakdown> WhereTableContains(string tableNameContains)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(tableNameContains))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tableNameContains));
|
||||
}
|
||||
|
||||
return _queryBreakdowns.Where(q =>
|
||||
q.FromClause?.Clause?.Contains(tableNameContains, StringComparison.OrdinalIgnoreCase) ?? false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters query breakdowns that have a WHERE clause.
|
||||
/// </summary>
|
||||
/// <returns>Query breakdowns with WHERE clauses.</returns>
|
||||
public IEnumerable<QueryBreakdown> WhereHaveWhereClause()
|
||||
{
|
||||
return _queryBreakdowns.Where(q =>
|
||||
!string.IsNullOrWhiteSpace(q.WhereClause?.Clause));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters query breakdowns that do NOT have a WHERE clause.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is useful for identifying potentially risky queries that affect all rows.
|
||||
/// </remarks>
|
||||
/// <returns>Query breakdowns without WHERE clauses.</returns>
|
||||
public IEnumerable<QueryBreakdown> WhereHaveNoWhereClause()
|
||||
{
|
||||
return _queryBreakdowns.Where(q =>
|
||||
string.IsNullOrWhiteSpace(q.WhereClause?.Clause));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters query breakdowns that have a GROUP BY clause.
|
||||
/// </summary>
|
||||
/// <returns>Query breakdowns with GROUP BY clauses.</returns>
|
||||
public IEnumerable<QueryBreakdown> WhereHaveGroupByClause()
|
||||
{
|
||||
return _queryBreakdowns.Where(q =>
|
||||
!string.IsNullOrWhiteSpace(q.GroupByClause?.Clause));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters query breakdowns that have an ORDER BY clause.
|
||||
/// </summary>
|
||||
/// <returns>Query breakdowns with ORDER BY clauses.</returns>
|
||||
public IEnumerable<QueryBreakdown> WhereHaveOrderByClause()
|
||||
{
|
||||
return _queryBreakdowns.Where(q =>
|
||||
!string.IsNullOrWhiteSpace(q.OrderByClause?.Clause));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters query breakdowns that have WITH clauses (CTEs).
|
||||
/// </summary>
|
||||
/// <returns>Query breakdowns with CTE definitions.</returns>
|
||||
public IEnumerable<QueryBreakdown> WhereHaveCommonTableExpressions()
|
||||
{
|
||||
return _queryBreakdowns.Where(q => q.WithClauses.Count > 0);
|
||||
return QueryBreakdownList.Where(q => q.WithClauses.Count > 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -233,7 +89,7 @@ public class QueryBreakdownCollection : SqlBreakdownCollection
|
||||
/// <returns>Query breakdowns with JOINs.</returns>
|
||||
public IEnumerable<QueryBreakdown> WhereHaveJoins()
|
||||
{
|
||||
return _queryBreakdowns.Where(q => q.GetSql().Contains("JOIN", StringComparison.OrdinalIgnoreCase));
|
||||
return QueryBreakdownList.Where(q => q.GetSql().Contains("JOIN", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -248,47 +104,10 @@ public class QueryBreakdownCollection : SqlBreakdownCollection
|
||||
throw new ArgumentNullException(nameof(parameterName));
|
||||
}
|
||||
|
||||
return _queryBreakdowns.Where(q =>
|
||||
return QueryBreakdownList.Where(q =>
|
||||
q.ParameterList.Any(p => p.Name == parameterName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total number of columns selected across all queries.
|
||||
/// </summary>
|
||||
/// <returns>Total column count.</returns>
|
||||
public int GetTotalSelectedColumns()
|
||||
=> QueryCollectionAnalysisHelper.GetTotalSelectedColumns(_queryBreakdowns);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all unique table names referenced across all queries.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This provides a quick overview of which tables are being queried.
|
||||
/// Note: This is a best-effort extraction and may not capture all table references,
|
||||
/// especially in complex subqueries or with aliasing.
|
||||
/// </remarks>
|
||||
/// <returns>List of unique table names.</returns>
|
||||
public IEnumerable<string> GetUniqueTableReferences()
|
||||
=> QueryCollectionAnalysisHelper.GetUniqueTableReferences(_queryBreakdowns);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a summary of all queries including their types and basic composition.
|
||||
/// </summary>
|
||||
/// <returns>Summary information for each query.</returns>
|
||||
public IEnumerable<QuerySummary> GetQuerySummaries()
|
||||
=> QueryCollectionAnalysisHelper.GetQuerySummaries(_queryBreakdowns);
|
||||
|
||||
/// <summary>
|
||||
/// Synchronizes parameters across all queries in the collection.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This ensures all queries share the same parameter values based on parameter name.
|
||||
/// Later parameter values override earlier ones if there are conflicts.
|
||||
/// Only synchronizes parameters that the query already defines to avoid adding unused parameters.
|
||||
/// </remarks>
|
||||
public void SynchronizeParameters()
|
||||
=> QueryCollectionAnalysisHelper.SynchronizeParameters(_queryBreakdowns);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a parameter to all queries in the collection.
|
||||
/// </summary>
|
||||
@@ -301,23 +120,18 @@ public class QueryBreakdownCollection : SqlBreakdownCollection
|
||||
throw new ArgumentNullException(nameof(parameterName));
|
||||
}
|
||||
|
||||
foreach (var query in _queryBreakdowns)
|
||||
foreach (var query in QueryBreakdownList)
|
||||
{
|
||||
query.Parameters[parameterName] = value!;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all unique parameters from all queries in the collection as a combined dictionary.
|
||||
/// Gets a summary of all queries including their types and basic composition.
|
||||
/// </summary>
|
||||
/// <returns>A dictionary containing all unique parameters across all queries.</returns>
|
||||
protected Dictionary<string, object?> GetCombinedParameterDictionary()
|
||||
=> QueryCollectionAnalysisHelper.GetCombinedParameters(_queryBreakdowns);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all unique parameters from all queries in the collection.
|
||||
/// </summary>
|
||||
/// <returns>A collection of unique QueryParam objects.</returns>
|
||||
/// <returns>Summary information for each query.</returns>
|
||||
public IEnumerable<QuerySummary> GetQuerySummaries()
|
||||
=> QueryCollectionAnalysisHelper.GetQuerySummaries(QueryBreakdownList);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all T-SQL parameters as a formatted string suitable for SQL Server.
|
||||
@@ -363,7 +177,7 @@ public class QueryBreakdownCollection : SqlBreakdownCollection
|
||||
/// <returns>Parameter usage information.</returns>
|
||||
public IEnumerable<ParameterUsageReport> GetParameterUsageReport()
|
||||
{
|
||||
return QueryCollectionAnalysisHelper.GetParameterUsage(_queryBreakdowns)
|
||||
return QueryCollectionAnalysisHelper.GetParameterUsage(QueryBreakdownList)
|
||||
.Select(usage => new ParameterUsageReport
|
||||
{
|
||||
ParameterName = usage.Name,
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
using Strata.SqlTools.SqlBreakdown.Classes;
|
||||
using Strata.SqlTools.SqlBreakdown.Interfaces;
|
||||
|
||||
namespace Strata.SqlTools.Breakdowns.SqlServer;
|
||||
|
||||
/// <summary>
|
||||
/// Base collection that manages a strongly typed list of query breakdowns and provides the
|
||||
/// dialect-agnostic plumbing, filters, and analysis shared by the per-dialect collections.
|
||||
/// </summary>
|
||||
/// <typeparam name="TQuery">The concrete query breakdown type held by the collection.</typeparam>
|
||||
/// <remarks>
|
||||
/// Each dialect collection closes the generic over its own query type (for example,
|
||||
/// <c>QueryBreakdownCollectionBase<Snowflake.QueryBreakdown></c>) so that public members such as
|
||||
/// <see cref="QueryBreakdowns"/> and the <c>Where*</c> filters keep their dialect-specific element type.
|
||||
/// </remarks>
|
||||
public abstract class QueryBreakdownCollectionBase<TQuery> : SqlBreakdownCollection
|
||||
where TQuery : QueryBreakdown
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the backing list of typed query breakdowns.
|
||||
/// </summary>
|
||||
protected List<TQuery> QueryBreakdownList { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new, empty instance.
|
||||
/// </summary>
|
||||
protected QueryBreakdownCollectionBase() : base()
|
||||
{
|
||||
QueryBreakdownList = new List<TQuery>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance with initial query breakdowns.
|
||||
/// </summary>
|
||||
/// <param name="queryBreakdowns">The initial collection of query breakdowns.</param>
|
||||
protected QueryBreakdownCollectionBase(IEnumerable<TQuery> queryBreakdowns)
|
||||
: base(queryBreakdowns?.Cast<ISqlBreakdown>() ?? Enumerable.Empty<ISqlBreakdown>())
|
||||
{
|
||||
QueryBreakdownList = new List<TQuery>(queryBreakdowns ?? Enumerable.Empty<TQuery>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of query breakdowns.
|
||||
/// </summary>
|
||||
public IReadOnlyList<TQuery> QueryBreakdowns => QueryBreakdownList.AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a query breakdown to the collection.
|
||||
/// </summary>
|
||||
/// <param name="queryBreakdown">The query breakdown to add.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="queryBreakdown"/> is null.</exception>
|
||||
public void Add(TQuery queryBreakdown)
|
||||
{
|
||||
if (queryBreakdown == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(queryBreakdown));
|
||||
}
|
||||
|
||||
QueryBreakdownList.Add(queryBreakdown);
|
||||
base.Add(queryBreakdown);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds multiple query breakdowns to the collection.
|
||||
/// </summary>
|
||||
/// <param name="queryBreakdowns">The query breakdowns to add.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="queryBreakdowns"/> is null.</exception>
|
||||
public void AddRange(IEnumerable<TQuery> queryBreakdowns)
|
||||
{
|
||||
if (queryBreakdowns == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(queryBreakdowns));
|
||||
}
|
||||
|
||||
foreach (var breakdown in queryBreakdowns)
|
||||
{
|
||||
Add(breakdown);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a query breakdown from the collection.
|
||||
/// </summary>
|
||||
/// <param name="queryBreakdown">The query breakdown to remove.</param>
|
||||
/// <returns>True if removed; otherwise, false.</returns>
|
||||
public bool Remove(TQuery queryBreakdown)
|
||||
{
|
||||
var removed = QueryBreakdownList.Remove(queryBreakdown);
|
||||
if (removed)
|
||||
{
|
||||
base.Remove(queryBreakdown);
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all query breakdowns from the collection.
|
||||
/// </summary>
|
||||
public new void Clear()
|
||||
{
|
||||
QueryBreakdownList.Clear();
|
||||
base.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters query breakdowns where the SELECT clause contains specific text.
|
||||
/// </summary>
|
||||
/// <param name="selectContains">The text to find in the SELECT clause.</param>
|
||||
/// <returns>Filtered query breakdowns.</returns>
|
||||
public IEnumerable<TQuery> WhereSelectContains(string selectContains)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(selectContains))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(selectContains));
|
||||
}
|
||||
|
||||
return QueryBreakdownList.Where(q =>
|
||||
q.SelectClause?.Clause?.Contains(selectContains, StringComparison.OrdinalIgnoreCase) ?? false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters query breakdowns where the FROM clause contains specific text.
|
||||
/// </summary>
|
||||
/// <param name="tableNameContains">The table name or pattern to find.</param>
|
||||
/// <returns>Filtered query breakdowns.</returns>
|
||||
public IEnumerable<TQuery> WhereTableContains(string tableNameContains)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(tableNameContains))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tableNameContains));
|
||||
}
|
||||
|
||||
return QueryBreakdownList.Where(q =>
|
||||
q.FromClause?.Clause?.Contains(tableNameContains, StringComparison.OrdinalIgnoreCase) ?? false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters query breakdowns that have a WHERE clause.
|
||||
/// </summary>
|
||||
/// <returns>Query breakdowns with WHERE clauses.</returns>
|
||||
public IEnumerable<TQuery> WhereHaveWhereClause()
|
||||
{
|
||||
return QueryBreakdownList.Where(q =>
|
||||
!string.IsNullOrWhiteSpace(q.WhereClause?.Clause));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters query breakdowns that do NOT have a WHERE clause.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is useful for identifying potentially risky queries that affect all rows.
|
||||
/// </remarks>
|
||||
/// <returns>Query breakdowns without WHERE clauses.</returns>
|
||||
public IEnumerable<TQuery> WhereHaveNoWhereClause()
|
||||
{
|
||||
return QueryBreakdownList.Where(q =>
|
||||
string.IsNullOrWhiteSpace(q.WhereClause?.Clause));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters query breakdowns that have a GROUP BY clause.
|
||||
/// </summary>
|
||||
/// <returns>Query breakdowns with GROUP BY clauses.</returns>
|
||||
public IEnumerable<TQuery> WhereHaveGroupByClause()
|
||||
{
|
||||
return QueryBreakdownList.Where(q =>
|
||||
!string.IsNullOrWhiteSpace(q.GroupByClause?.Clause));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters query breakdowns that have an ORDER BY clause.
|
||||
/// </summary>
|
||||
/// <returns>Query breakdowns with ORDER BY clauses.</returns>
|
||||
public IEnumerable<TQuery> WhereHaveOrderByClause()
|
||||
{
|
||||
return QueryBreakdownList.Where(q =>
|
||||
!string.IsNullOrWhiteSpace(q.OrderByClause?.Clause));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total number of columns selected across all queries.
|
||||
/// </summary>
|
||||
/// <returns>Total column count.</returns>
|
||||
public int GetTotalSelectedColumns()
|
||||
=> QueryCollectionAnalysisHelper.GetTotalSelectedColumns(QueryBreakdownList);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all unique table names referenced across all queries.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This provides a quick overview of which tables are being queried.
|
||||
/// Note: This is a best-effort extraction and may not capture all table references,
|
||||
/// especially in complex subqueries or with aliasing.
|
||||
/// </remarks>
|
||||
/// <returns>List of unique table names.</returns>
|
||||
public IEnumerable<string> GetUniqueTableReferences()
|
||||
=> QueryCollectionAnalysisHelper.GetUniqueTableReferences(QueryBreakdownList);
|
||||
|
||||
/// <summary>
|
||||
/// Synchronizes parameter values across all queries in the collection.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Ensures all queries share the same parameter value based on parameter name. Later parameter
|
||||
/// values override earlier ones if there are conflicts. Only parameters a query already defines
|
||||
/// are synchronized, to avoid adding unused parameters.
|
||||
/// </remarks>
|
||||
public void SynchronizeParameters()
|
||||
=> QueryCollectionAnalysisHelper.SynchronizeParameters(QueryBreakdownList);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all unique parameters from all queries in the collection as a combined dictionary.
|
||||
/// </summary>
|
||||
/// <returns>A dictionary containing all unique parameters across all queries.</returns>
|
||||
protected Dictionary<string, object?> GetCombinedParameterDictionary()
|
||||
=> QueryCollectionAnalysisHelper.GetCombinedParameters(QueryBreakdownList);
|
||||
}
|
||||
@@ -10,6 +10,8 @@ namespace Strata.SqlTools.Breakdowns.SqlServer;
|
||||
/// </remarks>
|
||||
public static class QueryCollectionAnalysisHelper
|
||||
{
|
||||
private static readonly string[] TableAliasSeparators = { " AS ", " " };
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total number of columns selected across all queries.
|
||||
/// </summary>
|
||||
@@ -105,9 +107,9 @@ public static class QueryCollectionAnalysisHelper
|
||||
// Find the last query that has this parameter and get its value
|
||||
for (int i = queryList.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (queryList[i].Parameters.ContainsKey(paramName))
|
||||
if (queryList[i].Parameters.TryGetValue(paramName, out var value))
|
||||
{
|
||||
lastValue = queryList[i].Parameters[paramName];
|
||||
lastValue = value;
|
||||
parameterFound = true;
|
||||
break;
|
||||
}
|
||||
@@ -232,7 +234,7 @@ public static class QueryCollectionAnalysisHelper
|
||||
var trimmed = part.Trim();
|
||||
|
||||
// Remove alias (assuming format: table AS alias or table alias)
|
||||
var tokens = trimmed.Split(new[] { " AS ", " " }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var tokens = trimmed.Split(TableAliasSeparators, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (tokens.Length > 0)
|
||||
{
|
||||
var tableName = tokens[0].Trim();
|
||||
|
||||
Reference in New Issue
Block a user