mirror of https://github.com/AMT-Cheif/drift.git
Add static analysis for the `IIF` function (#2392)
This commit is contained in:
parent
a988b38ec1
commit
11b563f9de
|
@ -1,3 +1,7 @@
|
|||
## 0.28.2-dev
|
||||
|
||||
- Support resolving `IIF` functions.
|
||||
|
||||
## 0.28.1
|
||||
|
||||
- Fix false-positive warnings about `AS` aliases in subqueries used in triggers.
|
||||
|
|
|
@ -65,6 +65,7 @@ enum AnalysisErrorType {
|
|||
ambiguousReference,
|
||||
synctactic,
|
||||
unknownFunction,
|
||||
invalidAmountOfParameters,
|
||||
starColumnWithoutTable,
|
||||
compoundColumnCountMismatch,
|
||||
cteColumnCountMismatch,
|
||||
|
|
|
@ -518,6 +518,17 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
|
|||
session._addRelation(NullableIfSomeOtherIs(e, params));
|
||||
}
|
||||
|
||||
void checkArgumentCount(int expectedArgs) {
|
||||
if (params.length != expectedArgs) {
|
||||
session.context.reportError(AnalysisError(
|
||||
type: AnalysisErrorType.invalidAmountOfParameters,
|
||||
message:
|
||||
'${e.name} expects $expectedArgs arguments, got ${params.length}.',
|
||||
relevantNode: e.parameters,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
final lowercaseName = e.name.toLowerCase();
|
||||
switch (lowercaseName) {
|
||||
case 'round':
|
||||
|
@ -594,6 +605,18 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
|
|||
case 'likely':
|
||||
case 'unlikely':
|
||||
session._addRelation(CopyTypeFrom(e, params.first));
|
||||
return null;
|
||||
case 'iif':
|
||||
checkArgumentCount(3);
|
||||
|
||||
if (params.length == 3) {
|
||||
// IIF(a, b, c) is essentially CASE WHEN a THEN b ELSE c END
|
||||
final cases = [params[1], params[2]];
|
||||
session
|
||||
.._addRelation(CopyEncapsulating(e, cases))
|
||||
.._addRelation(HaveSameType(cases));
|
||||
}
|
||||
|
||||
return null;
|
||||
case 'coalesce':
|
||||
case 'ifnull':
|
||||
|
@ -645,6 +668,15 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
|
|||
final name = e.name.toLowerCase();
|
||||
|
||||
switch (name) {
|
||||
case 'iif':
|
||||
if (params.isNotEmpty) {
|
||||
final condition = params[0];
|
||||
if (condition is Expression) {
|
||||
visited.add(condition);
|
||||
visit(condition, _expectCondition);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'nth_value':
|
||||
if (params.length >= 2 && params[1] is Expression) {
|
||||
// the second argument of nth_value is always an integer
|
||||
|
|
|
@ -856,7 +856,8 @@ class Parser {
|
|||
} else {
|
||||
_error(
|
||||
'Expected an expression here, but got a reserved keyword. Did you '
|
||||
'mean to use it as an identifier? Try wrapping it in double quotes.',
|
||||
'mean to use it as a column? Try wrapping it in double quotes '
|
||||
'("${_peek.lexeme}").',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import 'package:sqlparser/sqlparser.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'utils.dart';
|
||||
|
||||
void main() {
|
||||
test('is forbidden on older sqlite versions', () {
|
||||
final engine = SqlEngine();
|
||||
final result = engine.analyze('SELECT iif (0, 1)');
|
||||
|
||||
expect(result.errors, [
|
||||
analysisErrorWith(
|
||||
lexeme: '0, 1',
|
||||
type: AnalysisErrorType.invalidAmountOfParameters,
|
||||
message: 'iif expects 3 arguments, got 2.'),
|
||||
]);
|
||||
});
|
||||
}
|
|
@ -14,7 +14,7 @@ extension ExpectErrors on AnalysisContext {
|
|||
}
|
||||
}
|
||||
|
||||
Matcher analysisErrorWith({String? lexeme, AnalysisErrorType? type}) {
|
||||
Matcher analysisErrorWith({String? lexeme, AnalysisErrorType? type, message}) {
|
||||
var matcher = isA<AnalysisError>();
|
||||
|
||||
if (lexeme != null) {
|
||||
|
@ -23,6 +23,9 @@ Matcher analysisErrorWith({String? lexeme, AnalysisErrorType? type}) {
|
|||
if (type != null) {
|
||||
matcher = matcher.having((e) => e.type, 'type', type);
|
||||
}
|
||||
if (message != null) {
|
||||
matcher = matcher.having((e) => e.message, 'message', message);
|
||||
}
|
||||
|
||||
return matcher;
|
||||
}
|
||||
|
|
|
@ -83,6 +83,35 @@ void main() {
|
|||
});
|
||||
});
|
||||
|
||||
group('iif', () {
|
||||
test('has type of arguments', () {
|
||||
expect(resolveResultColumn('SELECT IIF(false, 0, 1)'),
|
||||
const ResolvedType(type: BasicType.int));
|
||||
});
|
||||
|
||||
test('is nullable if argument is', () {
|
||||
expect(resolveResultColumn('SELECT IIF(false, NULL, 1)'),
|
||||
const ResolvedType(type: BasicType.int, nullable: true));
|
||||
});
|
||||
|
||||
test('is not nullable just because the condition is', () {
|
||||
expect(resolveResultColumn('SELECT IIF(NULL, 0, 1)'),
|
||||
const ResolvedType(type: BasicType.int));
|
||||
});
|
||||
|
||||
test('infers one argument based on the other', () {
|
||||
expect(resolveFirstVariable('SELECT IIF(false, ?, 1)'),
|
||||
const ResolvedType(type: BasicType.int));
|
||||
expect(resolveFirstVariable('SELECT IIF(false, 0, ?)'),
|
||||
const ResolvedType(type: BasicType.int));
|
||||
});
|
||||
|
||||
test('infers condition', () {
|
||||
expect(resolveFirstVariable('SELECT IIF(?, 0, 1)'),
|
||||
const ResolvedType(type: BasicType.int, hint: IsBoolean()));
|
||||
});
|
||||
});
|
||||
|
||||
group('types in insert statements', () {
|
||||
test('for VALUES', () {
|
||||
final resolver =
|
||||
|
|
|
@ -20,7 +20,7 @@ void main() {
|
|||
test('as identifiers', () {
|
||||
expectError('SELECT group FROM foo;', [
|
||||
isParsingError(
|
||||
message: contains('Did you mean to use it as an identifier?'),
|
||||
message: contains('Did you mean to use it as a column?'),
|
||||
lexeme: 'group',
|
||||
),
|
||||
]);
|
||||
|
|
Loading…
Reference in New Issue