Better error handling when parsing multiple sql statements

This commit is contained in:
Simon Binder 2019-09-03 21:24:59 +02:00
parent 867f953107
commit ba772ef07f
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
3 changed files with 68 additions and 5 deletions

View File

@ -124,7 +124,14 @@ class DartTask extends FileTask<ParsedDartFile> {
Future<List<SpecifiedTable>> resolveIncludes(Iterable<String> paths) { Future<List<SpecifiedTable>> resolveIncludes(Iterable<String> paths) {
return Stream.fromFutures(paths.map( return Stream.fromFutures(paths.map(
(path) => session.startMoorTask(backendTask, uri: Uri.parse(path)))) (path) => session.startMoorTask(backendTask, uri: Uri.parse(path))))
.asyncMap((task) => task.compute()) .asyncMap((task) async {
final result = await task.compute();
// add errors from nested task to this task as well.
task.errors.errors.forEach(reportError);
return result;
})
.expand((file) => file.declaredTables) .expand((file) => file.declaredTables)
.toList(); .toList();
} }

View File

@ -151,9 +151,6 @@ abstract class ParserBase {
WindowDefinition _windowDefinition(); WindowDefinition _windowDefinition();
} }
// todo better error handling and synchronisation, like it's done here:
// https://craftinginterpreters.com/parsing-expressions.html#synchronizing-a-recursive-descent-parser
class Parser extends ParserBase class Parser extends ParserBase
with ExpressionParser, SchemaParser, CrudParser { with ExpressionParser, SchemaParser, CrudParser {
Parser(List<Token> tokens, {bool useMoor = false}) : super(tokens, useMoor); Parser(List<Token> tokens, {bool useMoor = false}) : super(tokens, useMoor);
@ -180,8 +177,19 @@ class Parser extends ParserBase
List<Statement> statements() { List<Statement> statements() {
final stmts = <Statement>[]; final stmts = <Statement>[];
while (!_isAtEnd) { while (!_isAtEnd) {
try {
stmts.add(statement(expectEnd: false)); stmts.add(statement(expectEnd: false));
} on ParsingError catch (_) {
// the error is added to the list errors, so ignore. We skip to the next
// semicolon to parse the next statement.
_synchronize();
}
} }
return stmts; return stmts;
} }
void _synchronize() {
// fast-forward to the token after th next semicolon
while (!_isAtEnd && _advance().type != TokenType.semicolon) {}
}
} }

View File

@ -0,0 +1,48 @@
import 'package:sqlparser/sqlparser.dart';
import 'package:sqlparser/src/reader/parser/parser.dart';
import 'package:sqlparser/src/reader/tokenizer/scanner.dart';
import 'package:sqlparser/src/utils/ast_equality.dart';
import 'package:test/test.dart';
void main() {
test('can parse multiple statements', () {
final sql = 'UPDATE tbl SET a = b; SELECT * FROM tbl;';
final tokens = Scanner(sql).scanTokens();
final statements = Parser(tokens).statements();
enforceEqual(
statements[0],
UpdateStatement(
table: TableReference('tbl', null),
set: [
SetComponent(
column: Reference(columnName: 'a'),
expression: Reference(columnName: 'b'),
),
],
),
);
enforceEqual(
statements[1],
SelectStatement(
columns: [StarResultColumn(null)],
from: [TableReference('tbl', null)],
),
);
});
test('recovers from invalid statements', () {
final sql = 'UPDATE tbl SET a = * d; SELECT * FROM tbl;';
final tokens = Scanner(sql).scanTokens();
final statements = Parser(tokens).statements();
expect(statements, hasLength(1));
enforceEqual(
statements[0],
SelectStatement(
columns: [StarResultColumn(null)],
from: [TableReference('tbl', null)],
),
);
});
}