mirror of https://github.com/AMT-Cheif/drift.git
sqlparser: Recover from invalid column definitions
This commit is contained in:
parent
83a3344719
commit
eb77d06cac
|
@ -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> {
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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)],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue