Fix crash at trailing comma in FROM clause

This commit is contained in:
Simon Binder 2023-08-09 15:24:10 +02:00
parent 29ba50a0ca
commit 0ed132f642
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
3 changed files with 38 additions and 32 deletions

View File

@ -62,7 +62,12 @@ class Parser {
bool get _isAtEnd => _peek.type == TokenType.eof;
Token get _peek => tokens[_current];
Token get _peekNext => tokens[_current + 1];
Token? get _peekNext {
if (_isAtEnd) return null;
return tokens[_current + 1];
}
Token get _previous => tokens[_current - 1];
bool _match(Iterable<TokenType> types) {
@ -96,14 +101,14 @@ class Parser {
/// "NOT" followed by [type]. Does not consume any tokens.
bool _checkWithNot(TokenType type) {
if (_check(type)) return true;
if (_check(TokenType.not) && _peekNext.type == type) return true;
if (_check(TokenType.not) && _peekNext?.type == type) return true;
return false;
}
/// Like [_checkWithNot], but with more than one token type.
bool _checkAnyWithNot(List<TokenType> types) {
if (types.any(_check)) return true;
if (_check(TokenType.not) && types.contains(_peekNext.type)) return true;
if (_check(TokenType.not) && types.contains(_peekNext?.type)) return true;
return false;
}
@ -866,7 +871,7 @@ class Parser {
if (_peek is KeywordToken) {
// Improve error messages for possible function calls, https://github.com/simolus3/drift/discussions/2277
if (tokens.length > _current + 1 &&
_peekNext.type == TokenType.leftParen) {
_peekNext?.type == TokenType.leftParen) {
_error(
'Expected an expression here, but got a reserved keyword. Did you '
'mean to call a function? Try wrapping the keyword in double quotes.',
@ -1346,6 +1351,7 @@ class Parser {
// Can either be a list of <TableOrSubquery> or a join. Joins also start
// with a TableOrSubquery, so let's first parse that.
final start = _tableOrSubquery();
// parse join, if there is one
return _joinClause(start) ?? start;
}
@ -1399,7 +1405,7 @@ class Parser {
final joins = <Join>[];
while (operator != null) {
final first = _peekNext;
final first = _peek;
final subquery = _tableOrSubquery();
final constraint = _joinConstraint();
@ -2786,10 +2792,8 @@ class Parser {
DeferrableClause? deferrable;
if (_checkWithNot(TokenType.deferrable)) {
final first = _peekNext;
final not = _matchOne(TokenType.not);
_consume(TokenType.deferrable);
final not = _matchOneAndGet(TokenType.not);
final deferrableToken = _consume(TokenType.deferrable);
InitialDeferrableMode? mode;
if (_matchOne(TokenType.initially)) {
@ -2802,7 +2806,8 @@ class Parser {
}
}
deferrable = DeferrableClause(not, mode)..setSpan(first, _previous);
deferrable = DeferrableClause(not != null, mode)
..setSpan(not ?? deferrableToken, _previous);
}
return ForeignKeyClause(

View File

@ -1,6 +1,8 @@
import 'package:sqlparser/sqlparser.dart';
import 'package:test/test.dart';
import 'utils.dart';
void main() {
test('WITH without following statement', () {
expectError('WITH foo AS (SELECT * FROM bar)',
@ -12,7 +14,7 @@ void main() {
expectError('SELECT replace(a, b, c);', [
isParsingError(
message: contains('Did you mean to call a function?'),
lexeme: 'replace',
span: 'replace',
),
]);
});
@ -21,14 +23,22 @@ void main() {
expectError('SELECT group FROM foo;', [
isParsingError(
message: contains('Did you mean to use it as a column?'),
lexeme: 'group',
span: 'group',
),
]);
expectError('CREATE TABLE x (table TEXT NOT NULL, foo INTEGER);', [
isParsingError(
message: 'Expected a column name (got keyword TABLE)',
lexeme: 'table',
span: 'table',
),
]);
});
test('recovers from invalid comma after table reference', () {
expectError('SELECT * FROM table_name,', [
isParsingError(
message: 'Expected a table name or a nested select statement',
),
]);
});
@ -40,17 +50,3 @@ void expectError(String sql, errorsMatcher) {
expect(parsed.errors, errorsMatcher);
}
TypeMatcher<ParsingError> isParsingError({message, lexeme}) {
var matcher = isA<ParsingError>();
if (lexeme != null) {
matcher = matcher.having((e) => e.token.lexeme, 'token.lexeme', lexeme);
}
if (message != null) {
matcher = matcher.having((e) => e.message, 'message', message);
}
return matcher;
}

View File

@ -54,11 +54,16 @@ void expectParseError(
}) {
final result = SqlEngine().parse(sql);
expect(result.errors, [
isA<ParsingError>()
.having((e) => e.message, 'message', wrapMatcher(message))
.having((e) => e.token.span.text, 'span', wrapMatcher(span))
]);
expect(result.errors, [isParsingError(message: message, span: span)]);
}
TypeMatcher<ParsingError> isParsingError({
dynamic message = anything,
dynamic span = anything,
}) {
return isA<ParsingError>()
.having((e) => e.message, 'message', wrapMatcher(message))
.having((e) => e.token.span.text, 'span', wrapMatcher(span));
}
FileSpan fakeSpan(String content) {