view analyzer class added

This commit is contained in:
Hossein Yousefi 2021-02-11 18:38:41 +01:00
parent bf813cf67b
commit c0fc691a94
17 changed files with 116 additions and 49 deletions

View File

@ -13,6 +13,7 @@ import 'package:moor_generator/src/analyzer/sql_queries/custom_result_class.dart
import 'package:moor_generator/src/analyzer/sql_queries/query_analyzer.dart';
import 'package:moor_generator/src/analyzer/sql_queries/type_mapping.dart';
import 'package:moor_generator/src/analyzer/runner/task.dart';
import 'package:moor_generator/src/analyzer/view/view_analyzer.dart';
import 'package:moor_generator/src/model/sql_query.dart';
import 'package:moor_generator/src/model/view.dart';
import 'package:moor_generator/src/utils/entity_reference_sorter.dart';

View File

@ -22,12 +22,14 @@ class AnalyzeMoorStep extends AnalyzingStep {
.followedBy(parseResult.declaredViews)
.toList();
EntityHandler(this, parseResult, availableTables, availableViews).handle();
ViewAnalyzer(this, availableTables, availableViews).resolve();
final parser =
SqlAnalyzer(this, availableTables, availableViews, parseResult.queries)
..parse();
EntityHandler(this, parseResult, availableTables, availableViews).handle();
parseResult.resolvedQueries = parser.foundQueries;
}
}

View File

@ -30,23 +30,11 @@ abstract class BaseAnalyzer {
if (_engine == null) {
_engine = step.task.session.spawnEngine();
tables.map(mapper.extractStructure).forEach(_engine.registerTable);
resolveViews();
views.map(mapper.extractView).forEach(_engine.registerView);
}
return _engine;
}
/// Parses the view and adds columns to its resolved columns.
@protected
void resolveViews() {
for (final view in views) {
final ctx = _engine.analyzeNode(
view.declaration.node, view.declaration.createSql);
view.parserView = const SchemaFromCreateTable(moorExtensions: true)
.readView(ctx, view.declaration.creatingStatement);
}
}
@protected
void report(AnalysisError error, {String Function() msg, Severity severity}) {
if (step.file.type == FileType.moor) {

View File

@ -97,7 +97,10 @@ class TypeMapper {
_engineViewsToSpecified[parserView] = view;
return parserView;
}
throw StateError('Views are currently only supported in moor files.');
final engineView = View(name: view.name, resolvedColumns: []);
engineView.setMeta<MoorView>(view);
_engineViewsToSpecified[engineView] = view;
return engineView;
}
/// Extracts variables and Dart templates from the [ctx]. Variables are

View File

@ -0,0 +1,52 @@
import 'package:moor_generator/src/analyzer/runner/steps.dart';
import 'package:moor_generator/src/analyzer/sql_queries/query_analyzer.dart';
import 'package:moor_generator/src/model/table.dart';
import 'package:moor_generator/src/model/view.dart';
import 'package:sqlparser/sqlparser.dart';
class ViewAnalyzer extends BaseAnalyzer {
ViewAnalyzer(Step step, List<MoorTable> tables, List<MoorView> views)
: super(tables, views, step);
List<MoorView> _viewsOrder;
Set<MoorView> _resolvedViews;
/// Resolves all the views in topological order.
void resolve() {
_viewsOrder = [];
_resolvedViews = {};
// Topologically sorting all the views.
for (final view in views) {
if (!_resolvedViews.contains(view)) {
_topologicalSort(view);
}
}
// Going through the topologically sorted list and analyzing each view.
for (final view in _viewsOrder) {
// Registering every table dependency.
for (final referencedEntity in view.references) {
if (referencedEntity is MoorTable) {
engine.registerTable(mapper.extractStructure(referencedEntity));
}
}
final ctx =
engine.analyzeNode(view.declaration.node, view.file.parseResult.sql);
view.parserView = const SchemaFromCreateTable(moorExtensions: true)
.readView(ctx, view.declaration.creatingStatement);
engine.registerView(mapper.extractView(view));
}
}
void _topologicalSort(MoorView view) {
_resolvedViews.add(view);
for (final referencedEntity in view.references) {
if (referencedEntity is MoorView) {
if (!_resolvedViews.contains(referencedEntity)) {
_topologicalSort(referencedEntity);
}
}
}
_viewsOrder.add(view);
}
}

View File

@ -35,27 +35,32 @@ void main() {
'foo|lib/table.moor': '''
CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, name TEXT NOT NULL);
''',
'foo|lib/view.moor': '''
CREATE VIEW v AS
SELECT id, name FROM t WHERE id % 2 = 0;
''',
'foo|lib/a.moor': '''
import 'view.moor';
CREATE VIEW random_view AS
SELECT name FROM v;
import 'table.moor';
CREATE VIEW parent_view AS
SELECT id, name FROM t WHERE id % 2 = 0;
CREATE VIEW child_view AS
SELECT name FROM parent_view;
''',
});
final file = await state.analyze('package:foo/a.moor');
final view = file.currentResult.declaredViews.single;
expect(view.parserView.resolvedColumns.length, equals(1));
final column = view.parserView.resolvedColumns.single;
final parentView = file.currentResult.declaredViews
.singleWhere((element) => element.name == 'parent_view');
final childView = file.currentResult.declaredViews
.singleWhere((element) => element.name == 'child_view');
expect(parentView.parserView.resolvedColumns.length, equals(2));
expect(childView.parserView.resolvedColumns.length, equals(1));
final column = childView.parserView.resolvedColumns.single;
state.close();
expect(column.type.type, BasicType.text);
// Currently failing:
// is it a problem with sqlparser?
expect(file.errors.errors, isEmpty);
expect(column.type.type, BasicType.text);
});
test('view without table', () async {

View File

@ -79,7 +79,7 @@ class LintingVisitor extends RecursiveVisitor<void, void> {
// Primary key clauses may only include simple columns
for (final column in e.columns) {
final expr = column.expression;
if (expr is! Reference || expr.tableName != null) {
if (expr is! Reference || expr.entityName != null) {
context.reportError(AnalysisError(
type: AnalysisErrorType.synctactic,
message: 'Only column names can be used in a PRIMARY KEY clause',

View File

@ -32,6 +32,19 @@ class AstPreparingVisitor extends RecursiveVisitor<void, void> {
}
}
@override
void visitCreateViewStatement(CreateViewStatement e, void arg) {
final scope = e.scope = e.scope.createChild();
final registeredView = scope.resolve(e.viewName) as View?;
if (registeredView != null) {
scope.availableColumns = registeredView.resolvedColumns;
for (final column in registeredView.resolvedColumns) {
print(column.name);
scope.register(column.name, column);
}
}
}
@override
void visitSelectStatement(SelectStatement e, void arg) {
// a select statement can appear as a sub query which has its own scope, so

View File

@ -36,15 +36,16 @@ class ReferenceResolver extends RecursiveVisitor<void, void> {
final scope = e.scope;
if (e.tableName != null) {
// first find the referenced table, then use the column on that table.
final tableResolver = scope.resolve<ResolvesToResultSet>(e.tableName!);
final resultSet = tableResolver?.resultSet;
if (e.entityName != null) {
// first find the referenced table or view,
// then use the column on that table or view.
final entityResolver = scope.resolve<ResolvesToResultSet>(e.entityName!);
final resultSet = entityResolver?.resultSet;
if (resultSet == null) {
context.reportError(AnalysisError(
type: AnalysisErrorType.referencedUnknownTable,
message: 'Unknown table: ${e.tableName}',
message: 'Unknown table or view: ${e.entityName}',
relevantNode: e,
));
} else {

View File

@ -8,12 +8,13 @@ part of '../ast.dart';
/// 2 * c AS d FROM table", the "c" after the "2 *" is a reference that refers
/// to the expression "COUNT(*)".
class Reference extends Expression with ReferenceOwner {
final String? tableName;
/// Entity can be either a table or a view.
final String? entityName;
final String columnName;
Column? get resolvedColumn => resolved as Column?;
Reference({this.tableName, required this.columnName});
Reference({this.entityName, required this.columnName});
@override
R accept<A, R>(AstVisitor<A, R> visitor, A arg) {
@ -28,8 +29,8 @@ class Reference extends Expression with ReferenceOwner {
@override
String toString() {
if (tableName != null) {
return 'Reference to the column $tableName.$columnName';
if (entityName != null) {
return 'Reference to the column $entityName.$columnName';
} else {
return 'Reference to the column $columnName';
}

View File

@ -721,7 +721,7 @@ class Parser {
final second =
_consumeIdentifier('Expected a column name here', lenient: true);
return Reference(
tableName: first.identifier, columnName: second.identifier)
entityName: first.identifier, columnName: second.identifier)
..setSpan(first, second);
} else if (_matchOne(TokenType.leftParen)) {
// regular function invocation

View File

@ -514,7 +514,8 @@ class EqualityEnforcingVisitor implements AstVisitor<void, void> {
void visitReference(Reference e, void arg) {
final current = _currentAs<Reference>(e);
_assert(
current.tableName == e.tableName && current.columnName == e.columnName,
current.entityName == e.entityName &&
current.columnName == e.columnName,
e);
_checkChildren(e);
}

View File

@ -909,9 +909,9 @@ class NodeSqlBuilder extends AstVisitor<void, void> {
@override
void visitReference(Reference e, void arg) {
var hasTable = false;
if (e.tableName != null) {
if (e.entityName != null) {
hasTable = true;
_identifier(e.tableName!, spaceAfter: false);
_identifier(e.entityName!, spaceAfter: false);
_symbol('.');
}

View File

@ -88,7 +88,7 @@ void main() {
BinaryExpression(
NumericLiteral(3, token(TokenType.numberLiteral)),
token(TokenType.star),
Reference(tableName: 'd', columnName: 'id'),
Reference(entityName: 'd', columnName: 'id'),
),
);
});

View File

@ -86,7 +86,7 @@ void main() {
onTable: TableReference('tbl'),
when: IsExpression(
false,
Reference(tableName: 'new', columnName: 'foo'),
Reference(entityName: 'new', columnName: 'foo'),
NullLiteral(token(TokenType.$null)),
),
action: _block,

View File

@ -18,20 +18,20 @@ void main() {
),
then: IsExpression(
true,
Reference(tableName: 'n', columnName: 'nextReviewTime'),
Reference(entityName: 'n', columnName: 'nextReviewTime'),
NullLiteral(token(TokenType.$null)),
),
),
],
elseExpr: IsExpression(
false,
Reference(tableName: 'n', columnName: 'nextReviewTime'),
Reference(entityName: 'n', columnName: 'nextReviewTime'),
NullLiteral(token(TokenType.$null)),
),
);
final folderExpr = BinaryExpression(
Reference(tableName: 'n', columnName: 'folderId'),
Reference(entityName: 'n', columnName: 'folderId'),
token(TokenType.equal),
ColonNamedVariable(_colon(':selectedFolderId')),
);

View File

@ -162,7 +162,7 @@ WHERE json_each.value LIKE '704-%';
distinct: true,
columns: [
ExpressionResultColumn(
expression: Reference(tableName: 'user', columnName: 'name'),
expression: Reference(entityName: 'user', columnName: 'name'),
),
],
from: JoinClause(
@ -173,14 +173,14 @@ WHERE json_each.value LIKE '704-%';
query: TableValuedFunction(
'json_each',
ExprFunctionParameters(parameters: [
Reference(tableName: 'user', columnName: 'phone')
Reference(entityName: 'user', columnName: 'phone')
]),
),
),
],
),
where: StringComparisonExpression(
left: Reference(tableName: 'json_each', columnName: 'value'),
left: Reference(entityName: 'json_each', columnName: 'value'),
operator: token(TokenType.like),
right: StringLiteral(stringLiteral('704-%')),
),