mirror of https://github.com/AMT-Cheif/drift.git
Start migration of writer code
This commit is contained in:
parent
6e89a319ad
commit
ad8bdba4b8
|
@ -7,14 +7,19 @@ import 'package:moor/sqlite_keywords.dart';
|
||||||
import 'package:moor_generator/src/analyzer/errors.dart';
|
import 'package:moor_generator/src/analyzer/errors.dart';
|
||||||
import 'package:moor_generator/src/analyzer/session.dart';
|
import 'package:moor_generator/src/analyzer/session.dart';
|
||||||
import 'package:moor_generator/src/model/specified_column.dart';
|
import 'package:moor_generator/src/model/specified_column.dart';
|
||||||
|
import 'package:moor_generator/src/model/specified_dao.dart';
|
||||||
|
import 'package:moor_generator/src/model/specified_database.dart';
|
||||||
import 'package:moor_generator/src/model/specified_table.dart';
|
import 'package:moor_generator/src/model/specified_table.dart';
|
||||||
import 'package:moor_generator/src/model/used_type_converter.dart';
|
import 'package:moor_generator/src/model/used_type_converter.dart';
|
||||||
import 'package:moor_generator/src/utils/names.dart';
|
import 'package:moor_generator/src/utils/names.dart';
|
||||||
import 'package:moor_generator/src/utils/type_utils.dart';
|
import 'package:moor_generator/src/utils/type_utils.dart';
|
||||||
import 'package:recase/recase.dart';
|
import 'package:recase/recase.dart';
|
||||||
|
import 'package:source_gen/source_gen.dart';
|
||||||
|
|
||||||
part 'column_parser.dart';
|
part 'column_parser.dart';
|
||||||
part 'table_parser.dart';
|
part 'table_parser.dart';
|
||||||
|
part 'use_dao_parser.dart';
|
||||||
|
part 'use_moor_parser.dart';
|
||||||
|
|
||||||
class MoorDartParser {
|
class MoorDartParser {
|
||||||
final DartTask task;
|
final DartTask task;
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
import 'package:analyzer/dart/element/element.dart';
|
part of 'parser.dart';
|
||||||
import 'package:moor_generator/src/model/specified_dao.dart';
|
|
||||||
import 'package:moor_generator/src/state/session.dart';
|
|
||||||
import 'package:source_gen/source_gen.dart';
|
|
||||||
|
|
||||||
class UseDaoParser {
|
class UseDaoParser {
|
||||||
final GeneratorSession session;
|
final DartTask dartTask;
|
||||||
|
|
||||||
UseDaoParser(this.session);
|
UseDaoParser(this.dartTask);
|
||||||
|
|
||||||
/// If [element] has a `@UseDao` annotation, parses the database model
|
/// If [element] has a `@UseDao` annotation, parses the database model
|
||||||
/// declared by that class and the referenced tables.
|
/// declared by that class and the referenced tables.
|
||||||
|
@ -24,11 +21,11 @@ class UseDaoParser {
|
||||||
?.map((e) => e.toStringValue()) ??
|
?.map((e) => e.toStringValue()) ??
|
||||||
{};
|
{};
|
||||||
|
|
||||||
final parsedTables = await session.parseTables(tableTypes, element);
|
final parsedTables = await dartTask.parseTables(tableTypes, element);
|
||||||
parsedTables.addAll(await session.resolveIncludes(includes));
|
parsedTables.addAll(await dartTask.resolveIncludes(includes));
|
||||||
|
|
||||||
final parsedQueries =
|
final parsedQueries =
|
||||||
await session.parseQueries(queryStrings, parsedTables);
|
await dartTask.parseQueries(queryStrings, parsedTables);
|
||||||
|
|
||||||
return SpecifiedDao(element, parsedTables, parsedQueries);
|
return SpecifiedDao(element, parsedTables, parsedQueries);
|
||||||
}
|
}
|
|
@ -1,13 +1,9 @@
|
||||||
import 'package:analyzer/dart/element/element.dart';
|
part of 'parser.dart';
|
||||||
import 'package:analyzer/dart/element/type.dart';
|
|
||||||
import 'package:moor_generator/src/model/specified_database.dart';
|
|
||||||
import 'package:moor_generator/src/state/session.dart';
|
|
||||||
import 'package:source_gen/source_gen.dart';
|
|
||||||
|
|
||||||
class UseMoorParser {
|
class UseMoorParser {
|
||||||
final GeneratorSession session;
|
final DartTask task;
|
||||||
|
|
||||||
UseMoorParser(this.session);
|
UseMoorParser(this.task);
|
||||||
|
|
||||||
/// If [element] has a `@UseMoor` annotation, parses the database model
|
/// If [element] has a `@UseMoor` annotation, parses the database model
|
||||||
/// declared by that class and the referenced tables.
|
/// declared by that class and the referenced tables.
|
||||||
|
@ -25,11 +21,10 @@ class UseMoorParser {
|
||||||
?.map((e) => e.toStringValue()) ??
|
?.map((e) => e.toStringValue()) ??
|
||||||
{};
|
{};
|
||||||
|
|
||||||
final parsedTables = await session.parseTables(tableTypes, element);
|
final parsedTables = await task.parseTables(tableTypes, element);
|
||||||
parsedTables.addAll(await session.resolveIncludes(includes));
|
parsedTables.addAll(await task.resolveIncludes(includes));
|
||||||
|
|
||||||
final parsedQueries =
|
final parsedQueries = await task.parseQueries(queryStrings, parsedTables);
|
||||||
await session.parseQueries(queryStrings, parsedTables);
|
|
||||||
final daoTypes = _readDaoTypes(annotation);
|
final daoTypes = _readDaoTypes(annotation);
|
||||||
|
|
||||||
return SpecifiedDatabase(element, parsedTables, daoTypes, parsedQueries);
|
return SpecifiedDatabase(element, parsedTables, daoTypes, parsedQueries);
|
|
@ -1,22 +1,34 @@
|
||||||
import 'package:analyzer/dart/element/element.dart';
|
import 'package:analyzer/dart/element/element.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:source_span/source_span.dart';
|
||||||
|
|
||||||
/// Base class for errors that can be presented to an user.
|
/// Base class for errors that can be presented to an user.
|
||||||
class MoorError {
|
class MoorError {
|
||||||
final Severity severity;
|
final Severity severity;
|
||||||
|
final String message;
|
||||||
|
|
||||||
MoorError(this.severity);
|
MoorError({@required this.severity, this.message});
|
||||||
}
|
}
|
||||||
|
|
||||||
class ErrorInDartCode extends MoorError {
|
class ErrorInDartCode extends MoorError {
|
||||||
final String message;
|
|
||||||
final Element affectedElement;
|
final Element affectedElement;
|
||||||
|
|
||||||
ErrorInDartCode(
|
ErrorInDartCode(
|
||||||
{this.message,
|
{String message,
|
||||||
this.affectedElement,
|
this.affectedElement,
|
||||||
Severity severity = Severity.warning})
|
Severity severity = Severity.warning})
|
||||||
: super(severity);
|
: super(severity: severity, message: message);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ErrorInMoorFile extends MoorError {
|
||||||
|
final FileSpan span;
|
||||||
|
|
||||||
|
ErrorInMoorFile(
|
||||||
|
{@required this.span,
|
||||||
|
String message,
|
||||||
|
Severity severity = Severity.warning})
|
||||||
|
: super(message: message, severity: severity);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ErrorSink {
|
class ErrorSink {
|
||||||
|
|
|
@ -1,40 +1,17 @@
|
||||||
|
import 'package:moor_generator/src/analyzer/sql_queries/type_mapping.dart';
|
||||||
import 'package:moor_generator/src/model/specified_column.dart';
|
import 'package:moor_generator/src/model/specified_column.dart';
|
||||||
import 'package:moor_generator/src/model/specified_table.dart';
|
import 'package:moor_generator/src/model/specified_table.dart';
|
||||||
import 'package:moor_generator/src/model/used_type_converter.dart';
|
import 'package:moor_generator/src/model/used_type_converter.dart';
|
||||||
import 'package:moor_generator/src/parser/sql/type_mapping.dart';
|
|
||||||
import 'package:moor_generator/src/utils/names.dart';
|
import 'package:moor_generator/src/utils/names.dart';
|
||||||
import 'package:moor_generator/src/utils/string_escaper.dart';
|
import 'package:moor_generator/src/utils/string_escaper.dart';
|
||||||
import 'package:recase/recase.dart';
|
import 'package:recase/recase.dart';
|
||||||
import 'package:sqlparser/sqlparser.dart';
|
import 'package:sqlparser/sqlparser.dart';
|
||||||
|
|
||||||
/*
|
class CreateTableReader {
|
||||||
We're in the process of defining what a .moor file could actually look like.
|
|
||||||
At the moment, we only support "CREATE TABLE" statements:
|
|
||||||
``` // content of a .moor file
|
|
||||||
CREATE TABLE users (
|
|
||||||
id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
|
||||||
name VARCHAR(100) NOT NULL,
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
In the future, we'd also like to support
|
|
||||||
- import statements between moor files
|
|
||||||
- import statements from moor files referencing tables declared via the Dart DSL
|
|
||||||
- declaring statements in these files, similar to how compiled statements work
|
|
||||||
with the annotation.
|
|
||||||
*/
|
|
||||||
|
|
||||||
class ParsedMoorFile {
|
|
||||||
final List<CreateTable> declaredTables;
|
|
||||||
|
|
||||||
ParsedMoorFile(this.declaredTables);
|
|
||||||
}
|
|
||||||
|
|
||||||
class CreateTable {
|
|
||||||
/// The AST of this `CREATE TABLE` statement.
|
/// The AST of this `CREATE TABLE` statement.
|
||||||
final ParseResult ast;
|
final ParseResult ast;
|
||||||
|
|
||||||
CreateTable(this.ast);
|
CreateTableReader(this.ast);
|
||||||
|
|
||||||
SpecifiedTable extractTable(TypeMapper mapper) {
|
SpecifiedTable extractTable(TypeMapper mapper) {
|
||||||
final table =
|
final table =
|
|
@ -0,0 +1,45 @@
|
||||||
|
import 'package:moor_generator/src/analyzer/errors.dart';
|
||||||
|
import 'package:moor_generator/src/analyzer/moor/create_table_reader.dart';
|
||||||
|
import 'package:moor_generator/src/analyzer/results.dart';
|
||||||
|
import 'package:moor_generator/src/analyzer/session.dart';
|
||||||
|
import 'package:sqlparser/sqlparser.dart';
|
||||||
|
|
||||||
|
class MoorParser {
|
||||||
|
final MoorTask task;
|
||||||
|
|
||||||
|
MoorParser(this.task);
|
||||||
|
|
||||||
|
Future<ParsedMoorFile> parseAndAnalyze() {
|
||||||
|
final results =
|
||||||
|
SqlEngine(useMoorExtensions: true).parseMultiple(task.content);
|
||||||
|
|
||||||
|
final createdReaders = <CreateTableReader>[];
|
||||||
|
|
||||||
|
for (var parsedStmt in results) {
|
||||||
|
if (parsedStmt.rootNode is CreateTableStatement) {
|
||||||
|
createdReaders.add(CreateTableReader(parsedStmt));
|
||||||
|
} else {
|
||||||
|
task.reportError(ErrorInMoorFile(
|
||||||
|
span: parsedStmt.rootNode.span,
|
||||||
|
message: 'At the moment, only CREATE TABLE statements are supported'
|
||||||
|
'in .moor files'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// all results have the same list of errors
|
||||||
|
final sqlErrors = results.isEmpty ? <ParsingError>[] : results.first.errors;
|
||||||
|
|
||||||
|
for (var error in sqlErrors) {
|
||||||
|
task.reportError(ErrorInMoorFile(
|
||||||
|
span: error.token.span,
|
||||||
|
message: error.message,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
final createdTables =
|
||||||
|
createdReaders.map((r) => r.extractTable(task.mapper)).toList();
|
||||||
|
final parsedFile = ParsedMoorFile(createdTables);
|
||||||
|
|
||||||
|
return Future.value(parsedFile);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +1,37 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:analyzer/dart/constant/value.dart';
|
||||||
import 'package:analyzer/dart/element/element.dart';
|
import 'package:analyzer/dart/element/element.dart';
|
||||||
|
import 'package:analyzer/dart/element/type.dart';
|
||||||
|
import 'package:moor/moor.dart' show Table;
|
||||||
|
import 'package:moor_generator/src/analyzer/dart/parser.dart';
|
||||||
import 'package:moor_generator/src/analyzer/errors.dart';
|
import 'package:moor_generator/src/analyzer/errors.dart';
|
||||||
|
import 'package:moor_generator/src/analyzer/moor/parser.dart';
|
||||||
import 'package:moor_generator/src/analyzer/results.dart';
|
import 'package:moor_generator/src/analyzer/results.dart';
|
||||||
|
import 'package:moor_generator/src/analyzer/sql_queries/sql_parser.dart';
|
||||||
|
import 'package:moor_generator/src/analyzer/sql_queries/type_mapping.dart';
|
||||||
import 'package:moor_generator/src/backends/backend.dart';
|
import 'package:moor_generator/src/backends/backend.dart';
|
||||||
|
import 'package:moor_generator/src/model/specified_dao.dart';
|
||||||
|
import 'package:moor_generator/src/model/specified_database.dart';
|
||||||
|
import 'package:moor_generator/src/model/specified_table.dart';
|
||||||
|
import 'package:moor_generator/src/model/sql_query.dart';
|
||||||
|
import 'package:source_gen/source_gen.dart';
|
||||||
|
|
||||||
/// Will store cached data about files that have already been analyzed.
|
/// Will store cached data about files that have already been analyzed.
|
||||||
class MoorSession {
|
class MoorSession {
|
||||||
MoorSession();
|
MoorSession();
|
||||||
|
|
||||||
Future<DartTask> startDartTask(BackendTask backendTask) async {
|
Future<DartTask> startDartTask(BackendTask backendTask, {String uri}) async {
|
||||||
final library = await backendTask.resolveDart(backendTask.entrypoint);
|
final input = uri ?? backendTask.entrypoint;
|
||||||
|
final library = await backendTask.resolveDart(input);
|
||||||
return DartTask(this, backendTask, library);
|
return DartTask(this, backendTask, library);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<MoorTask> startMoorTask(BackendTask backendTask, {String uri}) async {
|
||||||
|
final input = uri ?? backendTask.entrypoint;
|
||||||
|
final source = await backendTask.readMoor(input);
|
||||||
|
return MoorTask(backendTask, this, source);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used to parse and analyze a single file.
|
/// Used to parse and analyze a single file.
|
||||||
|
@ -31,27 +50,89 @@ abstract class FileTask<R extends ParsedFile> {
|
||||||
|
|
||||||
/// Session used to parse a Dart file and extract table information.
|
/// Session used to parse a Dart file and extract table information.
|
||||||
class DartTask extends FileTask<ParsedDartFile> {
|
class DartTask extends FileTask<ParsedDartFile> {
|
||||||
|
static const tableTypeChecker = const TypeChecker.fromRuntime(Table);
|
||||||
|
|
||||||
final LibraryElement library;
|
final LibraryElement library;
|
||||||
|
MoorDartParser _parser;
|
||||||
|
MoorDartParser get parser => _parser;
|
||||||
|
|
||||||
DartTask(MoorSession session, BackendTask task, this.library)
|
DartTask(MoorSession session, BackendTask task, this.library)
|
||||||
: super(task, session);
|
: super(task, session) {
|
||||||
|
_parser = MoorDartParser(this);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<ParsedDartFile> compute() {
|
FutureOr<ParsedDartFile> compute() {
|
||||||
// TODO: implement compute
|
// TODO: implement compute
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses a [SpecifiedDatabase] from the [ClassElement] which was annotated
|
||||||
|
/// with `@UseMoor` and the [annotation] reader that reads the `@UseMoor`
|
||||||
|
/// annotation.
|
||||||
|
Future<SpecifiedDatabase> parseDatabase(
|
||||||
|
ClassElement element, ConstantReader annotation) {
|
||||||
|
return UseMoorParser(this).parseDatabase(element, annotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses a [SpecifiedDao] from a class declaration that has a `UseDao`
|
||||||
|
/// [annotation].
|
||||||
|
Future<SpecifiedDao> parseDao(
|
||||||
|
ClassElement element, ConstantReader annotation) {
|
||||||
|
return UseDaoParser(this).parseDao(element, annotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves a [SpecifiedTable] for the class of each [DartType] in [types].
|
||||||
|
/// The [initializedBy] element should be the piece of code that caused the
|
||||||
|
/// parsing (e.g. the database class that is annotated with `@UseMoor`). This
|
||||||
|
/// will allow for more descriptive error messages.
|
||||||
|
Future<List<SpecifiedTable>> parseTables(
|
||||||
|
Iterable<DartType> types, Element initializedBy) {
|
||||||
|
return Future.wait(types.map((type) {
|
||||||
|
if (!tableTypeChecker.isAssignableFrom(type.element)) {
|
||||||
|
reportError(ErrorInDartCode(
|
||||||
|
severity: Severity.criticalError,
|
||||||
|
message: 'The type $type is not a moor table',
|
||||||
|
affectedElement: initializedBy,
|
||||||
|
));
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return parser.parseTable(type.element as ClassElement);
|
||||||
|
}
|
||||||
|
})).then((list) => List.from(list)); // make growable
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads all tables declared in sql by a `.moor` file in [paths].
|
||||||
|
Future<List<SpecifiedTable>> resolveIncludes(Iterable<String> paths) {
|
||||||
|
return Stream.fromFutures(
|
||||||
|
paths.map((path) => session.startMoorTask(backendTask, uri: path)))
|
||||||
|
.asyncMap((task) => task.compute())
|
||||||
|
.expand((file) => file.declaredTables)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<SqlQuery>> parseQueries(
|
||||||
|
Map<DartObject, DartObject> fromAnnotation,
|
||||||
|
List<SpecifiedTable> availableTables) {
|
||||||
|
// no queries declared, so there is no point in starting a sql engine
|
||||||
|
if (fromAnnotation.isEmpty) return Future.value([]);
|
||||||
|
|
||||||
|
final parser = SqlParser(this, availableTables, fromAnnotation)..parse();
|
||||||
|
|
||||||
|
return Future.value(parser.foundQueries);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MoorTask extends FileTask<ParsedMoorFile> {
|
class MoorTask extends FileTask<ParsedMoorFile> {
|
||||||
final List<String> content;
|
final String content;
|
||||||
|
final TypeMapper mapper = TypeMapper();
|
||||||
|
|
||||||
MoorTask(BackendTask task, MoorSession session, this.content)
|
MoorTask(BackendTask task, MoorSession session, this.content)
|
||||||
: super(task, session);
|
: super(task, session);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<ParsedMoorFile> compute() {
|
FutureOr<ParsedMoorFile> compute() {
|
||||||
// TODO: implement compute
|
final parser = MoorParser(this);
|
||||||
return null;
|
return parser.parseAndAnalyze();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
import 'package:moor_generator/src/model/sql_query.dart';
|
import 'package:moor_generator/src/model/sql_query.dart';
|
||||||
import 'package:moor_generator/src/model/used_type_converter.dart';
|
import 'package:moor_generator/src/model/used_type_converter.dart';
|
||||||
import 'package:moor_generator/src/parser/sql/type_mapping.dart';
|
import 'package:moor_generator/src/analyzer/sql_queries/type_mapping.dart';
|
||||||
import 'package:moor_generator/src/utils/type_converter_hint.dart';
|
import 'package:moor_generator/src/utils/type_converter_hint.dart';
|
||||||
import 'package:sqlparser/sqlparser.dart' hide ResultColumn;
|
import 'package:sqlparser/sqlparser.dart' hide ResultColumn;
|
||||||
|
|
||||||
import 'affected_tables_visitor.dart';
|
import 'affected_tables_visitor.dart';
|
||||||
|
|
||||||
|
/// Maps an [AnalysisContext] from the sqlparser to a [SqlQuery] from this
|
||||||
|
/// generator package by determining its type, return columns, variables and so
|
||||||
|
/// on.
|
||||||
class QueryHandler {
|
class QueryHandler {
|
||||||
final String name;
|
final String name;
|
||||||
final AnalysisContext context;
|
final AnalysisContext context;
|
|
@ -1,16 +1,16 @@
|
||||||
import 'package:analyzer/dart/constant/value.dart';
|
import 'package:analyzer/dart/constant/value.dart';
|
||||||
import 'package:build/build.dart';
|
import 'package:build/build.dart';
|
||||||
import 'package:moor_generator/src/state/errors.dart';
|
import 'package:moor_generator/src/analyzer/errors.dart';
|
||||||
|
import 'package:moor_generator/src/analyzer/session.dart';
|
||||||
import 'package:moor_generator/src/model/specified_table.dart';
|
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:moor_generator/src/parser/sql/query_handler.dart';
|
import 'package:moor_generator/src/analyzer/sql_queries/query_handler.dart';
|
||||||
import 'package:moor_generator/src/parser/sql/type_mapping.dart';
|
import 'package:moor_generator/src/analyzer/sql_queries/type_mapping.dart';
|
||||||
import 'package:moor_generator/src/state/session.dart';
|
|
||||||
import 'package:sqlparser/sqlparser.dart' hide ResultColumn;
|
import 'package:sqlparser/sqlparser.dart' hide ResultColumn;
|
||||||
|
|
||||||
class SqlParser {
|
class SqlParser {
|
||||||
final List<SpecifiedTable> tables;
|
final List<SpecifiedTable> tables;
|
||||||
final GeneratorSession session;
|
final FileTask task;
|
||||||
final Map<DartObject, DartObject> definedQueries;
|
final Map<DartObject, DartObject> definedQueries;
|
||||||
|
|
||||||
final TypeMapper _mapper = TypeMapper();
|
final TypeMapper _mapper = TypeMapper();
|
||||||
|
@ -18,7 +18,7 @@ class SqlParser {
|
||||||
|
|
||||||
final List<SqlQuery> foundQueries = [];
|
final List<SqlQuery> foundQueries = [];
|
||||||
|
|
||||||
SqlParser(this.session, this.tables, this.definedQueries);
|
SqlParser(this.task, this.tables, this.definedQueries);
|
||||||
|
|
||||||
void _spawnEngine() {
|
void _spawnEngine() {
|
||||||
_engine = SqlEngine();
|
_engine = SqlEngine();
|
||||||
|
@ -36,14 +36,15 @@ class SqlParser {
|
||||||
try {
|
try {
|
||||||
context = _engine.analyze(sql);
|
context = _engine.analyze(sql);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
session.errors.add(MoorError(
|
task.reportError(MoorError(
|
||||||
critical: true,
|
severity: Severity.criticalError,
|
||||||
message: 'Error while trying to parse $sql: $e, $s'));
|
message: 'Error while trying to parse $sql: $e, $s'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var error in context.errors) {
|
for (var error in context.errors) {
|
||||||
session.errors.add(MoorError(
|
task.reportError(MoorError(
|
||||||
|
severity: Severity.warning,
|
||||||
message: 'The sql query $sql is invalid: $error',
|
message: 'The sql query $sql is invalid: $error',
|
||||||
));
|
));
|
||||||
}
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
import 'package:analyzer/dart/element/element.dart';
|
||||||
|
import 'package:build/build.dart';
|
||||||
|
import 'package:moor_generator/src/backends/backend.dart';
|
||||||
|
|
||||||
|
class BuildBackend extends Backend {}
|
||||||
|
|
||||||
|
class BuildBackendTask extends BackendTask {
|
||||||
|
final BuildStep step;
|
||||||
|
|
||||||
|
BuildBackendTask(this.step);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get entrypoint => step.inputId.path;
|
||||||
|
|
||||||
|
AssetId _resolve(String uri) {
|
||||||
|
return AssetId.resolve(uri, from: step.inputId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> readMoor(String path) {
|
||||||
|
return step.readAsString(_resolve(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<LibraryElement> resolveDart(String path) {
|
||||||
|
return step.resolver.libraryFor(_resolve(path));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,14 @@
|
||||||
import 'package:build/build.dart';
|
import 'package:build/build.dart';
|
||||||
|
import 'package:moor_generator/src/backends/build/build_backend.dart';
|
||||||
import 'package:moor_generator/src/dao_generator.dart';
|
import 'package:moor_generator/src/dao_generator.dart';
|
||||||
import 'package:moor_generator/src/moor_generator.dart';
|
import 'package:moor_generator/src/moor_generator.dart';
|
||||||
import 'package:moor_generator/src/state/options.dart';
|
|
||||||
import 'package:source_gen/source_gen.dart';
|
import 'package:source_gen/source_gen.dart';
|
||||||
|
|
||||||
|
part 'options.dart';
|
||||||
|
|
||||||
class MoorBuilder extends SharedPartBuilder {
|
class MoorBuilder extends SharedPartBuilder {
|
||||||
|
final BuildBackend backend = BuildBackend();
|
||||||
|
|
||||||
factory MoorBuilder(BuilderOptions options) {
|
factory MoorBuilder(BuilderOptions options) {
|
||||||
final parsedOptions = MoorOptions.fromBuilder(options.config);
|
final parsedOptions = MoorOptions.fromBuilder(options.config);
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
part of 'moor_builder.dart';
|
||||||
|
|
||||||
class MoorOptions {
|
class MoorOptions {
|
||||||
final bool generateFromJsonStringConstructor;
|
final bool generateFromJsonStringConstructor;
|
||||||
|
|
|
@ -1,223 +0,0 @@
|
||||||
import 'package:analyzer/dart/ast/ast.dart';
|
|
||||||
import 'package:analyzer/dart/element/element.dart';
|
|
||||||
import 'package:analyzer/dart/element/type.dart';
|
|
||||||
import 'package:moor_generator/src/model/used_type_converter.dart';
|
|
||||||
import 'package:moor_generator/src/state/errors.dart';
|
|
||||||
import 'package:moor_generator/src/model/specified_column.dart';
|
|
||||||
import 'package:moor_generator/src/parser/parser.dart';
|
|
||||||
import 'package:moor_generator/src/state/session.dart';
|
|
||||||
import 'package:moor_generator/src/utils/type_utils.dart';
|
|
||||||
import 'package:recase/recase.dart';
|
|
||||||
|
|
||||||
const String startInt = 'integer';
|
|
||||||
const String startString = 'text';
|
|
||||||
const String startBool = 'boolean';
|
|
||||||
const String startDateTime = 'dateTime';
|
|
||||||
const String startBlob = 'blob';
|
|
||||||
const String startReal = 'real';
|
|
||||||
|
|
||||||
final Set<String> starters = {
|
|
||||||
startInt,
|
|
||||||
startString,
|
|
||||||
startBool,
|
|
||||||
startDateTime,
|
|
||||||
startBlob,
|
|
||||||
startReal,
|
|
||||||
};
|
|
||||||
|
|
||||||
const String _methodNamed = 'named';
|
|
||||||
const String _methodReferences = 'references';
|
|
||||||
const String _methodAutoIncrement = 'autoIncrement';
|
|
||||||
const String _methodWithLength = 'withLength';
|
|
||||||
const String _methodNullable = 'nullable';
|
|
||||||
const String _methodCustomConstraint = 'customConstraint';
|
|
||||||
const String _methodDefault = 'withDefault';
|
|
||||||
const String _methodMap = 'map';
|
|
||||||
|
|
||||||
const String _errorMessage = 'This getter does not create a valid column that '
|
|
||||||
'can be parsed by moor. Please refer to the readme from moor to see how '
|
|
||||||
'columns are formed. If you have any questions, feel free to raise an issue.';
|
|
||||||
|
|
||||||
class ColumnParser extends ParserBase {
|
|
||||||
ColumnParser(GeneratorSession session) : super(session);
|
|
||||||
|
|
||||||
SpecifiedColumn parse(MethodDeclaration getter, Element getterElement) {
|
|
||||||
/*
|
|
||||||
These getters look like this: ... get id => integer().autoIncrement()();
|
|
||||||
The last () is a FunctionExpressionInvocation, the entries before that
|
|
||||||
(here autoIncrement and integer) are MethodInvocations.
|
|
||||||
We go through each of the method invocations until we hit one that starts
|
|
||||||
the chain (integer, text, boolean, etc.). From each method in the chain,
|
|
||||||
we can extract what it means for the column (name, auto increment, PK,
|
|
||||||
constraints...).
|
|
||||||
*/
|
|
||||||
|
|
||||||
final expr = returnExpressionOfMethod(getter);
|
|
||||||
|
|
||||||
if (!(expr is FunctionExpressionInvocation)) {
|
|
||||||
session.errors.add(MoorError(
|
|
||||||
affectedElement: getter.declaredElement,
|
|
||||||
message: _errorMessage,
|
|
||||||
critical: true,
|
|
||||||
));
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var remainingExpr =
|
|
||||||
(expr as FunctionExpressionInvocation).function as MethodInvocation;
|
|
||||||
|
|
||||||
String foundStartMethod;
|
|
||||||
String foundExplicitName;
|
|
||||||
String foundCustomConstraint;
|
|
||||||
Expression foundDefaultExpression;
|
|
||||||
Expression createdTypeConverter;
|
|
||||||
DartType typeConverterRuntime;
|
|
||||||
var nullable = false;
|
|
||||||
|
|
||||||
final foundFeatures = <ColumnFeature>[];
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
final methodName = remainingExpr.methodName.name;
|
|
||||||
|
|
||||||
if (starters.contains(methodName)) {
|
|
||||||
foundStartMethod = methodName;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (methodName) {
|
|
||||||
case _methodNamed:
|
|
||||||
if (foundExplicitName != null) {
|
|
||||||
session.errors.add(
|
|
||||||
MoorError(
|
|
||||||
critical: false,
|
|
||||||
affectedElement: getter.declaredElement,
|
|
||||||
message:
|
|
||||||
"You're setting more than one name here, the first will "
|
|
||||||
'be used',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
foundExplicitName =
|
|
||||||
readStringLiteral(remainingExpr.argumentList.arguments.first, () {
|
|
||||||
session.errors.add(
|
|
||||||
MoorError(
|
|
||||||
critical: false,
|
|
||||||
affectedElement: getter.declaredElement,
|
|
||||||
message:
|
|
||||||
'This table name is cannot be resolved! Please only use '
|
|
||||||
'a constant string as parameter for .named().',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case _methodReferences:
|
|
||||||
break;
|
|
||||||
case _methodWithLength:
|
|
||||||
final args = remainingExpr.argumentList;
|
|
||||||
final minArg = findNamedArgument(args, 'min');
|
|
||||||
final maxArg = findNamedArgument(args, 'max');
|
|
||||||
|
|
||||||
foundFeatures.add(LimitingTextLength.withLength(
|
|
||||||
min: readIntLiteral(minArg, () {}),
|
|
||||||
max: readIntLiteral(maxArg, () {}),
|
|
||||||
));
|
|
||||||
break;
|
|
||||||
case _methodAutoIncrement:
|
|
||||||
foundFeatures.add(AutoIncrement());
|
|
||||||
// a column declared as auto increment is always a primary key
|
|
||||||
foundFeatures.add(const PrimaryKey());
|
|
||||||
break;
|
|
||||||
case _methodNullable:
|
|
||||||
nullable = true;
|
|
||||||
break;
|
|
||||||
case _methodCustomConstraint:
|
|
||||||
foundCustomConstraint =
|
|
||||||
readStringLiteral(remainingExpr.argumentList.arguments.first, () {
|
|
||||||
session.errors.add(
|
|
||||||
MoorError(
|
|
||||||
critical: false,
|
|
||||||
affectedElement: getter.declaredElement,
|
|
||||||
message:
|
|
||||||
'This constraint is cannot be resolved! Please only use '
|
|
||||||
'a constant string as parameter for .customConstraint().',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case _methodDefault:
|
|
||||||
final args = remainingExpr.argumentList;
|
|
||||||
final expression = args.arguments.single;
|
|
||||||
foundDefaultExpression = expression;
|
|
||||||
break;
|
|
||||||
case _methodMap:
|
|
||||||
final args = remainingExpr.argumentList;
|
|
||||||
final expression = args.arguments.single;
|
|
||||||
|
|
||||||
// the map method has a parameter type that resolved to the runtime
|
|
||||||
// type of the custom object
|
|
||||||
final type = remainingExpr.typeArgumentTypes.single;
|
|
||||||
|
|
||||||
createdTypeConverter = expression;
|
|
||||||
typeConverterRuntime = type;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We're not at a starting method yet, so we need to go deeper!
|
|
||||||
final inner = (remainingExpr.target) as MethodInvocation;
|
|
||||||
remainingExpr = inner;
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnName name;
|
|
||||||
if (foundExplicitName != null) {
|
|
||||||
name = ColumnName.explicitly(foundExplicitName);
|
|
||||||
} else {
|
|
||||||
name = ColumnName.implicitly(ReCase(getter.name.name).snakeCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
final columnType = _startMethodToColumnType(foundStartMethod);
|
|
||||||
|
|
||||||
UsedTypeConverter converter;
|
|
||||||
if (createdTypeConverter != null && typeConverterRuntime != null) {
|
|
||||||
converter = UsedTypeConverter(
|
|
||||||
expression: createdTypeConverter,
|
|
||||||
mappedType: typeConverterRuntime,
|
|
||||||
sqlType: columnType);
|
|
||||||
}
|
|
||||||
|
|
||||||
return SpecifiedColumn(
|
|
||||||
type: columnType,
|
|
||||||
dartGetterName: getter.name.name,
|
|
||||||
name: name,
|
|
||||||
overriddenJsonName: _readJsonKey(getterElement),
|
|
||||||
customConstraints: foundCustomConstraint,
|
|
||||||
nullable: nullable,
|
|
||||||
features: foundFeatures,
|
|
||||||
defaultArgument: foundDefaultExpression?.toSource(),
|
|
||||||
typeConverter: converter);
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnType _startMethodToColumnType(String startMethod) {
|
|
||||||
return const {
|
|
||||||
startBool: ColumnType.boolean,
|
|
||||||
startString: ColumnType.text,
|
|
||||||
startInt: ColumnType.integer,
|
|
||||||
startDateTime: ColumnType.datetime,
|
|
||||||
startBlob: ColumnType.blob,
|
|
||||||
startReal: ColumnType.real,
|
|
||||||
}[startMethod];
|
|
||||||
}
|
|
||||||
|
|
||||||
String _readJsonKey(Element getter) {
|
|
||||||
final annotations = getter.metadata;
|
|
||||||
final object = annotations.singleWhere((e) {
|
|
||||||
final value = e.computeConstantValue();
|
|
||||||
return isFromMoor(value.type) && value.type.name == 'JsonKey';
|
|
||||||
}, orElse: () => null);
|
|
||||||
|
|
||||||
if (object == null) return null;
|
|
||||||
|
|
||||||
return object.constantValue.getField('key').toStringValue();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
import 'package:moor_generator/src/parser/moor/parsed_moor_file.dart';
|
|
||||||
import 'package:source_span/source_span.dart';
|
|
||||||
import 'package:sqlparser/sqlparser.dart';
|
|
||||||
|
|
||||||
/// Parses and analyzes the experimental `.moor` files containing sql
|
|
||||||
/// statements.
|
|
||||||
class MoorAnalyzer {
|
|
||||||
/// Content of the `.moor` file we're analyzing.
|
|
||||||
final String content;
|
|
||||||
|
|
||||||
MoorAnalyzer(this.content);
|
|
||||||
|
|
||||||
Future<MoorParsingResult> analyze() {
|
|
||||||
final results = SqlEngine().parseMultiple(content);
|
|
||||||
|
|
||||||
final createdTables = <CreateTable>[];
|
|
||||||
final errors = <MoorParsingError>[];
|
|
||||||
|
|
||||||
for (var parsedStmt in results) {
|
|
||||||
if (parsedStmt.rootNode is CreateTableStatement) {
|
|
||||||
createdTables.add(CreateTable(parsedStmt));
|
|
||||||
} else {
|
|
||||||
errors.add(
|
|
||||||
MoorParsingError(
|
|
||||||
parsedStmt.rootNode.span,
|
|
||||||
message:
|
|
||||||
'At the moment, only CREATE TABLE statements are supported in .moor files',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// all results have the same list of errors
|
|
||||||
final sqlErrors = results.isEmpty ? <ParsingError>[] : results.first.errors;
|
|
||||||
|
|
||||||
for (var error in sqlErrors) {
|
|
||||||
errors.add(MoorParsingError(error.token.span, message: error.message));
|
|
||||||
}
|
|
||||||
|
|
||||||
final parsedFile = ParsedMoorFile(createdTables);
|
|
||||||
|
|
||||||
return Future.value(MoorParsingResult(parsedFile, errors));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MoorParsingResult {
|
|
||||||
final ParsedMoorFile parsedFile;
|
|
||||||
final List<MoorParsingError> errors;
|
|
||||||
|
|
||||||
MoorParsingResult(this.parsedFile, this.errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
class MoorParsingError {
|
|
||||||
final FileSpan span;
|
|
||||||
final String message;
|
|
||||||
|
|
||||||
MoorParsingError(this.span, {this.message});
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return span.message(message, color: true);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
import 'package:analyzer/dart/ast/ast.dart';
|
|
||||||
import 'package:moor_generator/src/state/errors.dart';
|
|
||||||
import 'package:moor_generator/src/model/specified_table.dart';
|
|
||||||
import 'package:moor_generator/src/state/session.dart';
|
|
||||||
|
|
||||||
class Parser {
|
|
||||||
List<SpecifiedTable> specifiedTables;
|
|
||||||
|
|
||||||
void init() async {}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ParserBase {
|
|
||||||
final GeneratorSession session;
|
|
||||||
|
|
||||||
ParserBase(this.session);
|
|
||||||
|
|
||||||
Expression returnExpressionOfMethod(MethodDeclaration method) {
|
|
||||||
final body = method.body;
|
|
||||||
|
|
||||||
if (!(body is ExpressionFunctionBody)) {
|
|
||||||
session.errors.add(MoorError(
|
|
||||||
affectedElement: method.declaredElement,
|
|
||||||
critical: true,
|
|
||||||
message:
|
|
||||||
'This method must have an expression body (use => instead of {return ...})'));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (method.body as ExpressionFunctionBody).expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
String readStringLiteral(Expression expression, void onError()) {
|
|
||||||
if (!(expression is StringLiteral)) {
|
|
||||||
onError();
|
|
||||||
} else {
|
|
||||||
final value = (expression as StringLiteral).stringValue;
|
|
||||||
if (value == null) {
|
|
||||||
onError();
|
|
||||||
} else {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
int readIntLiteral(Expression expression, void onError()) {
|
|
||||||
if (!(expression is IntegerLiteral)) {
|
|
||||||
onError();
|
|
||||||
// ignore: avoid_returning_null
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return (expression as IntegerLiteral).value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Expression findNamedArgument(ArgumentList args, String argName) {
|
|
||||||
final argument = args.arguments.singleWhere(
|
|
||||||
(e) => e is NamedExpression && e.name.label.name == argName,
|
|
||||||
orElse: () => null) as NamedExpression;
|
|
||||||
|
|
||||||
return argument?.expression;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,129 +0,0 @@
|
||||||
import 'package:analyzer/dart/ast/ast.dart';
|
|
||||||
import 'package:analyzer/dart/element/element.dart';
|
|
||||||
import 'package:moor_generator/src/state/errors.dart';
|
|
||||||
import 'package:moor_generator/src/model/specified_column.dart';
|
|
||||||
import 'package:moor_generator/src/model/specified_table.dart';
|
|
||||||
import 'package:moor_generator/src/parser/parser.dart';
|
|
||||||
import 'package:moor_generator/src/state/session.dart';
|
|
||||||
import 'package:moor_generator/src/utils/names.dart';
|
|
||||||
import 'package:moor_generator/src/utils/type_utils.dart';
|
|
||||||
import 'package:recase/recase.dart';
|
|
||||||
import 'package:moor/sqlite_keywords.dart';
|
|
||||||
|
|
||||||
class TableParser extends ParserBase {
|
|
||||||
TableParser(GeneratorSession session) : super(session);
|
|
||||||
|
|
||||||
Future<SpecifiedTable> parse(ClassElement element) async {
|
|
||||||
final sqlName = await _parseTableName(element);
|
|
||||||
if (sqlName == null) return null;
|
|
||||||
|
|
||||||
final columns = await _parseColumns(element);
|
|
||||||
|
|
||||||
final table = SpecifiedTable(
|
|
||||||
fromClass: element,
|
|
||||||
columns: columns,
|
|
||||||
sqlName: escapeIfNeeded(sqlName),
|
|
||||||
dartTypeName: _readDartTypeName(element),
|
|
||||||
primaryKey: await _readPrimaryKey(element, columns),
|
|
||||||
);
|
|
||||||
|
|
||||||
var index = 0;
|
|
||||||
for (var converter in table.converters) {
|
|
||||||
converter
|
|
||||||
..index = index++
|
|
||||||
..table = table;
|
|
||||||
}
|
|
||||||
|
|
||||||
return table;
|
|
||||||
}
|
|
||||||
|
|
||||||
String _readDartTypeName(ClassElement element) {
|
|
||||||
final nameAnnotation = element.metadata.singleWhere(
|
|
||||||
(e) => e.computeConstantValue().type.name == 'DataClassName',
|
|
||||||
orElse: () => null);
|
|
||||||
|
|
||||||
if (nameAnnotation == null) {
|
|
||||||
return dataClassNameForClassName(element.name);
|
|
||||||
} else {
|
|
||||||
return nameAnnotation.constantValue.getField('name').toStringValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> _parseTableName(ClassElement element) async {
|
|
||||||
// todo allow override via a field (final String tableName = '') as well
|
|
||||||
|
|
||||||
final tableNameGetter = element.getGetter('tableName');
|
|
||||||
if (tableNameGetter == null) {
|
|
||||||
// class does not override tableName. So just use the dart class name
|
|
||||||
// instead. Will use placed_orders for a class called PlacedOrders
|
|
||||||
return ReCase(element.name).snakeCase;
|
|
||||||
}
|
|
||||||
|
|
||||||
// we expect something like get tableName => "myTableName", the getter
|
|
||||||
// must do nothing more complicated
|
|
||||||
final tableNameDeclaration =
|
|
||||||
await session.loadElementDeclaration(tableNameGetter);
|
|
||||||
final returnExpr = returnExpressionOfMethod(
|
|
||||||
tableNameDeclaration.node as MethodDeclaration);
|
|
||||||
|
|
||||||
final tableName = readStringLiteral(returnExpr, () {
|
|
||||||
session.errors.add(MoorError(
|
|
||||||
critical: true,
|
|
||||||
message:
|
|
||||||
'This getter must return a string literal, and do nothing more',
|
|
||||||
affectedElement: tableNameGetter));
|
|
||||||
});
|
|
||||||
|
|
||||||
return tableName;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Set<SpecifiedColumn>> _readPrimaryKey(
|
|
||||||
ClassElement element, List<SpecifiedColumn> columns) async {
|
|
||||||
final primaryKeyGetter = element.getGetter('primaryKey');
|
|
||||||
if (primaryKeyGetter == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final resolved = await session.loadElementDeclaration(primaryKeyGetter);
|
|
||||||
final ast = resolved.node as MethodDeclaration;
|
|
||||||
final body = ast.body;
|
|
||||||
if (body is! ExpressionFunctionBody) {
|
|
||||||
session.errors.add(MoorError(
|
|
||||||
affectedElement: primaryKeyGetter,
|
|
||||||
message: 'This must return a set literal using the => syntax!'));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final expression = (body as ExpressionFunctionBody).expression;
|
|
||||||
final parsedPrimaryKey = <SpecifiedColumn>{};
|
|
||||||
|
|
||||||
if (expression is SetOrMapLiteral) {
|
|
||||||
for (var entry in expression.elements) {
|
|
||||||
if (entry is Identifier) {
|
|
||||||
final column = columns
|
|
||||||
.singleWhere((column) => column.dartGetterName == entry.name);
|
|
||||||
parsedPrimaryKey.add(column);
|
|
||||||
} else {
|
|
||||||
print('Unexpected entry in expression.elements: $entry');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
session.errors.add(MoorError(
|
|
||||||
affectedElement: primaryKeyGetter,
|
|
||||||
message: 'This must return a set literal!'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsedPrimaryKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<SpecifiedColumn>> _parseColumns(ClassElement element) {
|
|
||||||
final columns = element.fields
|
|
||||||
.where((field) => isColumn(field.type) && field.getter != null);
|
|
||||||
|
|
||||||
return Future.wait(columns.map((field) async {
|
|
||||||
final resolved = await session.loadElementDeclaration(field.getter);
|
|
||||||
final node = resolved.node as MethodDeclaration;
|
|
||||||
|
|
||||||
return await session.parseColumn(node, field.getter);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
import 'package:analyzer/dart/element/element.dart';
|
|
||||||
|
|
||||||
class MoorError {
|
|
||||||
final bool critical;
|
|
||||||
final String message;
|
|
||||||
final Element affectedElement;
|
|
||||||
|
|
||||||
MoorError({this.critical = false, this.message, this.affectedElement});
|
|
||||||
}
|
|
||||||
|
|
||||||
class ErrorStore {
|
|
||||||
final List<MoorError> errors = [];
|
|
||||||
|
|
||||||
void add(MoorError error) => errors.add(error);
|
|
||||||
|
|
||||||
bool get hasCriticalError => errors.any((e) => e.critical);
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
import 'package:analyzer/dart/element/type.dart';
|
|
||||||
import 'package:build/build.dart';
|
|
||||||
import 'package:moor/moor.dart';
|
|
||||||
import 'package:moor_generator/src/model/specified_table.dart';
|
|
||||||
import 'package:moor_generator/src/state/session.dart';
|
|
||||||
import 'package:source_gen/source_gen.dart';
|
|
||||||
|
|
||||||
import 'options.dart';
|
|
||||||
|
|
||||||
GeneratorState _state;
|
|
||||||
|
|
||||||
/// Uses the created instance of the generator state or creates one via the
|
|
||||||
/// [create] callback if necessary.
|
|
||||||
GeneratorState useState(GeneratorState Function() create) {
|
|
||||||
return _state ??= create();
|
|
||||||
}
|
|
||||||
|
|
||||||
class GeneratorState {
|
|
||||||
final MoorOptions options;
|
|
||||||
|
|
||||||
final Map<DartType, Future<SpecifiedTable>> _foundTables = {};
|
|
||||||
final tableTypeChecker = const TypeChecker.fromRuntime(Table);
|
|
||||||
|
|
||||||
GeneratorState(this.options);
|
|
||||||
|
|
||||||
GeneratorSession startSession(BuildStep step) {
|
|
||||||
return GeneratorSession(this, step);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parses the [SpecifiedTable] from a [type]. As this operation is very
|
|
||||||
/// expensive, we always try to only perform it once.
|
|
||||||
///
|
|
||||||
/// The [resolve] function is responsible for performing the actual analysis
|
|
||||||
/// and it will be called when the [type] has not yet been resolved.
|
|
||||||
Future<SpecifiedTable> parseTable(
|
|
||||||
DartType type, Future<SpecifiedTable> Function() resolve) {
|
|
||||||
return _foundTables.putIfAbsent(type, resolve);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,129 +0,0 @@
|
||||||
import 'package:analyzer/dart/analysis/results.dart';
|
|
||||||
import 'package:analyzer/dart/ast/ast.dart';
|
|
||||||
import 'package:analyzer/dart/constant/value.dart';
|
|
||||||
import 'package:analyzer/dart/element/element.dart';
|
|
||||||
import 'package:analyzer/dart/element/type.dart';
|
|
||||||
import 'package:build/build.dart';
|
|
||||||
import 'package:moor_generator/src/model/specified_column.dart';
|
|
||||||
import 'package:moor_generator/src/model/specified_dao.dart';
|
|
||||||
import 'package:moor_generator/src/model/specified_database.dart';
|
|
||||||
import 'package:moor_generator/src/model/specified_table.dart';
|
|
||||||
import 'package:moor_generator/src/model/sql_query.dart';
|
|
||||||
import 'package:moor_generator/src/parser/column_parser.dart';
|
|
||||||
import 'package:moor_generator/src/parser/moor/moor_analyzer.dart';
|
|
||||||
import 'package:moor_generator/src/parser/sql/sql_parser.dart';
|
|
||||||
import 'package:moor_generator/src/parser/sql/type_mapping.dart';
|
|
||||||
import 'package:moor_generator/src/parser/table_parser.dart';
|
|
||||||
import 'package:moor_generator/src/parser/use_dao_parser.dart';
|
|
||||||
import 'package:moor_generator/src/parser/use_moor_parser.dart';
|
|
||||||
import 'package:source_gen/source_gen.dart';
|
|
||||||
|
|
||||||
import 'errors.dart';
|
|
||||||
import 'generator_state.dart';
|
|
||||||
import 'options.dart';
|
|
||||||
import 'writer.dart';
|
|
||||||
|
|
||||||
class GeneratorSession {
|
|
||||||
final GeneratorState state;
|
|
||||||
final ErrorStore errors = ErrorStore();
|
|
||||||
final BuildStep step;
|
|
||||||
|
|
||||||
final Writer writer = Writer();
|
|
||||||
|
|
||||||
TableParser _tableParser;
|
|
||||||
ColumnParser _columnParser;
|
|
||||||
|
|
||||||
MoorOptions get options => state.options;
|
|
||||||
|
|
||||||
GeneratorSession(this.state, this.step) {
|
|
||||||
_tableParser = TableParser(this);
|
|
||||||
_columnParser = ColumnParser(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<ElementDeclarationResult> loadElementDeclaration(
|
|
||||||
Element element) async {
|
|
||||||
final resolvedLibrary = await element.library.session
|
|
||||||
.getResolvedLibraryByElement(element.library);
|
|
||||||
|
|
||||||
return resolvedLibrary.getElementDeclaration(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parses a [SpecifiedDatabase] from the [ClassElement] which was annotated
|
|
||||||
/// with `@UseMoor` and the [annotation] reader that reads the `@UseMoor`
|
|
||||||
/// annotation.
|
|
||||||
Future<SpecifiedDatabase> parseDatabase(
|
|
||||||
ClassElement element, ConstantReader annotation) {
|
|
||||||
return UseMoorParser(this).parseDatabase(element, annotation);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parses a [SpecifiedDao] from a class declaration that has a `UseDao`
|
|
||||||
/// [annotation].
|
|
||||||
Future<SpecifiedDao> parseDao(
|
|
||||||
ClassElement element, ConstantReader annotation) {
|
|
||||||
return UseDaoParser(this).parseDao(element, annotation);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resolves a [SpecifiedTable] for the class of each [DartType] in [types].
|
|
||||||
/// The [initializedBy] element should be the piece of code that caused the
|
|
||||||
/// parsing (e.g. the database class that is annotated with `@UseMoor`). This
|
|
||||||
/// will allow for more descriptive error messages.
|
|
||||||
Future<List<SpecifiedTable>> parseTables(
|
|
||||||
Iterable<DartType> types, Element initializedBy) {
|
|
||||||
return Future.wait(types.map((type) {
|
|
||||||
if (!state.tableTypeChecker.isAssignableFrom(type.element)) {
|
|
||||||
errors.add(MoorError(
|
|
||||||
critical: true,
|
|
||||||
message: 'The type $type is not a moor table',
|
|
||||||
affectedElement: initializedBy,
|
|
||||||
));
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return _tableParser.parse(type.element as ClassElement);
|
|
||||||
}
|
|
||||||
})).then((list) => List.from(list)); // make growable
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<SpecifiedTable>> resolveIncludes(Iterable<String> paths) async {
|
|
||||||
final mapper = TypeMapper();
|
|
||||||
final foundTables = <SpecifiedTable>[];
|
|
||||||
|
|
||||||
for (var path in paths) {
|
|
||||||
final asset = AssetId.resolve(path, from: step.inputId);
|
|
||||||
String content;
|
|
||||||
try {
|
|
||||||
content = await step.readAsString(asset);
|
|
||||||
} catch (e) {
|
|
||||||
errors.add(MoorError(
|
|
||||||
critical: true,
|
|
||||||
message: 'The included file $path could not be found'));
|
|
||||||
}
|
|
||||||
|
|
||||||
final parsed = await MoorAnalyzer(content).analyze();
|
|
||||||
foundTables.addAll(
|
|
||||||
parsed.parsedFile.declaredTables.map((t) => t.extractTable(mapper)));
|
|
||||||
|
|
||||||
for (var parseError in parsed.errors) {
|
|
||||||
errors.add(MoorError(message: "Can't parse sql in $path: $parseError"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return foundTables;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parses a column from a getter [e] declared inside a table class and its
|
|
||||||
/// resolved AST node [m].
|
|
||||||
Future<SpecifiedColumn> parseColumn(MethodDeclaration m, Element e) {
|
|
||||||
return Future.value(_columnParser.parse(m, e));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<SqlQuery>> parseQueries(
|
|
||||||
Map<DartObject, DartObject> fromAnnotation,
|
|
||||||
List<SpecifiedTable> availableTables) {
|
|
||||||
// no queries declared, so there is no point in starting a sql engine
|
|
||||||
if (fromAnnotation.isEmpty) return Future.value([]);
|
|
||||||
|
|
||||||
final parser = SqlParser(this, availableTables, fromAnnotation)..parse();
|
|
||||||
|
|
||||||
return Future.value(parser.foundQueries);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +1,8 @@
|
||||||
import 'package:moor_generator/src/model/sql_query.dart';
|
import 'package:moor_generator/src/model/sql_query.dart';
|
||||||
import 'package:moor_generator/src/state/session.dart';
|
import 'package:moor_generator/src/writer/utils/memoized_getter.dart';
|
||||||
import 'package:moor_generator/src/writer/query_writer.dart';
|
|
||||||
import 'package:moor_generator/src/writer/result_set_writer.dart';
|
|
||||||
import 'package:recase/recase.dart';
|
import 'package:recase/recase.dart';
|
||||||
import 'package:moor_generator/src/model/specified_database.dart';
|
import 'package:moor_generator/src/model/specified_database.dart';
|
||||||
import 'package:moor_generator/src/writer/table_writer.dart';
|
import 'package:moor_generator/src/writer/table_writer.dart';
|
||||||
import 'utils.dart';
|
|
||||||
|
|
||||||
class DatabaseWriter {
|
class DatabaseWriter {
|
||||||
final SpecifiedDatabase db;
|
final SpecifiedDatabase db;
|
||||||
|
@ -19,13 +16,6 @@ class DatabaseWriter {
|
||||||
TableWriter(table, session).writeInto(buffer);
|
TableWriter(table, session).writeInto(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write additional classes to hold the result of custom queries
|
|
||||||
for (final query in db.queries) {
|
|
||||||
if (query is SqlSelectQuery && query.resultSet.matchingTable == null) {
|
|
||||||
ResultSetWriter(query).write(buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the database class
|
// Write the database class
|
||||||
final className = '_\$${db.fromClass.name}';
|
final className = '_\$${db.fromClass.name}';
|
||||||
buffer.write('abstract class $className extends GeneratedDatabase {\n'
|
buffer.write('abstract class $className extends GeneratedDatabase {\n'
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import 'dart:math' show max;
|
import 'dart:math' show max;
|
||||||
|
|
||||||
|
import 'package:moor_generator/src/backends/build/moor_builder.dart';
|
||||||
import 'package:moor_generator/src/model/specified_column.dart';
|
import 'package:moor_generator/src/model/specified_column.dart';
|
||||||
import 'package:moor_generator/src/model/sql_query.dart';
|
import 'package:moor_generator/src/model/sql_query.dart';
|
||||||
import 'package:moor_generator/src/state/session.dart';
|
|
||||||
import 'package:moor_generator/src/utils/string_escaper.dart';
|
import 'package:moor_generator/src/utils/string_escaper.dart';
|
||||||
|
import 'package:moor_generator/src/writer/queries/result_set_writer.dart';
|
||||||
|
import 'package:moor_generator/src/writer/writer.dart';
|
||||||
import 'package:recase/recase.dart';
|
import 'package:recase/recase.dart';
|
||||||
import 'package:sqlparser/sqlparser.dart';
|
import 'package:sqlparser/sqlparser.dart';
|
||||||
|
|
||||||
|
@ -16,13 +18,18 @@ const highestAssignedIndexVar = '\$highestIndex';
|
||||||
/// should be included in a generated database or dao class.
|
/// should be included in a generated database or dao class.
|
||||||
class QueryWriter {
|
class QueryWriter {
|
||||||
final SqlQuery query;
|
final SqlQuery query;
|
||||||
final GeneratorSession session;
|
final Scope scope;
|
||||||
SqlSelectQuery get _select => query as SqlSelectQuery;
|
SqlSelectQuery get _select => query as SqlSelectQuery;
|
||||||
UpdatingQuery get _update => query as UpdatingQuery;
|
UpdatingQuery get _update => query as UpdatingQuery;
|
||||||
|
|
||||||
|
MoorOptions get options => scope.writer.options;
|
||||||
|
StringBuffer _buffer;
|
||||||
|
|
||||||
final Set<String> _writtenMappingMethods;
|
final Set<String> _writtenMappingMethods;
|
||||||
|
|
||||||
QueryWriter(this.query, this.session, this._writtenMappingMethods);
|
QueryWriter(this.query, this.scope, this._writtenMappingMethods) {
|
||||||
|
_buffer = scope.leaf();
|
||||||
|
}
|
||||||
|
|
||||||
/// The expanded sql that we insert into queries whenever an array variable
|
/// The expanded sql that we insert into queries whenever an array variable
|
||||||
/// appears. For the query "SELECT * FROM t WHERE x IN ?", we generate
|
/// appears. For the query "SELECT * FROM t WHERE x IN ?", we generate
|
||||||
|
@ -36,19 +43,25 @@ class QueryWriter {
|
||||||
return 'expanded${v.dartParameterName}';
|
return 'expanded${v.dartParameterName}';
|
||||||
}
|
}
|
||||||
|
|
||||||
void writeInto(StringBuffer buffer) {
|
void writeInto() {
|
||||||
if (query is SqlSelectQuery) {
|
if (query is SqlSelectQuery) {
|
||||||
_writeSelect(buffer);
|
final select = query as SqlSelectQuery;
|
||||||
|
if (select.resultSet.matchingTable == null) {
|
||||||
|
// query needs its own result set - write that now
|
||||||
|
final buffer = scope.findScopeOfLevel(DartScope.library).leaf();
|
||||||
|
ResultSetWriter(select).write(buffer);
|
||||||
|
}
|
||||||
|
_writeSelect();
|
||||||
} else if (query is UpdatingQuery) {
|
} else if (query is UpdatingQuery) {
|
||||||
_writeUpdatingQuery(buffer);
|
_writeUpdatingQuery();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _writeSelect(StringBuffer buffer) {
|
void _writeSelect() {
|
||||||
_writeMapping(buffer);
|
_writeMapping();
|
||||||
_writeSelectStatementCreator(buffer);
|
_writeSelectStatementCreator();
|
||||||
_writeOneTimeReader(buffer);
|
_writeOneTimeReader();
|
||||||
_writeStreamReader(buffer);
|
_writeStreamReader();
|
||||||
}
|
}
|
||||||
|
|
||||||
String _nameOfMappingMethod() {
|
String _nameOfMappingMethod() {
|
||||||
|
@ -61,11 +74,11 @@ class QueryWriter {
|
||||||
|
|
||||||
/// Writes a mapping method that turns a "QueryRow" into the desired custom
|
/// Writes a mapping method that turns a "QueryRow" into the desired custom
|
||||||
/// return type.
|
/// return type.
|
||||||
void _writeMapping(StringBuffer buffer) {
|
void _writeMapping() {
|
||||||
// avoid writing mapping methods twice if the same result class is written
|
// avoid writing mapping methods twice if the same result class is written
|
||||||
// more than once.
|
// more than once.
|
||||||
if (!_writtenMappingMethods.contains(_nameOfMappingMethod())) {
|
if (!_writtenMappingMethods.contains(_nameOfMappingMethod())) {
|
||||||
buffer
|
_buffer
|
||||||
..write('${_select.resultClassName} ${_nameOfMappingMethod()}')
|
..write('${_select.resultClassName} ${_nameOfMappingMethod()}')
|
||||||
..write('(QueryRow row) {\n')
|
..write('(QueryRow row) {\n')
|
||||||
..write('return ${_select.resultClassName}(');
|
..write('return ${_select.resultClassName}(');
|
||||||
|
@ -84,35 +97,35 @@ class QueryWriter {
|
||||||
code = '$field.mapToDart($code)';
|
code = '$field.mapToDart($code)';
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.write('$fieldName: $code,');
|
_buffer.write('$fieldName: $code,');
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.write(');\n}\n');
|
_buffer.write(');\n}\n');
|
||||||
_writtenMappingMethods.add(_nameOfMappingMethod());
|
_writtenMappingMethods.add(_nameOfMappingMethod());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes a method returning a `Selectable<T>`, where `T` is the return type
|
/// Writes a method returning a `Selectable<T>`, where `T` is the return type
|
||||||
/// of the custom query.
|
/// of the custom query.
|
||||||
void _writeSelectStatementCreator(StringBuffer buffer) {
|
void _writeSelectStatementCreator() {
|
||||||
final returnType = 'Selectable<${_select.resultClassName}>';
|
final returnType = 'Selectable<${_select.resultClassName}>';
|
||||||
final methodName = _nameOfCreationMethod();
|
final methodName = _nameOfCreationMethod();
|
||||||
|
|
||||||
buffer.write('$returnType $methodName(');
|
_buffer.write('$returnType $methodName(');
|
||||||
_writeParameters(buffer);
|
_writeParameters();
|
||||||
buffer.write(') {\n');
|
_buffer.write(') {\n');
|
||||||
|
|
||||||
_writeExpandedDeclarations(buffer);
|
_writeExpandedDeclarations();
|
||||||
buffer
|
_buffer
|
||||||
..write('return (operateOn ?? this).')
|
..write('return (operateOn ?? this).')
|
||||||
..write('customSelectQuery(${_queryCode()}, ');
|
..write('customSelectQuery(${_queryCode()}, ');
|
||||||
_writeVariables(buffer);
|
_writeVariables();
|
||||||
buffer.write(', ');
|
_buffer.write(', ');
|
||||||
_writeReadsFrom(buffer);
|
_writeReadsFrom();
|
||||||
|
|
||||||
buffer.write(').map(');
|
_buffer.write(').map(');
|
||||||
buffer.write(_nameOfMappingMethod());
|
_buffer.write(_nameOfMappingMethod());
|
||||||
buffer.write(');\n}\n');
|
_buffer.write(');\n}\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -122,34 +135,35 @@ class QueryWriter {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void _writeOneTimeReader(StringBuffer buffer) {
|
void _writeOneTimeReader() {
|
||||||
buffer.write('Future<List<${_select.resultClassName}>> ${query.name}(');
|
_buffer.write('Future<List<${_select.resultClassName}>> ${query.name}(');
|
||||||
_writeParameters(buffer);
|
_writeParameters();
|
||||||
buffer..write(') {\n')..write('return ${_nameOfCreationMethod()}(');
|
_buffer..write(') {\n')..write('return ${_nameOfCreationMethod()}(');
|
||||||
_writeUseParameters(buffer);
|
_writeUseParameters();
|
||||||
buffer.write(').get();\n}\n');
|
_buffer.write(').get();\n}\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
void _writeStreamReader(StringBuffer buffer) {
|
void _writeStreamReader() {
|
||||||
final upperQueryName = ReCase(query.name).pascalCase;
|
final upperQueryName = ReCase(query.name).pascalCase;
|
||||||
|
|
||||||
String methodName;
|
String methodName;
|
||||||
// turning the query name into pascal case will remove underscores, add the
|
// turning the query name into pascal case will remove underscores, add the
|
||||||
// "private" modifier back in if needed
|
// "private" modifier back in if needed
|
||||||
if (session.options.fixPrivateWatchMethods && query.name.startsWith('_')) {
|
if (scope.writer.options.fixPrivateWatchMethods &&
|
||||||
|
query.name.startsWith('_')) {
|
||||||
methodName = '_watch$upperQueryName';
|
methodName = '_watch$upperQueryName';
|
||||||
} else {
|
} else {
|
||||||
methodName = 'watch$upperQueryName';
|
methodName = 'watch$upperQueryName';
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.write('Stream<List<${_select.resultClassName}>> $methodName(');
|
_buffer.write('Stream<List<${_select.resultClassName}>> $methodName(');
|
||||||
_writeParameters(buffer, dontOverrideEngine: true);
|
_writeParameters(dontOverrideEngine: true);
|
||||||
buffer..write(') {\n')..write('return ${_nameOfCreationMethod()}(');
|
_buffer..write(') {\n')..write('return ${_nameOfCreationMethod()}(');
|
||||||
_writeUseParameters(buffer, dontUseEngine: true);
|
_writeUseParameters(dontUseEngine: true);
|
||||||
buffer.write(').watch();\n}\n');
|
_buffer.write(').watch();\n}\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
void _writeUpdatingQuery(StringBuffer buffer) {
|
void _writeUpdatingQuery() {
|
||||||
/*
|
/*
|
||||||
Future<int> test() {
|
Future<int> test() {
|
||||||
return customUpdate('', variables: [], updates: {});
|
return customUpdate('', variables: [], updates: {});
|
||||||
|
@ -157,24 +171,23 @@ class QueryWriter {
|
||||||
*/
|
*/
|
||||||
final implName = _update.isInsert ? 'customInsert' : 'customUpdate';
|
final implName = _update.isInsert ? 'customInsert' : 'customUpdate';
|
||||||
|
|
||||||
buffer.write('Future<int> ${query.name}(');
|
_buffer.write('Future<int> ${query.name}(');
|
||||||
_writeParameters(buffer);
|
_writeParameters();
|
||||||
buffer.write(') {\n');
|
_buffer.write(') {\n');
|
||||||
|
|
||||||
_writeExpandedDeclarations(buffer);
|
_writeExpandedDeclarations();
|
||||||
buffer
|
_buffer
|
||||||
..write('return (operateOn ?? this).')
|
..write('return (operateOn ?? this).')
|
||||||
..write('$implName(${_queryCode()},');
|
..write('$implName(${_queryCode()},');
|
||||||
|
|
||||||
_writeVariables(buffer);
|
_writeVariables();
|
||||||
buffer.write(',');
|
_buffer.write(',');
|
||||||
_writeUpdates(buffer);
|
_writeUpdates();
|
||||||
|
|
||||||
buffer..write(',);\n}\n');
|
_buffer..write(',);\n}\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
void _writeParameters(StringBuffer buffer,
|
void _writeParameters({bool dontOverrideEngine = false}) {
|
||||||
{bool dontOverrideEngine = false}) {
|
|
||||||
final paramList = query.variables.map((v) {
|
final paramList = query.variables.map((v) {
|
||||||
var dartType = dartTypeNames[v.type];
|
var dartType = dartTypeNames[v.type];
|
||||||
if (v.isArray) {
|
if (v.isArray) {
|
||||||
|
@ -183,13 +196,13 @@ class QueryWriter {
|
||||||
return '$dartType ${v.dartParameterName}';
|
return '$dartType ${v.dartParameterName}';
|
||||||
}).join(', ');
|
}).join(', ');
|
||||||
|
|
||||||
buffer.write(paramList);
|
_buffer.write(paramList);
|
||||||
|
|
||||||
// write named optional parameter to configure the query engine used to
|
// write named optional parameter to configure the query engine used to
|
||||||
// execute the statement,
|
// execute the statement,
|
||||||
if (!dontOverrideEngine) {
|
if (!dontOverrideEngine) {
|
||||||
if (query.variables.isNotEmpty) buffer.write(', ');
|
if (query.variables.isNotEmpty) _buffer.write(', ');
|
||||||
buffer.write('{@Deprecated(${asDartLiteral(queryEngineWarningDesc)}) '
|
_buffer.write('{@Deprecated(${asDartLiteral(queryEngineWarningDesc)}) '
|
||||||
'QueryEngine operateOn}');
|
'QueryEngine operateOn}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -197,11 +210,11 @@ class QueryWriter {
|
||||||
/// Writes code that uses the parameters as declared by [_writeParameters],
|
/// Writes code that uses the parameters as declared by [_writeParameters],
|
||||||
/// assuming that for each parameter, a variable with the same name exists
|
/// assuming that for each parameter, a variable with the same name exists
|
||||||
/// in the current scope.
|
/// in the current scope.
|
||||||
void _writeUseParameters(StringBuffer into, {bool dontUseEngine = false}) {
|
void _writeUseParameters({bool dontUseEngine = false}) {
|
||||||
into.write(query.variables.map((v) => v.dartParameterName).join(', '));
|
_buffer.write(query.variables.map((v) => v.dartParameterName).join(', '));
|
||||||
if (!dontUseEngine) {
|
if (!dontUseEngine) {
|
||||||
if (query.variables.isNotEmpty) into.write(', ');
|
if (query.variables.isNotEmpty) _buffer.write(', ');
|
||||||
into.write('operateOn: operateOn');
|
_buffer.write('operateOn: operateOn');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,7 +230,7 @@ class QueryWriter {
|
||||||
// "vars" variable twice. To do this, a local var called "$currentVarIndex"
|
// "vars" variable twice. To do this, a local var called "$currentVarIndex"
|
||||||
// keeps track of the highest variable number assigned.
|
// keeps track of the highest variable number assigned.
|
||||||
|
|
||||||
void _writeExpandedDeclarations(StringBuffer buffer) {
|
void _writeExpandedDeclarations() {
|
||||||
var indexCounterWasDeclared = false;
|
var indexCounterWasDeclared = false;
|
||||||
var highestIndexBeforeArray = 0;
|
var highestIndexBeforeArray = 0;
|
||||||
|
|
||||||
|
@ -228,12 +241,12 @@ class QueryWriter {
|
||||||
// add +1 because that's going to be the first index of the expanded
|
// add +1 because that's going to be the first index of the expanded
|
||||||
// array
|
// array
|
||||||
final firstVal = highestIndexBeforeArray + 1;
|
final firstVal = highestIndexBeforeArray + 1;
|
||||||
buffer.write('var $highestAssignedIndexVar = $firstVal;');
|
_buffer.write('var $highestAssignedIndexVar = $firstVal;');
|
||||||
indexCounterWasDeclared = true;
|
indexCounterWasDeclared = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// final expandedvar1 = $expandVar(<startIndex>, <amount>);
|
// final expandedvar1 = $expandVar(<startIndex>, <amount>);
|
||||||
buffer
|
_buffer
|
||||||
..write('final ')
|
..write('final ')
|
||||||
..write(_expandedName(variable))
|
..write(_expandedName(variable))
|
||||||
..write(' = ')
|
..write(' = ')
|
||||||
|
@ -244,7 +257,7 @@ class QueryWriter {
|
||||||
..write('.length);\n');
|
..write('.length);\n');
|
||||||
|
|
||||||
// increase highest index for the next array
|
// increase highest index for the next array
|
||||||
buffer
|
_buffer
|
||||||
..write('$highestAssignedIndexVar += ')
|
..write('$highestAssignedIndexVar += ')
|
||||||
..write(variable.dartParameterName)
|
..write(variable.dartParameterName)
|
||||||
..write('.length;');
|
..write('.length;');
|
||||||
|
@ -256,8 +269,8 @@ class QueryWriter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _writeVariables(StringBuffer buffer) {
|
void _writeVariables() {
|
||||||
buffer..write('variables: [');
|
_buffer..write('variables: [');
|
||||||
|
|
||||||
for (var variable in query.variables) {
|
for (var variable in query.variables) {
|
||||||
// for a regular variable: Variable.withInt(x),
|
// for a regular variable: Variable.withInt(x),
|
||||||
|
@ -266,15 +279,15 @@ class QueryWriter {
|
||||||
final name = variable.dartParameterName;
|
final name = variable.dartParameterName;
|
||||||
|
|
||||||
if (variable.isArray) {
|
if (variable.isArray) {
|
||||||
buffer.write('for (var \$ in $name) $constructor(\$)');
|
_buffer.write('for (var \$ in $name) $constructor(\$)');
|
||||||
} else {
|
} else {
|
||||||
buffer.write('$constructor($name)');
|
_buffer.write('$constructor($name)');
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.write(',');
|
_buffer.write(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer..write(']');
|
_buffer..write(']');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a Dart string literal representing the query after variables have
|
/// Returns a Dart string literal representing the query after variables have
|
||||||
|
@ -295,29 +308,29 @@ class QueryWriter {
|
||||||
.singleWhere((f) => f.variable.resolvedIndex == sqlVar.resolvedIndex);
|
.singleWhere((f) => f.variable.resolvedIndex == sqlVar.resolvedIndex);
|
||||||
if (!moorVar.isArray) continue;
|
if (!moorVar.isArray) continue;
|
||||||
|
|
||||||
// write everything that comes before this var into the buffer
|
// write everything that comes before this var into the_buffer
|
||||||
final currentIndex = sqlVar.firstPosition;
|
final currentIndex = sqlVar.firstPosition;
|
||||||
final queryPart = query.sql.substring(lastIndex, currentIndex);
|
final queryPart = query.sql.substring(lastIndex, currentIndex);
|
||||||
buffer.write(escapeForDart(queryPart));
|
_buffer.write(escapeForDart(queryPart));
|
||||||
lastIndex = sqlVar.lastPosition;
|
lastIndex = sqlVar.lastPosition;
|
||||||
|
|
||||||
// write the ($expandedVar) par
|
// write the ($expandedVar) par
|
||||||
buffer.write('(\$${_expandedName(moorVar)})');
|
_buffer.write('(\$${_expandedName(moorVar)})');
|
||||||
}
|
}
|
||||||
|
|
||||||
// write the final part after the last variable, plus the ending '
|
// write the final part after the last variable, plus the ending '
|
||||||
buffer..write(escapeForDart(query.sql.substring(lastIndex)))..write("'");
|
_buffer..write(escapeForDart(query.sql.substring(lastIndex)))..write("'");
|
||||||
|
|
||||||
return buffer.toString();
|
return buffer.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _writeReadsFrom(StringBuffer buffer) {
|
void _writeReadsFrom() {
|
||||||
final from = _select.readsFrom.map((t) => t.tableFieldName).join(', ');
|
final from = _select.readsFrom.map((t) => t.tableFieldName).join(', ');
|
||||||
buffer..write('readsFrom: {')..write(from)..write('}');
|
_buffer..write('readsFrom: {')..write(from)..write('}');
|
||||||
}
|
}
|
||||||
|
|
||||||
void _writeUpdates(StringBuffer buffer) {
|
void _writeUpdates() {
|
||||||
final from = _update.updates.map((t) => t.tableFieldName).join(', ');
|
final from = _update.updates.map((t) => t.tableFieldName).join(', ');
|
||||||
buffer..write('updates: {')..write(from)..write('}');
|
_buffer..write('updates: {')..write(from)..write('}');
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:moor_generator/src/model/specified_column.dart';
|
import 'package:moor_generator/src/model/specified_column.dart';
|
||||||
import 'package:moor_generator/src/model/sql_query.dart';
|
import 'package:moor_generator/src/model/sql_query.dart';
|
||||||
|
|
||||||
|
/// Writes a class holding the result of an sql query into Dart.
|
||||||
class ResultSetWriter {
|
class ResultSetWriter {
|
||||||
final SqlSelectQuery query;
|
final SqlSelectQuery query;
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
import 'package:moor_generator/src/model/specified_table.dart';
|
import 'package:moor_generator/src/model/specified_table.dart';
|
||||||
import 'package:moor_generator/src/state/session.dart';
|
|
||||||
import 'package:moor_generator/src/writer/utils/hash_code.dart';
|
import 'package:moor_generator/src/writer/utils/hash_code.dart';
|
||||||
|
import 'package:moor_generator/src/writer/writer.dart';
|
||||||
import 'package:recase/recase.dart';
|
import 'package:recase/recase.dart';
|
||||||
|
|
||||||
class DataClassWriter {
|
class DataClassWriter {
|
||||||
final SpecifiedTable table;
|
final SpecifiedTable table;
|
||||||
final GeneratorSession session;
|
final Scope scope;
|
||||||
|
|
||||||
DataClassWriter(this.table, this.session);
|
StringBuffer _buffer;
|
||||||
|
|
||||||
|
DataClassWriter(this.table, this.scope) {
|
||||||
|
_buffer = scope.leaf();
|
||||||
|
}
|
||||||
|
|
||||||
void writeInto(StringBuffer buffer) {
|
void writeInto(StringBuffer buffer) {
|
||||||
buffer.write(
|
buffer.write(
|
|
@ -4,7 +4,7 @@ import 'package:moor_generator/src/state/session.dart';
|
||||||
import 'package:moor_generator/src/utils/string_escaper.dart';
|
import 'package:moor_generator/src/utils/string_escaper.dart';
|
||||||
import 'package:moor_generator/src/writer/data_class_writer.dart';
|
import 'package:moor_generator/src/writer/data_class_writer.dart';
|
||||||
import 'package:moor_generator/src/writer/update_companion_writer.dart';
|
import 'package:moor_generator/src/writer/update_companion_writer.dart';
|
||||||
import 'package:moor_generator/src/writer/utils.dart';
|
import 'package:moor_generator/src/writer/memoized_getter.dart';
|
||||||
|
|
||||||
class TableWriter {
|
class TableWriter {
|
||||||
final SpecifiedTable table;
|
final SpecifiedTable table;
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:moor_generator/src/backends/build/moor_builder.dart';
|
||||||
|
|
||||||
/// Manages a tree structure which we use to generate code.
|
/// Manages a tree structure which we use to generate code.
|
||||||
///
|
///
|
||||||
|
@ -10,6 +11,9 @@ import 'package:meta/meta.dart';
|
||||||
/// passing a [Scope] we will always be able to write code in a parent scope.
|
/// passing a [Scope] we will always be able to write code in a parent scope.
|
||||||
class Writer {
|
class Writer {
|
||||||
final Scope _root = Scope(parent: null);
|
final Scope _root = Scope(parent: null);
|
||||||
|
final MoorOptions options;
|
||||||
|
|
||||||
|
Writer(this.options);
|
||||||
|
|
||||||
String writeGenerated() => _leafNodes(_root).join();
|
String writeGenerated() => _leafNodes(_root).join();
|
||||||
|
|
||||||
|
@ -41,8 +45,13 @@ abstract class _Node {
|
||||||
/// we just pass a single [StringBuffer] around, this is annoying to manage.
|
/// we just pass a single [StringBuffer] around, this is annoying to manage.
|
||||||
class Scope extends _Node {
|
class Scope extends _Node {
|
||||||
final List<_Node> _children = [];
|
final List<_Node> _children = [];
|
||||||
|
final DartScope scope;
|
||||||
|
final Writer writer;
|
||||||
|
|
||||||
Scope({@required Scope parent}) : super(parent);
|
Scope({@required Scope parent, Writer writer})
|
||||||
|
: scope = parent?.scope?.nextLevel ?? DartScope.library,
|
||||||
|
writer = writer ?? parent?.writer,
|
||||||
|
super(parent);
|
||||||
|
|
||||||
Scope get root {
|
Scope get root {
|
||||||
var found = this;
|
var found = this;
|
||||||
|
@ -52,6 +61,19 @@ class Scope extends _Node {
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Iterable<Scope> get _thisAndParents sync* {
|
||||||
|
var scope = this;
|
||||||
|
do {
|
||||||
|
yield scope;
|
||||||
|
scope = scope.parent;
|
||||||
|
} while (scope != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Scope findScopeOfLevel(DartScope level) {
|
||||||
|
return _thisAndParents
|
||||||
|
.firstWhere((scope) => scope.scope.isSuperScope(level));
|
||||||
|
}
|
||||||
|
|
||||||
Scope child() {
|
Scope child() {
|
||||||
final child = Scope(parent: this);
|
final child = Scope(parent: this);
|
||||||
_children.add(child);
|
_children.add(child);
|
||||||
|
@ -70,3 +92,27 @@ class _LeafNode extends _Node {
|
||||||
|
|
||||||
_LeafNode(Scope parent) : super(parent);
|
_LeafNode(Scope parent) : super(parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DartScope {
|
||||||
|
static const DartScope library = DartScope._(0);
|
||||||
|
static const DartScope topLevelMember = DartScope._(1);
|
||||||
|
static const DartScope inner = DartScope._(2);
|
||||||
|
|
||||||
|
static const List<DartScope> values = [library, topLevelMember, inner];
|
||||||
|
|
||||||
|
final int _id;
|
||||||
|
|
||||||
|
const DartScope._(this._id);
|
||||||
|
|
||||||
|
DartScope get nextLevel {
|
||||||
|
if (_id == values.length - 1) {
|
||||||
|
// already in innermost level
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return values[_id + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isSuperScope(DartScope other) {
|
||||||
|
return other._id >= _id;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue