Lint for distinct aggregate with more than one arg

This commit is contained in:
Simon Binder 2022-05-08 09:05:23 +02:00
parent 78470b4280
commit 98163103d5
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
6 changed files with 46 additions and 2 deletions

View File

@ -1,3 +1,7 @@
## 0.21.1-dev
- Lint for `DISTINCT` misuse in aggregate function calls.
## 0.21.0 ## 0.21.0
- Analysis support for new features in sqlite version 3.38. - Analysis support for new features in sqlite version 3.38.

View File

@ -80,6 +80,7 @@ enum AnalysisErrorType {
missingPrimaryKey, missingPrimaryKey,
noTypeNameInStrictTable, noTypeNameInStrictTable,
invalidTypeNameInStrictTable, invalidTypeNameInStrictTable,
distinctAggregateWithWrongParameterCount,
other, other,
hint, hint,
} }

View File

@ -204,6 +204,19 @@ class LintingVisitor extends RecursiveVisitor<void, void> {
visitChildren(e, arg); visitChildren(e, arg);
} }
@override
void visitExpressionFunctionParameters(ExprFunctionParameters e, void arg) {
if (e.distinct && e.parameters.length != 1) {
context.reportError(AnalysisError(
type: AnalysisErrorType.distinctAggregateWithWrongParameterCount,
message: 'A `DISTINCT` aggregate must have exactly one argument',
relevantNode: e.distinctKeyword ?? e,
));
}
visitChildren(e, arg);
}
@override @override
void visitInvocation(SqlInvocation e, void arg) { void visitInvocation(SqlInvocation e, void arg) {
final lowercaseCall = e.name.toLowerCase(); final lowercaseCall = e.name.toLowerCase();

View File

@ -66,6 +66,7 @@ class StarFunctionParameter extends FunctionParameters {
class ExprFunctionParameters extends FunctionParameters { class ExprFunctionParameters extends FunctionParameters {
final bool distinct; final bool distinct;
Token? distinctKeyword;
List<Expression> parameters; List<Expression> parameters;
ExprFunctionParameters({this.parameters = const [], this.distinct = false}); ExprFunctionParameters({this.parameters = const [], this.distinct = false});

View File

@ -89,6 +89,13 @@ class Parser {
return false; return false;
} }
Token? _matchOneAndGet(TokenType type) {
if (_matchOne(type)) {
return _previous;
}
return null;
}
/// Returns true if the next token is [type] or if the next two tokens are /// Returns true if the next token is [type] or if the next two tokens are
/// "NOT" followed by [type]. Does not consume any tokens. /// "NOT" followed by [type]. Does not consume any tokens.
bool _checkWithNot(TokenType type) { bool _checkWithNot(TokenType type) {
@ -901,7 +908,7 @@ class Parser {
return ExprFunctionParameters(parameters: const [])..synthetic = true; return ExprFunctionParameters(parameters: const [])..synthetic = true;
} }
final distinct = _matchOne(TokenType.distinct); final distinct = _matchOneAndGet(TokenType.distinct);
final parameters = <Expression>[]; final parameters = <Expression>[];
final first = _peek; final first = _peek;
@ -909,7 +916,9 @@ class Parser {
parameters.add(expression()); parameters.add(expression());
} while (_matchOne(TokenType.comma)); } while (_matchOne(TokenType.comma));
return ExprFunctionParameters(distinct: distinct, parameters: parameters) return ExprFunctionParameters(
distinct: distinct != null, parameters: parameters)
..distinctKeyword = distinct
..setSpan(first, _previous); ..setSpan(first, _previous);
} }

View File

@ -0,0 +1,16 @@
import 'package:sqlparser/sqlparser.dart';
import 'package:test/test.dart';
import '../data.dart';
import 'utils.dart';
void main() {
final engine = SqlEngine()..registerTable(demoTable);
test('warns about multiple parameters with DISTINCT', () {
engine
.analyze("SELECT group_concat(DISTINCT content, '-') FROM demo")
.expectError('DISTINCT',
type: AnalysisErrorType.distinctAggregateWithWrongParameterCount);
});
}