mirror of https://github.com/AMT-Cheif/drift.git
Add code generation for nested result queries
This commit is contained in:
parent
ca8482be71
commit
23de4c5cee
|
@ -1,6 +1,8 @@
|
||||||
// Mega compilation unit that includes all Dart apis related to generating SQL
|
// Mega compilation unit that includes all Dart apis related to generating SQL
|
||||||
// at runtime.
|
// at runtime.
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:drift/sqlite_keywords.dart';
|
import 'package:drift/sqlite_keywords.dart';
|
||||||
|
|
|
@ -250,28 +250,29 @@ abstract class Selectable<T>
|
||||||
///
|
///
|
||||||
/// Each entry emitted by this [Selectable] will be transformed by the
|
/// Each entry emitted by this [Selectable] will be transformed by the
|
||||||
/// [mapper] and then emitted to the selectable returned.
|
/// [mapper] and then emitted to the selectable returned.
|
||||||
Selectable<N> map<N>(N Function(T) mapper) {
|
Selectable<N> map<N>(FutureOr<N> Function(T) mapper) {
|
||||||
return _MappedSelectable<T, N>(this, mapper);
|
return _MappedSelectable<T, N>(this, mapper);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MappedSelectable<S, T> extends Selectable<T> {
|
class _MappedSelectable<S, T> extends Selectable<T> {
|
||||||
final Selectable<S> _source;
|
final Selectable<S> _source;
|
||||||
final T Function(S) _mapper;
|
final FutureOr<T> Function(S) _mapper;
|
||||||
|
|
||||||
_MappedSelectable(this._source, this._mapper);
|
_MappedSelectable(this._source, this._mapper);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<T>> get() {
|
Future<List<T>> get() async {
|
||||||
return _source.get().then(_mapResults);
|
return _source.get().then(_mapResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<List<T>> watch() {
|
Stream<List<T>> watch() {
|
||||||
return _source.watch().map(_mapResults);
|
return _source.watch().asyncMap(_mapResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<T> _mapResults(List<S> results) => results.map(_mapper).toList();
|
Future<List<T>> _mapResults(List<S> results) async =>
|
||||||
|
[for (final result in results) await _mapper(result)];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mixin for a [Query] that operates on a single primary table only.
|
/// Mixin for a [Query] that operates on a single primary table only.
|
||||||
|
|
|
@ -167,8 +167,8 @@ class SqlAnalyzer extends BaseAnalyzer {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final handled =
|
final handled =
|
||||||
QueryHandler(query, context, mapper, requiredVariables: variables)
|
QueryHandler(context, mapper, requiredVariables: variables)
|
||||||
.handle()
|
.handle(query)
|
||||||
..declaredInMoorFile = query is DeclaredMoorQuery;
|
..declaredInMoorFile = query is DeclaredMoorQuery;
|
||||||
foundQueries.add(handled);
|
foundQueries.add(handled);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
|
|
|
@ -11,7 +11,6 @@ import 'required_variables.dart';
|
||||||
/// generator package by determining its type, return columns, variables and so
|
/// generator package by determining its type, return columns, variables and so
|
||||||
/// on.
|
/// on.
|
||||||
class QueryHandler {
|
class QueryHandler {
|
||||||
final DeclaredQuery source;
|
|
||||||
final AnalysisContext context;
|
final AnalysisContext context;
|
||||||
final TypeMapper mapper;
|
final TypeMapper mapper;
|
||||||
final RequiredVariables requiredVariables;
|
final RequiredVariables requiredVariables;
|
||||||
|
@ -23,19 +22,30 @@ class QueryHandler {
|
||||||
Iterable<FoundVariable> get _foundVariables =>
|
Iterable<FoundVariable> get _foundVariables =>
|
||||||
_foundElements.whereType<FoundVariable>();
|
_foundElements.whereType<FoundVariable>();
|
||||||
|
|
||||||
BaseSelectStatement get _select => context.root as BaseSelectStatement;
|
QueryHandler(
|
||||||
|
this.context,
|
||||||
|
this.mapper, {
|
||||||
|
this.requiredVariables = RequiredVariables.empty,
|
||||||
|
});
|
||||||
|
|
||||||
QueryHandler(this.source, this.context, this.mapper,
|
SqlQuery handle(DeclaredQuery source) {
|
||||||
{this.requiredVariables = RequiredVariables.empty});
|
|
||||||
|
|
||||||
String get name => source.name;
|
|
||||||
|
|
||||||
SqlQuery handle() {
|
|
||||||
_foundElements =
|
_foundElements =
|
||||||
mapper.extractElements(context, required: requiredVariables);
|
mapper.extractElements(context, required: requiredVariables);
|
||||||
|
|
||||||
_verifyNoSkippedIndexes();
|
_verifyNoSkippedIndexes();
|
||||||
final query = _mapToMoor();
|
|
||||||
|
final String? requestedResultClass;
|
||||||
|
if (source is DeclaredMoorQuery) {
|
||||||
|
requestedResultClass = source.astNode.as;
|
||||||
|
} else {
|
||||||
|
requestedResultClass = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final query = _mapToMoor(
|
||||||
|
queryName: source.name,
|
||||||
|
requestedResultClass: requestedResultClass,
|
||||||
|
root: context.root,
|
||||||
|
);
|
||||||
|
|
||||||
final linter = Linter.forHandler(this);
|
final linter = Linter.forHandler(this);
|
||||||
linter.reportLints();
|
linter.reportLints();
|
||||||
|
@ -44,14 +54,21 @@ class QueryHandler {
|
||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
SqlQuery _mapToMoor() {
|
SqlQuery _mapToMoor({
|
||||||
final root = context.root;
|
required String queryName,
|
||||||
|
required String? requestedResultClass,
|
||||||
|
required AstNode root,
|
||||||
|
}) {
|
||||||
if (root is BaseSelectStatement) {
|
if (root is BaseSelectStatement) {
|
||||||
return _handleSelect();
|
return _handleSelect(
|
||||||
|
queryName: queryName,
|
||||||
|
requestedResultClass: requestedResultClass,
|
||||||
|
select: root,
|
||||||
|
);
|
||||||
} else if (root is UpdateStatement ||
|
} else if (root is UpdateStatement ||
|
||||||
root is DeleteStatement ||
|
root is DeleteStatement ||
|
||||||
root is InsertStatement) {
|
root is InsertStatement) {
|
||||||
return _handleUpdate();
|
return _handleUpdate(queryName, root);
|
||||||
} else {
|
} else {
|
||||||
throw StateError('Unexpected sql: Got $root, expected insert, select, '
|
throw StateError('Unexpected sql: Got $root, expected insert, select, '
|
||||||
'update or delete');
|
'update or delete');
|
||||||
|
@ -63,25 +80,25 @@ class QueryHandler {
|
||||||
_foundViews = visitor.foundViews;
|
_foundViews = visitor.foundViews;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdatingQuery _handleUpdate() {
|
UpdatingQuery _handleUpdate(String queryName, AstNode root) {
|
||||||
final updatedFinder = UpdatedTablesVisitor();
|
final updatedFinder = UpdatedTablesVisitor();
|
||||||
context.root.acceptWithoutArg(updatedFinder);
|
root.acceptWithoutArg(updatedFinder);
|
||||||
_applyFoundTables(updatedFinder);
|
_applyFoundTables(updatedFinder);
|
||||||
|
|
||||||
final root = context.root;
|
|
||||||
final isInsert = root is InsertStatement;
|
final isInsert = root is InsertStatement;
|
||||||
|
|
||||||
InferredResultSet? resultSet;
|
InferredResultSet? resultSet;
|
||||||
if (root is StatementReturningColumns) {
|
if (root is StatementReturningColumns) {
|
||||||
final columns = root.returnedResultSet?.resolvedColumns;
|
final columns = root.returnedResultSet?.resolvedColumns;
|
||||||
if (columns != null) {
|
if (columns != null) {
|
||||||
resultSet = _inferResultSet(columns);
|
resultSet = _inferResultSet(root, columns);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return UpdatingQuery(
|
return UpdatingQuery(
|
||||||
name,
|
queryName,
|
||||||
context,
|
context,
|
||||||
|
root,
|
||||||
_foundElements,
|
_foundElements,
|
||||||
updatedFinder.writtenTables
|
updatedFinder.writtenTables
|
||||||
.map(mapper.writtenToMoor)
|
.map(mapper.writtenToMoor)
|
||||||
|
@ -93,9 +110,14 @@ class QueryHandler {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
SqlSelectQuery _handleSelect() {
|
SqlSelectQuery _handleSelect({
|
||||||
|
required String queryName,
|
||||||
|
required String? requestedResultClass,
|
||||||
|
required BaseSelectStatement select,
|
||||||
|
}) {
|
||||||
final tableFinder = ReferencedTablesVisitor();
|
final tableFinder = ReferencedTablesVisitor();
|
||||||
_select.acceptWithoutArg(tableFinder);
|
select.acceptWithoutArg(tableFinder);
|
||||||
|
// fine
|
||||||
_applyFoundTables(tableFinder);
|
_applyFoundTables(tableFinder);
|
||||||
|
|
||||||
final moorTables =
|
final moorTables =
|
||||||
|
@ -105,22 +127,18 @@ class QueryHandler {
|
||||||
|
|
||||||
final moorEntities = [...moorTables, ...moorViews];
|
final moorEntities = [...moorTables, ...moorViews];
|
||||||
|
|
||||||
String? requestedName;
|
|
||||||
if (source is DeclaredMoorQuery) {
|
|
||||||
requestedName = (source as DeclaredMoorQuery).astNode.as;
|
|
||||||
}
|
|
||||||
|
|
||||||
return SqlSelectQuery(
|
return SqlSelectQuery(
|
||||||
name,
|
queryName,
|
||||||
context,
|
context,
|
||||||
|
select,
|
||||||
_foundElements,
|
_foundElements,
|
||||||
moorEntities,
|
moorEntities,
|
||||||
_inferResultSet(_select.resolvedColumns!),
|
_inferResultSet(select, select.resolvedColumns!),
|
||||||
requestedName,
|
requestedResultClass,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
InferredResultSet _inferResultSet(List<Column> rawColumns) {
|
InferredResultSet _inferResultSet(AstNode select, List<Column> rawColumns) {
|
||||||
final candidatesForSingleTable = {..._foundTables, ..._foundViews};
|
final candidatesForSingleTable = {..._foundTables, ..._foundViews};
|
||||||
final columns = <ResultColumn>[];
|
final columns = <ResultColumn>[];
|
||||||
|
|
||||||
|
@ -140,7 +158,7 @@ class QueryHandler {
|
||||||
candidatesForSingleTable.removeWhere((t) => t != resultSet);
|
candidatesForSingleTable.removeWhere((t) => t != resultSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
final nestedResults = _findNestedResultTables();
|
final nestedResults = _findNestedResultTables(select);
|
||||||
if (nestedResults.isNotEmpty) {
|
if (nestedResults.isNotEmpty) {
|
||||||
// The single table optimization doesn't make sense when nested result
|
// The single table optimization doesn't make sense when nested result
|
||||||
// sets are present.
|
// sets are present.
|
||||||
|
@ -202,12 +220,11 @@ class QueryHandler {
|
||||||
return InferredResultSet(null, columns, nestedResults: nestedResults);
|
return InferredResultSet(null, columns, nestedResults: nestedResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<NestedResultTable> _findNestedResultTables() {
|
List<NestedResult> _findNestedResultTables(AstNode query) {
|
||||||
final query = context.root;
|
|
||||||
// We don't currently support nested results for compound statements
|
// We don't currently support nested results for compound statements
|
||||||
if (query is! SelectStatement) return const [];
|
if (query is! SelectStatement) return const [];
|
||||||
|
|
||||||
final nestedTables = <NestedResultTable>[];
|
final nestedTables = <NestedResult>[];
|
||||||
final analysis = JoinModel.of(query);
|
final analysis = JoinModel.of(query);
|
||||||
|
|
||||||
for (final column in query.columns) {
|
for (final column in query.columns) {
|
||||||
|
@ -225,6 +242,15 @@ class QueryHandler {
|
||||||
moorTable,
|
moorTable,
|
||||||
isNullable: isNullable,
|
isNullable: isNullable,
|
||||||
));
|
));
|
||||||
|
} else if (column is NestedQueryColumn) {
|
||||||
|
nestedTables.add(NestedResultQuery(
|
||||||
|
from: column,
|
||||||
|
query: _handleSelect(
|
||||||
|
queryName: 'nested',
|
||||||
|
requestedResultClass: column.as,
|
||||||
|
select: column.select,
|
||||||
|
),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import 'package:build/build.dart';
|
import 'package:build/build.dart';
|
||||||
import 'package:drift_dev/moor_generator.dart';
|
|
||||||
import 'package:drift_dev/src/backends/build/moor_builder.dart';
|
import 'package:drift_dev/src/backends/build/moor_builder.dart';
|
||||||
import 'package:drift_dev/src/utils/type_utils.dart';
|
import 'package:drift_dev/src/utils/type_utils.dart';
|
||||||
import 'package:drift_dev/writer.dart';
|
import 'package:drift_dev/writer.dart';
|
||||||
|
@ -32,9 +31,8 @@ class DaoGenerator extends Generator implements BaseGenerator {
|
||||||
'$infoType get $getterName => attachedDatabase.$getterName;\n');
|
'$infoType get $getterName => attachedDatabase.$getterName;\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final query in dao.queries ?? const <SqlQuery>[]) {
|
dao.queries
|
||||||
QueryWriter(query, classScope.child()).write();
|
?.forEach((query) => QueryWriter(classScope.child()).write(query));
|
||||||
}
|
|
||||||
|
|
||||||
classScope.leaf().write('}');
|
classScope.leaf().write('}');
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,6 +68,7 @@ abstract class SqlQuery {
|
||||||
final String name;
|
final String name;
|
||||||
|
|
||||||
AnalysisContext? get fromContext;
|
AnalysisContext? get fromContext;
|
||||||
|
AstNode? get root;
|
||||||
List<AnalysisError>? lints;
|
List<AnalysisError>? lints;
|
||||||
|
|
||||||
/// Whether this query was declared in a `.moor` file.
|
/// Whether this query was declared in a `.moor` file.
|
||||||
|
@ -165,6 +166,8 @@ class SqlSelectQuery extends SqlQuery {
|
||||||
final InferredResultSet resultSet;
|
final InferredResultSet resultSet;
|
||||||
@override
|
@override
|
||||||
final AnalysisContext fromContext;
|
final AnalysisContext fromContext;
|
||||||
|
@override
|
||||||
|
final AstNode root;
|
||||||
|
|
||||||
/// The name of the result class, as requested by the user.
|
/// The name of the result class, as requested by the user.
|
||||||
// todo: Allow custom result classes for RETURNING as well?
|
// todo: Allow custom result classes for RETURNING as well?
|
||||||
|
@ -173,6 +176,7 @@ class SqlSelectQuery extends SqlQuery {
|
||||||
SqlSelectQuery(
|
SqlSelectQuery(
|
||||||
String name,
|
String name,
|
||||||
this.fromContext,
|
this.fromContext,
|
||||||
|
this.root,
|
||||||
List<FoundElement> elements,
|
List<FoundElement> elements,
|
||||||
this.readsFrom,
|
this.readsFrom,
|
||||||
this.resultSet,
|
this.resultSet,
|
||||||
|
@ -196,6 +200,7 @@ class SqlSelectQuery extends SqlQuery {
|
||||||
return SqlSelectQuery(
|
return SqlSelectQuery(
|
||||||
name,
|
name,
|
||||||
fromContext,
|
fromContext,
|
||||||
|
root,
|
||||||
elements,
|
elements,
|
||||||
readsFrom,
|
readsFrom,
|
||||||
resultSet,
|
resultSet,
|
||||||
|
@ -211,6 +216,8 @@ class UpdatingQuery extends SqlQuery {
|
||||||
final InferredResultSet? resultSet;
|
final InferredResultSet? resultSet;
|
||||||
@override
|
@override
|
||||||
final AnalysisContext fromContext;
|
final AnalysisContext fromContext;
|
||||||
|
@override
|
||||||
|
final AstNode root;
|
||||||
|
|
||||||
bool get isOnlyDelete => updates.every((w) => w.kind == UpdateKind.delete);
|
bool get isOnlyDelete => updates.every((w) => w.kind == UpdateKind.delete);
|
||||||
|
|
||||||
|
@ -219,6 +226,7 @@ class UpdatingQuery extends SqlQuery {
|
||||||
UpdatingQuery(
|
UpdatingQuery(
|
||||||
String name,
|
String name,
|
||||||
this.fromContext,
|
this.fromContext,
|
||||||
|
this.root,
|
||||||
List<FoundElement> elements,
|
List<FoundElement> elements,
|
||||||
this.updates, {
|
this.updates, {
|
||||||
this.isInsert = false,
|
this.isInsert = false,
|
||||||
|
@ -239,6 +247,9 @@ class InTransactionQuery extends SqlQuery {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AnalysisContext? get fromContext => null;
|
AnalysisContext? get fromContext => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
AstNode? get root => null;
|
||||||
}
|
}
|
||||||
|
|
||||||
class InferredResultSet {
|
class InferredResultSet {
|
||||||
|
@ -249,9 +260,9 @@ class InferredResultSet {
|
||||||
|
|
||||||
/// Tables in the result set that should appear as a class.
|
/// Tables in the result set that should appear as a class.
|
||||||
///
|
///
|
||||||
/// See [NestedResultTable] for further discussion and examples.
|
/// See [NestedResult] for further discussion and examples.
|
||||||
final List<NestedResultTable> nestedResults;
|
final List<NestedResult> nestedResults;
|
||||||
Map<NestedResultTable, String>? _expandedNestedPrefixes;
|
Map<NestedResult, String>? _expandedNestedPrefixes;
|
||||||
|
|
||||||
final List<ResultColumn> columns;
|
final List<ResultColumn> columns;
|
||||||
final Map<ResultColumn, String> _dartNames = {};
|
final Map<ResultColumn, String> _dartNames = {};
|
||||||
|
@ -293,7 +304,7 @@ class InferredResultSet {
|
||||||
bool get singleColumn =>
|
bool get singleColumn =>
|
||||||
matchingTable == null && nestedResults.isEmpty && columns.length == 1;
|
matchingTable == null && nestedResults.isEmpty && columns.length == 1;
|
||||||
|
|
||||||
String? nestedPrefixFor(NestedResultTable table) {
|
String? nestedPrefixFor(NestedResult table) {
|
||||||
if (_expandedNestedPrefixes == null) {
|
if (_expandedNestedPrefixes == null) {
|
||||||
var index = 0;
|
var index = 0;
|
||||||
_expandedNestedPrefixes = {
|
_expandedNestedPrefixes = {
|
||||||
|
@ -329,12 +340,17 @@ class InferredResultSet {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [hashCode] that matches [isCompatibleTo] instead of `==`.
|
||||||
|
int get compatibilityHashCode => Object.hash(
|
||||||
|
Object.hashAll(columns.map((e) => e.compatibilityHashCode)),
|
||||||
|
Object.hashAll(nestedResults.map((e) => e.compatibilityHashCode)),
|
||||||
|
);
|
||||||
|
|
||||||
/// Checks whether this and the [other] result set have the same columns and
|
/// Checks whether this and the [other] result set have the same columns and
|
||||||
/// nested result sets.
|
/// nested result sets.
|
||||||
bool isCompatibleTo(InferredResultSet other) {
|
bool isCompatibleTo(InferredResultSet other) {
|
||||||
const columnsEquality = UnorderedIterableEquality(_ResultColumnEquality());
|
const columnsEquality = UnorderedIterableEquality(_ResultColumnEquality());
|
||||||
const nestedEquality =
|
const nestedEquality = UnorderedIterableEquality(_NestedResultEquality());
|
||||||
UnorderedIterableEquality(_NestedResultTableEquality());
|
|
||||||
|
|
||||||
return columnsEquality.equals(columns, other.columns) &&
|
return columnsEquality.equals(columns, other.columns) &&
|
||||||
nestedEquality.equals(nestedResults, other.nestedResults);
|
nestedEquality.equals(nestedResults, other.nestedResults);
|
||||||
|
@ -406,6 +422,15 @@ class ResultColumn implements HasType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A nested result, could either be a NestedResultTable or a NestedQueryResult.
|
||||||
|
abstract class NestedResult {
|
||||||
|
/// [hashCode] that matches [isCompatibleTo] instead of `==`.
|
||||||
|
int get compatibilityHashCode;
|
||||||
|
|
||||||
|
/// Checks whether this is compatible to the [other] nested result.
|
||||||
|
bool isCompatibleTo(NestedResult other);
|
||||||
|
}
|
||||||
|
|
||||||
/// A nested table extracted from a `**` column.
|
/// A nested table extracted from a `**` column.
|
||||||
///
|
///
|
||||||
/// For instance, consider this query:
|
/// For instance, consider this query:
|
||||||
|
@ -432,7 +457,7 @@ class ResultColumn implements HasType {
|
||||||
///
|
///
|
||||||
/// Knowing that `User` should be extracted into a field is represented with a
|
/// Knowing that `User` should be extracted into a field is represented with a
|
||||||
/// [NestedResultTable] information as part of the result set.
|
/// [NestedResultTable] information as part of the result set.
|
||||||
class NestedResultTable {
|
class NestedResultTable extends NestedResult {
|
||||||
final bool isNullable;
|
final bool isNullable;
|
||||||
final NestedStarResultColumn from;
|
final NestedStarResultColumn from;
|
||||||
final String name;
|
final String name;
|
||||||
|
@ -443,19 +468,67 @@ class NestedResultTable {
|
||||||
String get dartFieldName => ReCase(name).camelCase;
|
String get dartFieldName => ReCase(name).camelCase;
|
||||||
|
|
||||||
/// [hashCode] that matches [isCompatibleTo] instead of `==`.
|
/// [hashCode] that matches [isCompatibleTo] instead of `==`.
|
||||||
|
@override
|
||||||
int get compatibilityHashCode {
|
int get compatibilityHashCode {
|
||||||
return Object.hash(name, table);
|
return Object.hash(name, table);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks whether this is compatible to the [other] nested result, which is
|
/// Checks whether this is compatible to the [other] nested result, which is
|
||||||
/// the case iff they have the same and read from the same table.
|
/// the case iff they have the same and read from the same table.
|
||||||
bool isCompatibleTo(NestedResultTable other) {
|
@override
|
||||||
|
bool isCompatibleTo(NestedResult other) {
|
||||||
|
if (other is! NestedResultTable) return false;
|
||||||
|
|
||||||
return other.name == name &&
|
return other.name == name &&
|
||||||
other.table == table &&
|
other.table == table &&
|
||||||
other.isNullable == isNullable;
|
other.isNullable == isNullable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class NestedResultQuery extends NestedResult {
|
||||||
|
final NestedQueryColumn from;
|
||||||
|
|
||||||
|
final SqlSelectQuery query;
|
||||||
|
|
||||||
|
NestedResultQuery({
|
||||||
|
required this.from,
|
||||||
|
required this.query,
|
||||||
|
});
|
||||||
|
|
||||||
|
String filedName(String? nestedPrefix) {
|
||||||
|
if (from.as != null) {
|
||||||
|
return from.as!;
|
||||||
|
}
|
||||||
|
|
||||||
|
final name = ReCase(query.resultClassName).camelCase;
|
||||||
|
|
||||||
|
if (nestedPrefix != null) {
|
||||||
|
return name + nestedPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
String resultTypeCode(String parentResultClassName) {
|
||||||
|
if (query.resultSet.needsOwnClass) {
|
||||||
|
return parentResultClassName + query.resultClassName;
|
||||||
|
} else {
|
||||||
|
return query.resultTypeCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every query should be unique.
|
||||||
|
|
||||||
|
/// [hashCode] that matches [isCompatibleTo] instead of `==`.
|
||||||
|
@override
|
||||||
|
int get compatibilityHashCode => hashCode;
|
||||||
|
|
||||||
|
/// Checks whether this is compatible to the [other] nested result, which is
|
||||||
|
/// the case iff they have the same and read from the same table.
|
||||||
|
@override
|
||||||
|
bool isCompatibleTo(NestedResult other) => this == other;
|
||||||
|
}
|
||||||
|
|
||||||
/// Something in the query that needs special attention when generating code,
|
/// Something in the query that needs special attention when generating code,
|
||||||
/// such as variables or Dart placeholders.
|
/// such as variables or Dart placeholders.
|
||||||
abstract class FoundElement {
|
abstract class FoundElement {
|
||||||
|
@ -742,16 +815,16 @@ class _ResultColumnEquality implements Equality<ResultColumn> {
|
||||||
bool isValidKey(Object? e) => e is ResultColumn;
|
bool isValidKey(Object? e) => e is ResultColumn;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _NestedResultTableEquality implements Equality<NestedResultTable> {
|
class _NestedResultEquality implements Equality<NestedResult> {
|
||||||
const _NestedResultTableEquality();
|
const _NestedResultEquality();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool equals(NestedResultTable e1, NestedResultTable e2) {
|
bool equals(NestedResult e1, NestedResult e2) {
|
||||||
return e1.isCompatibleTo(e2);
|
return e1.isCompatibleTo(e2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int hash(NestedResultTable e) => e.compatibilityHashCode;
|
int hash(NestedResult e) => e.compatibilityHashCode;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool isValidKey(Object? e) => e is NestedResultTable;
|
bool isValidKey(Object? e) => e is NestedResultTable;
|
||||||
|
|
|
@ -117,9 +117,7 @@ class DatabaseWriter {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write implementation for query methods
|
// Write implementation for query methods
|
||||||
for (final query in db.queries ?? const <Never>[]) {
|
db.queries?.forEach((query) => QueryWriter(dbScope.child()).write(query));
|
||||||
QueryWriter(query, dbScope.child()).write();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write List of tables
|
// Write List of tables
|
||||||
final schemaScope = dbScope.leaf();
|
final schemaScope = dbScope.leaf();
|
||||||
|
|
|
@ -18,25 +18,16 @@ int _compareNodes(AstNode a, AstNode b) =>
|
||||||
/// Writes the handling code for a query. The code emitted will be a method that
|
/// Writes the handling code for a query. The code emitted will be a method that
|
||||||
/// 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 Scope scope;
|
final Scope scope;
|
||||||
|
|
||||||
late ExplicitAliasTransformer _transformer;
|
late final ExplicitAliasTransformer _transformer;
|
||||||
|
final StringBuffer _buffer;
|
||||||
SqlSelectQuery get _select => query as SqlSelectQuery;
|
|
||||||
UpdatingQuery get _update => query as UpdatingQuery;
|
|
||||||
|
|
||||||
MoorOptions get options => scope.writer.options;
|
MoorOptions get options => scope.writer.options;
|
||||||
late StringBuffer _buffer;
|
|
||||||
|
|
||||||
bool get _newSelectableMode =>
|
QueryWriter(this.scope) : _buffer = scope.leaf();
|
||||||
query.declaredInMoorFile || options.compactQueryMethods;
|
|
||||||
|
|
||||||
QueryWriter(this.query, this.scope) {
|
void write(SqlQuery query) {
|
||||||
_buffer = scope.leaf();
|
|
||||||
}
|
|
||||||
|
|
||||||
void write() {
|
|
||||||
// Note that writing queries can have a result set if they use a RETURNING
|
// Note that writing queries can have a result set if they use a RETURNING
|
||||||
// clause.
|
// clause.
|
||||||
final resultSet = query.resultSet;
|
final resultSet = query.resultSet;
|
||||||
|
@ -54,40 +45,40 @@ class QueryWriter {
|
||||||
// analysis, Dart getter names stay the same.
|
// analysis, Dart getter names stay the same.
|
||||||
if (resultSet != null && options.newSqlCodeGeneration) {
|
if (resultSet != null && options.newSqlCodeGeneration) {
|
||||||
_transformer = ExplicitAliasTransformer();
|
_transformer = ExplicitAliasTransformer();
|
||||||
_transformer.rewrite(query.fromContext!.root);
|
_transformer.rewrite(query.root!);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query is SqlSelectQuery) {
|
if (query is SqlSelectQuery) {
|
||||||
_writeSelect();
|
_writeSelect(query);
|
||||||
} else if (query is UpdatingQuery) {
|
} else if (query is UpdatingQuery) {
|
||||||
if (resultSet != null) {
|
if (resultSet != null) {
|
||||||
_writeUpdatingQueryWithReturning();
|
_writeUpdatingQueryWithReturning(query);
|
||||||
} else {
|
} else {
|
||||||
_writeUpdatingQuery();
|
_writeUpdatingQuery(query);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _writeSelect() {
|
void _writeSelect(SqlSelectQuery select) {
|
||||||
_writeSelectStatementCreator();
|
_writeSelectStatementCreator(select);
|
||||||
|
|
||||||
if (!_newSelectableMode) {
|
if (!select.declaredInMoorFile && !options.compactQueryMethods) {
|
||||||
_writeOneTimeReader();
|
_writeOneTimeReader(select);
|
||||||
_writeStreamReader();
|
_writeStreamReader(select);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _nameOfCreationMethod() {
|
String _nameOfCreationMethod(SqlSelectQuery select) {
|
||||||
if (_newSelectableMode) {
|
if (!select.declaredInMoorFile && !options.compactQueryMethods) {
|
||||||
return query.name;
|
return select.name;
|
||||||
} else {
|
} else {
|
||||||
return '${query.name}Query';
|
return '${select.name}Query';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes the function literal that turns a "QueryRow" into the desired
|
/// Writes the function literal that turns a "QueryRow" into the desired
|
||||||
/// custom return type of a query.
|
/// custom return type of a query.
|
||||||
void _writeMappingLambda() {
|
void _writeMappingLambda(SqlQuery query) {
|
||||||
final resultSet = query.resultSet!;
|
final resultSet = query.resultSet!;
|
||||||
|
|
||||||
if (resultSet.singleColumn) {
|
if (resultSet.singleColumn) {
|
||||||
|
@ -119,7 +110,7 @@ class QueryWriter {
|
||||||
_buffer.write('})');
|
_buffer.write('})');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_buffer.write('(QueryRow row) { return ${query.resultClassName}(');
|
_buffer.write('(QueryRow row) async { return ${query.resultClassName}(');
|
||||||
|
|
||||||
if (options.rawResultSetData) {
|
if (options.rawResultSetData) {
|
||||||
_buffer.write('row: row,\n');
|
_buffer.write('row: row,\n');
|
||||||
|
@ -131,17 +122,35 @@ class QueryWriter {
|
||||||
'${readingCode(column, scope.generationOptions, options)},');
|
'${readingCode(column, scope.generationOptions, options)},');
|
||||||
}
|
}
|
||||||
for (final nested in resultSet.nestedResults) {
|
for (final nested in resultSet.nestedResults) {
|
||||||
final prefix = resultSet.nestedPrefixFor(nested);
|
if (nested is NestedResultTable) {
|
||||||
if (prefix == null) continue;
|
final prefix = resultSet.nestedPrefixFor(nested);
|
||||||
|
if (prefix == null) continue;
|
||||||
|
|
||||||
final fieldName = nested.dartFieldName;
|
final fieldName = nested.dartFieldName;
|
||||||
final tableGetter = nested.table.dbGetterName;
|
final tableGetter = nested.table.dbGetterName;
|
||||||
|
|
||||||
final mappingMethod =
|
final mappingMethod =
|
||||||
nested.isNullable ? 'mapFromRowOrNull' : 'mapFromRow';
|
nested.isNullable ? 'mapFromRowOrNull' : 'mapFromRow';
|
||||||
|
|
||||||
_buffer.write('$fieldName: $tableGetter.$mappingMethod(row, '
|
_buffer.write('$fieldName: $tableGetter.$mappingMethod(row, '
|
||||||
'tablePrefix: ${asDartLiteral(prefix)}),');
|
'tablePrefix: ${asDartLiteral(prefix)}),');
|
||||||
|
} else if (nested is NestedResultQuery) {
|
||||||
|
if (!scope.options.newSqlCodeGeneration) {
|
||||||
|
throw UnsupportedError(
|
||||||
|
'To use nested result queries enable new_sql_code_generation',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final prefix = resultSet.nestedPrefixFor(nested);
|
||||||
|
if (prefix == null) continue;
|
||||||
|
|
||||||
|
final fieldName = nested.filedName(prefix);
|
||||||
|
_buffer.write('$fieldName: await ');
|
||||||
|
|
||||||
|
_writeCustomSelectStatement(nested.query);
|
||||||
|
|
||||||
|
_buffer.write('.get()');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_buffer.write(');\n}');
|
_buffer.write(');\n}');
|
||||||
}
|
}
|
||||||
|
@ -180,101 +189,107 @@ class QueryWriter {
|
||||||
|
|
||||||
/// 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() {
|
void _writeSelectStatementCreator(SqlSelectQuery select) {
|
||||||
final returnType =
|
final returnType =
|
||||||
'Selectable<${_select.resultTypeCode(scope.generationOptions)}>';
|
'Selectable<${select.resultTypeCode(scope.generationOptions)}>';
|
||||||
final methodName = _nameOfCreationMethod();
|
final methodName = _nameOfCreationMethod(select);
|
||||||
|
|
||||||
_buffer.write('$returnType $methodName(');
|
_buffer.write('$returnType $methodName(');
|
||||||
_writeParameters();
|
_writeParameters(select);
|
||||||
_buffer.write(') {\n');
|
_buffer.write(') {\n');
|
||||||
|
|
||||||
_writeExpandedDeclarations();
|
_writeExpandedDeclarations(select);
|
||||||
_buffer.write('return customSelect(${_queryCode()}, ');
|
_buffer.write('return');
|
||||||
_writeVariables();
|
_writeCustomSelectStatement(select);
|
||||||
_buffer.write(', ');
|
_buffer.write(';\n}\n');
|
||||||
_writeReadsFrom();
|
|
||||||
|
|
||||||
_buffer.write(').map(');
|
|
||||||
_writeMappingLambda();
|
|
||||||
_buffer.write(');\n}\n');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _writeOneTimeReader() {
|
void _writeCustomSelectStatement(SqlSelectQuery select) {
|
||||||
|
_buffer.write(' customSelect(${_queryCode(select)}, ');
|
||||||
|
_writeVariables(select);
|
||||||
|
_buffer.write(', ');
|
||||||
|
_writeReadsFrom(select);
|
||||||
|
|
||||||
|
_buffer.write(').map(');
|
||||||
|
_writeMappingLambda(select);
|
||||||
|
_buffer.write(')');
|
||||||
|
}
|
||||||
|
|
||||||
|
void _writeOneTimeReader(SqlSelectQuery select) {
|
||||||
final returnType =
|
final returnType =
|
||||||
'Future<List<${_select.resultTypeCode(scope.generationOptions)}>>';
|
'Future<List<${select.resultTypeCode(scope.generationOptions)}>>';
|
||||||
_buffer.write('$returnType ${query.name}(');
|
_buffer.write('$returnType ${select.name}(');
|
||||||
_writeParameters();
|
_writeParameters(select);
|
||||||
_buffer
|
_buffer
|
||||||
..write(') {\n')
|
..write(') {\n')
|
||||||
..write('return ${_nameOfCreationMethod()}(');
|
..write('return ${_nameOfCreationMethod(select)}(');
|
||||||
_writeUseParameters();
|
_writeUseParameters(select);
|
||||||
_buffer.write(').get();\n}\n');
|
_buffer.write(').get();\n}\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
void _writeStreamReader() {
|
void _writeStreamReader(SqlSelectQuery select) {
|
||||||
final upperQueryName = ReCase(query.name).pascalCase;
|
final upperQueryName = ReCase(select.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
|
// "private" modifier back in
|
||||||
if (query.name.startsWith('_')) {
|
if (select.name.startsWith('_')) {
|
||||||
methodName = '_watch$upperQueryName';
|
methodName = '_watch$upperQueryName';
|
||||||
} else {
|
} else {
|
||||||
methodName = 'watch$upperQueryName';
|
methodName = 'watch$upperQueryName';
|
||||||
}
|
}
|
||||||
|
|
||||||
final returnType =
|
final returnType =
|
||||||
'Stream<List<${_select.resultTypeCode(scope.generationOptions)}>>';
|
'Stream<List<${select.resultTypeCode(scope.generationOptions)}>>';
|
||||||
_buffer.write('$returnType $methodName(');
|
_buffer.write('$returnType $methodName(');
|
||||||
_writeParameters();
|
_writeParameters(select);
|
||||||
_buffer
|
_buffer
|
||||||
..write(') {\n')
|
..write(') {\n')
|
||||||
..write('return ${_nameOfCreationMethod()}(');
|
..write('return ${_nameOfCreationMethod(select)}(');
|
||||||
_writeUseParameters();
|
_writeUseParameters(select);
|
||||||
_buffer.write(').watch();\n}\n');
|
_buffer.write(').watch();\n}\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
void _writeUpdatingQueryWithReturning() {
|
void _writeUpdatingQueryWithReturning(UpdatingQuery update) {
|
||||||
final type = query.resultTypeCode(scope.generationOptions);
|
final type = update.resultTypeCode(scope.generationOptions);
|
||||||
_buffer.write('Future<List<$type>> ${query.name}(');
|
_buffer.write('Future<List<$type>> ${update.name}(');
|
||||||
_writeParameters();
|
_writeParameters(update);
|
||||||
_buffer.write(') {\n');
|
_buffer.write(') {\n');
|
||||||
|
|
||||||
_writeExpandedDeclarations();
|
_writeExpandedDeclarations(update);
|
||||||
_buffer.write('return customWriteReturning(${_queryCode()},');
|
_buffer.write('return customWriteReturning(${_queryCode(update)},');
|
||||||
_writeCommonUpdateParameters();
|
_writeCommonUpdateParameters(update);
|
||||||
_buffer.write(').then((rows) => rows.map(');
|
_buffer.write(').then((rows) => rows.map(');
|
||||||
_writeMappingLambda();
|
_writeMappingLambda(update);
|
||||||
_buffer.write(').toList());\n}');
|
_buffer.write(').toList());\n}');
|
||||||
}
|
}
|
||||||
|
|
||||||
void _writeUpdatingQuery() {
|
void _writeUpdatingQuery(UpdatingQuery update) {
|
||||||
/*
|
/*
|
||||||
Future<int> test() {
|
Future<int> test() {
|
||||||
return customUpdate('', variables: [], updates: {});
|
return customUpdate('', variables: [], updates: {});
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
final implName = _update.isInsert ? 'customInsert' : 'customUpdate';
|
final implName = update.isInsert ? 'customInsert' : 'customUpdate';
|
||||||
|
|
||||||
_buffer.write('Future<int> ${query.name}(');
|
_buffer.write('Future<int> ${update.name}(');
|
||||||
_writeParameters();
|
_writeParameters(update);
|
||||||
_buffer.write(') {\n');
|
_buffer.write(') {\n');
|
||||||
|
|
||||||
_writeExpandedDeclarations();
|
_writeExpandedDeclarations(update);
|
||||||
_buffer.write('return $implName(${_queryCode()},');
|
_buffer.write('return $implName(${_queryCode(update)},');
|
||||||
_writeCommonUpdateParameters();
|
_writeCommonUpdateParameters(update);
|
||||||
_buffer.write(',);\n}\n');
|
_buffer.write(',);\n}\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
void _writeCommonUpdateParameters() {
|
void _writeCommonUpdateParameters(UpdatingQuery update) {
|
||||||
_writeVariables();
|
_writeVariables(update);
|
||||||
_buffer.write(',');
|
_buffer.write(',');
|
||||||
_writeUpdates();
|
_writeUpdates(update);
|
||||||
_writeUpdateKind();
|
_writeUpdateKind(update);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _writeParameters() {
|
void _writeParameters(SqlQuery query) {
|
||||||
final namedElements = <FoundElement>[];
|
final namedElements = <FoundElement>[];
|
||||||
|
|
||||||
String typeFor(FoundElement element) {
|
String typeFor(FoundElement element) {
|
||||||
|
@ -407,35 +422,37 @@ 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() {
|
void _writeUseParameters(SqlQuery query) {
|
||||||
final parameters = query.elements.map((e) => e.dartParameterName);
|
final parameters = query.elements.map((e) => e.dartParameterName);
|
||||||
_buffer.write(parameters.join(', '));
|
_buffer.write(parameters.join(', '));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _writeExpandedDeclarations() {
|
void _writeExpandedDeclarations(SqlQuery query) {
|
||||||
_ExpandedDeclarationWriter(query, options, _buffer)
|
_ExpandedDeclarationWriter(query, options, _buffer)
|
||||||
.writeExpandedDeclarations();
|
.writeExpandedDeclarations();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _writeVariables() {
|
void _writeVariables(SqlQuery query) {
|
||||||
_ExpandedVariableWriter(query, scope, _buffer).writeVariables();
|
_ExpandedVariableWriter(query, scope, _buffer).writeVariables();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a Dart string literal representing the query after variables have
|
/// Returns a Dart string literal representing the query after variables have
|
||||||
/// been expanded. For instance, 'SELECT * FROM t WHERE x IN ?' will be turned
|
/// been expanded. For instance, 'SELECT * FROM t WHERE x IN ?' will be turned
|
||||||
/// into 'SELECT * FROM t WHERE x IN ($expandedVar1)'.
|
/// into 'SELECT * FROM t WHERE x IN ($expandedVar1)'.
|
||||||
String _queryCode() {
|
String _queryCode(SqlQuery query) {
|
||||||
if (scope.options.newSqlCodeGeneration) {
|
if (scope.options.newSqlCodeGeneration) {
|
||||||
return SqlWriter(scope.options, query: query).write();
|
return SqlWriter(scope.options, query: query).write();
|
||||||
} else {
|
} else {
|
||||||
return _legacyQueryCode();
|
return _legacyQueryCode(query);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _legacyQueryCode() {
|
String _legacyQueryCode(SqlQuery query) {
|
||||||
final context = query.fromContext!;
|
final root = query.root!;
|
||||||
|
final sql = query.fromContext!.sql;
|
||||||
|
|
||||||
// sort variables and placeholders by the order in which they appear
|
// sort variables and placeholders by the order in which they appear
|
||||||
final toReplace = context.root.allDescendants
|
final toReplace = root.allDescendants
|
||||||
.where((node) =>
|
.where((node) =>
|
||||||
node is Variable ||
|
node is Variable ||
|
||||||
node is DartPlaceholder ||
|
node is DartPlaceholder ||
|
||||||
|
@ -450,17 +467,17 @@ class QueryWriter {
|
||||||
const <NestedStarResultColumn, NestedResultTable>{};
|
const <NestedStarResultColumn, NestedResultTable>{};
|
||||||
if (query is SqlSelectQuery) {
|
if (query is SqlSelectQuery) {
|
||||||
doubleStarColumnToResolvedTable = {
|
doubleStarColumnToResolvedTable = {
|
||||||
for (final nestedResult in _select.resultSet.nestedResults)
|
for (final nestedResult in query.resultSet.nestedResults)
|
||||||
nestedResult.from: nestedResult
|
if (nestedResult is NestedResultTable) nestedResult.from: nestedResult
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastIndex = context.root.firstPosition;
|
var lastIndex = root.firstPosition;
|
||||||
|
|
||||||
void replaceNode(AstNode node, String content) {
|
void replaceNode(AstNode node, String content) {
|
||||||
// write everything that comes before this var into the buffer
|
// write everything that comes before this var into the buffer
|
||||||
final currentIndex = node.firstPosition;
|
final currentIndex = node.firstPosition;
|
||||||
final queryPart = context.sql.substring(lastIndex, currentIndex);
|
final queryPart = sql.substring(lastIndex, currentIndex);
|
||||||
buffer.write(escapeForDart(queryPart));
|
buffer.write(escapeForDart(queryPart));
|
||||||
lastIndex = node.lastPosition;
|
lastIndex = node.lastPosition;
|
||||||
|
|
||||||
|
@ -486,7 +503,9 @@ class QueryWriter {
|
||||||
final result = doubleStarColumnToResolvedTable[rewriteTarget];
|
final result = doubleStarColumnToResolvedTable[rewriteTarget];
|
||||||
if (result == null) continue;
|
if (result == null) continue;
|
||||||
|
|
||||||
final prefix = _select.resultSet.nestedPrefixFor(result);
|
// weird cast here :O
|
||||||
|
final prefix =
|
||||||
|
(query as SqlSelectQuery).resultSet.nestedPrefixFor(result);
|
||||||
final table = rewriteTarget.tableName;
|
final table = rewriteTarget.tableName;
|
||||||
|
|
||||||
// Convert foo.** to "foo.a" AS "nested_0.a", ... for all columns in foo
|
// Convert foo.** to "foo.a" AS "nested_0.a", ... for all columns in foo
|
||||||
|
@ -509,40 +528,40 @@ class QueryWriter {
|
||||||
}
|
}
|
||||||
|
|
||||||
// write the final part after the last variable, plus the ending '
|
// write the final part after the last variable, plus the ending '
|
||||||
final lastPosition = context.root.lastPosition;
|
final lastPosition = root.lastPosition;
|
||||||
buffer
|
buffer
|
||||||
..write(escapeForDart(context.sql.substring(lastIndex, lastPosition)))
|
..write(escapeForDart(sql.substring(lastIndex, lastPosition)))
|
||||||
..write("'");
|
..write("'");
|
||||||
|
|
||||||
return buffer.toString();
|
return buffer.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _writeReadsFrom() {
|
void _writeReadsFrom(SqlSelectQuery select) {
|
||||||
_buffer.write('readsFrom: {');
|
_buffer.write('readsFrom: {');
|
||||||
|
|
||||||
for (final table in _select.readsFromTables) {
|
for (final table in select.readsFromTables) {
|
||||||
_buffer.write('${table.dbGetterName},');
|
_buffer.write('${table.dbGetterName},');
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final element in query.elements.whereType<FoundDartPlaceholder>()) {
|
for (final element in select.elements.whereType<FoundDartPlaceholder>()) {
|
||||||
_buffer.write('...${placeholderContextName(element)}.watchedTables,');
|
_buffer.write('...${placeholderContextName(element)}.watchedTables,');
|
||||||
}
|
}
|
||||||
|
|
||||||
_buffer.write('}');
|
_buffer.write('}');
|
||||||
}
|
}
|
||||||
|
|
||||||
void _writeUpdates() {
|
void _writeUpdates(UpdatingQuery update) {
|
||||||
final from = _update.updates.map((t) => t.table.dbGetterName).join(', ');
|
final from = update.updates.map((t) => t.table.dbGetterName).join(', ');
|
||||||
_buffer
|
_buffer
|
||||||
..write('updates: {')
|
..write('updates: {')
|
||||||
..write(from)
|
..write(from)
|
||||||
..write('}');
|
..write('}');
|
||||||
}
|
}
|
||||||
|
|
||||||
void _writeUpdateKind() {
|
void _writeUpdateKind(UpdatingQuery update) {
|
||||||
if (_update.isOnlyDelete) {
|
if (update.isOnlyDelete) {
|
||||||
_buffer.write(', updateKind: UpdateKind.delete');
|
_buffer.write(', updateKind: UpdateKind.delete');
|
||||||
} else if (_update.isOnlyUpdate) {
|
} else if (update.isOnlyUpdate) {
|
||||||
_buffer.write(', updateKind: UpdateKind.update');
|
_buffer.write(', updateKind: UpdateKind.update');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,17 +39,32 @@ class ResultSetWriter {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final nested in resultSet.nestedResults) {
|
for (final nested in resultSet.nestedResults) {
|
||||||
var typeName = nested.table.dartTypeCode(scope.generationOptions);
|
if (nested is NestedResultTable) {
|
||||||
final fieldName = nested.dartFieldName;
|
var typeName = nested.table.dartTypeCode(scope.generationOptions);
|
||||||
|
final fieldName = nested.dartFieldName;
|
||||||
|
|
||||||
if (nested.isNullable) {
|
if (nested.isNullable) {
|
||||||
typeName = scope.nullableType(typeName);
|
typeName = scope.nullableType(typeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
into.write('$modifier $typeName $fieldName;\n');
|
||||||
|
|
||||||
|
fieldNames.add(fieldName);
|
||||||
|
if (!nested.isNullable) nonNullableFields.add(fieldName);
|
||||||
|
} else if (nested is NestedResultQuery) {
|
||||||
|
final nestedPrefix = resultSet.nestedPrefixFor(nested);
|
||||||
|
final fieldName = nested.filedName(nestedPrefix);
|
||||||
|
final typeName = nested.resultTypeCode(className);
|
||||||
|
|
||||||
|
if (nested.query.resultSet.needsOwnClass) {
|
||||||
|
ResultSetWriter(nested.query, scope).write();
|
||||||
|
}
|
||||||
|
|
||||||
|
into.write('$modifier List<$typeName> $fieldName;\n');
|
||||||
|
|
||||||
|
fieldNames.add(fieldName);
|
||||||
|
nonNullableFields.add(fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
into.write('$modifier $typeName $fieldName;\n');
|
|
||||||
|
|
||||||
fieldNames.add(fieldName);
|
|
||||||
if (!nested.isNullable) nonNullableFields.add(fieldName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// write the constructor
|
// write the constructor
|
||||||
|
|
|
@ -46,7 +46,7 @@ class SqlWriter extends NodeSqlBuilder {
|
||||||
if (query is SqlSelectQuery) {
|
if (query is SqlSelectQuery) {
|
||||||
doubleStarColumnToResolvedTable = {
|
doubleStarColumnToResolvedTable = {
|
||||||
for (final nestedResult in query.resultSet.nestedResults)
|
for (final nestedResult in query.resultSet.nestedResults)
|
||||||
nestedResult.from: nestedResult
|
if (nestedResult is NestedResultTable) nestedResult.from: nestedResult
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return SqlWriter._(query, options, doubleStarColumnToResolvedTable,
|
return SqlWriter._(query, options, doubleStarColumnToResolvedTable,
|
||||||
|
@ -54,7 +54,7 @@ class SqlWriter extends NodeSqlBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
String write() {
|
String write() {
|
||||||
return writeNodeIntoStringLiteral(query!.fromContext!.root);
|
return writeNodeIntoStringLiteral(query!.root!);
|
||||||
}
|
}
|
||||||
|
|
||||||
String writeNodeIntoStringLiteral(AstNode node) {
|
String writeNodeIntoStringLiteral(AstNode node) {
|
||||||
|
|
|
@ -17,7 +17,7 @@ void main() {
|
||||||
|
|
||||||
test('warns when a result column is unresolved', () {
|
test('warns when a result column is unresolved', () {
|
||||||
final result = engine.analyze('SELECT ?;');
|
final result = engine.analyze('SELECT ?;');
|
||||||
final moorQuery = QueryHandler(fakeQuery, result, mapper).handle();
|
final moorQuery = QueryHandler(result, mapper).handle(fakeQuery);
|
||||||
|
|
||||||
expect(moorQuery.lints,
|
expect(moorQuery.lints,
|
||||||
anyElement((AnalysisError q) => q.message.contains('unknown type')));
|
anyElement((AnalysisError q) => q.message.contains('unknown type')));
|
||||||
|
@ -25,7 +25,7 @@ void main() {
|
||||||
|
|
||||||
test('warns when the result depends on a Dart template', () {
|
test('warns when the result depends on a Dart template', () {
|
||||||
final result = engine.analyze(r"SELECT 'string' = $expr;");
|
final result = engine.analyze(r"SELECT 'string' = $expr;");
|
||||||
final moorQuery = QueryHandler(fakeQuery, result, mapper).handle();
|
final moorQuery = QueryHandler(result, mapper).handle(fakeQuery);
|
||||||
|
|
||||||
expect(moorQuery.lints,
|
expect(moorQuery.lints,
|
||||||
anyElement((AnalysisError q) => q.message.contains('Dart template')));
|
anyElement((AnalysisError q) => q.message.contains('Dart template')));
|
||||||
|
@ -33,7 +33,7 @@ void main() {
|
||||||
|
|
||||||
test('warns when nested results refer to table-valued functions', () {
|
test('warns when nested results refer to table-valued functions', () {
|
||||||
final result = engine.analyze("SELECT json_each.** FROM json_each('')");
|
final result = engine.analyze("SELECT json_each.** FROM json_each('')");
|
||||||
final moorQuery = QueryHandler(fakeQuery, result, mapper).handle();
|
final moorQuery = QueryHandler(result, mapper).handle(fakeQuery);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
moorQuery.lints,
|
moorQuery.lints,
|
||||||
|
@ -95,7 +95,7 @@ in: INSERT INTO foo (id) $placeholder;
|
||||||
group('warns about wrong types in subexpressions', () {
|
group('warns about wrong types in subexpressions', () {
|
||||||
test('strings in arithmetic', () {
|
test('strings in arithmetic', () {
|
||||||
final result = engine.analyze("SELECT 'foo' + 3;");
|
final result = engine.analyze("SELECT 'foo' + 3;");
|
||||||
final moorQuery = QueryHandler(fakeQuery, result, mapper).handle();
|
final moorQuery = QueryHandler(result, mapper).handle(fakeQuery);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
moorQuery.lints,
|
moorQuery.lints,
|
||||||
|
@ -106,14 +106,14 @@ in: INSERT INTO foo (id) $placeholder;
|
||||||
|
|
||||||
test('allows numerics in arithmetic', () {
|
test('allows numerics in arithmetic', () {
|
||||||
final result = engine.analyze('SELECT 3.6 * 3;');
|
final result = engine.analyze('SELECT 3.6 * 3;');
|
||||||
final moorQuery = QueryHandler(fakeQuery, result, mapper).handle();
|
final moorQuery = QueryHandler(result, mapper).handle(fakeQuery);
|
||||||
|
|
||||||
expect(moorQuery.lints, isEmpty);
|
expect(moorQuery.lints, isEmpty);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('real in binary', () {
|
test('real in binary', () {
|
||||||
final result = engine.analyze('SELECT 3.5 | 3;');
|
final result = engine.analyze('SELECT 3.5 | 3;');
|
||||||
final moorQuery = QueryHandler(fakeQuery, result, mapper).handle();
|
final moorQuery = QueryHandler(result, mapper).handle(fakeQuery);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
moorQuery.lints,
|
moorQuery.lints,
|
||||||
|
|
|
@ -43,7 +43,7 @@ Future<void> main() async {
|
||||||
SqlQuery parse(String sql) {
|
SqlQuery parse(String sql) {
|
||||||
final parsed = engine.analyze(sql);
|
final parsed = engine.analyze(sql);
|
||||||
final fakeQuery = DeclaredDartQuery('query', sql);
|
final fakeQuery = DeclaredDartQuery('query', sql);
|
||||||
return QueryHandler(fakeQuery, parsed, mapper).handle();
|
return QueryHandler(parsed, mapper).handle(fakeQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
group('detects whether multiple tables are referenced', () {
|
group('detects whether multiple tables are referenced', () {
|
||||||
|
@ -105,9 +105,16 @@ FROM routes
|
||||||
|
|
||||||
expect(resultSet.columns.map((e) => e.name), ['id', 'from', 'to']);
|
expect(resultSet.columns.map((e) => e.name), ['id', 'from', 'to']);
|
||||||
expect(resultSet.matchingTable, isNull);
|
expect(resultSet.matchingTable, isNull);
|
||||||
expect(resultSet.nestedResults.map((e) => e.name), ['from', 'to']);
|
expect(
|
||||||
expect(resultSet.nestedResults.map((e) => e.table.displayName),
|
resultSet.nestedResults.cast<NestedResultTable>().map((e) => e.name),
|
||||||
['points', 'points']);
|
['from', 'to'],
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
resultSet.nestedResults
|
||||||
|
.cast<NestedResultTable>()
|
||||||
|
.map((e) => e.table.displayName),
|
||||||
|
['points', 'points'],
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('resolves nullability of aliases in nested result sets', () async {
|
test('resolves nullability of aliases in nested result sets', () async {
|
||||||
|
@ -144,9 +151,14 @@ LEFT JOIN tableB1 AS tableB2 -- nullable
|
||||||
final resultSet = (query as SqlSelectQuery).resultSet;
|
final resultSet = (query as SqlSelectQuery).resultSet;
|
||||||
|
|
||||||
final nested = resultSet.nestedResults;
|
final nested = resultSet.nestedResults;
|
||||||
expect(nested.map((e) => e.name),
|
expect(
|
||||||
['tableA1', 'tableA2', 'tableB1', 'tableB2']);
|
nested.cast<NestedResultTable>().map((e) => e.name),
|
||||||
expect(nested.map((e) => e.isNullable), [false, true, false, true]);
|
['tableA1', 'tableA2', 'tableB1', 'tableB2'],
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
nested.cast<NestedResultTable>().map((e) => e.isNullable),
|
||||||
|
[false, true, false, true],
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('infers result set for views', () async {
|
test('infers result set for views', () async {
|
||||||
|
|
|
@ -24,7 +24,7 @@ void main() {
|
||||||
final writer = Writer(
|
final writer = Writer(
|
||||||
const MoorOptions.defaults(generateNamedParameters: true),
|
const MoorOptions.defaults(generateNamedParameters: true),
|
||||||
generationOptions: const GenerationOptions(nnbd: true));
|
generationOptions: const GenerationOptions(nnbd: true));
|
||||||
QueryWriter(fileState.resolvedQueries!.single, writer.child()).write();
|
QueryWriter(writer.child()).write(fileState.resolvedQueries!.single);
|
||||||
|
|
||||||
expect(writer.writeGenerated(), contains('required List<int?> idList'));
|
expect(writer.writeGenerated(), contains('required List<int?> idList'));
|
||||||
});
|
});
|
||||||
|
@ -47,7 +47,7 @@ void main() {
|
||||||
final writer = Writer(
|
final writer = Writer(
|
||||||
const MoorOptions.defaults(newSqlCodeGeneration: true),
|
const MoorOptions.defaults(newSqlCodeGeneration: true),
|
||||||
generationOptions: const GenerationOptions(nnbd: true));
|
generationOptions: const GenerationOptions(nnbd: true));
|
||||||
QueryWriter(fileState.resolvedQueries!.single, writer.child()).write();
|
QueryWriter(writer.child()).write(fileState.resolvedQueries!.single);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
writer.writeGenerated(),
|
writer.writeGenerated(),
|
||||||
|
@ -76,7 +76,7 @@ void main() {
|
||||||
final writer = Writer(
|
final writer = Writer(
|
||||||
const MoorOptions.defaults(newSqlCodeGeneration: true),
|
const MoorOptions.defaults(newSqlCodeGeneration: true),
|
||||||
generationOptions: const GenerationOptions(nnbd: true));
|
generationOptions: const GenerationOptions(nnbd: true));
|
||||||
QueryWriter(fileState.resolvedQueries!.single, writer.child()).write();
|
QueryWriter(writer.child()).write(fileState.resolvedQueries!.single);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
writer.writeGenerated(),
|
writer.writeGenerated(),
|
||||||
|
@ -116,7 +116,7 @@ void main() {
|
||||||
options,
|
options,
|
||||||
generationOptions: const GenerationOptions(nnbd: true),
|
generationOptions: const GenerationOptions(nnbd: true),
|
||||||
);
|
);
|
||||||
QueryWriter(fileState.resolvedQueries!.single, writer.child()).write();
|
QueryWriter(writer.child()).write(fileState.resolvedQueries!.single);
|
||||||
|
|
||||||
expect(writer.writeGenerated(), expectation);
|
expect(writer.writeGenerated(), expectation);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,8 @@ void main() {
|
||||||
void check(String sql, String expectedDart) {
|
void check(String sql, String expectedDart) {
|
||||||
final engine = SqlEngine();
|
final engine = SqlEngine();
|
||||||
final context = engine.analyze(sql);
|
final context = engine.analyze(sql);
|
||||||
final query = SqlSelectQuery(
|
final query = SqlSelectQuery('name', context, context.root, [], [],
|
||||||
'name', context, [], [], InferredResultSet(null, []), null);
|
InferredResultSet(null, []), null);
|
||||||
|
|
||||||
final result =
|
final result =
|
||||||
SqlWriter(const MoorOptions.defaults(), query: query).write();
|
SqlWriter(const MoorOptions.defaults(), query: query).write();
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import '../../../sqlparser.dart';
|
import '../../analysis/analysis.dart';
|
||||||
import '../ast.dart' show ResultColumn, Renamable;
|
import '../ast.dart'
|
||||||
|
show StarResultColumn, ResultColumn, Renamable, SelectStatement;
|
||||||
import '../node.dart';
|
import '../node.dart';
|
||||||
import '../visitor.dart';
|
import '../visitor.dart';
|
||||||
import 'moor_file.dart';
|
import 'moor_file.dart';
|
||||||
|
|
Loading…
Reference in New Issue