mirror of https://github.com/AMT-Cheif/drift.git
Parse JOIN and INDEXED as identifiers when appropriate
This commit is contained in:
parent
e0fc4a3af6
commit
d9c2b5f342
|
@ -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');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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');
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue