chore: initial git load of code space
This commit is contained in:
@@ -0,0 +1,277 @@
|
||||
using Strata.SqlTools.Breakdowns.LinqToSql;
|
||||
|
||||
namespace Strata.SqlTools.Comparers.LinqToSql;
|
||||
|
||||
/// <summary>
|
||||
/// Result of comparing two LinqQueryBreakdown instances.
|
||||
/// </summary>
|
||||
public record QueryComparisonResult(
|
||||
bool AreEquivalent,
|
||||
double SimilarityScore, // 0.0 to 1.0
|
||||
List<string> Differences,
|
||||
bool HaveSameSelectColumns,
|
||||
bool HaveSameFromTable,
|
||||
bool HaveSameWhereClause,
|
||||
bool HaveSameGroupBy,
|
||||
bool HaveSameHaving,
|
||||
bool HaveSameOrderBy
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a formatted comparison report.
|
||||
/// </summary>
|
||||
public string GetReport()
|
||||
{
|
||||
var report = new System.Text.StringBuilder();
|
||||
report.AppendLine($"Query Comparison Report");
|
||||
report.AppendLine($"Similarity: {(SimilarityScore * 100):F1}%");
|
||||
report.AppendLine($"Equivalent: {(AreEquivalent ? "Yes" : "No")}");
|
||||
report.AppendLine();
|
||||
|
||||
if (Differences.Count == 0)
|
||||
{
|
||||
report.AppendLine("✓ Queries are identical");
|
||||
return report.ToString();
|
||||
}
|
||||
|
||||
report.AppendLine("Differences:");
|
||||
foreach (var diff in Differences)
|
||||
{
|
||||
report.AppendLine($" • {diff}");
|
||||
}
|
||||
|
||||
return report.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares LinqQueryBreakdown instances to detect similarity, equivalence, and duplicates.
|
||||
/// </summary>
|
||||
public class QueryComparator
|
||||
{
|
||||
private readonly LinqQueryBreakdown _query1;
|
||||
private readonly LinqQueryBreakdown _query2;
|
||||
private QueryComparisonResult? _result;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="QueryComparator"/> class.
|
||||
/// </summary>
|
||||
/// <param name="query1">The first query to compare.</param>
|
||||
/// <param name="query2">The second query to compare.</param>
|
||||
public QueryComparator(LinqQueryBreakdown query1, LinqQueryBreakdown query2)
|
||||
{
|
||||
_query1 = query1 ?? throw new ArgumentNullException(nameof(query1));
|
||||
_query2 = query2 ?? throw new ArgumentNullException(nameof(query2));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the comparison result, calculating it if needed.
|
||||
/// </summary>
|
||||
public QueryComparisonResult Result =>
|
||||
_result ??= PerformComparison();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the queries are equivalent (same structure).
|
||||
/// </summary>
|
||||
public bool AreEquivalent => Result.AreEquivalent;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the similarity score from 0.0 (completely different) to 1.0 (identical).
|
||||
/// </summary>
|
||||
public double SimilarityScore => Result.SimilarityScore;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of differences found between the queries.
|
||||
/// </summary>
|
||||
public List<string> Differences => Result.Differences;
|
||||
|
||||
/// <summary>
|
||||
/// Performs the actual comparison between the two queries.
|
||||
/// </summary>
|
||||
/// <returns>The comparison result.</returns>
|
||||
private QueryComparisonResult PerformComparison()
|
||||
{
|
||||
var differences = new List<string>();
|
||||
var scoreComponents = 0;
|
||||
var scoreMatches = 0;
|
||||
|
||||
// Compare SELECT clause
|
||||
var selectMatch = CompareSelectClauses(_query1, _query2);
|
||||
if (!selectMatch)
|
||||
{
|
||||
differences.Add("SELECT clauses differ");
|
||||
}
|
||||
scoreComponents++;
|
||||
if (selectMatch)
|
||||
{ scoreMatches++; }
|
||||
|
||||
// Compare FROM clause
|
||||
var fromMatch = CompareFromClauses(_query1, _query2);
|
||||
if (!fromMatch)
|
||||
{
|
||||
differences.Add("FROM clauses differ");
|
||||
}
|
||||
scoreComponents++;
|
||||
if (fromMatch)
|
||||
{ scoreMatches++; }
|
||||
|
||||
// Compare WHERE clause
|
||||
var whereMatch = CompareWhereClauses(_query1, _query2);
|
||||
if (!whereMatch)
|
||||
{
|
||||
differences.Add("WHERE clauses differ");
|
||||
}
|
||||
scoreComponents++;
|
||||
if (whereMatch)
|
||||
{ scoreMatches++; }
|
||||
|
||||
// Compare GROUP BY clause
|
||||
var groupByMatch = CompareGroupByClauses(_query1, _query2);
|
||||
if (!groupByMatch)
|
||||
{
|
||||
differences.Add("GROUP BY clauses differ");
|
||||
}
|
||||
scoreComponents++;
|
||||
if (groupByMatch)
|
||||
{ scoreMatches++; }
|
||||
|
||||
// Compare HAVING clause
|
||||
var havingMatch = CompareHavingClauses(_query1, _query2);
|
||||
if (!havingMatch)
|
||||
{
|
||||
differences.Add("HAVING clauses differ");
|
||||
}
|
||||
scoreComponents++;
|
||||
if (havingMatch)
|
||||
{ scoreMatches++; }
|
||||
|
||||
// Compare ORDER BY clause
|
||||
var orderByMatch = CompareOrderByClauses(_query1, _query2);
|
||||
if (!orderByMatch)
|
||||
{
|
||||
differences.Add("ORDER BY clauses differ");
|
||||
}
|
||||
scoreComponents++;
|
||||
if (orderByMatch)
|
||||
{ scoreMatches++; }
|
||||
|
||||
var similarityScore = scoreComponents > 0 ? (double)scoreMatches / scoreComponents : 0.0;
|
||||
var areEquivalent = differences.Count == 0;
|
||||
|
||||
return new QueryComparisonResult(
|
||||
areEquivalent,
|
||||
similarityScore,
|
||||
differences,
|
||||
selectMatch,
|
||||
fromMatch,
|
||||
whereMatch,
|
||||
groupByMatch,
|
||||
havingMatch,
|
||||
orderByMatch);
|
||||
}
|
||||
|
||||
private static bool CompareSelectClauses(LinqQueryBreakdown query1, LinqQueryBreakdown query2)
|
||||
{
|
||||
var select1 = NormalizeClause(query1.SelectClause?.Clause ?? string.Empty);
|
||||
var select2 = NormalizeClause(query2.SelectClause?.Clause ?? string.Empty);
|
||||
return StringEquals(select1, select2);
|
||||
}
|
||||
|
||||
private static bool CompareFromClauses(LinqQueryBreakdown query1, LinqQueryBreakdown query2)
|
||||
{
|
||||
var from1 = NormalizeClause(query1.FromClause?.Clause ?? string.Empty);
|
||||
var from2 = NormalizeClause(query2.FromClause?.Clause ?? string.Empty);
|
||||
return StringEquals(from1, from2);
|
||||
}
|
||||
|
||||
private static bool CompareWhereClauses(LinqQueryBreakdown query1, LinqQueryBreakdown query2)
|
||||
{
|
||||
var where1 = NormalizeClause(query1.WhereClause?.Clause ?? string.Empty);
|
||||
var where2 = NormalizeClause(query2.WhereClause?.Clause ?? string.Empty);
|
||||
return StringEquals(where1, where2);
|
||||
}
|
||||
|
||||
private static bool CompareGroupByClauses(LinqQueryBreakdown query1, LinqQueryBreakdown query2)
|
||||
{
|
||||
var groupBy1 = NormalizeClause(query1.GroupByClause?.Clause ?? string.Empty);
|
||||
var groupBy2 = NormalizeClause(query2.GroupByClause?.Clause ?? string.Empty);
|
||||
return StringEquals(groupBy1, groupBy2);
|
||||
}
|
||||
|
||||
private static bool CompareHavingClauses(LinqQueryBreakdown query1, LinqQueryBreakdown query2)
|
||||
{
|
||||
var having1 = NormalizeClause(query1.HavingClause?.Clause ?? string.Empty);
|
||||
var having2 = NormalizeClause(query2.HavingClause?.Clause ?? string.Empty);
|
||||
return StringEquals(having1, having2);
|
||||
}
|
||||
|
||||
private static bool CompareOrderByClauses(LinqQueryBreakdown query1, LinqQueryBreakdown query2)
|
||||
{
|
||||
var orderBy1 = NormalizeClause(query1.OrderByClause?.Clause ?? string.Empty);
|
||||
var orderBy2 = NormalizeClause(query2.OrderByClause?.Clause ?? string.Empty);
|
||||
return StringEquals(orderBy1, orderBy2);
|
||||
}
|
||||
|
||||
private static string NormalizeClause(string clause)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(clause))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// Normalize whitespace and case
|
||||
return System.Text.RegularExpressions.Regex
|
||||
.Replace(clause.Trim(), @"\s+", " ")
|
||||
.ToUpperInvariant();
|
||||
}
|
||||
|
||||
private static bool StringEquals(string? str1, string? str2)
|
||||
{
|
||||
return string.Equals(str1, str2, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new comparator for two queries.
|
||||
/// </summary>
|
||||
/// <param name="query1">The first query.</param>
|
||||
/// <param name="query2">The second query.</param>
|
||||
/// <returns>A new QueryComparator instance.</returns>
|
||||
public static QueryComparator Compare(LinqQueryBreakdown query1, LinqQueryBreakdown query2)
|
||||
{
|
||||
return new QueryComparator(query1, query2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if two queries are equivalent.
|
||||
/// </summary>
|
||||
/// <param name="query1">The first query.</param>
|
||||
/// <param name="query2">The second query.</param>
|
||||
/// <returns>True if the queries are equivalent; otherwise, false.</returns>
|
||||
public static bool AreQueriesEquivalent(LinqQueryBreakdown query1, LinqQueryBreakdown query2)
|
||||
{
|
||||
return new QueryComparator(query1, query2).AreEquivalent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if two queries are identical (same text after normalization).
|
||||
/// </summary>
|
||||
/// <param name="query1">The first query.</param>
|
||||
/// <param name="query2">The second query.</param>
|
||||
/// <returns>True if the queries are identical; otherwise, false.</returns>
|
||||
public static bool AreQueriesIdentical(LinqQueryBreakdown query1, LinqQueryBreakdown query2)
|
||||
{
|
||||
var comparator = new QueryComparator(query1, query2);
|
||||
return comparator.SimilarityScore >= 1.0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the similarity score between two queries (0.0 to 1.0).
|
||||
/// </summary>
|
||||
/// <param name="query1">The first query.</param>
|
||||
/// <param name="query2">The second query.</param>
|
||||
/// <returns>A similarity score from 0.0 (completely different) to 1.0 (identical).</returns>
|
||||
public static double GetSimilarity(LinqQueryBreakdown query1, LinqQueryBreakdown query2)
|
||||
{
|
||||
return new QueryComparator(query1, query2).SimilarityScore;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user