From 2e483c9fa818a607deb7dfcb567cd7629b24e606 Mon Sep 17 00:00:00 2001 From: Thom Lamb Date: Thu, 21 May 2026 14:53:21 -0500 Subject: [PATCH] fix: resolving more duplicate lines issue --- .../Breakdowns/QueryBreakdownCollection.cs | 207 ++--------------- .../Breakdowns/QueryBreakdownCollection.cs | 216 ++---------------- .../QueryBreakdownCollectionBase.cs | 216 ++++++++++++++++++ .../QueryCollectionAnalysisHelper.cs | 8 +- 4 files changed, 253 insertions(+), 394 deletions(-) create mode 100644 src/Strata.SqlTools.SqlServer/Breakdowns/QueryBreakdownCollectionBase.cs diff --git a/src/Strata.SqlTools.Snowflake/Breakdowns/QueryBreakdownCollection.cs b/src/Strata.SqlTools.Snowflake/Breakdowns/QueryBreakdownCollection.cs index 74d32a0..3fe389d 100644 --- a/src/Strata.SqlTools.Snowflake/Breakdowns/QueryBreakdownCollection.cs +++ b/src/Strata.SqlTools.Snowflake/Breakdowns/QueryBreakdownCollection.cs @@ -13,89 +13,21 @@ namespace Strata.SqlTools.Breakdowns.Snowflake; /// time travel, snowflake-specific parameters (:parameter and @parameter syntax), /// and proper batch handling. /// -public class QueryBreakdownCollection : SqlBreakdownCollection +public class QueryBreakdownCollection : SqlServer.QueryBreakdownCollectionBase { - private readonly List _queryBreakdowns; - /// /// Initializes a new instance of the class for Snowflake. /// public QueryBreakdownCollection() : base() { - _queryBreakdowns = new List(); } /// /// Initializes a new instance of the class with initial query breakdowns. /// /// The initial collection of query breakdowns. - public QueryBreakdownCollection(IEnumerable queryBreakdowns) - : base(queryBreakdowns?.Cast() ?? Enumerable.Empty()) + public QueryBreakdownCollection(IEnumerable queryBreakdowns) : base(queryBreakdowns) { - _queryBreakdowns = new List(queryBreakdowns ?? Enumerable.Empty()); - } - - /// - /// Gets the collection of QueryBreakdown objects. - /// - public IReadOnlyList QueryBreakdowns => _queryBreakdowns.AsReadOnly(); - - /// - /// Adds a QueryBreakdown to the collection. - /// - /// The query breakdown to add. - /// Thrown when queryBreakdown is null. - public void Add(QueryBreakdown queryBreakdown) - { - if (queryBreakdown == null) - { - throw new ArgumentNullException(nameof(queryBreakdown)); - } - - _queryBreakdowns.Add(queryBreakdown); - base.Add(queryBreakdown); - } - - /// - /// Adds multiple QueryBreakdowns to the collection. - /// - /// The query breakdowns to add. - /// Thrown when queryBreakdowns is null. - public void AddRange(IEnumerable queryBreakdowns) - { - if (queryBreakdowns == null) - { - throw new ArgumentNullException(nameof(queryBreakdowns)); - } - - foreach (var breakdown in queryBreakdowns) - { - Add(breakdown); - } - } - - /// - /// Removes a QueryBreakdown from the collection. - /// - /// The query breakdown to remove. - /// True if removed; otherwise, false. - public bool Remove(QueryBreakdown queryBreakdown) - { - var removed = _queryBreakdowns.Remove(queryBreakdown); - if (removed) - { - base.Remove(queryBreakdown); - } - return removed; - } - - /// - /// Clears all query breakdowns from the collection. - /// - public new void Clear() - { - _queryBreakdowns.Clear(); - base.Clear(); } /// @@ -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 /// Query breakdowns that reference stages. public IEnumerable WhereUseStageReference(string? stageName = null) { - return _queryBreakdowns.Where(q => + return QueryBreakdownList.Where(q => { var sql = q.GetSql(); @@ -193,7 +125,7 @@ public class QueryBreakdownCollection : SqlBreakdownCollection /// Query breakdowns that use JSON functions or colon notation. public IEnumerable 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 /// Query breakdowns using time travel syntax. public IEnumerable WhereUseTimeTravelFeature() { - return _queryBreakdowns.Where(q => + return QueryBreakdownList.Where(q => { var sql = q.GetSql().ToUpperInvariant(); @@ -256,7 +188,7 @@ public class QueryBreakdownCollection : SqlBreakdownCollection /// Query breakdowns using Snowflake-specific functions. public IEnumerable WhereUseSnowflakeFunctions() { - return _queryBreakdowns.Where(q => + return QueryBreakdownList.Where(q => { var sql = q.GetSql().ToUpperInvariant(); @@ -279,7 +211,7 @@ public class QueryBreakdownCollection : SqlBreakdownCollection /// Query breakdowns using temporary tables. public IEnumerable WhereUseTemporaryTables() { - return _queryBreakdowns.Where(q => + return QueryBreakdownList.Where(q => { var sql = q.GetSql().ToUpperInvariant(); @@ -296,7 +228,7 @@ public class QueryBreakdownCollection : SqlBreakdownCollection /// Query breakdowns using external data sources. public IEnumerable WhereUseExternalData() { - return _queryBreakdowns.Where(q => + return QueryBreakdownList.Where(q => { var sql = q.GetSql().ToUpperInvariant(); @@ -306,85 +238,13 @@ public class QueryBreakdownCollection : SqlBreakdownCollection }); } - /// - /// Filters queries by the SELECT clause content using Snowflake's format. - /// - /// The text to find in the SELECT clause. - /// Filtered query breakdowns. - public IEnumerable WhereSelectContains(string selectContains) - { - if (string.IsNullOrWhiteSpace(selectContains)) - { - throw new ArgumentNullException(nameof(selectContains)); - } - - return _queryBreakdowns.Where(q => - q.SelectClause?.Clause?.Contains(selectContains, StringComparison.OrdinalIgnoreCase) ?? false); - } - - /// - /// Filters queries that reference specific tables or schemas. - /// - /// The table name or schema pattern to find. - /// Filtered query breakdowns. - public IEnumerable WhereTableContains(string tableNameContains) - { - if (string.IsNullOrWhiteSpace(tableNameContains)) - { - throw new ArgumentNullException(nameof(tableNameContains)); - } - - return _queryBreakdowns.Where(q => - q.FromClause?.Clause?.Contains(tableNameContains, StringComparison.OrdinalIgnoreCase) ?? false); - } - - /// - /// Filters queries that have WHERE clauses. - /// - /// Query breakdowns with WHERE clauses. - public IEnumerable WhereHaveWhereClause() - { - return _queryBreakdowns.Where(q => - !string.IsNullOrWhiteSpace(q.WhereClause?.Clause)); - } - - /// - /// Filters queries without WHERE clauses (potentially risky for full table scans). - /// - /// Query breakdowns without WHERE clauses. - public IEnumerable WhereHaveNoWhereClause() - { - return _queryBreakdowns.Where(q => - string.IsNullOrWhiteSpace(q.WhereClause?.Clause)); - } - - /// - /// Filters queries that have GROUP BY clauses. - /// - /// Query breakdowns with GROUP BY clauses. - public IEnumerable WhereHaveGroupByClause() - { - return _queryBreakdowns.Where(q => - !string.IsNullOrWhiteSpace(q.GroupByClause?.Clause)); - } - - /// - /// Filters queries that have ORDER BY clauses. - /// - /// Query breakdowns with ORDER BY clauses. - public IEnumerable WhereHaveOrderByClause() - { - return _queryBreakdowns.Where(q => - !string.IsNullOrWhiteSpace(q.OrderByClause?.Clause)); - } - /// /// Gets a comprehensive analysis of all queries in the collection. /// /// Analysis summary for each query. public IEnumerable 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 @"); } - /// - /// 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. - /// - public void SynchronizeParameters() - => SqlServer.QueryCollectionAnalysisHelper.SynchronizeParameters(_queryBreakdowns); - /// /// Adds a parameter with a specific value to all queries in the collection. /// @@ -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!; } } - /// - /// Gets all unique parameters from all queries in the collection as a combined dictionary. - /// - /// A dictionary containing all unique parameters across all queries. - protected Dictionary GetCombinedParameterDictionary() - => SqlServer.QueryCollectionAnalysisHelper.GetCombinedParameters(_queryBreakdowns); - /// /// Gets a formatted string representation of all unique parameters with Snowflake-specific syntax. /// @@ -551,7 +397,7 @@ public class QueryBreakdownCollection : SqlBreakdownCollection /// Parameter usage information. public IEnumerable 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 }); } - /// - /// Gets the total number of columns selected across all queries. - /// - /// Total column count. - public int GetTotalSelectedColumns() - => SqlServer.QueryCollectionAnalysisHelper.GetTotalSelectedColumns(_queryBreakdowns); - - /// - /// Gets all unique table names referenced across all queries. - /// - /// - /// 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. - /// - /// List of unique table names. - public IEnumerable GetUniqueTableReferences() - => SqlServer.QueryCollectionAnalysisHelper.GetUniqueTableReferences(_queryBreakdowns); - /// /// Gets a summary of all queries including their types and basic composition. /// @@ -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), diff --git a/src/Strata.SqlTools.SqlServer/Breakdowns/QueryBreakdownCollection.cs b/src/Strata.SqlTools.SqlServer/Breakdowns/QueryBreakdownCollection.cs index d6a22af..f18d779 100644 --- a/src/Strata.SqlTools.SqlServer/Breakdowns/QueryBreakdownCollection.cs +++ b/src/Strata.SqlTools.SqlServer/Breakdowns/QueryBreakdownCollection.cs @@ -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. /// -public class QueryBreakdownCollection : SqlBreakdownCollection +public class QueryBreakdownCollection : QueryBreakdownCollectionBase { - private readonly List _queryBreakdowns; - /// /// Initializes a new instance of the class. /// public QueryBreakdownCollection() : base() { - _queryBreakdowns = new List(); } /// /// Initializes a new instance of the class with initial query breakdowns. /// /// The initial collection of query breakdowns. - public QueryBreakdownCollection(IEnumerable queryBreakdowns) : base(queryBreakdowns?.Cast() ?? Enumerable.Empty()) + public QueryBreakdownCollection(IEnumerable queryBreakdowns) : base(queryBreakdowns) { - _queryBreakdowns = new List(queryBreakdowns ?? Enumerable.Empty()); - } - - /// - /// Gets the collection of QueryBreakdown objects. - /// - public IReadOnlyList QueryBreakdowns => _queryBreakdowns.AsReadOnly(); - - /// - /// Adds a QueryBreakdown to the collection. - /// - /// The query breakdown to add. - /// Thrown when queryBreakdown is null. - public void Add(QueryBreakdown queryBreakdown) - { - if (queryBreakdown == null) - { - throw new ArgumentNullException(nameof(queryBreakdown)); - } - - _queryBreakdowns.Add(queryBreakdown); - base.Add(queryBreakdown); - } - - /// - /// Adds multiple QueryBreakdowns to the collection. - /// - /// The query breakdowns to add. - /// Thrown when queryBreakdowns is null. - public void AddRange(IEnumerable queryBreakdowns) - { - if (queryBreakdowns == null) - { - throw new ArgumentNullException(nameof(queryBreakdowns)); - } - - foreach (var breakdown in queryBreakdowns) - { - Add(breakdown); - } - } - - /// - /// Removes a QueryBreakdown from the collection. - /// - /// The query breakdown to remove. - /// True if removed; otherwise, false. - public bool Remove(QueryBreakdown queryBreakdown) - { - var removed = _queryBreakdowns.Remove(queryBreakdown); - if (removed) - { - base.Remove(queryBreakdown); - } - return removed; - } - - /// - /// Clears all query breakdowns from the collection. - /// - public new void Clear() - { - _queryBreakdowns.Clear(); - base.Clear(); } /// @@ -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(); } - /// - /// Filters query breakdowns where the SELECT clause contains specific text. - /// - /// The text to find in the SELECT clause. - /// Filtered query breakdowns. - public IEnumerable WhereSelectContains(string selectContains) - { - if (string.IsNullOrWhiteSpace(selectContains)) - { - throw new ArgumentNullException(nameof(selectContains)); - } - - return _queryBreakdowns.Where(q => - q.SelectClause?.Clause?.Contains(selectContains, StringComparison.OrdinalIgnoreCase) ?? false); - } - - /// - /// Filters query breakdowns where the FROM clause contains specific text. - /// - /// The table name or pattern to find. - /// Filtered query breakdowns. - public IEnumerable WhereTableContains(string tableNameContains) - { - if (string.IsNullOrWhiteSpace(tableNameContains)) - { - throw new ArgumentNullException(nameof(tableNameContains)); - } - - return _queryBreakdowns.Where(q => - q.FromClause?.Clause?.Contains(tableNameContains, StringComparison.OrdinalIgnoreCase) ?? false); - } - - /// - /// Filters query breakdowns that have a WHERE clause. - /// - /// Query breakdowns with WHERE clauses. - public IEnumerable WhereHaveWhereClause() - { - return _queryBreakdowns.Where(q => - !string.IsNullOrWhiteSpace(q.WhereClause?.Clause)); - } - - /// - /// Filters query breakdowns that do NOT have a WHERE clause. - /// - /// - /// This is useful for identifying potentially risky queries that affect all rows. - /// - /// Query breakdowns without WHERE clauses. - public IEnumerable WhereHaveNoWhereClause() - { - return _queryBreakdowns.Where(q => - string.IsNullOrWhiteSpace(q.WhereClause?.Clause)); - } - - /// - /// Filters query breakdowns that have a GROUP BY clause. - /// - /// Query breakdowns with GROUP BY clauses. - public IEnumerable WhereHaveGroupByClause() - { - return _queryBreakdowns.Where(q => - !string.IsNullOrWhiteSpace(q.GroupByClause?.Clause)); - } - - /// - /// Filters query breakdowns that have an ORDER BY clause. - /// - /// Query breakdowns with ORDER BY clauses. - public IEnumerable WhereHaveOrderByClause() - { - return _queryBreakdowns.Where(q => - !string.IsNullOrWhiteSpace(q.OrderByClause?.Clause)); - } - /// /// Filters query breakdowns that have WITH clauses (CTEs). /// /// Query breakdowns with CTE definitions. public IEnumerable WhereHaveCommonTableExpressions() { - return _queryBreakdowns.Where(q => q.WithClauses.Count > 0); + return QueryBreakdownList.Where(q => q.WithClauses.Count > 0); } /// @@ -233,7 +89,7 @@ public class QueryBreakdownCollection : SqlBreakdownCollection /// Query breakdowns with JOINs. public IEnumerable WhereHaveJoins() { - return _queryBreakdowns.Where(q => q.GetSql().Contains("JOIN", StringComparison.OrdinalIgnoreCase)); + return QueryBreakdownList.Where(q => q.GetSql().Contains("JOIN", StringComparison.OrdinalIgnoreCase)); } /// @@ -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)); } - /// - /// Gets the total number of columns selected across all queries. - /// - /// Total column count. - public int GetTotalSelectedColumns() - => QueryCollectionAnalysisHelper.GetTotalSelectedColumns(_queryBreakdowns); - - /// - /// Gets all unique table names referenced across all queries. - /// - /// - /// 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. - /// - /// List of unique table names. - public IEnumerable GetUniqueTableReferences() - => QueryCollectionAnalysisHelper.GetUniqueTableReferences(_queryBreakdowns); - - /// - /// Gets a summary of all queries including their types and basic composition. - /// - /// Summary information for each query. - public IEnumerable GetQuerySummaries() - => QueryCollectionAnalysisHelper.GetQuerySummaries(_queryBreakdowns); - - /// - /// Synchronizes parameters across all queries in the collection. - /// - /// - /// 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. - /// - public void SynchronizeParameters() - => QueryCollectionAnalysisHelper.SynchronizeParameters(_queryBreakdowns); - /// /// Adds a parameter to all queries in the collection. /// @@ -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!; } } /// - /// 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. /// - /// A dictionary containing all unique parameters across all queries. - protected Dictionary GetCombinedParameterDictionary() - => QueryCollectionAnalysisHelper.GetCombinedParameters(_queryBreakdowns); - - /// - /// Gets all unique parameters from all queries in the collection. - /// - /// A collection of unique QueryParam objects. + /// Summary information for each query. + public IEnumerable GetQuerySummaries() + => QueryCollectionAnalysisHelper.GetQuerySummaries(QueryBreakdownList); /// /// Gets all T-SQL parameters as a formatted string suitable for SQL Server. @@ -363,7 +177,7 @@ public class QueryBreakdownCollection : SqlBreakdownCollection /// Parameter usage information. public IEnumerable GetParameterUsageReport() { - return QueryCollectionAnalysisHelper.GetParameterUsage(_queryBreakdowns) + return QueryCollectionAnalysisHelper.GetParameterUsage(QueryBreakdownList) .Select(usage => new ParameterUsageReport { ParameterName = usage.Name, diff --git a/src/Strata.SqlTools.SqlServer/Breakdowns/QueryBreakdownCollectionBase.cs b/src/Strata.SqlTools.SqlServer/Breakdowns/QueryBreakdownCollectionBase.cs new file mode 100644 index 0000000..5a5d0f9 --- /dev/null +++ b/src/Strata.SqlTools.SqlServer/Breakdowns/QueryBreakdownCollectionBase.cs @@ -0,0 +1,216 @@ +using Strata.SqlTools.SqlBreakdown.Classes; +using Strata.SqlTools.SqlBreakdown.Interfaces; + +namespace Strata.SqlTools.Breakdowns.SqlServer; + +/// +/// 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. +/// +/// The concrete query breakdown type held by the collection. +/// +/// Each dialect collection closes the generic over its own query type (for example, +/// QueryBreakdownCollectionBase<Snowflake.QueryBreakdown>) so that public members such as +/// and the Where* filters keep their dialect-specific element type. +/// +public abstract class QueryBreakdownCollectionBase : SqlBreakdownCollection + where TQuery : QueryBreakdown +{ + /// + /// Gets the backing list of typed query breakdowns. + /// + protected List QueryBreakdownList { get; } + + /// + /// Initializes a new, empty instance. + /// + protected QueryBreakdownCollectionBase() : base() + { + QueryBreakdownList = new List(); + } + + /// + /// Initializes a new instance with initial query breakdowns. + /// + /// The initial collection of query breakdowns. + protected QueryBreakdownCollectionBase(IEnumerable queryBreakdowns) + : base(queryBreakdowns?.Cast() ?? Enumerable.Empty()) + { + QueryBreakdownList = new List(queryBreakdowns ?? Enumerable.Empty()); + } + + /// + /// Gets the collection of query breakdowns. + /// + public IReadOnlyList QueryBreakdowns => QueryBreakdownList.AsReadOnly(); + + /// + /// Adds a query breakdown to the collection. + /// + /// The query breakdown to add. + /// Thrown when is null. + public void Add(TQuery queryBreakdown) + { + if (queryBreakdown == null) + { + throw new ArgumentNullException(nameof(queryBreakdown)); + } + + QueryBreakdownList.Add(queryBreakdown); + base.Add(queryBreakdown); + } + + /// + /// Adds multiple query breakdowns to the collection. + /// + /// The query breakdowns to add. + /// Thrown when is null. + public void AddRange(IEnumerable queryBreakdowns) + { + if (queryBreakdowns == null) + { + throw new ArgumentNullException(nameof(queryBreakdowns)); + } + + foreach (var breakdown in queryBreakdowns) + { + Add(breakdown); + } + } + + /// + /// Removes a query breakdown from the collection. + /// + /// The query breakdown to remove. + /// True if removed; otherwise, false. + public bool Remove(TQuery queryBreakdown) + { + var removed = QueryBreakdownList.Remove(queryBreakdown); + if (removed) + { + base.Remove(queryBreakdown); + } + return removed; + } + + /// + /// Clears all query breakdowns from the collection. + /// + public new void Clear() + { + QueryBreakdownList.Clear(); + base.Clear(); + } + + /// + /// Filters query breakdowns where the SELECT clause contains specific text. + /// + /// The text to find in the SELECT clause. + /// Filtered query breakdowns. + public IEnumerable WhereSelectContains(string selectContains) + { + if (string.IsNullOrWhiteSpace(selectContains)) + { + throw new ArgumentNullException(nameof(selectContains)); + } + + return QueryBreakdownList.Where(q => + q.SelectClause?.Clause?.Contains(selectContains, StringComparison.OrdinalIgnoreCase) ?? false); + } + + /// + /// Filters query breakdowns where the FROM clause contains specific text. + /// + /// The table name or pattern to find. + /// Filtered query breakdowns. + public IEnumerable WhereTableContains(string tableNameContains) + { + if (string.IsNullOrWhiteSpace(tableNameContains)) + { + throw new ArgumentNullException(nameof(tableNameContains)); + } + + return QueryBreakdownList.Where(q => + q.FromClause?.Clause?.Contains(tableNameContains, StringComparison.OrdinalIgnoreCase) ?? false); + } + + /// + /// Filters query breakdowns that have a WHERE clause. + /// + /// Query breakdowns with WHERE clauses. + public IEnumerable WhereHaveWhereClause() + { + return QueryBreakdownList.Where(q => + !string.IsNullOrWhiteSpace(q.WhereClause?.Clause)); + } + + /// + /// Filters query breakdowns that do NOT have a WHERE clause. + /// + /// + /// This is useful for identifying potentially risky queries that affect all rows. + /// + /// Query breakdowns without WHERE clauses. + public IEnumerable WhereHaveNoWhereClause() + { + return QueryBreakdownList.Where(q => + string.IsNullOrWhiteSpace(q.WhereClause?.Clause)); + } + + /// + /// Filters query breakdowns that have a GROUP BY clause. + /// + /// Query breakdowns with GROUP BY clauses. + public IEnumerable WhereHaveGroupByClause() + { + return QueryBreakdownList.Where(q => + !string.IsNullOrWhiteSpace(q.GroupByClause?.Clause)); + } + + /// + /// Filters query breakdowns that have an ORDER BY clause. + /// + /// Query breakdowns with ORDER BY clauses. + public IEnumerable WhereHaveOrderByClause() + { + return QueryBreakdownList.Where(q => + !string.IsNullOrWhiteSpace(q.OrderByClause?.Clause)); + } + + /// + /// Gets the total number of columns selected across all queries. + /// + /// Total column count. + public int GetTotalSelectedColumns() + => QueryCollectionAnalysisHelper.GetTotalSelectedColumns(QueryBreakdownList); + + /// + /// Gets all unique table names referenced across all queries. + /// + /// + /// 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. + /// + /// List of unique table names. + public IEnumerable GetUniqueTableReferences() + => QueryCollectionAnalysisHelper.GetUniqueTableReferences(QueryBreakdownList); + + /// + /// Synchronizes parameter values across all queries in the collection. + /// + /// + /// 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. + /// + public void SynchronizeParameters() + => QueryCollectionAnalysisHelper.SynchronizeParameters(QueryBreakdownList); + + /// + /// Gets all unique parameters from all queries in the collection as a combined dictionary. + /// + /// A dictionary containing all unique parameters across all queries. + protected Dictionary GetCombinedParameterDictionary() + => QueryCollectionAnalysisHelper.GetCombinedParameters(QueryBreakdownList); +} diff --git a/src/Strata.SqlTools.SqlServer/Breakdowns/QueryCollectionAnalysisHelper.cs b/src/Strata.SqlTools.SqlServer/Breakdowns/QueryCollectionAnalysisHelper.cs index 651f24f..66359c4 100644 --- a/src/Strata.SqlTools.SqlServer/Breakdowns/QueryCollectionAnalysisHelper.cs +++ b/src/Strata.SqlTools.SqlServer/Breakdowns/QueryCollectionAnalysisHelper.cs @@ -10,6 +10,8 @@ namespace Strata.SqlTools.Breakdowns.SqlServer; /// public static class QueryCollectionAnalysisHelper { + private static readonly string[] TableAliasSeparators = { " AS ", " " }; + /// /// Gets the total number of columns selected across all queries. /// @@ -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();