mirror of https://github.com/AMT-Cheif/drift.git
Add parseMultiple API to sqlparser (#2519)
This commit is contained in:
parent
d0db9ac02f
commit
625eacf5f1
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>[];
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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/
|
||||
|
|
|
@ -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'),
|
||||
]),
|
||||
]),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue