sqlparser: Recover from invalid column definitions

This commit is contained in:
Simon Binder 2020-01-18 15:09:42 +01:00
parent 83a3344719
commit eb77d06cac
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
4 changed files with 119 additions and 21 deletions

View File

@ -200,6 +200,22 @@ abstract class ParserBase {
/// Parses a block, which consists of statements between `BEGIN` and `END`.
Block _consumeBlock();
/// Skips all tokens until it finds one with [type]. If [skipTarget] is true,
/// that token will be skipped as well.
///
/// When using `_synchronize(TokenType.semicolon, skipTarget: true)`,
/// this will move the parser to the next statement, which can be useful for
/// error recovery.
void _synchronize(TokenType type, {bool skipTarget = false}) {
if (skipTarget) {
while (!_isAtEnd && _advance().type != type) {}
} else {
while (!_isAtEnd && !_check(type)) {
_advance();
}
}
}
}
class Parser extends ParserBase
@ -355,11 +371,11 @@ class Parser extends ParserBase
T result;
try {
result = parser();
} on ParsingError catch (_) {
} on ParsingError {
_lastStmtHadParsingError = true;
// the error is added to the list errors, so ignore. We skip to the next
// semicolon to parse the next statement.
_synchronize();
// the error is added to the list errors, so ignore. We skip after the
// next semicolon to parse the next statement.
_synchronize(TokenType.semicolon, skipTarget: true);
}
if (result == null) return null;
@ -390,11 +406,6 @@ class Parser extends ParserBase
..begin = begin
..end = end;
}
void _synchronize() {
// fast-forward to the token after the next semicolon
while (!_isAtEnd && _advance().type != TokenType.semicolon) {}
}
}
extension on List<Token> {

View File

@ -43,17 +43,26 @@ mixin SchemaParser on ParserBase {
var encounteredTableConstraint = false;
do {
final tableConstraint = _tableConstraintOrNull();
try {
final tableConstraint = _tableConstraintOrNull();
if (tableConstraint != null) {
encounteredTableConstraint = true;
tableConstraints.add(tableConstraint);
} else {
if (encounteredTableConstraint) {
_error('Expected another table constraint');
if (tableConstraint != null) {
encounteredTableConstraint = true;
tableConstraints.add(tableConstraint);
} else {
columns.add(_columnDefinition());
if (encounteredTableConstraint) {
_error('Expected another table constraint');
} else {
columns.add(_columnDefinition());
}
}
} on ParsingError {
// if we're at the closing bracket, don't try to parse another column
if (_check(TokenType.rightParen)) break;
// error while parsing a column definition or table constraint. We try
// to recover to the next comma.
_synchronize(TokenType.comma);
if (_check(TokenType.rightParen)) break;
}
} while (_matchOne(TokenType.comma));

View File

@ -0,0 +1,75 @@
import 'package:sqlparser/sqlparser.dart';
import 'package:sqlparser/src/utils/ast_equality.dart';
import 'package:test/test.dart';
import 'utils.dart';
void main() {
test('parses create table statements with a previous malformed inport', () {
final file = parseMoor('''
import ;
CREATE TABLE foo (name TEXT);
''');
expect(
file.childNodes, contains(const TypeMatcher<CreateTableStatement>()));
});
test('recovers from parsing errors in column definition', () {
final file = parseMoor('''
CREATE TABLE foo (
id INTEGER PRIMARY,
name TEXT NOT NULL
);
''');
final stmt = file.childNodes.single as CreateTableStatement;
enforceEqual(
stmt,
CreateTableStatement(
tableName: 'foo',
columns: [
// id column can't be parsed because of the missing KEY
ColumnDefinition(
columnName: 'name',
typeName: 'TEXT',
constraints: [NotNull(null)],
),
],
),
);
});
test('parses trailing comma with error', () {
final engine =
SqlEngine.withOptions(EngineOptions(useMoorExtensions: true));
final result = engine.parseMoorFile('''
CREATE TABLE foo (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
);
''');
expect(result.errors, hasLength(1));
enforceEqual(
result.rootNode.childNodes.single,
CreateTableStatement(
tableName: 'foo',
columns: [
ColumnDefinition(
columnName: 'id',
typeName: 'INTEGER',
constraints: [PrimaryKeyColumn(null)],
),
ColumnDefinition(
columnName: 'name',
typeName: 'TEXT',
constraints: [NotNull(null)],
),
],
),
);
});
}

View File

@ -18,11 +18,14 @@ IdentifierToken identifier(String content) {
return IdentifierToken(false, fakeSpan(content));
}
void testMoorFile(String moorFile, MoorFile expected) {
final parsed = SqlEngine.withOptions(EngineOptions(useMoorExtensions: true))
.parseMoorFile(moorFile)
.rootNode;
MoorFile parseMoor(String content) {
return SqlEngine.withOptions(EngineOptions(useMoorExtensions: true))
.parseMoorFile(content)
.rootNode as MoorFile;
}
void testMoorFile(String moorFile, MoorFile expected) {
final parsed = parseMoor(moorFile);
enforceHasSpan(parsed);
enforceEqual(parsed, expected);
}