From 156ef1ceb5b080c44d5ea894bca484f7c1cd7f3c Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Thu, 12 Sep 2019 21:08:30 +0200 Subject: [PATCH] Support queries declared in .moor files --- .../lib/src/analyzer/runner/results.dart | 18 +++++--- .../lib/src/analyzer/runner/steps.dart | 17 +++++++ .../analyzer/runner/steps/analyze_dart.dart | 27 +++++++----- .../analyzer/runner/steps/analyze_moor.dart | 19 ++++++++ .../lib/src/analyzer/runner/task.dart | 44 ++++++++++++++++++- .../src/analyzer/sql_queries/sql_parser.dart | 2 +- .../lib/src/model/specified_db_classes.dart | 3 ++ 7 files changed, 108 insertions(+), 22 deletions(-) create mode 100644 moor_generator/lib/src/analyzer/runner/steps/analyze_moor.dart diff --git a/moor_generator/lib/src/analyzer/runner/results.dart b/moor_generator/lib/src/analyzer/runner/results.dart index c9a403ca..ba64ef07 100644 --- a/moor_generator/lib/src/analyzer/runner/results.dart +++ b/moor_generator/lib/src/analyzer/runner/results.dart @@ -6,12 +6,15 @@ import 'package:moor_generator/src/model/specified_table.dart'; import 'package:moor_generator/src/model/sql_query.dart'; import 'package:sqlparser/sqlparser.dart'; -abstract class FileResult {} +abstract class FileResult { + final List declaredTables; + + FileResult(this.declaredTables); +} class ParsedDartFile extends FileResult { final LibraryElement library; - final List declaredTables; final List declaredDaos; final List declaredDatabases; @@ -20,9 +23,10 @@ class ParsedDartFile extends FileResult { ParsedDartFile( {@required this.library, - this.declaredTables = const [], + List declaredTables = const [], this.declaredDaos = const [], - this.declaredDatabases = const []}); + this.declaredDatabases = const []}) + : super(declaredTables); } class ParsedMoorFile extends FileResult { @@ -30,14 +34,14 @@ class ParsedMoorFile extends FileResult { MoorFile get parsedFile => parseResult.rootNode as MoorFile; final List imports; - final List declaredTables; final List queries; List resolvedQueries; Map resolvedImports; ParsedMoorFile(this.parseResult, - {this.declaredTables = const [], + {List declaredTables = const [], this.queries = const [], - this.imports = const []}); + this.imports = const []}) + : super(declaredTables); } diff --git a/moor_generator/lib/src/analyzer/runner/steps.dart b/moor_generator/lib/src/analyzer/runner/steps.dart index 9ed58f15..9fc54fb9 100644 --- a/moor_generator/lib/src/analyzer/runner/steps.dart +++ b/moor_generator/lib/src/analyzer/runner/steps.dart @@ -17,6 +17,7 @@ import 'package:moor_generator/src/model/sql_query.dart'; import 'package:source_gen/source_gen.dart'; part 'steps/analyze_dart.dart'; +part 'steps/analyze_moor.dart'; part 'steps/parse_dart.dart'; part 'steps/parse_moor.dart'; @@ -35,3 +36,19 @@ abstract class Step { void reportError(MoorError error) => errors.report(error..wasDuringParsing = isParsing); } + +abstract class AnalyzingStep extends Step { + AnalyzingStep(Task task, FoundFile file) : super(task, file); + + @override + final bool isParsing = false; + + List _transitiveImports(Iterable directImports) { + return task.crawlImports(directImports).toList(); + } + + Iterable _availableTables(List imports) { + return imports.expand( + (file) => file.currentResult?.declaredTables ?? const Iterable.empty()); + } +} diff --git a/moor_generator/lib/src/analyzer/runner/steps/analyze_dart.dart b/moor_generator/lib/src/analyzer/runner/steps/analyze_dart.dart index 0d9b20da..8ee8fd46 100644 --- a/moor_generator/lib/src/analyzer/runner/steps/analyze_dart.dart +++ b/moor_generator/lib/src/analyzer/runner/steps/analyze_dart.dart @@ -1,28 +1,31 @@ part of '../steps.dart'; /// Analyzes the compiled queries found in a Dart file. -class AnalyzeDartStep extends Step { +class AnalyzeDartStep extends AnalyzingStep { AnalyzeDartStep(Task task, FoundFile file) : super(task, file); - @override - final bool isParsing = false; - void analyze() { final parseResult = file.currentResult as ParsedDartFile; for (var accessor in parseResult.dbAccessors) { - final transitivelyAvailable = accessor.resolvedImports - .where((file) => file.type == FileType.moor) - .map((file) => file.currentResult as ParsedMoorFile) - .expand((file) => file.declaredTables); - final availableTables = - accessor.tables.followedBy(transitivelyAvailable).toList(); - accessor.allTables = availableTables; + final transitiveImports = _transitiveImports(accessor.resolvedImports); + + final availableTables = _availableTables(transitiveImports) + .followedBy(accessor.tables) + .toList(); + + final availableQueries = transitiveImports + .map((f) => f.currentResult) + .whereType() + .expand((f) => f.resolvedQueries); final parser = SqlParser(this, availableTables, accessor.queries); parser.parse(); - accessor.resolvedQueries = parser.foundQueries; + accessor.allTables = availableTables; + + accessor.resolvedQueries = + availableQueries.followedBy(parser.foundQueries).toList(); } } } diff --git a/moor_generator/lib/src/analyzer/runner/steps/analyze_moor.dart b/moor_generator/lib/src/analyzer/runner/steps/analyze_moor.dart new file mode 100644 index 00000000..8ec99458 --- /dev/null +++ b/moor_generator/lib/src/analyzer/runner/steps/analyze_moor.dart @@ -0,0 +1,19 @@ +part of '../steps.dart'; + +class AnalyzeMoorStep extends AnalyzingStep { + AnalyzeMoorStep(Task task, FoundFile file) : super(task, file); + + void analyze() { + final parseResult = file.currentResult as ParsedMoorFile; + + final transitiveImports = + task.crawlImports(parseResult.resolvedImports.values).toList(); + + final availableTables = _availableTables(transitiveImports) + .followedBy(parseResult.declaredTables) + .toList(); + + final parser = SqlParser(this, availableTables, parseResult.queries); + parseResult.resolvedQueries = parser.foundQueries; + } +} diff --git a/moor_generator/lib/src/analyzer/runner/task.dart b/moor_generator/lib/src/analyzer/runner/task.dart index 3748e83a..c1e0f222 100644 --- a/moor_generator/lib/src/analyzer/runner/task.dart +++ b/moor_generator/lib/src/analyzer/runner/task.dart @@ -1,5 +1,6 @@ import 'package:moor_generator/src/analyzer/errors.dart'; import 'package:moor_generator/src/analyzer/runner/file_graph.dart'; +import 'package:moor_generator/src/analyzer/runner/results.dart'; import 'package:moor_generator/src/analyzer/runner/steps.dart'; import 'package:moor_generator/src/analyzer/session.dart'; import 'package:moor_generator/src/backends/backend.dart'; @@ -47,8 +48,14 @@ class Task { _analyzedFiles.add(file); } - // step 2: resolve queries in the input - for (var file in _analyzedFiles) { + // step 2: resolve queries in the input. + // todo we force that moor files are analyzed first because they contain + // resolved queries which are copied into database accessors. Can we find + // a way to remove this special-handling? + final moorFiles = _analyzedFiles.where((f) => f.type == FileType.moor); + final otherFiles = _analyzedFiles.where((f) => f.type != FileType.moor); + + for (var file in moorFiles.followedBy(otherFiles)) { file.errors.clearNonParsingErrors(); await _analyze(file); } @@ -130,6 +137,36 @@ class Task { return createdStep; } + /// Crawls through all (transitive) imports of the provided [roots]. Each + /// [FoundFile] in the iterable provides queries and tables that are available + /// to the entity that imports them. + /// + /// This is different to [FileGraph.crawl] because imports are not accurate on + /// Dart files: Two accessors in a single Dart file could reference different + /// imports, but the [FileGraph] would only know about the union. + Iterable crawlImports(Iterable roots) sync* { + final found = {}; + final unhandled = roots.toList(); + + while (unhandled.isNotEmpty) { + final available = unhandled.removeLast(); + found.add(available); + yield available; + + var importsFromHere = const Iterable.empty(); + if (available.type == FileType.moor) { + importsFromHere = + (available.currentResult as ParsedMoorFile).resolvedImports.values; + } + + for (var next in importsFromHere) { + if (!found.contains(next) && !unhandled.contains(next)) { + unhandled.add(next); + } + } + } + } + Future _analyze(FoundFile file) async { // skip if already analyzed. if (file.state == FileState.analyzed) return; @@ -140,6 +177,9 @@ class Task { case FileType.dart: step = AnalyzeDartStep(this, file)..analyze(); break; + case FileType.moor: + step = AnalyzeMoorStep(this, file)..analyze(); + break; default: break; } diff --git a/moor_generator/lib/src/analyzer/sql_queries/sql_parser.dart b/moor_generator/lib/src/analyzer/sql_queries/sql_parser.dart index 213ee26c..6bd97383 100644 --- a/moor_generator/lib/src/analyzer/sql_queries/sql_parser.dart +++ b/moor_generator/lib/src/analyzer/sql_queries/sql_parser.dart @@ -9,7 +9,7 @@ import 'package:sqlparser/sqlparser.dart' hide ResultColumn; class SqlParser { final List tables; - final AnalyzeDartStep step; + final Step step; final List definedQueries; final TypeMapper _mapper = TypeMapper(); diff --git a/moor_generator/lib/src/model/specified_db_classes.dart b/moor_generator/lib/src/model/specified_db_classes.dart index a581c9bf..d07c1d70 100644 --- a/moor_generator/lib/src/model/specified_db_classes.dart +++ b/moor_generator/lib/src/model/specified_db_classes.dart @@ -12,6 +12,9 @@ class SpecifiedDbAccessor { final List queries; List resolvedImports = []; + + /// Resolved queries. This includes queries that weren't declared on this + /// class but imported via an `includes` directive. List resolvedQueries = const []; /// All tables available to this class. This includes the [tables] and all