Parser is ready, work on analyzer was started.

This commit is contained in:
Markus Richter 2020-05-13 14:29:11 +02:00
parent 25ee06ab36
commit d86d7ab7e3
No known key found for this signature in database
GPG Key ID: D2EAD1C38ED66957
8 changed files with 263 additions and 0 deletions

View File

@ -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';

View File

@ -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 {

View File

@ -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)';
// }
//}

View File

@ -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';

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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>[];

View File

@ -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,