mirror of https://github.com/AMT-Cheif/drift.git
view analyzer class added
This commit is contained in:
parent
bf813cf67b
commit
c0fc691a94
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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('.');
|
||||
}
|
||||
|
||||
|
|
|
@ -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'),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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')),
|
||||
);
|
||||
|
|
|
@ -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-%')),
|
||||
),
|
||||
|
|
Loading…
Reference in New Issue