chore: initial git load of code space

This commit is contained in:
Thom Lamb
2026-05-12 08:52:33 -05:00
parent 9abada692f
commit 5e467bcc9c
384 changed files with 65960 additions and 2 deletions
@@ -0,0 +1,311 @@
using System.Collections.Immutable;
using Strata.SqlTools.Breakdowns.LinqToSql;
namespace Strata.SqlTools.Validators.LinqToSql;
/// <summary>
/// Severity level for validation issues.
/// </summary>
public enum ValidationSeverity
{
/// <summary>Informational message, no action required.</summary>
Info = 0,
/// <summary>Warning - potential issue that should be reviewed.</summary>
Warning = 1,
/// <summary>Error - definite issue that should be fixed.</summary>
Error = 2
}
/// <summary>
/// Represents a single validation issue found in a query.
/// </summary>
public record QueryValidationIssue(
ValidationSeverity Severity,
string Code,
string Message,
string? Details = null
)
{
/// <summary>
/// Gets a formatted string representation of the validation issue.
/// </summary>
public override string ToString()
{
var result = $"[{Severity}] {Code}: {Message}";
if (!string.IsNullOrWhiteSpace(Details))
{
result += $" - {Details}";
}
return result;
}
}
/// <summary>
/// Validates LinqQueryBreakdown instances and detects common anti-patterns.
/// </summary>
public class QueryValidator
{
private readonly List<QueryValidationIssue> _issues = new();
/// <summary>
/// Gets the list of validation issues found.
/// </summary>
public IReadOnlyList<QueryValidationIssue> Issues => _issues.AsReadOnly();
/// <summary>
/// Gets a value indicating whether any errors were found.
/// </summary>
public bool HasErrors => _issues.Any(i => i.Severity == ValidationSeverity.Error);
/// <summary>
/// Gets a value indicating whether any warnings were found.
/// </summary>
public bool HasWarnings => _issues.Any(i => i.Severity == ValidationSeverity.Warning);
/// <summary>
/// Validates a LinqQueryBreakdown instance and returns the result.
/// </summary>
/// <param name="breakdown">The breakdown to validate.</param>
/// <returns>This validator for method chaining.</returns>
public QueryValidator Validate(LinqQueryBreakdown breakdown)
{
if (breakdown == null)
{
throw new ArgumentNullException(nameof(breakdown));
}
_issues.Clear();
ValidateSelectClause(breakdown);
ValidateFromClause(breakdown);
ValidateWhereClause(breakdown);
ValidateGroupByClause(breakdown);
ValidateHavingClause(breakdown);
ValidateOrderByClause(breakdown);
ValidateCommonAntiPatterns(breakdown);
return this;
}
/// <summary>
/// Adds a custom validation issue.
/// </summary>
/// <param name="severity">The severity level.</param>
/// <param name="code">The issue code (e.g., "RULE_001").</param>
/// <param name="message">The issue message.</param>
/// <param name="details">Optional detailed information.</param>
/// <returns>This validator for method chaining.</returns>
public QueryValidator AddIssue(
ValidationSeverity severity,
string code,
string message,
string? details = null)
{
_issues.Add(new QueryValidationIssue(severity, code, message, details));
return this;
}
/// <summary>
/// Clears all validation issues.
/// </summary>
/// <returns>This validator for method chaining.</returns>
public QueryValidator Clear()
{
_issues.Clear();
return this;
}
/// <summary>
/// Gets validation issues by severity level.
/// </summary>
/// <param name="severity">The severity to filter by.</param>
/// <returns>Issues matching the severity level.</returns>
public IReadOnlyList<QueryValidationIssue> GetIssuesBySeverity(ValidationSeverity severity)
{
return _issues.Where(i => i.Severity == severity).ToList().AsReadOnly();
}
/// <summary>
/// Gets a formatted validation report.
/// </summary>
/// <returns>A formatted string containing all validation issues.</returns>
public string GetReport()
{
if (_issues.Count == 0)
{
return "✓ No validation issues found.";
}
var report = new System.Text.StringBuilder();
report.AppendLine($"Validation Report ({_issues.Count} issue{(_issues.Count != 1 ? "s" : "")}:");
report.AppendLine();
var errors = GetIssuesBySeverity(ValidationSeverity.Error);
if (errors.Count > 0)
{
report.AppendLine("ERRORS:");
foreach (var issue in errors)
{
report.AppendLine($" • {issue}");
}
report.AppendLine();
}
var warnings = GetIssuesBySeverity(ValidationSeverity.Warning);
if (warnings.Count > 0)
{
report.AppendLine("WARNINGS:");
foreach (var issue in warnings)
{
report.AppendLine($" • {issue}");
}
report.AppendLine();
}
var infos = GetIssuesBySeverity(ValidationSeverity.Info);
if (infos.Count > 0)
{
report.AppendLine("INFO:");
foreach (var issue in infos)
{
report.AppendLine($" • {issue}");
}
}
return report.ToString();
}
private void ValidateSelectClause(LinqQueryBreakdown breakdown)
{
if (breakdown.SelectClause == null || string.IsNullOrWhiteSpace(breakdown.SelectClause.Clause))
{
_issues.Add(new QueryValidationIssue(
ValidationSeverity.Error,
"SELECT_MISSING",
"SELECT clause is missing or empty",
"Every query must specify which columns to select."));
return;
}
var selectClause = breakdown.SelectClause.Clause;
// Check for SELECT *
if (selectClause.Trim() == "*")
{
_issues.Add(new QueryValidationIssue(
ValidationSeverity.Warning,
"SELECT_ALL_COLUMNS",
"Query selects all columns with SELECT *",
"Consider being explicit about which columns you need to avoid returning unnecessary data."));
}
// Check for excessive columns
var columnCount = selectClause.Split(',').Length;
if (columnCount > 20)
{
_issues.Add(new QueryValidationIssue(
ValidationSeverity.Warning,
"SELECT_TOO_MANY",
$"Query selects {columnCount} columns",
"Consider narrowing the selection to reduce data transfer and improve performance."));
}
}
private void ValidateFromClause(LinqQueryBreakdown breakdown)
{
if (breakdown.FromClause == null || string.IsNullOrWhiteSpace(breakdown.FromClause.Clause))
{
_issues.Add(new QueryValidationIssue(
ValidationSeverity.Error,
"FROM_MISSING",
"FROM clause is missing",
"Every query must specify a source table."));
}
}
private void ValidateWhereClause(LinqQueryBreakdown breakdown)
{
// No validation needed - WHERE is optional
}
private void ValidateGroupByClause(LinqQueryBreakdown breakdown)
{
var hasGroupBy = !string.IsNullOrWhiteSpace(breakdown.GroupByClause?.Clause);
var hasHaving = !string.IsNullOrWhiteSpace(breakdown.HavingClause?.Clause);
if (hasHaving && !hasGroupBy)
{
_issues.Add(new QueryValidationIssue(
ValidationSeverity.Error,
"HAVING_WITHOUT_GROUPBY",
"HAVING clause found without GROUP BY",
"HAVING must be used with GROUP BY to filter aggregated results."));
}
}
private void ValidateHavingClause(LinqQueryBreakdown breakdown)
{
// Validation delegated to ValidateGroupByClause
}
private void ValidateOrderByClause(LinqQueryBreakdown breakdown)
{
// No validation needed - ORDER BY is optional
}
private void ValidateCommonAntiPatterns(LinqQueryBreakdown breakdown)
{
// Check for DELETE/UPDATE without WHERE (dangerous!)
// Note: This is primarily for LINQ operations, but we can flag it for awareness
if (string.IsNullOrWhiteSpace(breakdown.WhereClause?.Clause))
{
_issues.Add(new QueryValidationIssue(
ValidationSeverity.Warning,
"NO_WHERE_CLAUSE",
"Query has no WHERE clause",
"Consider whether this is intentional. Queries without WHERE clauses affect all rows."));
}
// Check for missing ORDER BY on large results
var hasOrderBy = !string.IsNullOrWhiteSpace(breakdown.OrderByClause?.Clause);
var hasGroupBy = !string.IsNullOrWhiteSpace(breakdown.GroupByClause?.Clause);
if (!hasOrderBy && !hasGroupBy)
{
_issues.Add(new QueryValidationIssue(
ValidationSeverity.Info,
"NO_ORDER_BY",
"Query has no ORDER BY clause",
"Consider adding ORDER BY to ensure consistent result ordering, especially for pagination scenarios."));
}
// Check for SELECT without FROM (invalid in most SQL dialects except for SELECT constants)
var selectClause = breakdown.SelectClause?.Clause ?? string.Empty;
if (!string.IsNullOrWhiteSpace(selectClause) &&
string.IsNullOrWhiteSpace(breakdown.FromClause?.Clause))
{
// This is already caught by ValidateFromClause
}
}
/// <summary>
/// Creates a new instance of QueryValidator.
/// </summary>
/// <returns>A new QueryValidator instance.</returns>
public static QueryValidator Create()
{
return new QueryValidator();
}
/// <summary>
/// Validates a breakdown and returns a new validator with the results.
/// </summary>
/// <param name="breakdown">The breakdown to validate.</param>
/// <returns>A new validator containing the validation results.</returns>
public static QueryValidator ValidateQuery(LinqQueryBreakdown breakdown)
{
return new QueryValidator().Validate(breakdown);
}
}