chore: initial git load of code space
This commit is contained in:
@@ -0,0 +1,281 @@
|
||||
using Strata.SqlTools.Breakdowns.LinqToSql;
|
||||
using Strata.SqlTools.Comparers.LinqToSql;
|
||||
|
||||
namespace Strata.SqlTools.Analyzers.LinqToSql;
|
||||
|
||||
/// <summary>
|
||||
/// Statistics about a collection of analyzed queries.
|
||||
/// </summary>
|
||||
public record QueryCollectionStatistics(
|
||||
int TotalQueries,
|
||||
int UniqueQueries,
|
||||
List<LinqQueryBreakdown> DuplicateQueries,
|
||||
Dictionary<string, int> TableUsageFrequency,
|
||||
Dictionary<string, int> ColumnSelectionFrequency,
|
||||
int QueriesWithoutWhere,
|
||||
int QueriesWithoutOrderBy,
|
||||
int QueriesWithSelectAll,
|
||||
double AverageComplexity,
|
||||
int ComplexQueriesCount
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the deduplication rate (unique queries / total queries).
|
||||
/// </summary>
|
||||
public double DeduplicationRate => TotalQueries == 0 ? 0.0 : (double)UniqueQueries / TotalQueries;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a formatted statistics report.
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Analyzes a collection of LinqQueryBreakdown queries for patterns, duplicates, and statistics.
|
||||
/// </summary>
|
||||
public class QueryCollectionAnalyzer
|
||||
{
|
||||
private readonly List<LinqQueryBreakdown> _queries;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="QueryCollectionAnalyzer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="queries">The queries to analyze.</param>
|
||||
public QueryCollectionAnalyzer(IEnumerable<LinqQueryBreakdown> queries)
|
||||
{
|
||||
_queries = queries?.ToList() ?? throw new ArgumentNullException(nameof(queries));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Analyzes the query collection and returns comprehensive statistics.
|
||||
/// </summary>
|
||||
/// <returns>Statistics about the query collection.</returns>
|
||||
public QueryCollectionStatistics Analyze()
|
||||
{
|
||||
if (_queries.Count == 0)
|
||||
{
|
||||
return new QueryCollectionStatistics(
|
||||
0, 0, new List<LinqQueryBreakdown>(),
|
||||
new Dictionary<string, int>(),
|
||||
new Dictionary<string, int>(),
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds duplicate queries in the collection.
|
||||
/// </summary>
|
||||
/// <returns>List of queries that are identical to another query in the collection.</returns>
|
||||
public List<LinqQueryBreakdown> FindDuplicates()
|
||||
{
|
||||
var duplicates = new List<LinqQueryBreakdown>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds similar queries that are not identical but have high similarity.
|
||||
/// </summary>
|
||||
/// <param name="minimumSimilarity">Minimum similarity score (0.0-1.0).</param>
|
||||
/// <returns>Pairs of similar queries and their similarity scores.</returns>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Analyzes table usage frequency across all queries.
|
||||
/// </summary>
|
||||
/// <returns>Dictionary of table names and their usage counts.</returns>
|
||||
private Dictionary<string, int> AnalyzeTableUsage()
|
||||
{
|
||||
var tableUsage = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var query in _queries)
|
||||
{
|
||||
var table = query.FromClause?.Clause?.Trim();
|
||||
if (!string.IsNullOrWhiteSpace(table))
|
||||
{
|
||||
if (tableUsage.ContainsKey(table))
|
||||
{
|
||||
tableUsage[table]++;
|
||||
}
|
||||
else
|
||||
{
|
||||
tableUsage[table] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tableUsage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Analyzes column selection frequency across all queries.
|
||||
/// </summary>
|
||||
/// <returns>Dictionary of column names and their selection frequency.</returns>
|
||||
private Dictionary<string, int> AnalyzeColumnUsage()
|
||||
{
|
||||
var columnUsage = new Dictionary<string, int>(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.ContainsKey(columnName))
|
||||
{
|
||||
columnUsage[columnName]++;
|
||||
}
|
||||
else
|
||||
{
|
||||
columnUsage[columnName] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return columnUsage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates a complexity score for a query (0-10).
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new analyzer for the given queries.
|
||||
/// </summary>
|
||||
/// <param name="queries">The queries to analyze.</param>
|
||||
/// <returns>A new QueryCollectionAnalyzer instance.</returns>
|
||||
public static QueryCollectionAnalyzer Analyze(IEnumerable<LinqQueryBreakdown> queries)
|
||||
{
|
||||
return new QueryCollectionAnalyzer(queries);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a summary report for the query collection.
|
||||
/// </summary>
|
||||
/// <returns>A formatted analysis report.</returns>
|
||||
public string GetReport()
|
||||
{
|
||||
return Analyze().GetReport();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user