Add parseMultiple API to sqlparser (#2519)

This commit is contained in:
Simon Binder 2023-07-16 00:29:29 +02:00
parent d0db9ac02f
commit 625eacf5f1
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
11 changed files with 157 additions and 3 deletions

View File

@ -32,7 +32,7 @@ dependencies:
# Drift-specific analysis and apis
drift: '>=2.10.0 <2.11.0'
sqlite3: '>=0.1.6 <3.0.0'
sqlparser: '^0.30.0'
sqlparser: '^0.31.0'
# Dart analysis
analyzer: ^5.12.0

View File

@ -1,3 +1,7 @@
## 0.31.0-dev
- Add `SqlEngine.parseMultiple` to parse multiple statements into one AST.
## 0.30.3
- Fix `WITH` clauses not being resolved for compound select statements.

View File

@ -8,6 +8,26 @@ abstract class Statement extends AstNode {
Token? semicolon;
}
/// A list of statements, separated by semicolons.
class SemicolonSeparatedStatements extends AstNode {
List<Statement> statements;
SemicolonSeparatedStatements(this.statements);
@override
R accept<A, R>(AstVisitor<A, R> visitor, A arg) {
return visitor.visitSemicolonSeparatedStatements(this, arg);
}
@override
Iterable<AstNode> get childNodes => statements;
@override
void transformChildren<A>(Transformer<A> transformer, A arg) {
statements = transformer.transformChildren(statements, this, arg);
}
}
/// A statement that reads from an existing table structure and has an optional
/// `WITH` clause.
abstract class CrudStatement extends Statement {

View File

@ -92,6 +92,7 @@ abstract class AstVisitor<A, R> {
R visitNamedVariable(ColonNamedVariable e, A arg);
R visitBlock(Block block, A arg);
R visitSemicolonSeparatedStatements(SemicolonSeparatedStatements e, A arg);
R visitBeginTransaction(BeginTransactionStatement e, A arg);
R visitCommitStatement(CommitStatement e, A arg);
@ -382,6 +383,11 @@ class RecursiveVisitor<A, R> implements AstVisitor<A, R?> {
return defaultNode(e, arg);
}
@override
R? visitSemicolonSeparatedStatements(SemicolonSeparatedStatements e, A arg) {
return defaultNode(e, arg);
}
@override
R? visitBeginTransaction(BeginTransactionStatement e, A arg) {
return visitStatement(e, arg);

View File

@ -143,6 +143,18 @@ class SqlEngine {
return ParseResult._(stmt, tokens, parser.errors, sql, null);
}
/// Parses multiple [sql] statements, separated by a semicolon.
///
/// You can use the [AstNode.children] of the returned [ParseResult.rootNode]
/// to inspect the returned statements.
ParseResult parseMultiple(String sql) {
final tokens = tokenize(sql);
final parser = _createParser(tokens);
final ast = parser.safeStatements();
return ParseResult._(ast, tokens, parser.errors, sql, null);
}
/// Parses [sql] as a list of column constraints.
///
/// The [ParseResult.rootNode] will be a [ColumnDefinition] with the parsed

View File

@ -185,6 +185,19 @@ class Parser {
InvalidStatement();
}
SemicolonSeparatedStatements safeStatements() {
final first = _peek;
final statements = <Statement>[];
while (!_isAtEnd) {
final statement = _parseAsStatement(_statementWithoutSemicolon);
if (statement != null) {
statements.add(statement);
}
}
return SemicolonSeparatedStatements(statements)..setSpan(first, _previous);
}
/// Parses the remaining input as column constraints.
List<ColumnConstraint> columnConstraintsUntilEnd() {
final constraints = <ColumnConstraint>[];

View File

@ -600,6 +600,13 @@ class EqualityEnforcingVisitor implements AstVisitor<void, void> {
_checkChildren(e);
}
@override
void visitSemicolonSeparatedStatements(
SemicolonSeparatedStatements e, void arg) {
_currentAs<SemicolonSeparatedStatements>(e);
_checkChildren(e);
}
@override
void visitSetComponent(SetComponent e, void arg) {
_currentAs<SetComponent>(e);

View File

@ -1046,6 +1046,16 @@ class NodeSqlBuilder extends AstVisitor<void, void> {
}
}
@override
void visitSemicolonSeparatedStatements(
SemicolonSeparatedStatements e, void arg) {
for (final stmt in e.statements) {
visit(stmt, arg);
buffer.writeln(';');
needsSpace = false;
}
}
@override
void visitSetComponent(SetComponent e, void arg) {
visit(e.column, arg);

View File

@ -1,6 +1,6 @@
name: sqlparser
description: Parses sqlite statements and performs static analysis on them
version: 0.30.3
version: 0.31.0-dev
homepage: https://github.com/simolus3/drift/tree/develop/sqlparser
repository: https://github.com/simolus3/drift
#homepage: https://drift.simonbinder.eu/

View File

@ -83,4 +83,64 @@ void main() {
),
);
});
test('parses multiple statements with parseMultiple()', () {
const sql = '''
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT,
email TEXT
);
SELECT * FROM users WHERE id = 1;
INSERT INTO users (name, email) VALUES ('John Doe', 'john@example.com');
''';
final engine = SqlEngine();
final ast = engine.parseMultiple(sql).rootNode;
enforceHasSpan(ast);
enforceEqual(
ast,
SemicolonSeparatedStatements(
[
CreateTableStatement(
tableName: 'users',
columns: [
ColumnDefinition(
columnName: 'id',
typeName: 'INTEGER',
constraints: [PrimaryKeyColumn(null)],
),
ColumnDefinition(columnName: 'name', typeName: 'TEXT'),
ColumnDefinition(columnName: 'email', typeName: 'TEXT'),
],
),
SelectStatement(
columns: [StarResultColumn()],
from: TableReference('users'),
where: BinaryExpression(
Reference(columnName: 'id'),
token(TokenType.equal),
NumericLiteral(1),
),
),
InsertStatement(
table: TableReference('users'),
targetColumns: [
Reference(columnName: 'name'),
Reference(columnName: 'email')
],
source: ValuesSource([
Tuple(expressions: [
StringLiteral('John Doe'),
StringLiteral('john@example.com'),
]),
]),
),
],
),
);
});
}

View File

@ -3,7 +3,11 @@ import 'package:sqlparser/src/utils/ast_equality.dart';
import 'package:sqlparser/utils/node_to_text.dart';
import 'package:test/test.dart';
enum _ParseKind { statement, driftFile }
enum _ParseKind {
statement,
driftFile,
multipleStatements,
}
void main() {
final engine =
@ -21,6 +25,9 @@ void main() {
case _ParseKind.driftFile:
result = engine.parseDriftFile(input);
break;
case _ParseKind.multipleStatements:
result = engine.parseMultiple(input);
break;
}
if (result.errors.isNotEmpty) {
@ -599,4 +606,19 @@ COMMIT TRANSACTION;
test('does not format invalid statements', () {
expect(InvalidStatement().toSql, throwsUnsupportedError);
});
test('multiple statements', () {
testFormat('''
CREATE TABLE my_table (
id INTEGER NOT NULL PRIMARY KEY,
another TEXT
) STRICT;
BEGIN;
INSERT INTO foo (bar, baz) VALUES ('hi', 3);
COMMIT;
''', kind: _ParseKind.multipleStatements);
});
}