From a43be2639e8b3b5985ed858b8bdd39801d33d8c5 Mon Sep 17 00:00:00 2001 From: Thom Lamb Date: Thu, 21 May 2026 13:45:46 -0500 Subject: [PATCH] refactor(breakdowns): Extract shared collection analysis logic --- .../Breakdowns/QueryBreakdownCollection.cs | 123 +-------- .../Breakdowns/QueryBreakdownCollection.cs | 162 +----------- .../Breakdowns/QueryBreakdownCollection.cs | 185 +------------ .../QueryCollectionAnalysisHelper.cs | 246 ++++++++++++++++++ 4 files changed, 280 insertions(+), 436 deletions(-) create mode 100644 src/Strata.SqlTools.SqlServer/Breakdowns/QueryCollectionAnalysisHelper.cs diff --git a/src/Strata.SqlTools.PostgreSql/Breakdowns/QueryBreakdownCollection.cs b/src/Strata.SqlTools.PostgreSql/Breakdowns/QueryBreakdownCollection.cs index d0a15f2..c370704 100644 --- a/src/Strata.SqlTools.PostgreSql/Breakdowns/QueryBreakdownCollection.cs +++ b/src/Strata.SqlTools.PostgreSql/Breakdowns/QueryBreakdownCollection.cs @@ -163,35 +163,14 @@ public class QueryBreakdownCollection : SqlBreakdownCollection /// /// Summary information for each query. public IEnumerable GetQuerySummaries() - { - return _queryBreakdowns.Select((q, index) => new SqlServer.QuerySummary - { - Index = index, - HasSelectClause = !string.IsNullOrWhiteSpace(q.SelectClause?.Clause), - HasFromClause = !string.IsNullOrWhiteSpace(q.FromClause?.Clause), - HasWhereClause = !string.IsNullOrWhiteSpace(q.WhereClause?.Clause), - HasGroupByClause = !string.IsNullOrWhiteSpace(q.GroupByClause?.Clause), - HasHavingClause = !string.IsNullOrWhiteSpace(q.HavingClause?.Clause), - HasOrderByClause = !string.IsNullOrWhiteSpace(q.OrderByClause?.Clause), - HasJoins = false, - HasCTE = q.WithClauses.Count > 0, - ColumnCount = !string.IsNullOrWhiteSpace(q.SelectClause?.Clause) ? q.SelectClause.Clause.Split(',').Length : 0, - ParameterCount = q.ParameterList.Count(), - JoinCount = 0 - }); - } + => SqlServer.QueryCollectionAnalysisHelper.GetQuerySummaries(_queryBreakdowns); /// /// Gets the total number of selected columns across all queries. /// /// Total column count. public int GetTotalSelectedColumns() - { - return _queryBreakdowns.Sum(q => - !string.IsNullOrWhiteSpace(q.SelectClause?.Clause) - ? q.SelectClause.Clause.Split(',').Length - : 0); - } + => SqlServer.QueryCollectionAnalysisHelper.GetTotalSelectedColumns(_queryBreakdowns); /// /// Gets all unique table names referenced across all queries. @@ -203,20 +182,7 @@ public class QueryBreakdownCollection : SqlBreakdownCollection /// /// List of unique table names. public IEnumerable GetUniqueTableReferences() - { - var tables = new HashSet(StringComparer.OrdinalIgnoreCase); - - var tableNames = _queryBreakdowns - .Where(q => !string.IsNullOrWhiteSpace(q.FromClause?.Clause)) - .SelectMany(q => ExtractTableNames(q.FromClause!.Clause!)); - - foreach (var table in tableNames) - { - tables.Add(table); - } - - return tables; - } + => SqlServer.QueryCollectionAnalysisHelper.GetUniqueTableReferences(_queryBreakdowns); /// /// Gets parameter usage information across all queries. @@ -224,83 +190,14 @@ public class QueryBreakdownCollection : SqlBreakdownCollection /// Parameter usage information. public IEnumerable GetParameterUsageReport() { - // Collect all unique parameter names from both ParameterList and Parameters dictionary - var allParamNames = new HashSet(StringComparer.OrdinalIgnoreCase); - - foreach (var query in _queryBreakdowns) - { - // Add from ParameterList (parsed parameters) - foreach (var param in query.ParameterList) + return SqlServer.QueryCollectionAnalysisHelper.GetParameterUsage(_queryBreakdowns) + .Select(usage => new ParameterUsageReport { - allParamNames.Add(param.Name); - } - - // Add from Parameters dictionary (manually added parameters) - foreach (var paramName in query.Parameters.Keys) - { - allParamNames.Add(paramName); - } - } - - foreach (var paramName in allParamNames) - { - var queriesUsing = 0; - object? lastValue = null; - - foreach (var query in _queryBreakdowns) - { - // Check ParameterList first (parsed) - var param = query.ParameterList.FirstOrDefault(p => p.Name.Equals(paramName, StringComparison.OrdinalIgnoreCase)); - if (param != null) - { - queriesUsing++; - lastValue = param.Value; - } - // Also check Parameters dictionary (manually added) - else if (query.Parameters.TryGetValue(paramName, out var dictValue)) - { - queriesUsing++; - lastValue = dictValue; - } - } - - yield return new ParameterUsageReport - { - ParameterName = paramName, - Value = lastValue, - UsedInQueryCount = queriesUsing, - TotalQueries = _queryBreakdowns.Count - }; - } - } - - /// - /// Helper method to extract table names from a FROM clause. - /// - private static IEnumerable ExtractTableNames(string fromClause) - { - if (string.IsNullOrWhiteSpace(fromClause)) - { - yield break; - } - - // Simple extraction: split by comma and clean up aliases - var parts = fromClause.Split(','); - foreach (var part in parts) - { - var trimmed = part.Trim(); - - // Remove alias (assuming format: table AS alias or table alias) - var tokens = trimmed.Split(new[] { " AS ", " " }, StringSplitOptions.RemoveEmptyEntries); - if (tokens.Length > 0) - { - var tableName = tokens[0].Trim(); - if (!string.IsNullOrWhiteSpace(tableName)) - { - yield return tableName; - } - } - } + ParameterName = usage.Name, + Value = usage.Value, + UsedInQueryCount = usage.UsedInQueryCount, + TotalQueries = usage.TotalQueries + }); } } diff --git a/src/Strata.SqlTools.Snowflake/Breakdowns/QueryBreakdownCollection.cs b/src/Strata.SqlTools.Snowflake/Breakdowns/QueryBreakdownCollection.cs index 9a033a2..74d32a0 100644 --- a/src/Strata.SqlTools.Snowflake/Breakdowns/QueryBreakdownCollection.cs +++ b/src/Strata.SqlTools.Snowflake/Breakdowns/QueryBreakdownCollection.cs @@ -474,44 +474,7 @@ public class QueryBreakdownCollection : SqlBreakdownCollection /// Ensures that if a parameter with the same name exists in multiple queries, they all have the same value. /// public void SynchronizeParameters() - { - // Get all unique parameter names across all queries - var allParameterNames = new HashSet(StringComparer.OrdinalIgnoreCase); - foreach (var query in _queryBreakdowns) - { - foreach (var paramName in query.Parameters.Keys) - { - allParameterNames.Add(paramName); - } - } - - // For each parameter, use the last query's value and sync to all queries that have it - foreach (var paramName in allParameterNames) - { - object? lastValue = null; - bool parameterFound = false; - - // Find the last query that has this parameter and get its value - for (int i = _queryBreakdowns.Count - 1; i >= 0; i--) - { - if (_queryBreakdowns[i].Parameters.ContainsKey(paramName)) - { - lastValue = _queryBreakdowns[i].Parameters[paramName]; - parameterFound = true; - break; - } - } - - // Synchronize the parameter value to all queries that have it - if (parameterFound) - { - foreach (var query in _queryBreakdowns.Where(q => q.Parameters.ContainsKey(paramName))) - { - query.Parameters[paramName] = lastValue!; - } - } - } - } + => SqlServer.QueryCollectionAnalysisHelper.SynchronizeParameters(_queryBreakdowns); /// /// Adds a parameter with a specific value to all queries in the collection. @@ -536,26 +499,7 @@ public class QueryBreakdownCollection : SqlBreakdownCollection /// /// A dictionary containing all unique parameters across all queries. protected Dictionary GetCombinedParameterDictionary() - { - var combinedParams = new Dictionary(StringComparer.OrdinalIgnoreCase); - - foreach (var query in _queryBreakdowns) - { - // Add from ParameterList (parsed parameters) - foreach (var param in query.ParameterList) - { - combinedParams[param.Name] = param.Value; - } - - // Add/override from Parameters dictionary (manually added parameters) - foreach (var param in query.Parameters) - { - combinedParams[param.Key] = param.Value; - } - } - - return combinedParams; - } + => SqlServer.QueryCollectionAnalysisHelper.GetCombinedParameters(_queryBreakdowns); /// /// Gets a formatted string representation of all unique parameters with Snowflake-specific syntax. @@ -607,54 +551,14 @@ public class QueryBreakdownCollection : SqlBreakdownCollection /// Parameter usage information. public IEnumerable GetParameterUsageReport() { - // Collect all unique parameter names from both ParameterList and Parameters dictionary - var allParamNames = new HashSet(StringComparer.OrdinalIgnoreCase); - - foreach (var query in _queryBreakdowns) - { - // Add from ParameterList (parsed parameters) - foreach (var param in query.ParameterList) + return SqlServer.QueryCollectionAnalysisHelper.GetParameterUsage(_queryBreakdowns) + .Select(usage => new ParameterUsageReport { - allParamNames.Add(param.Name); - } - - // Add from Parameters dictionary (manually added parameters) - foreach (var paramName in query.Parameters.Keys) - { - allParamNames.Add(paramName); - } - } - - foreach (var paramName in allParamNames) - { - var queriesUsing = 0; - object? lastValue = null; - - foreach (var query in _queryBreakdowns) - { - // Check ParameterList first (parsed) - var param = query.ParameterList.FirstOrDefault(p => p.Name.Equals(paramName, StringComparison.OrdinalIgnoreCase)); - if (param != null) - { - queriesUsing++; - lastValue = param.Value; - } - // Also check Parameters dictionary (manually added) - else if (query.Parameters.TryGetValue(paramName, out var dictValue)) - { - queriesUsing++; - lastValue = dictValue; - } - } - - yield return new ParameterUsageReport - { - ParameterName = paramName, - Value = lastValue, - UsedInQueryCount = queriesUsing, - TotalQueries = _queryBreakdowns.Count - }; - } + ParameterName = usage.Name, + Value = usage.Value, + UsedInQueryCount = usage.UsedInQueryCount, + TotalQueries = usage.TotalQueries + }); } /// @@ -662,12 +566,7 @@ public class QueryBreakdownCollection : SqlBreakdownCollection /// /// Total column count. public int GetTotalSelectedColumns() - { - return _queryBreakdowns.Sum(q => - !string.IsNullOrWhiteSpace(q.SelectClause?.Clause) - ? q.SelectClause.Clause.Split(',').Length - : 0); - } + => SqlServer.QueryCollectionAnalysisHelper.GetTotalSelectedColumns(_queryBreakdowns); /// /// Gets all unique table names referenced across all queries. @@ -679,20 +578,7 @@ public class QueryBreakdownCollection : SqlBreakdownCollection /// /// List of unique table names. public IEnumerable GetUniqueTableReferences() - { - var tables = new HashSet(StringComparer.OrdinalIgnoreCase); - - var tableNames = _queryBreakdowns - .Where(q => !string.IsNullOrWhiteSpace(q.FromClause?.Clause)) - .SelectMany(q => ExtractTableNames(q.FromClause!.Clause!)); - - foreach (var table in tableNames) - { - tables.Add(table); - } - - return tables; - } + => SqlServer.QueryCollectionAnalysisHelper.GetUniqueTableReferences(_queryBreakdowns); /// /// Gets a summary of all queries including their types and basic composition. @@ -722,32 +608,6 @@ public class QueryBreakdownCollection : SqlBreakdownCollection }); } - /// - /// Helper method to extract table names from a FROM clause. - /// - private static IEnumerable ExtractTableNames(string fromClause) - { - if (string.IsNullOrWhiteSpace(fromClause)) - { - yield break; - } - - var parts = fromClause.Split(','); - foreach (var part in parts) - { - var trimmed = part.Trim(); - var tokens = trimmed.Split(new[] { " AS ", " " }, StringSplitOptions.RemoveEmptyEntries); - if (tokens.Length > 0) - { - var tableName = tokens[0].Trim(); - if (!string.IsNullOrWhiteSpace(tableName)) - { - yield return tableName; - } - } - } - } - /// /// Helper method to convert a .NET object to its corresponding Snowflake data type string. /// diff --git a/src/Strata.SqlTools.SqlServer/Breakdowns/QueryBreakdownCollection.cs b/src/Strata.SqlTools.SqlServer/Breakdowns/QueryBreakdownCollection.cs index 4fbf08c..d6a22af 100644 --- a/src/Strata.SqlTools.SqlServer/Breakdowns/QueryBreakdownCollection.cs +++ b/src/Strata.SqlTools.SqlServer/Breakdowns/QueryBreakdownCollection.cs @@ -257,12 +257,7 @@ public class QueryBreakdownCollection : SqlBreakdownCollection /// /// Total column count. public int GetTotalSelectedColumns() - { - return _queryBreakdowns.Sum(q => - !string.IsNullOrWhiteSpace(q.SelectClause?.Clause) - ? q.SelectClause.Clause.Split(',').Length - : 0); - } + => QueryCollectionAnalysisHelper.GetTotalSelectedColumns(_queryBreakdowns); /// /// Gets all unique table names referenced across all queries. @@ -274,72 +269,14 @@ public class QueryBreakdownCollection : SqlBreakdownCollection /// /// List of unique table names. public IEnumerable GetUniqueTableReferences() - { - var tables = new HashSet(StringComparer.OrdinalIgnoreCase); - - var tableNames = _queryBreakdowns - .Where(q => !string.IsNullOrWhiteSpace(q.FromClause?.Clause)) - .SelectMany(q => ExtractTableNames(q.FromClause!.Clause!)); - - foreach (var table in tableNames) - { - tables.Add(table); - } - - return tables; - } + => QueryCollectionAnalysisHelper.GetUniqueTableReferences(_queryBreakdowns); /// /// Gets a summary of all queries including their types and basic composition. /// /// Summary information for each query. public IEnumerable GetQuerySummaries() - { - return _queryBreakdowns.Select((q, index) => new QuerySummary - { - Index = index, - HasSelectClause = !string.IsNullOrWhiteSpace(q.SelectClause?.Clause), - HasFromClause = !string.IsNullOrWhiteSpace(q.FromClause?.Clause), - HasWhereClause = !string.IsNullOrWhiteSpace(q.WhereClause?.Clause), - HasGroupByClause = !string.IsNullOrWhiteSpace(q.GroupByClause?.Clause), - HasHavingClause = !string.IsNullOrWhiteSpace(q.HavingClause?.Clause), - HasOrderByClause = !string.IsNullOrWhiteSpace(q.OrderByClause?.Clause), - HasJoins = false, - HasCTE = q.WithClauses.Count > 0, - ColumnCount = !string.IsNullOrWhiteSpace(q.SelectClause?.Clause) ? q.SelectClause.Clause.Split(',').Length : 0, - ParameterCount = q.ParameterList.Count(), - JoinCount = 0 - }); - } - - /// - /// Helper method to extract table names from a FROM clause. - /// - private static IEnumerable ExtractTableNames(string fromClause) - { - if (string.IsNullOrWhiteSpace(fromClause)) - { - yield break; - } - - // Simple extraction: split by comma and clean up aliases - var parts = fromClause.Split(','); - foreach (var part in parts) - { - var trimmed = part.Trim(); - - // Remove alias (assuming format: table AS alias or table alias) - var tokens = trimmed.Split(new[] { " AS ", " " }, StringSplitOptions.RemoveEmptyEntries); - if (tokens.Length > 0) - { - var tableName = tokens[0].Trim(); - if (!string.IsNullOrWhiteSpace(tableName)) - { - yield return tableName; - } - } - } - } + => QueryCollectionAnalysisHelper.GetQuerySummaries(_queryBreakdowns); /// /// Synchronizes parameters across all queries in the collection. @@ -350,44 +287,7 @@ public class QueryBreakdownCollection : SqlBreakdownCollection /// Only synchronizes parameters that the query already defines to avoid adding unused parameters. /// public void SynchronizeParameters() - { - // Get all unique parameter names across all queries - var allParameterNames = new HashSet(StringComparer.OrdinalIgnoreCase); - foreach (var query in _queryBreakdowns) - { - foreach (var paramName in query.Parameters.Keys) - { - allParameterNames.Add(paramName); - } - } - - // For each parameter, use the last query's value and sync to all queries that have it - foreach (var paramName in allParameterNames) - { - object? lastValue = null; - bool parameterFound = false; - - // Find the last query that has this parameter and get its value - for (int i = _queryBreakdowns.Count - 1; i >= 0; i--) - { - if (_queryBreakdowns[i].Parameters.ContainsKey(paramName)) - { - lastValue = _queryBreakdowns[i].Parameters[paramName]; - parameterFound = true; - break; - } - } - - // Synchronize the parameter value to all queries that have it - if (parameterFound) - { - foreach (var query in _queryBreakdowns.Where(q => q.Parameters.ContainsKey(paramName))) - { - query.Parameters[paramName] = lastValue!; - } - } - } - } + => QueryCollectionAnalysisHelper.SynchronizeParameters(_queryBreakdowns); /// /// Adds a parameter to all queries in the collection. @@ -411,27 +311,8 @@ public class QueryBreakdownCollection : SqlBreakdownCollection /// 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() - { - var combinedParams = new Dictionary(StringComparer.OrdinalIgnoreCase); - - foreach (var query in _queryBreakdowns) - { - // Add from ParameterList (parsed parameters) - foreach (var param in query.ParameterList) - { - combinedParams[param.Name] = param.Value; - } - - // Add/override from Parameters dictionary (manually added parameters) - foreach (var param in query.Parameters) - { - combinedParams[param.Key] = param.Value; - } - } - - return combinedParams; - } + protected Dictionary GetCombinedParameterDictionary() + => QueryCollectionAnalysisHelper.GetCombinedParameters(_queryBreakdowns); /// /// Gets all unique parameters from all queries in the collection. @@ -482,54 +363,14 @@ public class QueryBreakdownCollection : SqlBreakdownCollection /// Parameter usage information. public IEnumerable GetParameterUsageReport() { - // Collect all unique parameter names from both ParameterList and Parameters dictionary - var allParamNames = new HashSet(StringComparer.OrdinalIgnoreCase); - - foreach (var query in _queryBreakdowns) - { - // Add from ParameterList (parsed parameters) - foreach (var param in query.ParameterList) + return QueryCollectionAnalysisHelper.GetParameterUsage(_queryBreakdowns) + .Select(usage => new ParameterUsageReport { - allParamNames.Add(param.Name); - } - - // Add from Parameters dictionary (manually added parameters) - foreach (var paramName in query.Parameters.Keys) - { - allParamNames.Add(paramName); - } - } - - foreach (var paramName in allParamNames) - { - var queriesUsing = 0; - object? lastValue = null; - - foreach (var query in _queryBreakdowns) - { - // Check ParameterList first (parsed) - var param = query.ParameterList.FirstOrDefault(p => p.Name.Equals(paramName, StringComparison.OrdinalIgnoreCase)); - if (param != null) - { - queriesUsing++; - lastValue = param.Value; - } - // Also check Parameters dictionary (manually added) - else if (query.Parameters.TryGetValue(paramName, out var dictValue)) - { - queriesUsing++; - lastValue = dictValue; - } - } - - yield return new ParameterUsageReport - { - ParameterName = paramName, - Value = lastValue, - UsedInQueryCount = queriesUsing, - TotalQueries = _queryBreakdowns.Count - }; - } + ParameterName = usage.Name, + Value = usage.Value, + UsedInQueryCount = usage.UsedInQueryCount, + TotalQueries = usage.TotalQueries + }); } /// diff --git a/src/Strata.SqlTools.SqlServer/Breakdowns/QueryCollectionAnalysisHelper.cs b/src/Strata.SqlTools.SqlServer/Breakdowns/QueryCollectionAnalysisHelper.cs new file mode 100644 index 0000000..651f24f --- /dev/null +++ b/src/Strata.SqlTools.SqlServer/Breakdowns/QueryCollectionAnalysisHelper.cs @@ -0,0 +1,246 @@ +namespace Strata.SqlTools.Breakdowns.SqlServer; + +/// +/// Provides dialect-agnostic analysis routines shared by the query breakdown collections. +/// +/// +/// These operations depend only on members exposed by . Because the +/// PostgreSQL and Snowflake query types derive from , their collections +/// reuse this logic through covariance instead of duplicating it. +/// +public static class QueryCollectionAnalysisHelper +{ + /// + /// Gets the total number of columns selected across all queries. + /// + /// The queries to inspect. + /// Total column count. + public static int GetTotalSelectedColumns(IEnumerable queries) + { + return queries.Sum(q => + !string.IsNullOrWhiteSpace(q.SelectClause?.Clause) + ? q.SelectClause.Clause.Split(',').Length + : 0); + } + + /// + /// 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. + /// + /// The queries to inspect. + /// List of unique table names. + public static IEnumerable GetUniqueTableReferences(IEnumerable queries) + { + var tables = new HashSet(StringComparer.OrdinalIgnoreCase); + + var tableNames = queries + .Where(q => !string.IsNullOrWhiteSpace(q.FromClause?.Clause)) + .SelectMany(q => ExtractTableNames(q.FromClause!.Clause!)); + + foreach (var table in tableNames) + { + tables.Add(table); + } + + return tables; + } + + /// + /// Gets a summary of all queries including their types and basic composition. + /// + /// The queries to summarize. + /// Summary information for each query. + public static IEnumerable GetQuerySummaries(IEnumerable queries) + { + return queries.Select((q, index) => new QuerySummary + { + Index = index, + HasSelectClause = !string.IsNullOrWhiteSpace(q.SelectClause?.Clause), + HasFromClause = !string.IsNullOrWhiteSpace(q.FromClause?.Clause), + HasWhereClause = !string.IsNullOrWhiteSpace(q.WhereClause?.Clause), + HasGroupByClause = !string.IsNullOrWhiteSpace(q.GroupByClause?.Clause), + HasHavingClause = !string.IsNullOrWhiteSpace(q.HavingClause?.Clause), + HasOrderByClause = !string.IsNullOrWhiteSpace(q.OrderByClause?.Clause), + HasJoins = false, + HasCTE = q.WithClauses.Count > 0, + ColumnCount = !string.IsNullOrWhiteSpace(q.SelectClause?.Clause) ? q.SelectClause.Clause.Split(',').Length : 0, + ParameterCount = q.ParameterList.Count(), + JoinCount = 0 + }); + } + + /// + /// 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. + /// + /// The queries whose parameters should be synchronized. + public static void SynchronizeParameters(IEnumerable queries) + { + var queryList = queries as IReadOnlyList ?? queries.ToList(); + + // Get all unique parameter names across all queries + var allParameterNames = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var query in queryList) + { + foreach (var paramName in query.Parameters.Keys) + { + allParameterNames.Add(paramName); + } + } + + // For each parameter, use the last query's value and sync to all queries that have it + foreach (var paramName in allParameterNames) + { + object? lastValue = null; + bool parameterFound = false; + + // 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)) + { + lastValue = queryList[i].Parameters[paramName]; + parameterFound = true; + break; + } + } + + // Synchronize the parameter value to all queries that have it + if (parameterFound) + { + foreach (var query in queryList.Where(q => q.Parameters.ContainsKey(paramName))) + { + query.Parameters[paramName] = lastValue!; + } + } + } + } + + /// + /// Gets all unique parameters from all queries in the collection as a combined dictionary. + /// + /// + /// Parameters parsed into ParameterList are added first; values from the manually managed + /// Parameters dictionary override them when names collide. + /// + /// The queries to inspect. + /// A dictionary containing all unique parameters across all queries. + public static Dictionary GetCombinedParameters(IEnumerable queries) + { + var combinedParams = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var query in queries) + { + // Add from ParameterList (parsed parameters) + foreach (var param in query.ParameterList) + { + combinedParams[param.Name] = param.Value; + } + + // Add/override from Parameters dictionary (manually added parameters) + foreach (var param in query.Parameters) + { + combinedParams[param.Key] = param.Value; + } + } + + return combinedParams; + } + + /// + /// Computes parameter usage statistics across all queries in the collection. + /// + /// + /// Callers project each tuple into their dialect-specific parameter usage report type so that + /// dialect-specific formatting (for example, @ versus : parameter prefixes) is preserved. + /// + /// The queries to inspect. + /// + /// One entry per unique parameter, carrying the parameter name, its last-seen value, the number of + /// queries using it, and the total query count. + /// + public static IEnumerable<(string Name, object? Value, int UsedInQueryCount, int TotalQueries)> GetParameterUsage( + IEnumerable queries) + { + var queryList = queries as IReadOnlyList ?? queries.ToList(); + + // Collect all unique parameter names from both ParameterList and Parameters dictionary + var allParamNames = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var query in queryList) + { + foreach (var param in query.ParameterList) + { + allParamNames.Add(param.Name); + } + + foreach (var paramName in query.Parameters.Keys) + { + allParamNames.Add(paramName); + } + } + + foreach (var paramName in allParamNames) + { + var queriesUsing = 0; + object? lastValue = null; + + foreach (var query in queryList) + { + // Check ParameterList first (parsed) + var param = query.ParameterList.FirstOrDefault(p => p.Name.Equals(paramName, StringComparison.OrdinalIgnoreCase)); + if (param != null) + { + queriesUsing++; + lastValue = param.Value; + } + // Also check Parameters dictionary (manually added) + else if (query.Parameters.TryGetValue(paramName, out var dictValue)) + { + queriesUsing++; + lastValue = dictValue; + } + } + + yield return (paramName, lastValue, queriesUsing, queryList.Count); + } + } + + /// + /// Extracts table names from a FROM clause on a best-effort basis. + /// + /// The FROM clause text. + /// The table names referenced by the clause. + private static IEnumerable ExtractTableNames(string fromClause) + { + if (string.IsNullOrWhiteSpace(fromClause)) + { + yield break; + } + + // Simple extraction: split by comma and clean up aliases + var parts = fromClause.Split(','); + foreach (var part in parts) + { + var trimmed = part.Trim(); + + // Remove alias (assuming format: table AS alias or table alias) + var tokens = trimmed.Split(new[] { " AS ", " " }, StringSplitOptions.RemoveEmptyEntries); + if (tokens.Length > 0) + { + var tableName = tokens[0].Trim(); + if (!string.IsNullOrWhiteSpace(tableName)) + { + yield return tableName; + } + } + } + } +}