Support queries declared in .moor files

This commit is contained in:
Simon Binder 2019-09-12 21:08:30 +02:00
parent 6a0716daaf
commit 156ef1ceb5
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
7 changed files with 108 additions and 22 deletions

View File

@ -6,12 +6,15 @@ import 'package:moor_generator/src/model/specified_table.dart';
import 'package:moor_generator/src/model/sql_query.dart'; import 'package:moor_generator/src/model/sql_query.dart';
import 'package:sqlparser/sqlparser.dart'; import 'package:sqlparser/sqlparser.dart';
abstract class FileResult {} abstract class FileResult {
final List<SpecifiedTable> declaredTables;
FileResult(this.declaredTables);
}
class ParsedDartFile extends FileResult { class ParsedDartFile extends FileResult {
final LibraryElement library; final LibraryElement library;
final List<SpecifiedTable> declaredTables;
final List<SpecifiedDao> declaredDaos; final List<SpecifiedDao> declaredDaos;
final List<SpecifiedDatabase> declaredDatabases; final List<SpecifiedDatabase> declaredDatabases;
@ -20,9 +23,10 @@ class ParsedDartFile extends FileResult {
ParsedDartFile( ParsedDartFile(
{@required this.library, {@required this.library,
this.declaredTables = const [], List<SpecifiedTable> declaredTables = const [],
this.declaredDaos = const [], this.declaredDaos = const [],
this.declaredDatabases = const []}); this.declaredDatabases = const []})
: super(declaredTables);
} }
class ParsedMoorFile extends FileResult { class ParsedMoorFile extends FileResult {
@ -30,14 +34,14 @@ class ParsedMoorFile extends FileResult {
MoorFile get parsedFile => parseResult.rootNode as MoorFile; MoorFile get parsedFile => parseResult.rootNode as MoorFile;
final List<ImportStatement> imports; final List<ImportStatement> imports;
final List<SpecifiedTable> declaredTables;
final List<DeclaredQuery> queries; final List<DeclaredQuery> queries;
List<SqlQuery> resolvedQueries; List<SqlQuery> resolvedQueries;
Map<ImportStatement, FoundFile> resolvedImports; Map<ImportStatement, FoundFile> resolvedImports;
ParsedMoorFile(this.parseResult, ParsedMoorFile(this.parseResult,
{this.declaredTables = const [], {List<SpecifiedTable> declaredTables = const [],
this.queries = const [], this.queries = const [],
this.imports = const []}); this.imports = const []})
: super(declaredTables);
} }

View File

@ -17,6 +17,7 @@ import 'package:moor_generator/src/model/sql_query.dart';
import 'package:source_gen/source_gen.dart'; import 'package:source_gen/source_gen.dart';
part 'steps/analyze_dart.dart'; part 'steps/analyze_dart.dart';
part 'steps/analyze_moor.dart';
part 'steps/parse_dart.dart'; part 'steps/parse_dart.dart';
part 'steps/parse_moor.dart'; part 'steps/parse_moor.dart';
@ -35,3 +36,19 @@ abstract class Step {
void reportError(MoorError error) => void reportError(MoorError error) =>
errors.report(error..wasDuringParsing = isParsing); errors.report(error..wasDuringParsing = isParsing);
} }
abstract class AnalyzingStep extends Step {
AnalyzingStep(Task task, FoundFile file) : super(task, file);
@override
final bool isParsing = false;
List<FoundFile> _transitiveImports(Iterable<FoundFile> directImports) {
return task.crawlImports(directImports).toList();
}
Iterable<SpecifiedTable> _availableTables(List<FoundFile> imports) {
return imports.expand<SpecifiedTable>(
(file) => file.currentResult?.declaredTables ?? const Iterable.empty());
}
}

View File

@ -1,28 +1,31 @@
part of '../steps.dart'; part of '../steps.dart';
/// Analyzes the compiled queries found in a Dart file. /// 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); AnalyzeDartStep(Task task, FoundFile file) : super(task, file);
@override
final bool isParsing = false;
void analyze() { void analyze() {
final parseResult = file.currentResult as ParsedDartFile; final parseResult = file.currentResult as ParsedDartFile;
for (var accessor in parseResult.dbAccessors) { for (var accessor in parseResult.dbAccessors) {
final transitivelyAvailable = accessor.resolvedImports final transitiveImports = _transitiveImports(accessor.resolvedImports);
.where((file) => file.type == FileType.moor)
.map((file) => file.currentResult as ParsedMoorFile) final availableTables = _availableTables(transitiveImports)
.expand((file) => file.declaredTables); .followedBy(accessor.tables)
final availableTables = .toList();
accessor.tables.followedBy(transitivelyAvailable).toList();
accessor.allTables = availableTables; final availableQueries = transitiveImports
.map((f) => f.currentResult)
.whereType<ParsedMoorFile>()
.expand((f) => f.resolvedQueries);
final parser = SqlParser(this, availableTables, accessor.queries); final parser = SqlParser(this, availableTables, accessor.queries);
parser.parse(); parser.parse();
accessor.resolvedQueries = parser.foundQueries; accessor.allTables = availableTables;
accessor.resolvedQueries =
availableQueries.followedBy(parser.foundQueries).toList();
} }
} }
} }

View File

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

View File

@ -1,5 +1,6 @@
import 'package:moor_generator/src/analyzer/errors.dart'; 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/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/runner/steps.dart';
import 'package:moor_generator/src/analyzer/session.dart'; import 'package:moor_generator/src/analyzer/session.dart';
import 'package:moor_generator/src/backends/backend.dart'; import 'package:moor_generator/src/backends/backend.dart';
@ -47,8 +48,14 @@ class Task {
_analyzedFiles.add(file); _analyzedFiles.add(file);
} }
// step 2: resolve queries in the input // step 2: resolve queries in the input.
for (var file in _analyzedFiles) { // 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(); file.errors.clearNonParsingErrors();
await _analyze(file); await _analyze(file);
} }
@ -130,6 +137,36 @@ class Task {
return createdStep; 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<FoundFile> crawlImports(Iterable<FoundFile> roots) sync* {
final found = <FoundFile>{};
final unhandled = roots.toList();
while (unhandled.isNotEmpty) {
final available = unhandled.removeLast();
found.add(available);
yield available;
var importsFromHere = const Iterable<FoundFile>.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<void> _analyze(FoundFile file) async { Future<void> _analyze(FoundFile file) async {
// skip if already analyzed. // skip if already analyzed.
if (file.state == FileState.analyzed) return; if (file.state == FileState.analyzed) return;
@ -140,6 +177,9 @@ class Task {
case FileType.dart: case FileType.dart:
step = AnalyzeDartStep(this, file)..analyze(); step = AnalyzeDartStep(this, file)..analyze();
break; break;
case FileType.moor:
step = AnalyzeMoorStep(this, file)..analyze();
break;
default: default:
break; break;
} }

View File

@ -9,7 +9,7 @@ import 'package:sqlparser/sqlparser.dart' hide ResultColumn;
class SqlParser { class SqlParser {
final List<SpecifiedTable> tables; final List<SpecifiedTable> tables;
final AnalyzeDartStep step; final Step step;
final List<DeclaredQuery> definedQueries; final List<DeclaredQuery> definedQueries;
final TypeMapper _mapper = TypeMapper(); final TypeMapper _mapper = TypeMapper();

View File

@ -12,6 +12,9 @@ class SpecifiedDbAccessor {
final List<DeclaredQuery> queries; final List<DeclaredQuery> queries;
List<FoundFile> resolvedImports = []; List<FoundFile> resolvedImports = [];
/// Resolved queries. This includes queries that weren't declared on this
/// class but imported via an `includes` directive.
List<SqlQuery> resolvedQueries = const []; List<SqlQuery> resolvedQueries = const [];
/// All tables available to this class. This includes the [tables] and all /// All tables available to this class. This includes the [tables] and all