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

View File

@ -101,6 +101,15 @@ abstract class ParserBase {
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() {
if (!_isAtEnd) {
_current++;
@ -108,16 +117,6 @@ abstract class ParserBase {
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
void _error(String message) {
final error = ParsingError(_peek, message);
@ -130,11 +129,11 @@ abstract class ParserBase {
_error(message);
}
/// Consumes an identifier. If [lenient] is true and the next token is not
/// an identifier but rather a [KeywordToken], that token will be converted
/// to an identifier.
IdentifierToken _consumeIdentifier(String message, {bool lenient = false}) {
if (lenient && _peek is KeywordToken) {
/// Consumes an identifier.
IdentifierToken _consumeIdentifier(String message) {
final next = _peek;
// non-standard keywords can be parsed as an identifier
if (next is KeywordToken && next.canConvertToIdentifier()) {
return (_advance() as KeywordToken).convertToIdentifier();
}
return _consume(TokenType.identifier, message) as IdentifierToken;
@ -262,8 +261,7 @@ class Parser extends ParserBase
DeclaredStatement _declaredStatement() {
if (_check(TokenType.identifier) || _peek is KeywordToken) {
final name = _consumeIdentifier('Expected a name for a declared query',
lenient: true);
final name = _consumeIdentifier('Expected a name for a declared query');
final colon =
_consume(TokenType.colon, 'Expected colon (:) followed by a query');

View File

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

View File

@ -349,6 +349,11 @@ class KeywordToken extends Token {
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() {
isIdentifier = true;