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-specific analysis and apis
|
||||||
drift: '>=2.10.0 <2.11.0'
|
drift: '>=2.10.0 <2.11.0'
|
||||||
sqlite3: '>=0.1.6 <3.0.0'
|
sqlite3: '>=0.1.6 <3.0.0'
|
||||||
sqlparser: '^0.30.0'
|
sqlparser: '^0.31.0'
|
||||||
|
|
||||||
# Dart analysis
|
# Dart analysis
|
||||||
analyzer: ^5.12.0
|
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
|
## 0.30.3
|
||||||
|
|
||||||
- Fix `WITH` clauses not being resolved for compound select statements.
|
- Fix `WITH` clauses not being resolved for compound select statements.
|
||||||
|
|
|
@ -8,6 +8,26 @@ abstract class Statement extends AstNode {
|
||||||
Token? semicolon;
|
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
|
/// A statement that reads from an existing table structure and has an optional
|
||||||
/// `WITH` clause.
|
/// `WITH` clause.
|
||||||
abstract class CrudStatement extends Statement {
|
abstract class CrudStatement extends Statement {
|
||||||
|
|
|
@ -92,6 +92,7 @@ abstract class AstVisitor<A, R> {
|
||||||
R visitNamedVariable(ColonNamedVariable e, A arg);
|
R visitNamedVariable(ColonNamedVariable e, A arg);
|
||||||
|
|
||||||
R visitBlock(Block block, A arg);
|
R visitBlock(Block block, A arg);
|
||||||
|
R visitSemicolonSeparatedStatements(SemicolonSeparatedStatements e, A arg);
|
||||||
R visitBeginTransaction(BeginTransactionStatement e, A arg);
|
R visitBeginTransaction(BeginTransactionStatement e, A arg);
|
||||||
R visitCommitStatement(CommitStatement 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);
|
return defaultNode(e, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
R? visitSemicolonSeparatedStatements(SemicolonSeparatedStatements e, A arg) {
|
||||||
|
return defaultNode(e, arg);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
R? visitBeginTransaction(BeginTransactionStatement e, A arg) {
|
R? visitBeginTransaction(BeginTransactionStatement e, A arg) {
|
||||||
return visitStatement(e, arg);
|
return visitStatement(e, arg);
|
||||||
|
|
|
@ -143,6 +143,18 @@ class SqlEngine {
|
||||||
return ParseResult._(stmt, tokens, parser.errors, sql, null);
|
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.
|
/// Parses [sql] as a list of column constraints.
|
||||||
///
|
///
|
||||||
/// The [ParseResult.rootNode] will be a [ColumnDefinition] with the parsed
|
/// The [ParseResult.rootNode] will be a [ColumnDefinition] with the parsed
|
||||||
|
|
|
@ -185,6 +185,19 @@ class Parser {
|
||||||
InvalidStatement();
|
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.
|
/// Parses the remaining input as column constraints.
|
||||||
List<ColumnConstraint> columnConstraintsUntilEnd() {
|
List<ColumnConstraint> columnConstraintsUntilEnd() {
|
||||||
final constraints = <ColumnConstraint>[];
|
final constraints = <ColumnConstraint>[];
|
||||||
|
|
|
@ -600,6 +600,13 @@ class EqualityEnforcingVisitor implements AstVisitor<void, void> {
|
||||||
_checkChildren(e);
|
_checkChildren(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void visitSemicolonSeparatedStatements(
|
||||||
|
SemicolonSeparatedStatements e, void arg) {
|
||||||
|
_currentAs<SemicolonSeparatedStatements>(e);
|
||||||
|
_checkChildren(e);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void visitSetComponent(SetComponent e, void arg) {
|
void visitSetComponent(SetComponent e, void arg) {
|
||||||
_currentAs<SetComponent>(e);
|
_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
|
@override
|
||||||
void visitSetComponent(SetComponent e, void arg) {
|
void visitSetComponent(SetComponent e, void arg) {
|
||||||
visit(e.column, arg);
|
visit(e.column, arg);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
name: sqlparser
|
name: sqlparser
|
||||||
description: Parses sqlite statements and performs static analysis on them
|
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
|
homepage: https://github.com/simolus3/drift/tree/develop/sqlparser
|
||||||
repository: https://github.com/simolus3/drift
|
repository: https://github.com/simolus3/drift
|
||||||
#homepage: https://drift.simonbinder.eu/
|
#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:sqlparser/utils/node_to_text.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
enum _ParseKind { statement, driftFile }
|
enum _ParseKind {
|
||||||
|
statement,
|
||||||
|
driftFile,
|
||||||
|
multipleStatements,
|
||||||
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
final engine =
|
final engine =
|
||||||
|
@ -21,6 +25,9 @@ void main() {
|
||||||
case _ParseKind.driftFile:
|
case _ParseKind.driftFile:
|
||||||
result = engine.parseDriftFile(input);
|
result = engine.parseDriftFile(input);
|
||||||
break;
|
break;
|
||||||
|
case _ParseKind.multipleStatements:
|
||||||
|
result = engine.parseMultiple(input);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.errors.isNotEmpty) {
|
if (result.errors.isNotEmpty) {
|
||||||
|
@ -599,4 +606,19 @@ COMMIT TRANSACTION;
|
||||||
test('does not format invalid statements', () {
|
test('does not format invalid statements', () {
|
||||||
expect(InvalidStatement().toSql, throwsUnsupportedError);
|
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