sqlparser: Give CASE a higher precedence

This commit is contained in:
Simon Binder 2020-10-22 13:30:43 +02:00
parent 85206d2fd7
commit 732369c25c
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
2 changed files with 118 additions and 33 deletions

View File

@ -1,6 +1,10 @@
part of 'parser.dart';
/// Parses expressions. Expressions have the following precedence:
/// Parses expressions. Expressions have the following precedence, in descending
/// order
/// - primary expressions: Parenthesis, etc.
/// - Case expressions
/// - postfix expressions: NOT NULL, etc.
/// - `-`, `+`, `~`, unary not
/// - `||` (concatenation)
/// - `*`, '/', '%'
@ -11,40 +15,9 @@ part of 'parser.dart';
/// `REGEXP`
/// - `AND`
/// - `OR`
/// - Case expressions
mixin ExpressionParser on ParserBase {
@override
Expression expression() {
return _case();
}
Expression _case() {
if (_matchOne(TokenType.$case)) {
final caseToken = _previous;
final base = _check(TokenType.when) ? null : _or();
final whens = <WhenComponent>[];
Expression $else;
while (_matchOne(TokenType.when)) {
final whenToken = _previous;
final whenExpr = _or();
_consume(TokenType.then, 'Expected THEN');
final then = expression();
whens.add(WhenComponent(when: whenExpr, then: then)
..setSpan(whenToken, _previous));
}
if (_matchOne(TokenType.$else)) {
$else = expression();
}
_consume(TokenType.end, 'Expected END to finish the case operator');
return CaseExpression(whens: whens, base: base, elseExpr: $else)
..setSpan(caseToken, _previous);
}
return _or();
}
@ -205,7 +178,7 @@ mixin ExpressionParser on ParserBase {
}
Expression _postfix() {
var expression = _primary();
var expression = _case();
// todo we don't currently parse "NOT NULL" (2 tokens) because of ambiguity
// with NOT BETWEEN / NOT IN / ... expressions
@ -245,6 +218,36 @@ mixin ExpressionParser on ParserBase {
return expression;
}
Expression _case() {
if (_matchOne(TokenType.$case)) {
final caseToken = _previous;
final base = _check(TokenType.when) ? null : _primary();
final whens = <WhenComponent>[];
Expression $else;
while (_matchOne(TokenType.when)) {
final whenToken = _previous;
final whenExpr = _or();
_consume(TokenType.then, 'Expected THEN');
final then = expression();
whens.add(WhenComponent(when: whenExpr, then: then)
..setSpan(whenToken, _previous));
}
if (_matchOne(TokenType.$else)) {
$else = expression();
}
_consume(TokenType.end, 'Expected END to finish the case operator');
return CaseExpression(whens: whens, base: base, elseExpr: $else)
..setSpan(caseToken, _previous);
}
return _primary();
}
@override
Literal _literalOrNull() {
final token = _peek;

View File

@ -0,0 +1,82 @@
import 'package:sqlparser/sqlparser.dart';
import 'package:test/test.dart';
import 'utils.dart';
void main() {
final caseExpr = CaseExpression(
whens: [
WhenComponent(
when: BinaryExpression(
NumericLiteral(1, token(TokenType.numberLiteral)),
token(TokenType.equal),
ColonNamedVariable(
ColonVariableToken(null, ':isReviewFolderSelected'),
),
),
then: IsExpression(
true,
Reference(tableName: 'n', columnName: 'nextReviewTime'),
NullLiteral(token(TokenType.$null)),
),
),
],
elseExpr: IsExpression(
false,
Reference(tableName: 'n', columnName: 'nextReviewTime'),
NullLiteral(token(TokenType.$null)),
),
);
final folderExpr = BinaryExpression(
Reference(tableName: 'n', columnName: 'folderId'),
token(TokenType.equal),
ColonNamedVariable(
ColonVariableToken(null, ':selectedFolderId'),
),
);
test('repro 1', () {
testStatement(
'''
SELECT * FROM notes n WHERE
CASE
WHEN 1 = :isReviewFolderSelected THEN n.nextReviewTime IS NOT NULL
ELSE n.nextReviewTime IS NULL
END
and n.folderId = :selectedFolderId;
''',
SelectStatement(
from: TableReference('notes', 'n'),
columns: [StarResultColumn()],
where: BinaryExpression(
caseExpr,
token(TokenType.and),
folderExpr,
),
),
);
});
test('repro 2', () {
testStatement(
'''
SELECT * FROM notes n WHERE
n.folderId = :selectedFolderId and
CASE
WHEN 1 = :isReviewFolderSelected THEN n.nextReviewTime IS NOT NULL
ELSE n.nextReviewTime IS NULL
END;
''',
SelectStatement(
from: TableReference('notes', 'n'),
columns: [StarResultColumn()],
where: BinaryExpression(
folderExpr,
token(TokenType.and),
caseExpr,
),
),
);
});
}