mirror of https://github.com/AMT-Cheif/drift.git
sqlparser: Parse CREATE INDEX statements
This commit is contained in:
parent
fc0949ebd1
commit
6924543a47
|
@ -4,6 +4,7 @@
|
||||||
- Experimental new type inference algorithm
|
- Experimental new type inference algorithm
|
||||||
- Support `CAST` expressions.
|
- Support `CAST` expressions.
|
||||||
- Support parsing `CREATE TRIGGER` statements
|
- Support parsing `CREATE TRIGGER` statements
|
||||||
|
- Support parsing `CREATE INDEX` statement
|
||||||
|
|
||||||
## 0.5.0
|
## 0.5.0
|
||||||
- Optionally support the `json1` module
|
- Optionally support the `json1` module
|
||||||
|
|
|
@ -11,14 +11,21 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void visitCrudStatement(CrudStatement stmt, TypeExpectation arg) {
|
void visitCrudStatement(CrudStatement stmt, TypeExpectation arg) {
|
||||||
if (stmt is HasWhereClause && stmt.where != null) {
|
if (stmt is HasWhereClause) {
|
||||||
_handleWhereClause(stmt);
|
final typedStmt = stmt as HasWhereClause;
|
||||||
_visitExcept(stmt, stmt.where, arg);
|
_handleWhereClause(typedStmt);
|
||||||
|
_visitExcept(stmt, typedStmt.where, arg);
|
||||||
} else {
|
} else {
|
||||||
visitChildren(stmt, arg);
|
visitChildren(stmt, arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void visitCreateIndexStatement(CreateIndexStatement e, TypeExpectation arg) {
|
||||||
|
_handleWhereClause(e);
|
||||||
|
_visitExcept(e, e.where, arg);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void visitJoin(Join e, TypeExpectation arg) {
|
void visitJoin(Join e, TypeExpectation arg) {
|
||||||
final constraint = e.constraint;
|
final constraint = e.constraint;
|
||||||
|
@ -165,9 +172,12 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleWhereClause(HasWhereClause stmt) {
|
void _handleWhereClause(HasWhereClause stmt) {
|
||||||
// assume that a where statement is a boolean expression. Sqlite internally
|
if (stmt.where != null) {
|
||||||
// casts (https://www.sqlite.org/lang_expr.html#booleanexpr), so be lax
|
// assume that a where statement is a boolean expression. Sqlite
|
||||||
visit(stmt.where, const ExactTypeExpectation.laxly(ResolvedType.bool()));
|
// internally casts (https://www.sqlite.org/lang_expr.html#booleanexpr),
|
||||||
|
// so be lax
|
||||||
|
visit(stmt.where, const ExactTypeExpectation.laxly(ResolvedType.bool()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _visitExcept(AstNode node, AstNode skip, TypeExpectation arg) {
|
void _visitExcept(AstNode node, AstNode skip, TypeExpectation arg) {
|
||||||
|
|
|
@ -30,6 +30,7 @@ part 'schema/column_definition.dart';
|
||||||
part 'schema/table_definition.dart';
|
part 'schema/table_definition.dart';
|
||||||
part 'statements/block.dart';
|
part 'statements/block.dart';
|
||||||
part 'statements/create_table.dart';
|
part 'statements/create_table.dart';
|
||||||
|
part 'statements/create_index.dart';
|
||||||
part 'statements/create_trigger.dart';
|
part 'statements/create_trigger.dart';
|
||||||
part 'statements/delete.dart';
|
part 'statements/delete.dart';
|
||||||
part 'statements/insert.dart';
|
part 'statements/insert.dart';
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
part of '../ast.dart';
|
||||||
|
|
||||||
|
class CreateIndexStatement extends Statement
|
||||||
|
implements CreatingStatement, HasWhereClause {
|
||||||
|
final String indexName;
|
||||||
|
final bool unique;
|
||||||
|
final bool ifNotExists;
|
||||||
|
IdentifierToken nameToken;
|
||||||
|
|
||||||
|
final TableReference on;
|
||||||
|
final List<IndexedColumn> columns;
|
||||||
|
@override
|
||||||
|
final Expression where;
|
||||||
|
|
||||||
|
CreateIndexStatement(
|
||||||
|
{@required this.indexName,
|
||||||
|
this.unique = false,
|
||||||
|
this.ifNotExists = false,
|
||||||
|
@required this.on,
|
||||||
|
@required this.columns,
|
||||||
|
this.where});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get createdName => indexName;
|
||||||
|
|
||||||
|
@override
|
||||||
|
R accept<A, R>(AstVisitor<A, R> visitor, A arg) {
|
||||||
|
return visitor.visitCreateIndexStatement(this, arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<AstNode> get childNodes =>
|
||||||
|
[on, ...columns, if (where != null) where];
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool contentEquals(CreateIndexStatement other) {
|
||||||
|
return other.indexName == indexName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Note that this class matches the productions listed at https://www.sqlite.org/syntax/indexed-column.html
|
||||||
|
/// We don't have a special case for `column-name` (those are [Reference]s).
|
||||||
|
/// The `COLLATE` branch is covered by parsing a [CollateExpression] for
|
||||||
|
/// [expression].
|
||||||
|
class IndexedColumn extends AstNode {
|
||||||
|
/// The expression on which the index should be created. Most commonly a
|
||||||
|
/// [Reference], for simple column names.
|
||||||
|
final Expression expression;
|
||||||
|
// nullable
|
||||||
|
final OrderingMode ordering;
|
||||||
|
|
||||||
|
IndexedColumn(this.expression, [this.ordering]);
|
||||||
|
|
||||||
|
@override
|
||||||
|
R accept<A, R>(AstVisitor<A, R> visitor, A arg) {
|
||||||
|
return visitor.visitIndexedColumn(this, arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<AstNode> get childNodes => [expression];
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool contentEquals(IndexedColumn other) => other.ordering == ordering;
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
part of '../ast.dart';
|
part of '../ast.dart';
|
||||||
|
|
||||||
abstract class TableInducingStatement extends Statement
|
abstract class TableInducingStatement extends Statement
|
||||||
implements SchemaStatement {
|
implements CreatingStatement {
|
||||||
final bool ifNotExists;
|
final bool ifNotExists;
|
||||||
final String tableName;
|
final String tableName;
|
||||||
|
|
||||||
|
@ -14,6 +14,9 @@ abstract class TableInducingStatement extends Statement
|
||||||
|
|
||||||
TableInducingStatement._(this.ifNotExists, this.tableName,
|
TableInducingStatement._(this.ifNotExists, this.tableName,
|
||||||
[this.overriddenDataClassName]);
|
[this.overriddenDataClassName]);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get createdName => tableName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A "CREATE TABLE" statement, see https://www.sqlite.org/lang_createtable.html
|
/// A "CREATE TABLE" statement, see https://www.sqlite.org/lang_createtable.html
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
part of '../ast.dart';
|
part of '../ast.dart';
|
||||||
|
|
||||||
/// A "CREATE TRIGGER" statement, see https://sqlite.org/lang_createtrigger.html
|
/// A "CREATE TRIGGER" statement, see https://sqlite.org/lang_createtrigger.html
|
||||||
class CreateTriggerStatement extends Statement implements SchemaStatement {
|
class CreateTriggerStatement extends Statement implements CreatingStatement {
|
||||||
final bool ifNotExists;
|
final bool ifNotExists;
|
||||||
final String triggerName;
|
final String triggerName;
|
||||||
IdentifierToken triggerNameToken;
|
IdentifierToken triggerNameToken;
|
||||||
|
@ -23,6 +23,9 @@ class CreateTriggerStatement extends Statement implements SchemaStatement {
|
||||||
this.when,
|
this.when,
|
||||||
@required this.action});
|
@required this.action});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get createdName => triggerName;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
R accept<A, R>(AstVisitor<A, R> visitor, A arg) {
|
R accept<A, R>(AstVisitor<A, R> visitor, A arg) {
|
||||||
return visitor.visitCreateTriggerStatement(this, arg);
|
return visitor.visitCreateTriggerStatement(this, arg);
|
||||||
|
|
|
@ -14,9 +14,14 @@ abstract class CrudStatement extends Statement {
|
||||||
|
|
||||||
/// Interface for statements that have a primary where clause (select, update,
|
/// Interface for statements that have a primary where clause (select, update,
|
||||||
/// delete).
|
/// delete).
|
||||||
abstract class HasWhereClause implements CrudStatement {
|
abstract class HasWhereClause extends Statement {
|
||||||
Expression get where;
|
Expression get where;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Marker interface for statements that change the table structure.
|
/// Marker interface for statements that change the table structure.
|
||||||
abstract class SchemaStatement extends Statement implements PartOfMoorFile {}
|
abstract class SchemaStatement extends Statement implements PartOfMoorFile {}
|
||||||
|
|
||||||
|
/// Marker interface for schema statements that create a schematic entity.
|
||||||
|
abstract class CreatingStatement extends SchemaStatement {
|
||||||
|
String get createdName;
|
||||||
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ abstract class AstVisitor<A, R> {
|
||||||
R visitCreateTableStatement(CreateTableStatement e, A arg);
|
R visitCreateTableStatement(CreateTableStatement e, A arg);
|
||||||
R visitCreateVirtualTableStatement(CreateVirtualTableStatement e, A arg);
|
R visitCreateVirtualTableStatement(CreateVirtualTableStatement e, A arg);
|
||||||
R visitCreateTriggerStatement(CreateTriggerStatement e, A arg);
|
R visitCreateTriggerStatement(CreateTriggerStatement e, A arg);
|
||||||
|
R visitCreateIndexStatement(CreateIndexStatement e, A arg);
|
||||||
|
|
||||||
R visitWithClause(WithClause e, A arg);
|
R visitWithClause(WithClause e, A arg);
|
||||||
R visitCommonTableExpression(CommonTableExpression e, A arg);
|
R visitCommonTableExpression(CommonTableExpression e, A arg);
|
||||||
|
@ -47,6 +48,7 @@ abstract class AstVisitor<A, R> {
|
||||||
R visitAggregateExpression(AggregateExpression e, A arg);
|
R visitAggregateExpression(AggregateExpression e, A arg);
|
||||||
R visitWindowDefinition(WindowDefinition e, A arg);
|
R visitWindowDefinition(WindowDefinition e, A arg);
|
||||||
R visitFrameSpec(FrameSpec e, A arg);
|
R visitFrameSpec(FrameSpec e, A arg);
|
||||||
|
R visitIndexedColumn(IndexedColumn e, A arg);
|
||||||
|
|
||||||
R visitNumberedVariable(NumberedVariable e, A arg);
|
R visitNumberedVariable(NumberedVariable e, A arg);
|
||||||
R visitNamedVariable(ColonNamedVariable e, A arg);
|
R visitNamedVariable(ColonNamedVariable e, A arg);
|
||||||
|
@ -104,6 +106,11 @@ class RecursiveVisitor<A, R> implements AstVisitor<A, R> {
|
||||||
return visitSchemaStatement(e, arg);
|
return visitSchemaStatement(e, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
R visitCreateIndexStatement(CreateIndexStatement e, A arg) {
|
||||||
|
return visitSchemaStatement(e, arg);
|
||||||
|
}
|
||||||
|
|
||||||
R visitBaseSelectStatement(BaseSelectStatement stmt, A arg) {
|
R visitBaseSelectStatement(BaseSelectStatement stmt, A arg) {
|
||||||
return visitCrudStatement(stmt, arg);
|
return visitCrudStatement(stmt, arg);
|
||||||
}
|
}
|
||||||
|
@ -211,6 +218,11 @@ class RecursiveVisitor<A, R> implements AstVisitor<A, R> {
|
||||||
return visitChildren(e, arg);
|
return visitChildren(e, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
R visitIndexedColumn(IndexedColumn e, A arg) {
|
||||||
|
return visitChildren(e, arg);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
R visitBlock(Block e, A arg) {
|
R visitBlock(Block e, A arg) {
|
||||||
return visitChildren(e, arg);
|
return visitChildren(e, arg);
|
||||||
|
|
|
@ -8,6 +8,8 @@ mixin SchemaParser on ParserBase {
|
||||||
return _createTable();
|
return _createTable();
|
||||||
} else if (_check(TokenType.trigger)) {
|
} else if (_check(TokenType.trigger)) {
|
||||||
return _createTrigger();
|
return _createTrigger();
|
||||||
|
} else if (_check(TokenType.unique) || _check(TokenType.$index)) {
|
||||||
|
return _createIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -234,6 +236,52 @@ mixin SchemaParser on ParserBase {
|
||||||
..triggerNameToken = trigger;
|
..triggerNameToken = trigger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses a [CreateIndexStatement]. The `CREATE` token must have already been
|
||||||
|
/// accepted.
|
||||||
|
CreateIndexStatement _createIndex() {
|
||||||
|
final create = _previous;
|
||||||
|
assert(create.type == TokenType.create);
|
||||||
|
|
||||||
|
final unique = _matchOne(TokenType.unique);
|
||||||
|
if (!_matchOne(TokenType.$index)) return null;
|
||||||
|
|
||||||
|
final ifNotExists = _ifNotExists();
|
||||||
|
final name = _consumeIdentifier('Expected a name for this index');
|
||||||
|
|
||||||
|
_consume(TokenType.on, 'Expected ON table');
|
||||||
|
final nameToken = _consumeIdentifier('Expected a table name');
|
||||||
|
final tableRef = TableReference(nameToken.identifier)
|
||||||
|
..setSpan(nameToken, nameToken);
|
||||||
|
|
||||||
|
_consume(TokenType.leftParen, 'Expected indexed columns in parentheses');
|
||||||
|
|
||||||
|
final indexes = <IndexedColumn>[];
|
||||||
|
do {
|
||||||
|
final expr = expression();
|
||||||
|
final mode = _orderingModeOrNull();
|
||||||
|
|
||||||
|
indexes.add(IndexedColumn(expr, mode)..setSpan(expr.first, _previous));
|
||||||
|
} while (_matchOne(TokenType.comma));
|
||||||
|
|
||||||
|
_consume(TokenType.rightParen, 'Expected closing bracket');
|
||||||
|
|
||||||
|
Expression where;
|
||||||
|
if (_matchOne(TokenType.where)) {
|
||||||
|
where = expression();
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateIndexStatement(
|
||||||
|
indexName: name.identifier,
|
||||||
|
unique: unique,
|
||||||
|
ifNotExists: ifNotExists,
|
||||||
|
on: tableRef,
|
||||||
|
columns: indexes,
|
||||||
|
where: where,
|
||||||
|
)
|
||||||
|
..nameToken = name
|
||||||
|
..setSpan(create, _previous);
|
||||||
|
}
|
||||||
|
|
||||||
/// Parses `IF NOT EXISTS` | epsilon
|
/// Parses `IF NOT EXISTS` | epsilon
|
||||||
bool _ifNotExists() {
|
bool _ifNotExists() {
|
||||||
if (_matchOne(TokenType.$if)) {
|
if (_matchOne(TokenType.$if)) {
|
||||||
|
|
|
@ -122,6 +122,7 @@ enum TokenType {
|
||||||
virtual,
|
virtual,
|
||||||
table,
|
table,
|
||||||
trigger,
|
trigger,
|
||||||
|
$index,
|
||||||
$if,
|
$if,
|
||||||
$with,
|
$with,
|
||||||
without,
|
without,
|
||||||
|
@ -222,6 +223,7 @@ const Map<String, TokenType> keywords = {
|
||||||
'CREATE': TokenType.create,
|
'CREATE': TokenType.create,
|
||||||
'TABLE': TokenType.table,
|
'TABLE': TokenType.table,
|
||||||
'TRIGGER': TokenType.trigger,
|
'TRIGGER': TokenType.trigger,
|
||||||
|
'INDEX': TokenType.$index,
|
||||||
'IF': TokenType.$if,
|
'IF': TokenType.$if,
|
||||||
'WITH': TokenType.$with,
|
'WITH': TokenType.$with,
|
||||||
'WITHOUT': TokenType.without,
|
'WITHOUT': TokenType.without,
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
import 'package:sqlparser/sqlparser.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import 'utils.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('parses CREATE INDEX statements', () {
|
||||||
|
testStatement(
|
||||||
|
'CREATE INDEX foo ON bar (baz, inga) WHERE TRUE',
|
||||||
|
CreateIndexStatement(
|
||||||
|
indexName: 'foo',
|
||||||
|
on: TableReference('bar'),
|
||||||
|
columns: [
|
||||||
|
IndexedColumn(Reference(columnName: 'baz')),
|
||||||
|
IndexedColumn(Reference(columnName: 'inga')),
|
||||||
|
],
|
||||||
|
where: BooleanLiteral.withTrue(token(TokenType.$true)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('with unique and IF NOT EXISTS', () {
|
||||||
|
testStatement(
|
||||||
|
'CREATE UNIQUE INDEX IF NOT EXISTS foo ON bar (baz);',
|
||||||
|
CreateIndexStatement(
|
||||||
|
unique: true,
|
||||||
|
ifNotExists: true,
|
||||||
|
indexName: 'foo',
|
||||||
|
on: TableReference('bar'),
|
||||||
|
columns: [
|
||||||
|
IndexedColumn(Reference(columnName: 'baz')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can have ordering modes on index expressions', () {
|
||||||
|
testStatement(
|
||||||
|
'CREATE INDEX foo ON bar (a + b DESC);',
|
||||||
|
CreateIndexStatement(
|
||||||
|
indexName: 'foo',
|
||||||
|
on: TableReference('bar'),
|
||||||
|
columns: [
|
||||||
|
IndexedColumn(
|
||||||
|
BinaryExpression(
|
||||||
|
Reference(columnName: 'a'),
|
||||||
|
token(TokenType.plus),
|
||||||
|
Reference(columnName: 'b'),
|
||||||
|
),
|
||||||
|
OrderingMode.descending,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can have collate expressions', () {
|
||||||
|
testStatement(
|
||||||
|
'CREATE INDEX foo ON bar (baz COLLATE RTRIM);',
|
||||||
|
CreateIndexStatement(
|
||||||
|
indexName: 'foo',
|
||||||
|
on: TableReference('bar'),
|
||||||
|
columns: [
|
||||||
|
IndexedColumn(
|
||||||
|
CollateExpression(
|
||||||
|
inner: Reference(columnName: 'baz'),
|
||||||
|
operator: token(TokenType.collate),
|
||||||
|
collateFunction: identifier('RTRIM'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in New Issue