Skip to content

Commit

Permalink
groundwork for aggregate
Browse files Browse the repository at this point in the history
  • Loading branch information
mgravell committed Nov 17, 2023
1 parent 217ddc8 commit 039fb24
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ public static readonly DiagnosticDescriptor
VariableParameterConflict = SqlError("DAP240", "Parameter/variable conflict", "The declaration of variable '{0}' conflicts with a parameter"),
InterpolatedStringSqlExpression = SqlWarning("DAP241", "Interpolated string usage", "Data values should not be interpolated into SQL string - use parameters instead"),
ConcatenatedStringSqlExpression = SqlWarning("DAP242", "Concatenated string usage", "Data values should not be concatenated into SQL string - use parameters instead"),
InvalidDatepartToken = SqlWarning("DAP243", "Valid datepart token expected", "Date functions require a recognized datepart argument");
InvalidDatepartToken = SqlWarning("DAP243", "Valid datepart token expected", "Date functions require a recognized datepart argument"),
OnSelectAggregateAndNonAggregate = SqlWarning("DAP244", "SELECT aggregate mismatch", "SELECT has mixture of aggregate and non-aggregate expressions");
}
}
2 changes: 2 additions & 0 deletions src/Dapper.AOT.Analyzers/Internal/DiagnosticTSqlProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ protected override void OnSelectFirstTopError(Location location)
=> OnDiagnostic(DapperAnalyzer.Diagnostics.SelectFirstTopError, location);
protected override void OnSelectSingleRowWithoutWhere(Location location)
=> OnDiagnostic(DapperAnalyzer.Diagnostics.SelectSingleRowWithoutWhere, location);
protected override void OnSelectAggregateAndNonAggregate(Location location)
=> OnDiagnostic(DapperAnalyzer.Diagnostics.OnSelectAggregateAndNonAggregate, location);
protected override void OnSelectSingleTopError(Location location)
=> OnDiagnostic(DapperAnalyzer.Diagnostics.SelectSingleTopError, location);

Expand Down
27 changes: 27 additions & 0 deletions src/Dapper.AOT.Analyzers/SqlAnalysis/TSqlProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@ protected virtual void OnSelectFirstTopError(Location location)
=> OnError($"SELECT for First* should use TOP 1", location);
protected virtual void OnSelectSingleRowWithoutWhere(Location location)
=> OnError($"SELECT for single row without WHERE or (TOP and ORDER BY)", location);
protected virtual void OnSelectAggregateAndNonAggregate(Location location)
=> OnError($"SELECT has mixture of aggregate and non-aggregate expressions", location);
protected virtual void OnNonPositiveTop(Location location)
=> OnError($"TOP literals should be positive", location);
protected virtual void OnNonPositiveFetch(Location location)
Expand Down Expand Up @@ -780,13 +782,15 @@ public override void Visit(SelectStatement node)
var checkNames = ValidateSelectNames;
HashSet<string> names = checkNames ? new HashSet<string>(StringComparer.InvariantCultureIgnoreCase) : null!;

var aggregate = AggregateFlags.None;
int index = 0;
foreach (var el in spec.SelectElements)
{
switch (el)
{
case SelectStarExpression:
parser.OnSelectStar(el);
aggregate |= AggregateFlags.HaveNonAggregate;
reads++;
break;
case SelectScalarExpression scalar:
Expand All @@ -810,6 +814,7 @@ public override void Visit(SelectStatement node)
parser.OnSelectDuplicateColumnName(scalar, name!);
}
}
aggregate |= IsAggregate(scalar.Expression);
reads++;
break;
case SelectSetVariable:
Expand All @@ -819,6 +824,11 @@ public override void Visit(SelectStatement node)
index++;
}

if (aggregate == (AggregateFlags.HaveAggregate | AggregateFlags.HaveNonAggregate))
{
parser.OnSelectAggregateAndNonAggregate(spec);
}

if (reads != 0)
{
if (sets != 0)
Expand All @@ -845,6 +855,7 @@ public override void Visit(SelectStatement node)
// or a TOP + ORDER BY
if (!IsUnfiltered(spec.FromClause, spec.WhereClause)) { } // fine
else if (haveTopOrFetch && spec.OrderByClause is not null) { } // fine
else if ((aggregate & (AggregateFlags.HaveAggregate | AggregateFlags.Uncertain)) != 0) { } // fine
else
{
parser.OnSelectSingleRowWithoutWhere(node);
Expand All @@ -857,6 +868,22 @@ public override void Visit(SelectStatement node)
base.Visit(node);
}

enum AggregateFlags
{
None = 0,
HaveAggregate = 1 << 0,
HaveNonAggregate = 1 << 1,
Uncertain = 1 << 2,
}
private AggregateFlags IsAggregate(ScalarExpression expression)
{
// any use of an aggregate function contributes HaveAggregate
// - there could be unary/binary operations on that aggregate function
// - column references inside the aggregate expression or a sub-query are fine,
// otherwise they contribute HaveNonAggregate
return AggregateFlags.Uncertain;
}

private bool EnforceTop(ScalarExpression expr)
{
if (IsInt32(expr, out var i) == TryEvaluateResult.SuccessConstant && i.HasValue)
Expand Down

0 comments on commit 039fb24

Please sign in to comment.