using Strata.SqlTools.Breakdowns.LinqToSql; using Strata.SqlTools.Comparers.LinqToSql; namespace Strata.SqlTools.Analyzers.LinqToSql; /// /// Statistics about a collection of analyzed queries. /// public record QueryCollectionStatistics( int TotalQueries, int UniqueQueries, List DuplicateQueries, Dictionary TableUsageFrequency, Dictionary ColumnSelectionFrequency, int QueriesWithoutWhere, int QueriesWithoutOrderBy, int QueriesWithSelectAll, double AverageComplexity, int ComplexQueriesCount ) { /// /// Gets the deduplication rate (unique queries / total queries). /// public double DeduplicationRate => TotalQueries == 0 ? 0.0 : (double)UniqueQueries / TotalQueries; /// /// Gets a formatted statistics report. /// public string GetReport() { var report = new System.Text.StringBuilder(); report.AppendLine("Query Collection Analysis Report"); report.AppendLine("================================"); report.AppendLine($"Total Queries: {TotalQueries}"); report.AppendLine($"Unique Queries: {UniqueQueries} ({DeduplicationRate * 100:F1}%)"); report.AppendLine($"Duplicate Queries: {DuplicateQueries.Count}"); report.AppendLine(); report.AppendLine("Query Characteristics:"); report.AppendLine($" Queries without WHERE: {QueriesWithoutWhere}"); report.AppendLine($" Queries without ORDER BY: {QueriesWithoutOrderBy}"); report.AppendLine($" Queries with SELECT *: {QueriesWithSelectAll}"); report.AppendLine(); report.AppendLine("Complexity Analysis:"); report.AppendLine($" Average Complexity Level: {AverageComplexity:F2}"); report.AppendLine($" Complex Queries: {ComplexQueriesCount}"); report.AppendLine(); if (TableUsageFrequency.Count > 0) { report.AppendLine("Most Frequently Used Tables:"); foreach (var kvp in TableUsageFrequency.OrderByDescending(x => x.Value).Take(5)) { report.AppendLine($" {kvp.Key}: {kvp.Value} times"); } report.AppendLine(); } if (ColumnSelectionFrequency.Count > 0) { report.AppendLine("Most Frequently Selected Columns:"); foreach (var kvp in ColumnSelectionFrequency.OrderByDescending(x => x.Value).Take(5)) { report.AppendLine($" {kvp.Key}: {kvp.Value} times"); } } return report.ToString(); } } /// /// Analyzes a collection of LinqQueryBreakdown queries for patterns, duplicates, and statistics. /// public class QueryCollectionAnalyzer { private readonly List _queries; /// /// Initializes a new instance of the class. /// /// The queries to analyze. public QueryCollectionAnalyzer(IEnumerable queries) { _queries = queries?.ToList() ?? throw new ArgumentNullException(nameof(queries)); } /// /// Analyzes the query collection and returns comprehensive statistics. /// /// Statistics about the query collection. public QueryCollectionStatistics Analyze() { if (_queries.Count == 0) { return new QueryCollectionStatistics( 0, 0, [], [], [], 0, 0, 0, 0.0, 0); } var duplicates = FindDuplicates(); var uniqueCount = _queries.Count - duplicates.Count; var tableUsage = AnalyzeTableUsage(); var columnUsage = AnalyzeColumnUsage(); var queriesWithoutWhere = _queries.Count(q => string.IsNullOrWhiteSpace(q.WhereClause?.Clause)); var queriesWithoutOrderBy = _queries.Count(q => string.IsNullOrWhiteSpace(q.OrderByClause?.Clause)); var queriesWithSelectAll = _queries.Count(q => q.SelectClause?.Clause?.Trim() == "*"); var complexityScores = _queries.Select(q => GetComplexityScore(q)).ToList(); var avgComplexity = complexityScores.Average(); var complexQueries = complexityScores.Count(c => c >= 7); return new QueryCollectionStatistics( _queries.Count, uniqueCount, duplicates, tableUsage, columnUsage, queriesWithoutWhere, queriesWithoutOrderBy, queriesWithSelectAll, avgComplexity, complexQueries); } /// /// Finds duplicate queries in the collection. /// /// List of queries that are identical to another query in the collection. public List FindDuplicates() { var duplicates = new List(); for (int i = 0; i < _queries.Count; i++) { for (int j = i + 1; j < _queries.Count; j++) { if (QueryComparator.AreQueriesIdentical(_queries[i], _queries[j]) && !duplicates.Contains(_queries[j])) { duplicates.Add(_queries[j]); } } } return duplicates; } /// /// Finds similar queries that are not identical but have high similarity. /// /// Minimum similarity score (0.0-1.0). /// Pairs of similar queries and their similarity scores. public List<(LinqQueryBreakdown Query1, LinqQueryBreakdown Query2, double Similarity)> FindSimilarQueries(double minimumSimilarity = 0.75) { var similarPairs = new List<(LinqQueryBreakdown, LinqQueryBreakdown, double)>(); for (int i = 0; i < _queries.Count; i++) { for (int j = i + 1; j < _queries.Count; j++) { var similarity = QueryComparator.GetSimilarity(_queries[i], _queries[j]); if (similarity >= minimumSimilarity && similarity < 1.0) { similarPairs.Add((_queries[i], _queries[j], similarity)); } } } return similarPairs.OrderByDescending(x => x.Item3).ToList(); } /// /// Analyzes table usage frequency across all queries. /// /// Dictionary of table names and their usage counts. private Dictionary AnalyzeTableUsage() { var tableUsage = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var query in _queries) { var table = query.FromClause?.Clause?.Trim(); if (!string.IsNullOrWhiteSpace(table)) { if (tableUsage.TryGetValue(table, out int value)) { tableUsage[table] = ++value; } else { tableUsage[table] = 1; } } } return tableUsage; } /// /// Analyzes column selection frequency across all queries. /// /// Dictionary of column names and their selection frequency. private Dictionary AnalyzeColumnUsage() { var columnUsage = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var query in _queries) { var selectClause = query.SelectClause?.Clause; if (string.IsNullOrWhiteSpace(selectClause) || selectClause.Trim() == "*") { continue; } // Split columns and count them var columns = selectClause.Split(','); foreach (var col in columns) { var columnName = col.Trim(); if (columnUsage.TryGetValue(columnName, out int value)) { columnUsage[columnName] = ++value; } else { columnUsage[columnName] = 1; } } } return columnUsage; } /// /// Calculates a complexity score for a query (0-10). /// private static int GetComplexityScore(LinqQueryBreakdown query) { int score = 1; // Base score for having a query if (!string.IsNullOrWhiteSpace(query.WhereClause?.Clause)) { score += 2; } if (!string.IsNullOrWhiteSpace(query.GroupByClause?.Clause)) { score += 2; } if (!string.IsNullOrWhiteSpace(query.HavingClause?.Clause)) { score += 2; } if (!string.IsNullOrWhiteSpace(query.OrderByClause?.Clause)) { score += 1; } // Bonus points for complex WHERE conditions var whereClause = query.WhereClause?.Clause ?? string.Empty; var complexityIndicators = new[] { " AND ", " OR ", "IN (", "BETWEEN", "LIKE" }; var complexParts = complexityIndicators.Count(ind => whereClause.Contains(ind, StringComparison.OrdinalIgnoreCase)); score += Math.Min(complexParts, 2); // Cap at +2 return Math.Min(score, 10); // Cap at 10 } /// /// Creates a new analyzer for the given queries. /// /// The queries to analyze. /// A new QueryCollectionAnalyzer instance. public static QueryCollectionAnalyzer Analyze(IEnumerable queries) { return new QueryCollectionAnalyzer(queries); } /// /// Gets a summary report for the query collection. /// /// A formatted analysis report. public string GetReport() { return Analyze().GetReport(); } }