Parse JOIN and INDEXED as identifiers when appropriate

This commit is contained in:
Simon Binder 2019-09-25 11:37:03 +02:00
parent e0fc4a3af6
commit d9c2b5f342
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
4 changed files with 64 additions and 78 deletions

View File

@ -261,64 +261,51 @@ mixin ExpressionParser on ParserBase {
final variable = _variableOrNull(); final variable = _variableOrNull();
if (variable != null) return variable; if (variable != null) return variable;
final token = _advance(); if (_matchOne(TokenType.leftParen)) {
final type = token.type; final left = _previous;
switch (type) { if (_peek.type == TokenType.select) {
case TokenType.leftParen: final stmt = select();
// An opening bracket in the context of an expression could either be _consume(TokenType.rightParen, 'Expected a closing bracket');
// an inner select statement or a parenthesised expression. return SubQuery(select: stmt)..setSpan(left, _previous);
final left = token; } else {
if (_peek.type == TokenType.select) { final expr = expression();
final stmt = select(); _consume(TokenType.rightParen, 'Expected a closing bracket');
_consume(TokenType.rightParen, 'Expected a closing bracket'); return Parentheses(left, expr, _previous)..setSpan(left, _previous);
return SubQuery(select: stmt)..setSpan(left, _previous); }
} else { } else if (_matchOne(TokenType.dollarSignVariable)) {
final expr = expression(); if (enableMoorExtensions) {
_consume(TokenType.rightParen, 'Expected a closing bracket'); final typedToken = _previous as DollarSignVariableToken;
return Parentheses(left, expr, token)..setSpan(left, _previous); return DartExpressionPlaceholder(name: typedToken.name)
} ..token = typedToken
break; ..setSpan(_previous, _previous);
case TokenType.identifier: }
// could be table.column, function(...) or just column } else if (_checkLenientIdentifier()) {
final first = token as IdentifierToken; final first = _consumeIdentifier(
'This error message should never be displayed. Please report.');
if (_matchOne(TokenType.dot)) { // could be table.column, function(...) or just column
final second = if (_matchOne(TokenType.dot)) {
_consume(TokenType.identifier, 'Expected a column name here') final second = _consumeIdentifier('Expected a column name here');
as IdentifierToken; return Reference(
return Reference( tableName: first.identifier, columnName: second.identifier)
tableName: first.identifier, columnName: second.identifier) ..setSpan(first, second);
..setSpan(first, second); } else if (_matchOne(TokenType.leftParen)) {
} else if (_matchOne(TokenType.leftParen)) { final parameters = _functionParameters();
final parameters = _functionParameters(); final rightParen = _consume(TokenType.rightParen,
final rightParen = _consume(TokenType.rightParen, 'Expected closing bracket after argument list');
'Expected closing bracket after argument list');
if (_peek.type == TokenType.filter || _peek.type == TokenType.over) { if (_peek.type == TokenType.filter || _peek.type == TokenType.over) {
return _aggregate(first, parameters); return _aggregate(first, parameters);
} }
return FunctionExpression( return FunctionExpression(
name: first.identifier, parameters: parameters) name: first.identifier, parameters: parameters)
..setSpan(first, rightParen); ..setSpan(first, rightParen);
} else { } else {
return Reference(columnName: first.identifier)..setSpan(first, first); return Reference(columnName: first.identifier)..setSpan(first, first);
} }
break;
case TokenType.dollarSignVariable:
if (enableMoorExtensions) {
final typedToken = token as DollarSignVariableToken;
return DartExpressionPlaceholder(name: typedToken.name)
..token = typedToken
..setSpan(token, token);
}
break;
default:
break;
} }
// nothing found -> issue error. Step back to revert the _advance() above
_stepBack();
_error('Could not parse this expression'); _error('Could not parse this expression');
} }

View File

@ -101,6 +101,15 @@ abstract class ParserBase {
return _peek.type == type; return _peek.type == type;
} }
/// Returns whether the next token is an [TokenType.identifier] or a
/// [KeywordToken]. If this method returns true, calling [_consumeIdentifier]
/// with the lenient parameter will now throw.
bool _checkLenientIdentifier() {
final next = _peek;
return next.type == TokenType.identifier ||
(next is KeywordToken && next.canConvertToIdentifier());
}
Token _advance() { Token _advance() {
if (!_isAtEnd) { if (!_isAtEnd) {
_current++; _current++;
@ -108,16 +117,6 @@ abstract class ParserBase {
return _previous; return _previous;
} }
/// Steps back a token. This needs to be used very carefully. We basically
/// only use it in [ExpressionParser._primary] because we unconditionally
/// [_advance] in there and we'd like to report more accurate errors when no
/// matching token was found.
void _stepBack() {
if (_current != null) {
_current--;
}
}
@alwaysThrows @alwaysThrows
void _error(String message) { void _error(String message) {
final error = ParsingError(_peek, message); final error = ParsingError(_peek, message);
@ -130,11 +129,11 @@ abstract class ParserBase {
_error(message); _error(message);
} }
/// Consumes an identifier. If [lenient] is true and the next token is not /// Consumes an identifier.
/// an identifier but rather a [KeywordToken], that token will be converted IdentifierToken _consumeIdentifier(String message) {
/// to an identifier. final next = _peek;
IdentifierToken _consumeIdentifier(String message, {bool lenient = false}) { // non-standard keywords can be parsed as an identifier
if (lenient && _peek is KeywordToken) { if (next is KeywordToken && next.canConvertToIdentifier()) {
return (_advance() as KeywordToken).convertToIdentifier(); return (_advance() as KeywordToken).convertToIdentifier();
} }
return _consume(TokenType.identifier, message) as IdentifierToken; return _consume(TokenType.identifier, message) as IdentifierToken;
@ -262,8 +261,7 @@ class Parser extends ParserBase
DeclaredStatement _declaredStatement() { DeclaredStatement _declaredStatement() {
if (_check(TokenType.identifier) || _peek is KeywordToken) { if (_check(TokenType.identifier) || _peek is KeywordToken) {
final name = _consumeIdentifier('Expected a name for a declared query', final name = _consumeIdentifier('Expected a name for a declared query');
lenient: true);
final colon = final colon =
_consume(TokenType.colon, 'Expected colon (:) followed by a query'); _consume(TokenType.colon, 'Expected colon (:) followed by a query');

View File

@ -18,8 +18,7 @@ mixin SchemaParser on ParserBase {
ifNotExists = true; ifNotExists = true;
} }
final tableIdentifier = final tableIdentifier = _consumeIdentifier('Expected a table name');
_consumeIdentifier('Expected a table name', lenient: true);
// we don't currently support CREATE TABLE x AS SELECT ... statements // we don't currently support CREATE TABLE x AS SELECT ... statements
final leftParen = _consume( final leftParen = _consume(
@ -57,10 +56,8 @@ mixin SchemaParser on ParserBase {
String overriddenName; String overriddenName;
if (enableMoorExtensions && _matchOne(TokenType.as)) { if (enableMoorExtensions && _matchOne(TokenType.as)) {
overriddenName = _consumeIdentifier( overriddenName =
'Expected the name for the data class', _consumeIdentifier('Expected the name for the data class').identifier;
lenient: true)
.identifier;
} }
return CreateTableStatement( return CreateTableStatement(
@ -249,8 +246,7 @@ mixin SchemaParser on ParserBase {
String _constraintNameOrNull() { String _constraintNameOrNull() {
if (_matchOne(TokenType.constraint)) { if (_matchOne(TokenType.constraint)) {
final name = _consumeIdentifier('Expect a name for the constraint here', final name = _consumeIdentifier('Expect a name for the constraint here');
lenient: true);
return name.identifier; return name.identifier;
} }
return null; return null;

View File

@ -349,6 +349,11 @@ class KeywordToken extends Token {
KeywordToken(TokenType type, FileSpan span) : super(type, span); KeywordToken(TokenType type, FileSpan span) : super(type, span);
bool canConvertToIdentifier() {
// https://stackoverflow.com/a/45775719, but we don't parse indexed yet.
return type == TokenType.join;
}
IdentifierToken convertToIdentifier() { IdentifierToken convertToIdentifier() {
isIdentifier = true; isIdentifier = true;