mirror of https://github.com/AMT-Cheif/drift.git
Parser is ready, work on analyzer was started.
This commit is contained in:
parent
25ee06ab36
commit
d86d7ab7e3
|
@ -17,6 +17,7 @@ part 'schema/column.dart';
|
|||
part 'schema/from_create_table.dart';
|
||||
part 'schema/references.dart';
|
||||
part 'schema/table.dart';
|
||||
part 'schema/view.dart';
|
||||
part 'steps/column_resolver.dart';
|
||||
part 'steps/linting_visitor.dart';
|
||||
part 'steps/prepare_ast.dart';
|
||||
|
|
|
@ -103,6 +103,73 @@ class TableColumn extends Column {
|
|||
}
|
||||
}
|
||||
|
||||
/// A column that is part of a table.
|
||||
class ViewColumn extends Column {
|
||||
@override
|
||||
final String name;
|
||||
|
||||
/// The type of this column, which is available before any resolution happens
|
||||
/// (we know ii from the table).
|
||||
ResolvedType get type => _type;
|
||||
ResolvedType _type;
|
||||
|
||||
/// The column constraints set on this column.
|
||||
///
|
||||
/// This only works columns where [hasDefinition] is true, otherwise this
|
||||
/// getter will throw. The columns in a `CREATE TABLE` statement always have
|
||||
/// a definition, but those from a `CREATE VIRTUAL TABLE` likely don't.
|
||||
///
|
||||
/// See also:
|
||||
/// - https://www.sqlite.org/syntax/column-constraint.html
|
||||
List<ColumnConstraint> get constraints => definition.constraints;
|
||||
|
||||
/// The definition in the AST that was used to create this column model.
|
||||
final ColumnDefinition definition;
|
||||
|
||||
/// Whether this column has a definition from the ast.
|
||||
bool get hasDefinition => definition != null;
|
||||
|
||||
/// The table this column belongs to.
|
||||
View view;
|
||||
|
||||
ViewColumn(this.name, this._type, {this.definition});
|
||||
|
||||
/// Applies a type hint to this column.
|
||||
///
|
||||
/// The [hint] will then be reflected in the [type].
|
||||
void applyTypeHint(TypeHint hint) {
|
||||
_type = _type?.copyWith(hint: hint);
|
||||
}
|
||||
|
||||
/// Whether this column is an alias for the rowid, as defined in
|
||||
/// https://www.sqlite.org/lang_createtable.html#rowid
|
||||
///
|
||||
/// To summarize, a column is an alias for the rowid if all of the following
|
||||
/// conditions are met:
|
||||
/// - the table has a primary key that consists of exactly one (this) column
|
||||
/// - the column is declared to be an integer
|
||||
/// - if this column has a [PrimaryKeyColumn], the [OrderingMode] of that
|
||||
/// constraint is not [OrderingMode.descending].
|
||||
bool isAliasForRowId() {
|
||||
//TODO is this applicable to views?
|
||||
if (definition == null ||
|
||||
view == null ||
|
||||
type?.type != BasicType.int ||
|
||||
view.withoutRowId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
String humanReadableDescription() {
|
||||
return '$name in ${view.humanReadableDescription()}';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Refers to the special "rowid", "oid" or "_rowid_" column defined for tables
|
||||
/// that weren't created with an `WITHOUT ROWID` clause.
|
||||
class RowId extends TableColumn {
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
part of '../analysis.dart';
|
||||
|
||||
/// A database view. The information stored here will be used to resolve
|
||||
/// references and for type inference.
|
||||
class View with HasMetaMixin implements HumanReadable, ResolvesToResultSet {
|
||||
/// The name of this table, as it appears in sql statements. This should be
|
||||
/// the raw name, not an escaped version.
|
||||
///
|
||||
/// To obtain an escaped name, use [escapedName].
|
||||
final String name;
|
||||
|
||||
/// If [name] is a reserved sql keyword, wraps it in double ticks. Otherwise
|
||||
/// just returns the [name] directly.
|
||||
String get escapedName {
|
||||
return isKeywordLexeme(name) ? '"$name"' : name;
|
||||
}
|
||||
|
||||
final List<ViewColumn> resolvedColumns;
|
||||
|
||||
/// Filter the [resolvedColumns] for those that are
|
||||
/// [Column.includedInResults].
|
||||
List<ViewColumn> get resultColumns =>
|
||||
resolvedColumns.where((c) => c.includedInResults).toList();
|
||||
|
||||
/// Whether this table was created with an "WITHOUT ROWID" modifier
|
||||
final bool withoutRowId;
|
||||
|
||||
/// Additional constraints set on this table.
|
||||
final List<TableConstraint> tableConstraints;
|
||||
|
||||
/// The ast node that created this table
|
||||
final TableInducingStatement definition;
|
||||
|
||||
@override
|
||||
bool get visibleToChildren => true;
|
||||
|
||||
ViewColumn _rowIdColumn;
|
||||
|
||||
/// Constructs a table from the known [name] and [resolvedColumns].
|
||||
View(
|
||||
{@required this.name,
|
||||
this.resolvedColumns,
|
||||
this.withoutRowId = false,
|
||||
this.tableConstraints = const [],
|
||||
this.definition}) {
|
||||
for (final column in resolvedColumns) {
|
||||
column.view = this;
|
||||
|
||||
if (_rowIdColumn == null && column.isAliasForRowId()) {
|
||||
_rowIdColumn = column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Column findColumn(String name) {
|
||||
// final defaultSearch = super.findColumn(name);
|
||||
// if (defaultSearch != null) return defaultSearch;
|
||||
//
|
||||
// // handle aliases to rowids, see https://www.sqlite.org/lang_createtable.html#rowid
|
||||
// if (aliasesForRowId.contains(name.toLowerCase()) && !withoutRowId) {
|
||||
// return _rowIdColumn ?? RowId()
|
||||
// ..table = this;
|
||||
// }
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String humanReadableDescription() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@override
|
||||
// TODO: implement resultSet
|
||||
ResultSet get resultSet => null;
|
||||
}
|
||||
//
|
||||
//class TableAlias implements ResultSet, HumanReadable {
|
||||
// final ResultSet delegate;
|
||||
// final String alias;
|
||||
//
|
||||
// TableAlias(this.delegate, this.alias);
|
||||
//
|
||||
// @override
|
||||
// List<Column> get resolvedColumns => delegate.resolvedColumns;
|
||||
//
|
||||
// @override
|
||||
// Column findColumn(String name) => delegate.findColumn(name);
|
||||
//
|
||||
// @override
|
||||
// ResultSet get resultSet => this;
|
||||
//
|
||||
// @override
|
||||
// bool get visibleToChildren => delegate.visibleToChildren;
|
||||
//
|
||||
// @override
|
||||
// String humanReadableDescription() {
|
||||
// final delegateDescription = delegate is HumanReadable
|
||||
// ? (delegate as HumanReadable).humanReadableDescription()
|
||||
// : delegate.toString();
|
||||
//
|
||||
// return '$alias (alias to $delegateDescription)';
|
||||
// }
|
||||
//}
|
|
@ -34,6 +34,7 @@ part 'statements/block.dart';
|
|||
part 'statements/create_table.dart';
|
||||
part 'statements/create_index.dart';
|
||||
part 'statements/create_trigger.dart';
|
||||
part 'statements/create_view.dart';
|
||||
part 'statements/delete.dart';
|
||||
part 'statements/insert.dart';
|
||||
part 'statements/select.dart';
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
part of '../ast.dart';
|
||||
|
||||
/// A "CREATE VIEW" statement, see https://sqlite.org/lang_createview.html
|
||||
class CreateViewStatement extends Statement implements CreatingStatement {
|
||||
final bool ifNotExists;
|
||||
|
||||
final String viewName;
|
||||
IdentifierToken viewNameToken;
|
||||
|
||||
final BaseSelectStatement query;
|
||||
|
||||
final List<String> columns;
|
||||
|
||||
CreateViewStatement(
|
||||
{this.ifNotExists = false,
|
||||
@required this.viewName,
|
||||
this.columns,
|
||||
@required this.query});
|
||||
|
||||
@override
|
||||
String get createdName => viewName;
|
||||
|
||||
@override
|
||||
R accept<A, R>(AstVisitor<A, R> visitor, A arg) {
|
||||
return visitor.visitCreateViewStatement(this, arg);
|
||||
}
|
||||
|
||||
@override
|
||||
Iterable<AstNode> get childNodes => [query];
|
||||
|
||||
@override
|
||||
bool contentEquals(CreateViewStatement other) {
|
||||
return other.ifNotExists == ifNotExists &&
|
||||
other.viewName == viewName &&
|
||||
const ListEquality().equals(other.columns,columns);
|
||||
}
|
||||
}
|
|
@ -13,6 +13,7 @@ abstract class AstVisitor<A, R> {
|
|||
R visitCreateVirtualTableStatement(CreateVirtualTableStatement e, A arg);
|
||||
R visitCreateTriggerStatement(CreateTriggerStatement e, A arg);
|
||||
R visitCreateIndexStatement(CreateIndexStatement e, A arg);
|
||||
R visitCreateViewStatement(CreateViewStatement e, A arg);
|
||||
|
||||
R visitWithClause(WithClause e, A arg);
|
||||
R visitUpsertClause(UpsertClause e, A arg);
|
||||
|
@ -122,6 +123,11 @@ class RecursiveVisitor<A, R> implements AstVisitor<A, R> {
|
|||
return visitTableInducingStatement(e, arg);
|
||||
}
|
||||
|
||||
@override
|
||||
R visitCreateViewStatement(CreateViewStatement e, A arg) {
|
||||
return visitStatement(e, arg);
|
||||
}
|
||||
|
||||
@override
|
||||
R visitCreateTriggerStatement(CreateTriggerStatement e, A arg) {
|
||||
return visitSchemaStatement(e, arg);
|
||||
|
|
|
@ -10,6 +10,8 @@ mixin SchemaParser on ParserBase {
|
|||
return _createTrigger();
|
||||
} else if (_check(TokenType.unique) || _check(TokenType.$index)) {
|
||||
return _createIndex();
|
||||
} else if (_check(TokenType.view)) {
|
||||
return _createView();
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -247,6 +249,37 @@ mixin SchemaParser on ParserBase {
|
|||
..triggerNameToken = trigger;
|
||||
}
|
||||
|
||||
/// Parses a [CreateViewStatement]. The `CREATE` token must have already been
|
||||
/// accepted.
|
||||
CreateViewStatement _createView() {
|
||||
final create = _previous;
|
||||
assert(create.type == TokenType.create);
|
||||
|
||||
if (!_matchOne(TokenType.view)) return null;
|
||||
|
||||
final ifNotExists = _ifNotExists();
|
||||
final name = _consumeIdentifier('Expected a name for this index');
|
||||
|
||||
List<String> columnNames = null;
|
||||
if (_matchOne(TokenType.leftParen)) {
|
||||
columnNames = _columnNames();
|
||||
_consume(TokenType.rightParen, 'Expected closing bracket');
|
||||
}
|
||||
|
||||
_consume(TokenType.as, 'Expected AS SELECT');
|
||||
|
||||
final query = select();
|
||||
|
||||
return CreateViewStatement(
|
||||
ifNotExists: ifNotExists,
|
||||
viewName: name.identifier,
|
||||
columns: columnNames,
|
||||
query: query,
|
||||
)
|
||||
..viewNameToken = name
|
||||
..setSpan(create, _previous);
|
||||
}
|
||||
|
||||
/// Parses a [CreateIndexStatement]. The `CREATE` token must have already been
|
||||
/// accepted.
|
||||
CreateIndexStatement _createIndex() {
|
||||
|
@ -288,6 +321,18 @@ mixin SchemaParser on ParserBase {
|
|||
..setSpan(create, _previous);
|
||||
}
|
||||
|
||||
@override
|
||||
List<String> _columnNames() {
|
||||
final columns = <String>[];
|
||||
do {
|
||||
final colName = _consumeIdentifier('Expected a name for this column');
|
||||
|
||||
columns.add(colName.identifier);
|
||||
} while (_matchOne(TokenType.comma));
|
||||
|
||||
return columns;
|
||||
}
|
||||
|
||||
@override
|
||||
List<IndexedColumn> _indexedColumns() {
|
||||
final indexes = <IndexedColumn>[];
|
||||
|
|
|
@ -141,6 +141,7 @@ enum TokenType {
|
|||
unique,
|
||||
update,
|
||||
using,
|
||||
view,
|
||||
virtual,
|
||||
when,
|
||||
where,
|
||||
|
@ -262,6 +263,7 @@ const Map<String, TokenType> keywords = {
|
|||
'UPDATE': TokenType.update,
|
||||
'USING': TokenType.using,
|
||||
'VALUES': TokenType.$values,
|
||||
'VIEW': TokenType.view,
|
||||
'VIRTUAL': TokenType.virtual,
|
||||
'WHEN': TokenType.when,
|
||||
'WHERE': TokenType.where,
|
||||
|
|
Loading…
Reference in New Issue