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/session.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/used_type_converter.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:source_gen/source_gen.dart';
|
||||
|
||||
part 'column_parser.dart';
|
||||
part 'table_parser.dart';
|
||||
part 'use_dao_parser.dart';
|
||||
part 'use_moor_parser.dart';
|
||||
|
||||
class MoorDartParser {
|
||||
final DartTask task;
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
import 'package:analyzer/dart/element/element.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';
|
||||
part of 'parser.dart';
|
||||
|
||||
class UseDaoParser {
|
||||
final GeneratorSession session;
|
||||
final DartTask dartTask;
|
||||
|
||||
UseDaoParser(this.session);
|
||||
UseDaoParser(this.dartTask);
|
||||
|
||||
/// If [element] has a `@UseDao` annotation, parses the database model
|
||||
/// declared by that class and the referenced tables.
|
||||
|
@ -24,11 +21,11 @@ class UseDaoParser {
|
|||
?.map((e) => e.toStringValue()) ??
|
||||
{};
|
||||
|
||||
final parsedTables = await session.parseTables(tableTypes, element);
|
||||
parsedTables.addAll(await session.resolveIncludes(includes));
|
||||
final parsedTables = await dartTask.parseTables(tableTypes, element);
|
||||
parsedTables.addAll(await dartTask.resolveIncludes(includes));
|
||||
|
||||
final parsedQueries =
|
||||
await session.parseQueries(queryStrings, parsedTables);
|
||||
await dartTask.parseQueries(queryStrings, parsedTables);
|
||||
|
||||
return SpecifiedDao(element, parsedTables, parsedQueries);
|
||||
}
|
|
@ -1,13 +1,9 @@
|
|||
import 'package:analyzer/dart/element/element.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';
|
||||
part of 'parser.dart';
|
||||
|
||||
class UseMoorParser {
|
||||
final GeneratorSession session;
|
||||
final DartTask task;
|
||||
|
||||
UseMoorParser(this.session);
|
||||
UseMoorParser(this.task);
|
||||
|
||||
/// If [element] has a `@UseMoor` annotation, parses the database model
|
||||
/// declared by that class and the referenced tables.
|
||||
|
@ -25,11 +21,10 @@ class UseMoorParser {
|
|||
?.map((e) => e.toStringValue()) ??
|
||||
{};
|
||||
|
||||
final parsedTables = await session.parseTables(tableTypes, element);
|
||||
parsedTables.addAll(await session.resolveIncludes(includes));
|
||||
final parsedTables = await task.parseTables(tableTypes, element);
|
||||
parsedTables.addAll(await task.resolveIncludes(includes));
|
||||
|
||||
final parsedQueries =
|
||||
await session.parseQueries(queryStrings, parsedTables);
|
||||
final parsedQueries = await task.parseQueries(queryStrings, parsedTables);
|
||||
final daoTypes = _readDaoTypes(annotation);
|
||||
|
||||
return SpecifiedDatabase(element, parsedTables, daoTypes, parsedQueries);
|
|
@ -1,22 +1,34 @@
|
|||
import 'package:analyzer/dart/element/element.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.
|
||||
class MoorError {
|
||||
final Severity severity;
|
||||
final String message;
|
||||
|
||||
MoorError(this.severity);
|
||||
MoorError({@required this.severity, this.message});
|
||||
}
|
||||
|
||||
class ErrorInDartCode extends MoorError {
|
||||
final String message;
|
||||
final Element affectedElement;
|
||||
|
||||
ErrorInDartCode(
|
||||
{this.message,
|
||||
{String message,
|
||||
this.affectedElement,
|
||||
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 {
|
||||
|
|
|
@ -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_table.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/string_escaper.dart';
|
||||
import 'package:recase/recase.dart';
|
||||
import 'package:sqlparser/sqlparser.dart';
|
||||
|
||||
/*
|
||||
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 {
|
||||
class CreateTableReader {
|
||||
/// The AST of this `CREATE TABLE` statement.
|
||||
final ParseResult ast;
|
||||
|
||||
CreateTable(this.ast);
|
||||
CreateTableReader(this.ast);
|
||||
|
||||
SpecifiedTable extractTable(TypeMapper mapper) {
|
||||
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 'package:analyzer/dart/constant/value.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/moor/parser.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/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.
|
||||
class MoorSession {
|
||||
MoorSession();
|
||||
|
||||
Future<DartTask> startDartTask(BackendTask backendTask) async {
|
||||
final library = await backendTask.resolveDart(backendTask.entrypoint);
|
||||
Future<DartTask> startDartTask(BackendTask backendTask, {String uri}) async {
|
||||
final input = uri ?? backendTask.entrypoint;
|
||||
final library = await backendTask.resolveDart(input);
|
||||
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.
|
||||
|
@ -31,27 +50,89 @@ abstract class FileTask<R extends ParsedFile> {
|
|||
|
||||
/// Session used to parse a Dart file and extract table information.
|
||||
class DartTask extends FileTask<ParsedDartFile> {
|
||||
static const tableTypeChecker = const TypeChecker.fromRuntime(Table);
|
||||
|
||||
final LibraryElement library;
|
||||
MoorDartParser _parser;
|
||||
MoorDartParser get parser => _parser;
|
||||
|
||||
DartTask(MoorSession session, BackendTask task, this.library)
|
||||
: super(task, session);
|
||||
: super(task, session) {
|
||||
_parser = MoorDartParser(this);
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<ParsedDartFile> compute() {
|
||||
// TODO: implement compute
|
||||
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> {
|
||||
final List<String> content;
|
||||
final String content;
|
||||
final TypeMapper mapper = TypeMapper();
|
||||
|
||||
MoorTask(BackendTask task, MoorSession session, this.content)
|
||||
: super(task, session);
|
||||
|
||||
@override
|
||||
FutureOr<ParsedMoorFile> compute() {
|
||||
// TODO: implement compute
|
||||
return null;
|
||||
final parser = MoorParser(this);
|
||||
return parser.parseAndAnalyze();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import 'package:moor_generator/src/model/sql_query.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:sqlparser/sqlparser.dart' hide ResultColumn;
|
||||
|
||||
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 {
|
||||
final String name;
|
||||
final AnalysisContext context;
|
|
@ -1,16 +1,16 @@
|
|||
import 'package:analyzer/dart/constant/value.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/sql_query.dart';
|
||||
import 'package:moor_generator/src/parser/sql/query_handler.dart';
|
||||
import 'package:moor_generator/src/parser/sql/type_mapping.dart';
|
||||
import 'package:moor_generator/src/state/session.dart';
|
||||
import 'package:moor_generator/src/analyzer/sql_queries/query_handler.dart';
|
||||
import 'package:moor_generator/src/analyzer/sql_queries/type_mapping.dart';
|
||||
import 'package:sqlparser/sqlparser.dart' hide ResultColumn;
|
||||
|
||||
class SqlParser {
|
||||
final List<SpecifiedTable> tables;
|
||||
final GeneratorSession session;
|
||||
final FileTask task;
|
||||
final Map<DartObject, DartObject> definedQueries;
|
||||
|
||||
final TypeMapper _mapper = TypeMapper();
|
||||
|
@ -18,7 +18,7 @@ class SqlParser {
|
|||
|
||||
final List<SqlQuery> foundQueries = [];
|
||||
|
||||
SqlParser(this.session, this.tables, this.definedQueries);
|
||||
SqlParser(this.task, this.tables, this.definedQueries);
|
||||
|
||||
void _spawnEngine() {
|
||||
_engine = SqlEngine();
|
||||
|
@ -36,14 +36,15 @@ class SqlParser {
|
|||
try {
|
||||
context = _engine.analyze(sql);
|
||||
} catch (e, s) {
|
||||
session.errors.add(MoorError(
|
||||
critical: true,
|
||||
task.reportError(MoorError(
|
||||
severity: Severity.criticalError,
|
||||
message: 'Error while trying to parse $sql: $e, $s'));
|
||||
return;
|
||||
}
|
||||
|
||||
for (var error in context.errors) {
|
||||
session.errors.add(MoorError(
|
||||
task.reportError(MoorError(
|
||||
severity: Severity.warning,
|
||||
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:moor_generator/src/backends/build/build_backend.dart';
|
||||
import 'package:moor_generator/src/dao_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';
|
||||
|
||||
part 'options.dart';
|
||||
|
||||
class MoorBuilder extends SharedPartBuilder {
|
||||
final BuildBackend backend = BuildBackend();
|
||||
|
||||
factory MoorBuilder(BuilderOptions options) {
|
||||
final parsedOptions = MoorOptions.fromBuilder(options.config);
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
part of 'moor_builder.dart';
|
||||
|
||||
class MoorOptions {
|
||||
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/state/session.dart';
|
||||
import 'package:moor_generator/src/writer/query_writer.dart';
|
||||
import 'package:moor_generator/src/writer/result_set_writer.dart';
|
||||
import 'package:moor_generator/src/writer/utils/memoized_getter.dart';
|
||||
import 'package:recase/recase.dart';
|
||||
import 'package:moor_generator/src/model/specified_database.dart';
|
||||
import 'package:moor_generator/src/writer/table_writer.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
class DatabaseWriter {
|
||||
final SpecifiedDatabase db;
|
||||
|
@ -19,13 +16,6 @@ class DatabaseWriter {
|
|||
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
|
||||
final className = '_\$${db.fromClass.name}';
|
||||
buffer.write('abstract class $className extends GeneratedDatabase {\n'
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
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/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/writer/queries/result_set_writer.dart';
|
||||
import 'package:moor_generator/src/writer/writer.dart';
|
||||
import 'package:recase/recase.dart';
|
||||
import 'package:sqlparser/sqlparser.dart';
|
||||
|
||||
|
@ -16,13 +18,18 @@ const highestAssignedIndexVar = '\$highestIndex';
|
|||
/// should be included in a generated database or dao class.
|
||||
class QueryWriter {
|
||||
final SqlQuery query;
|
||||
final GeneratorSession session;
|
||||
final Scope scope;
|
||||
SqlSelectQuery get _select => query as SqlSelectQuery;
|
||||
UpdatingQuery get _update => query as UpdatingQuery;
|
||||
|
||||
MoorOptions get options => scope.writer.options;
|
||||
StringBuffer _buffer;
|
||||
|
||||
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
|
||||
/// appears. For the query "SELECT * FROM t WHERE x IN ?", we generate
|
||||
|
@ -36,19 +43,25 @@ class QueryWriter {
|
|||
return 'expanded${v.dartParameterName}';
|
||||
}
|
||||
|
||||
void writeInto(StringBuffer buffer) {
|
||||
void writeInto() {
|
||||
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) {
|
||||
_writeUpdatingQuery(buffer);
|
||||
_writeUpdatingQuery();
|
||||
}
|
||||
}
|
||||
|
||||
void _writeSelect(StringBuffer buffer) {
|
||||
_writeMapping(buffer);
|
||||
_writeSelectStatementCreator(buffer);
|
||||
_writeOneTimeReader(buffer);
|
||||
_writeStreamReader(buffer);
|
||||
void _writeSelect() {
|
||||
_writeMapping();
|
||||
_writeSelectStatementCreator();
|
||||
_writeOneTimeReader();
|
||||
_writeStreamReader();
|
||||
}
|
||||
|
||||
String _nameOfMappingMethod() {
|
||||
|
@ -61,11 +74,11 @@ class QueryWriter {
|
|||
|
||||
/// Writes a mapping method that turns a "QueryRow" into the desired custom
|
||||
/// return type.
|
||||
void _writeMapping(StringBuffer buffer) {
|
||||
void _writeMapping() {
|
||||
// avoid writing mapping methods twice if the same result class is written
|
||||
// more than once.
|
||||
if (!_writtenMappingMethods.contains(_nameOfMappingMethod())) {
|
||||
buffer
|
||||
_buffer
|
||||
..write('${_select.resultClassName} ${_nameOfMappingMethod()}')
|
||||
..write('(QueryRow row) {\n')
|
||||
..write('return ${_select.resultClassName}(');
|
||||
|
@ -84,35 +97,35 @@ class QueryWriter {
|
|||
code = '$field.mapToDart($code)';
|
||||
}
|
||||
|
||||
buffer.write('$fieldName: $code,');
|
||||
_buffer.write('$fieldName: $code,');
|
||||
}
|
||||
|
||||
buffer.write(');\n}\n');
|
||||
_buffer.write(');\n}\n');
|
||||
_writtenMappingMethods.add(_nameOfMappingMethod());
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes a method returning a `Selectable<T>`, where `T` is the return type
|
||||
/// of the custom query.
|
||||
void _writeSelectStatementCreator(StringBuffer buffer) {
|
||||
void _writeSelectStatementCreator() {
|
||||
final returnType = 'Selectable<${_select.resultClassName}>';
|
||||
final methodName = _nameOfCreationMethod();
|
||||
|
||||
buffer.write('$returnType $methodName(');
|
||||
_writeParameters(buffer);
|
||||
buffer.write(') {\n');
|
||||
_buffer.write('$returnType $methodName(');
|
||||
_writeParameters();
|
||||
_buffer.write(') {\n');
|
||||
|
||||
_writeExpandedDeclarations(buffer);
|
||||
buffer
|
||||
_writeExpandedDeclarations();
|
||||
_buffer
|
||||
..write('return (operateOn ?? this).')
|
||||
..write('customSelectQuery(${_queryCode()}, ');
|
||||
_writeVariables(buffer);
|
||||
buffer.write(', ');
|
||||
_writeReadsFrom(buffer);
|
||||
_writeVariables();
|
||||
_buffer.write(', ');
|
||||
_writeReadsFrom();
|
||||
|
||||
buffer.write(').map(');
|
||||
buffer.write(_nameOfMappingMethod());
|
||||
buffer.write(');\n}\n');
|
||||
_buffer.write(').map(');
|
||||
_buffer.write(_nameOfMappingMethod());
|
||||
_buffer.write(');\n}\n');
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -122,34 +135,35 @@ class QueryWriter {
|
|||
}
|
||||
*/
|
||||
|
||||
void _writeOneTimeReader(StringBuffer buffer) {
|
||||
buffer.write('Future<List<${_select.resultClassName}>> ${query.name}(');
|
||||
_writeParameters(buffer);
|
||||
buffer..write(') {\n')..write('return ${_nameOfCreationMethod()}(');
|
||||
_writeUseParameters(buffer);
|
||||
buffer.write(').get();\n}\n');
|
||||
void _writeOneTimeReader() {
|
||||
_buffer.write('Future<List<${_select.resultClassName}>> ${query.name}(');
|
||||
_writeParameters();
|
||||
_buffer..write(') {\n')..write('return ${_nameOfCreationMethod()}(');
|
||||
_writeUseParameters();
|
||||
_buffer.write(').get();\n}\n');
|
||||
}
|
||||
|
||||
void _writeStreamReader(StringBuffer buffer) {
|
||||
void _writeStreamReader() {
|
||||
final upperQueryName = ReCase(query.name).pascalCase;
|
||||
|
||||
String methodName;
|
||||
// turning the query name into pascal case will remove underscores, add the
|
||||
// "private" modifier back in if needed
|
||||
if (session.options.fixPrivateWatchMethods && query.name.startsWith('_')) {
|
||||
if (scope.writer.options.fixPrivateWatchMethods &&
|
||||
query.name.startsWith('_')) {
|
||||
methodName = '_watch$upperQueryName';
|
||||
} else {
|
||||
methodName = 'watch$upperQueryName';
|
||||
}
|
||||
|
||||
buffer.write('Stream<List<${_select.resultClassName}>> $methodName(');
|
||||
_writeParameters(buffer, dontOverrideEngine: true);
|
||||
buffer..write(') {\n')..write('return ${_nameOfCreationMethod()}(');
|
||||
_writeUseParameters(buffer, dontUseEngine: true);
|
||||
buffer.write(').watch();\n}\n');
|
||||
_buffer.write('Stream<List<${_select.resultClassName}>> $methodName(');
|
||||
_writeParameters(dontOverrideEngine: true);
|
||||
_buffer..write(') {\n')..write('return ${_nameOfCreationMethod()}(');
|
||||
_writeUseParameters(dontUseEngine: true);
|
||||
_buffer.write(').watch();\n}\n');
|
||||
}
|
||||
|
||||
void _writeUpdatingQuery(StringBuffer buffer) {
|
||||
void _writeUpdatingQuery() {
|
||||
/*
|
||||
Future<int> test() {
|
||||
return customUpdate('', variables: [], updates: {});
|
||||
|
@ -157,24 +171,23 @@ class QueryWriter {
|
|||
*/
|
||||
final implName = _update.isInsert ? 'customInsert' : 'customUpdate';
|
||||
|
||||
buffer.write('Future<int> ${query.name}(');
|
||||
_writeParameters(buffer);
|
||||
buffer.write(') {\n');
|
||||
_buffer.write('Future<int> ${query.name}(');
|
||||
_writeParameters();
|
||||
_buffer.write(') {\n');
|
||||
|
||||
_writeExpandedDeclarations(buffer);
|
||||
buffer
|
||||
_writeExpandedDeclarations();
|
||||
_buffer
|
||||
..write('return (operateOn ?? this).')
|
||||
..write('$implName(${_queryCode()},');
|
||||
|
||||
_writeVariables(buffer);
|
||||
buffer.write(',');
|
||||
_writeUpdates(buffer);
|
||||
_writeVariables();
|
||||
_buffer.write(',');
|
||||
_writeUpdates();
|
||||
|
||||
buffer..write(',);\n}\n');
|
||||
_buffer..write(',);\n}\n');
|
||||
}
|
||||
|
||||
void _writeParameters(StringBuffer buffer,
|
||||
{bool dontOverrideEngine = false}) {
|
||||
void _writeParameters({bool dontOverrideEngine = false}) {
|
||||
final paramList = query.variables.map((v) {
|
||||
var dartType = dartTypeNames[v.type];
|
||||
if (v.isArray) {
|
||||
|
@ -183,13 +196,13 @@ class QueryWriter {
|
|||
return '$dartType ${v.dartParameterName}';
|
||||
}).join(', ');
|
||||
|
||||
buffer.write(paramList);
|
||||
_buffer.write(paramList);
|
||||
|
||||
// write named optional parameter to configure the query engine used to
|
||||
// execute the statement,
|
||||
if (!dontOverrideEngine) {
|
||||
if (query.variables.isNotEmpty) buffer.write(', ');
|
||||
buffer.write('{@Deprecated(${asDartLiteral(queryEngineWarningDesc)}) '
|
||||
if (query.variables.isNotEmpty) _buffer.write(', ');
|
||||
_buffer.write('{@Deprecated(${asDartLiteral(queryEngineWarningDesc)}) '
|
||||
'QueryEngine operateOn}');
|
||||
}
|
||||
}
|
||||
|
@ -197,11 +210,11 @@ class QueryWriter {
|
|||
/// Writes code that uses the parameters as declared by [_writeParameters],
|
||||
/// assuming that for each parameter, a variable with the same name exists
|
||||
/// in the current scope.
|
||||
void _writeUseParameters(StringBuffer into, {bool dontUseEngine = false}) {
|
||||
into.write(query.variables.map((v) => v.dartParameterName).join(', '));
|
||||
void _writeUseParameters({bool dontUseEngine = false}) {
|
||||
_buffer.write(query.variables.map((v) => v.dartParameterName).join(', '));
|
||||
if (!dontUseEngine) {
|
||||
if (query.variables.isNotEmpty) into.write(', ');
|
||||
into.write('operateOn: operateOn');
|
||||
if (query.variables.isNotEmpty) _buffer.write(', ');
|
||||
_buffer.write('operateOn: operateOn');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -217,7 +230,7 @@ class QueryWriter {
|
|||
// "vars" variable twice. To do this, a local var called "$currentVarIndex"
|
||||
// keeps track of the highest variable number assigned.
|
||||
|
||||
void _writeExpandedDeclarations(StringBuffer buffer) {
|
||||
void _writeExpandedDeclarations() {
|
||||
var indexCounterWasDeclared = false;
|
||||
var highestIndexBeforeArray = 0;
|
||||
|
||||
|
@ -228,12 +241,12 @@ class QueryWriter {
|
|||
// add +1 because that's going to be the first index of the expanded
|
||||
// array
|
||||
final firstVal = highestIndexBeforeArray + 1;
|
||||
buffer.write('var $highestAssignedIndexVar = $firstVal;');
|
||||
_buffer.write('var $highestAssignedIndexVar = $firstVal;');
|
||||
indexCounterWasDeclared = true;
|
||||
}
|
||||
|
||||
// final expandedvar1 = $expandVar(<startIndex>, <amount>);
|
||||
buffer
|
||||
_buffer
|
||||
..write('final ')
|
||||
..write(_expandedName(variable))
|
||||
..write(' = ')
|
||||
|
@ -244,7 +257,7 @@ class QueryWriter {
|
|||
..write('.length);\n');
|
||||
|
||||
// increase highest index for the next array
|
||||
buffer
|
||||
_buffer
|
||||
..write('$highestAssignedIndexVar += ')
|
||||
..write(variable.dartParameterName)
|
||||
..write('.length;');
|
||||
|
@ -256,8 +269,8 @@ class QueryWriter {
|
|||
}
|
||||
}
|
||||
|
||||
void _writeVariables(StringBuffer buffer) {
|
||||
buffer..write('variables: [');
|
||||
void _writeVariables() {
|
||||
_buffer..write('variables: [');
|
||||
|
||||
for (var variable in query.variables) {
|
||||
// for a regular variable: Variable.withInt(x),
|
||||
|
@ -266,15 +279,15 @@ class QueryWriter {
|
|||
final name = variable.dartParameterName;
|
||||
|
||||
if (variable.isArray) {
|
||||
buffer.write('for (var \$ in $name) $constructor(\$)');
|
||||
_buffer.write('for (var \$ in $name) $constructor(\$)');
|
||||
} 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
|
||||
|
@ -295,29 +308,29 @@ class QueryWriter {
|
|||
.singleWhere((f) => f.variable.resolvedIndex == sqlVar.resolvedIndex);
|
||||
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 queryPart = query.sql.substring(lastIndex, currentIndex);
|
||||
buffer.write(escapeForDart(queryPart));
|
||||
_buffer.write(escapeForDart(queryPart));
|
||||
lastIndex = sqlVar.lastPosition;
|
||||
|
||||
// write the ($expandedVar) par
|
||||
buffer.write('(\$${_expandedName(moorVar)})');
|
||||
_buffer.write('(\$${_expandedName(moorVar)})');
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
void _writeReadsFrom(StringBuffer buffer) {
|
||||
void _writeReadsFrom() {
|
||||
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(', ');
|
||||
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/sql_query.dart';
|
||||
|
||||
/// Writes a class holding the result of an sql query into Dart.
|
||||
class ResultSetWriter {
|
||||
final SqlSelectQuery query;
|
||||
|
|
@ -1,13 +1,17 @@
|
|||
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/writer.dart';
|
||||
import 'package:recase/recase.dart';
|
||||
|
||||
class DataClassWriter {
|
||||
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) {
|
||||
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/writer/data_class_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 {
|
||||
final SpecifiedTable table;
|
|
@ -1,4 +1,5 @@
|
|||
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.
|
||||
///
|
||||
|
@ -10,6 +11,9 @@ import 'package:meta/meta.dart';
|
|||
/// passing a [Scope] we will always be able to write code in a parent scope.
|
||||
class Writer {
|
||||
final Scope _root = Scope(parent: null);
|
||||
final MoorOptions options;
|
||||
|
||||
Writer(this.options);
|
||||
|
||||
String writeGenerated() => _leafNodes(_root).join();
|
||||
|
||||
|
@ -41,8 +45,13 @@ abstract class _Node {
|
|||
/// we just pass a single [StringBuffer] around, this is annoying to manage.
|
||||
class Scope extends _Node {
|
||||
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 {
|
||||
var found = this;
|
||||
|
@ -52,6 +61,19 @@ class Scope extends _Node {
|
|||
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() {
|
||||
final child = Scope(parent: this);
|
||||
_children.add(child);
|
||||
|
@ -70,3 +92,27 @@ class _LeafNode extends _Node {
|
|||
|
||||
_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