mirror of https://github.com/AMT-Cheif/drift.git
Order all result columns syntactically
This commit is contained in:
parent
20aff8a9db
commit
44cae65170
|
@ -77,7 +77,6 @@ Map<SqlSelectQuery, SqlSelectQuery> transformCustomResultClasses(
|
|||
null,
|
||||
query.resultSet.columns,
|
||||
resultClassName: resultSetName,
|
||||
nestedResults: query.resultSet.nestedResults,
|
||||
// Only generate a result class for the first query in the group
|
||||
dontGenerateResultClass: !isFirst,
|
||||
);
|
||||
|
@ -86,7 +85,7 @@ Map<SqlSelectQuery, SqlSelectQuery> transformCustomResultClasses(
|
|||
// Dart name.
|
||||
newResultSet.forceDartNames({
|
||||
for (final entry in dartNames.entries)
|
||||
newResultSet.columns.singleWhere((e) => e.compatibleTo(entry.key)):
|
||||
newResultSet.columns.singleWhere((e) => e.isCompatibleTo(entry.key)):
|
||||
entry.value,
|
||||
});
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'package:drift/drift.dart' show DriftSqlType;
|
|||
import 'package:drift/drift.dart' as drift;
|
||||
import 'package:recase/recase.dart';
|
||||
import 'package:sqlparser/sqlparser.dart' hide ResultColumn;
|
||||
import 'package:sqlparser/sqlparser.dart' as sql;
|
||||
import 'package:sqlparser/utils/find_referenced_tables.dart';
|
||||
|
||||
import '../../driver/driver.dart';
|
||||
|
@ -140,8 +141,10 @@ class QueryAnalyzer {
|
|||
InferredResultSet? resultSet;
|
||||
if (root is StatementReturningColumns) {
|
||||
final columns = root.returnedResultSet?.resolvedColumns;
|
||||
|
||||
if (columns != null) {
|
||||
resultSet = _inferResultSet(queryContext, columns);
|
||||
final syntacticSource = root.returning?.columns;
|
||||
resultSet = _inferResultSet(queryContext, columns, syntacticSource);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -179,6 +182,7 @@ class QueryAnalyzer {
|
|||
|
||||
SqlSelectQuery _handleSelect(_QueryHandlerContext queryContext) {
|
||||
final tableFinder = ReferencedTablesVisitor();
|
||||
final root = queryContext.root;
|
||||
queryContext.root.acceptWithoutArg(tableFinder);
|
||||
|
||||
_applyFoundTables(tableFinder);
|
||||
|
@ -194,16 +198,20 @@ class QueryAnalyzer {
|
|||
|
||||
final driftEntities = [...driftTables, ...driftViews];
|
||||
|
||||
final resolvedColumns = (root as BaseSelectStatement).resolvedColumns!;
|
||||
List<sql.ResultColumn>? syntacticColumns;
|
||||
|
||||
if (root is SelectStatement) {
|
||||
syntacticColumns = root.columns;
|
||||
}
|
||||
|
||||
return SqlSelectQuery(
|
||||
queryContext.queryName,
|
||||
context,
|
||||
queryContext.root,
|
||||
queryContext.foundElements,
|
||||
driftEntities,
|
||||
_inferResultSet(
|
||||
queryContext,
|
||||
(queryContext.root as BaseSelectStatement).resolvedColumns!,
|
||||
),
|
||||
_inferResultSet(queryContext, resolvedColumns, syntacticColumns),
|
||||
queryContext.requestedResultClass,
|
||||
queryContext.nestedScope,
|
||||
);
|
||||
|
@ -212,12 +220,12 @@ class QueryAnalyzer {
|
|||
InferredResultSet _inferResultSet(
|
||||
_QueryHandlerContext queryContext,
|
||||
List<Column> rawColumns,
|
||||
List<sql.ResultColumn>? syntacticColumns,
|
||||
) {
|
||||
final candidatesForSingleTable = {..._foundTables, ..._foundViews};
|
||||
final columns = <ResultColumn>[];
|
||||
|
||||
// First, go through regular result columns
|
||||
for (final column in rawColumns) {
|
||||
void handleScalarColumn(Column column) {
|
||||
final type = context.typeOf(column).type;
|
||||
final driftType = driver.typeMapping.sqlTypeToDrift(type);
|
||||
AppliedTypeConverter? converter;
|
||||
|
@ -225,23 +233,52 @@ class QueryAnalyzer {
|
|||
converter = (type!.hint as TypeConverterHint).converter;
|
||||
}
|
||||
|
||||
columns.add(ResultColumn(column.name, driftType, type?.nullable ?? true,
|
||||
columns.add(ScalarResultColumn(
|
||||
column.name, driftType, type?.nullable ?? true,
|
||||
typeConverter: converter, sqlParserColumn: column));
|
||||
|
||||
final resultSet = _resultSetOfColumn(column);
|
||||
candidatesForSingleTable.removeWhere((t) => t != resultSet);
|
||||
}
|
||||
|
||||
final nestedResults = _findNestedResultTables(queryContext);
|
||||
if (nestedResults.isNotEmpty) {
|
||||
// The single table optimization doesn't make sense when nested result
|
||||
// sets are present.
|
||||
candidatesForSingleTable.clear();
|
||||
// We prefer to extract the result set from syntactic columns, as this gives
|
||||
// us the opportunity to get the ordering between "scalar" result columns
|
||||
// and nested result sets right. We might not have a syntactic source
|
||||
// though (for instance if a top-level `VALUES` select is used), so we
|
||||
// fall-back to the resolved schema columns if necessary.
|
||||
if (syntacticColumns == null) {
|
||||
rawColumns.forEach(handleScalarColumn);
|
||||
} else {
|
||||
for (final column in syntacticColumns) {
|
||||
final resolvedColumns = column.resolvedColumns;
|
||||
|
||||
if (column is NestedStarResultColumn) {
|
||||
final resolved = _resolveNestedResultTable(queryContext, column);
|
||||
if (resolved != null) {
|
||||
// The single table optimization doesn't make sense when nested result
|
||||
// sets are present.
|
||||
candidatesForSingleTable.clear();
|
||||
columns.add(resolved);
|
||||
}
|
||||
} else if (column is NestedQueryColumn) {
|
||||
candidatesForSingleTable.clear();
|
||||
columns.add(_resolveNestedResultQuery(queryContext, column));
|
||||
} else {
|
||||
if (resolvedColumns == null) continue;
|
||||
|
||||
// "Regular" column that either is or expands to a list of scalar
|
||||
// result columns.
|
||||
for (final column in resolvedColumns) {
|
||||
handleScalarColumn(column);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if all columns read from the same table, and all columns in that table
|
||||
// are present in the result set, we can use the data class we generate for
|
||||
// that table instead of generating another class just for this result set.
|
||||
|
||||
if (candidatesForSingleTable.length == 1) {
|
||||
final table = candidatesForSingleTable.single;
|
||||
final driftTable =
|
||||
|
@ -268,7 +305,7 @@ class QueryAnalyzer {
|
|||
// it is! Remember the correct getter name from the data class for
|
||||
// later when we write the mapping code.
|
||||
final columnIndex = rawColumns.indexOf(inResultSet.single);
|
||||
final resultColumn = columns[columnIndex];
|
||||
final resultColumn = columns[columnIndex] as ScalarResultColumn;
|
||||
|
||||
resultEntryToColumn[resultColumn] = column.nameInDart;
|
||||
resultColumnNameToDrift[resultColumn.name] = column;
|
||||
|
@ -295,73 +332,75 @@ class QueryAnalyzer {
|
|||
return InferredResultSet(
|
||||
null,
|
||||
columns,
|
||||
nestedResults: nestedResults,
|
||||
resultClassName: queryContext.requestedResultClass,
|
||||
);
|
||||
}
|
||||
|
||||
List<NestedResult> _findNestedResultTables(
|
||||
_QueryHandlerContext queryContext) {
|
||||
// We don't currently support nested results for compound statements
|
||||
if (queryContext.root is! SelectStatement) return const [];
|
||||
final query = queryContext.root as SelectStatement;
|
||||
|
||||
final nestedTables = <NestedResult>[];
|
||||
final analysis = JoinModel.of(query);
|
||||
|
||||
for (final column in query.columns) {
|
||||
if (column is NestedStarResultColumn) {
|
||||
final originalResult = column.resultSet;
|
||||
final result = originalResult?.unalias();
|
||||
if (result is! Table && result is! View) continue;
|
||||
|
||||
final driftTable = _lookupReference<DriftElementWithResultSet>(
|
||||
(result as NamedResultSet).name);
|
||||
final isNullable =
|
||||
analysis == null || analysis.isNullableTable(originalResult!);
|
||||
nestedTables.add(NestedResultTable(
|
||||
column,
|
||||
column.as ?? column.tableName,
|
||||
driftTable,
|
||||
isNullable: isNullable,
|
||||
));
|
||||
} else if (column is NestedQueryColumn) {
|
||||
final childScope = queryContext.nestedScope?.nestedQueries[column];
|
||||
|
||||
final foundElements = _extractElements(
|
||||
ctx: context,
|
||||
root: column.select,
|
||||
required: requiredVariables,
|
||||
nestedScope: childScope,
|
||||
);
|
||||
_verifyNoSkippedIndexes(foundElements);
|
||||
|
||||
final queryIndex = nestedQueryCounter++;
|
||||
|
||||
final name = 'nested_query_$queryIndex';
|
||||
column.queryName = name;
|
||||
|
||||
var resultClassName = ReCase(queryContext.queryName).pascalCase;
|
||||
if (column.as != null) {
|
||||
resultClassName += ReCase(column.as!).pascalCase;
|
||||
} else {
|
||||
resultClassName += 'NestedQuery$queryIndex';
|
||||
}
|
||||
|
||||
nestedTables.add(NestedResultQuery(
|
||||
from: column,
|
||||
query: _handleSelect(_QueryHandlerContext(
|
||||
queryName: name,
|
||||
requestedResultClass: resultClassName,
|
||||
root: column.select,
|
||||
foundElements: foundElements,
|
||||
nestedScope: childScope,
|
||||
)),
|
||||
));
|
||||
}
|
||||
/// Resolves a "nested star" column.
|
||||
///
|
||||
/// Nested star columns refer to an existing result set, but instructs drift
|
||||
/// that this result set should be handled as a nested type in Dart. For an
|
||||
/// example, see https://drift.simonbinder.eu/docs/using-sql/drift_files/#nested-results
|
||||
NestedResultTable? _resolveNestedResultTable(
|
||||
_QueryHandlerContext queryContext, NestedStarResultColumn column) {
|
||||
final originalResult = column.resultSet;
|
||||
final result = originalResult?.unalias();
|
||||
if (result is! Table && result is! View) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return nestedTables;
|
||||
final driftTable = _lookupReference<DriftElementWithResultSet>(
|
||||
(result as NamedResultSet).name);
|
||||
final analysis = JoinModel.of(column);
|
||||
final isNullable =
|
||||
analysis == null || analysis.isNullableTable(originalResult!);
|
||||
return NestedResultTable(
|
||||
column,
|
||||
column.as ?? column.tableName,
|
||||
driftTable,
|
||||
isNullable: isNullable,
|
||||
);
|
||||
}
|
||||
|
||||
/// Resolves a `LIST` result column.
|
||||
///
|
||||
/// The `LIST` macro allows defining a subquery whose results should be
|
||||
/// exposed as a Dart list.
|
||||
/// For an example, see https://drift.simonbinder.eu/docs/using-sql/drift_files/#list-subqueries
|
||||
NestedResultQuery _resolveNestedResultQuery(
|
||||
_QueryHandlerContext queryContext, NestedQueryColumn column) {
|
||||
final childScope = queryContext.nestedScope?.nestedQueries[column];
|
||||
|
||||
final foundElements = _extractElements(
|
||||
ctx: context,
|
||||
root: column.select,
|
||||
required: requiredVariables,
|
||||
nestedScope: childScope,
|
||||
);
|
||||
_verifyNoSkippedIndexes(foundElements);
|
||||
|
||||
final queryIndex = nestedQueryCounter++;
|
||||
|
||||
final name = 'nested_query_$queryIndex';
|
||||
column.queryName = name;
|
||||
|
||||
var resultClassName = ReCase(queryContext.queryName).pascalCase;
|
||||
if (column.as != null) {
|
||||
resultClassName += ReCase(column.as!).pascalCase;
|
||||
} else {
|
||||
resultClassName += 'NestedQuery$queryIndex';
|
||||
}
|
||||
|
||||
return NestedResultQuery(
|
||||
from: column,
|
||||
query: _handleSelect(_QueryHandlerContext(
|
||||
queryName: name,
|
||||
requestedResultClass: resultClassName,
|
||||
root: column.select,
|
||||
foundElements: foundElements,
|
||||
nestedScope: childScope,
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
Column? _toTableOrViewColumn(Column? c) {
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import 'package:analyzer/dart/element/type.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:drift/drift.dart' show DriftSqlType, UpdateKind;
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:recase/recase.dart';
|
||||
import 'package:sqlparser/sqlparser.dart';
|
||||
|
||||
import '../options.dart';
|
||||
import '../resolver/shared/column_name.dart';
|
||||
import 'column.dart';
|
||||
import 'dart.dart';
|
||||
import 'element.dart';
|
||||
import 'result_sets.dart';
|
||||
import 'table.dart';
|
||||
|
@ -362,12 +364,13 @@ class InferredResultSet {
|
|||
/// to create another class.
|
||||
final MatchingDriftTable? matchingTable;
|
||||
|
||||
/// Tables in the result set that should appear as a class.
|
||||
///
|
||||
/// See [NestedResult] for further discussion and examples.
|
||||
final List<NestedResult> nestedResults;
|
||||
Map<NestedResult, String>? _expandedNestedPrefixes;
|
||||
|
||||
/// All columns that are part of this result set.
|
||||
///
|
||||
/// This includes [ScalarResultColumn]s, which hold simple SQL values, but
|
||||
/// also [NestedResult]s, which hold subqueries or tables that are structured
|
||||
/// into a single logical Dart column.
|
||||
final List<ResultColumn> columns;
|
||||
final Map<ResultColumn, String> _dartNames = {};
|
||||
|
||||
|
@ -385,11 +388,13 @@ class InferredResultSet {
|
|||
InferredResultSet(
|
||||
this.matchingTable,
|
||||
this.columns, {
|
||||
this.nestedResults = const [],
|
||||
this.resultClassName,
|
||||
this.dontGenerateResultClass = false,
|
||||
});
|
||||
|
||||
Iterable<ScalarResultColumn> get scalarColumns => columns.whereType();
|
||||
Iterable<NestedResult> get nestedResults => columns.whereType();
|
||||
|
||||
/// Whether a new class needs to be written to store the result of this query.
|
||||
///
|
||||
/// We don't need to introduce result classes for queries which
|
||||
|
@ -399,14 +404,16 @@ class InferredResultSet {
|
|||
/// We always need to generate a class if the query contains nested results.
|
||||
bool get needsOwnClass {
|
||||
return matchingTable == null &&
|
||||
(columns.length > 1 || nestedResults.isNotEmpty) &&
|
||||
(scalarColumns.length > 1 || nestedResults.isNotEmpty) &&
|
||||
!dontGenerateResultClass;
|
||||
}
|
||||
|
||||
/// Whether this query returns a single column that should be returned
|
||||
/// directly.
|
||||
bool get singleColumn =>
|
||||
matchingTable == null && nestedResults.isEmpty && columns.length == 1;
|
||||
matchingTable == null &&
|
||||
nestedResults.isEmpty &&
|
||||
scalarColumns.length == 1;
|
||||
|
||||
String? nestedPrefixFor(NestedResult table) {
|
||||
if (_expandedNestedPrefixes == null) {
|
||||
|
@ -429,8 +436,7 @@ class InferredResultSet {
|
|||
/// [column].
|
||||
String dartNameFor(ResultColumn column) {
|
||||
return _dartNames.putIfAbsent(column, () {
|
||||
return dartNameForSqlColumn(column.name,
|
||||
existingNames: _dartNames.values);
|
||||
return column.dartGetterName(_dartNames.values);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -444,10 +450,7 @@ class InferredResultSet {
|
|||
/// nested result sets.
|
||||
bool isCompatibleTo(InferredResultSet other) {
|
||||
const columnsEquality = UnorderedIterableEquality(_ResultColumnEquality());
|
||||
const nestedEquality = UnorderedIterableEquality(_NestedResultEquality());
|
||||
|
||||
return columnsEquality.equals(columns, other.columns) &&
|
||||
nestedEquality.equals(nestedResults, other.nestedResults);
|
||||
return columnsEquality.equals(columns, other.columns);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -471,7 +474,20 @@ class MatchingDriftTable {
|
|||
}
|
||||
}
|
||||
|
||||
class ResultColumn implements HasType {
|
||||
@sealed
|
||||
abstract class ResultColumn {
|
||||
/// A unique name for this column in Dart.
|
||||
String dartGetterName(Iterable<String> existingNames);
|
||||
|
||||
/// [hashCode] that matches [isCompatibleTo] instead of `==`.
|
||||
int get compatibilityHashCode;
|
||||
|
||||
/// Checks whether this column is compatible to the [other] column, meaning
|
||||
/// that they have the same name and type.
|
||||
bool isCompatibleTo(ResultColumn other);
|
||||
}
|
||||
|
||||
class ScalarResultColumn extends ResultColumn implements HasType {
|
||||
final String name;
|
||||
@override
|
||||
final DriftSqlType sqlType;
|
||||
|
@ -484,36 +500,36 @@ class ResultColumn implements HasType {
|
|||
/// The analyzed column from the `sqlparser` package.
|
||||
final Column? sqlParserColumn;
|
||||
|
||||
ResultColumn(this.name, this.sqlType, this.nullable,
|
||||
ScalarResultColumn(this.name, this.sqlType, this.nullable,
|
||||
{this.typeConverter, this.sqlParserColumn});
|
||||
|
||||
@override
|
||||
bool get isArray => false;
|
||||
|
||||
/// Hash-code that matching [compatibleTo], so that two compatible columns
|
||||
/// will have the same [compatibilityHashCode].
|
||||
int get compatibilityHashCode {
|
||||
return Object.hash(name, sqlType, nullable, typeConverter);
|
||||
@override
|
||||
String dartGetterName(Iterable<String> existingNames) {
|
||||
return dartNameForSqlColumn(name, existingNames: existingNames);
|
||||
}
|
||||
|
||||
/// Checks whether this column is compatible to the [other], meaning that they
|
||||
/// have the same name and type.
|
||||
bool compatibleTo(ResultColumn other) {
|
||||
return other.name == name &&
|
||||
@override
|
||||
int get compatibilityHashCode {
|
||||
return Object.hash(
|
||||
ScalarResultColumn, name, sqlType, nullable, typeConverter);
|
||||
}
|
||||
|
||||
@override
|
||||
bool isCompatibleTo(ResultColumn other) {
|
||||
return other is ScalarResultColumn &&
|
||||
other.name == name &&
|
||||
other.sqlType == sqlType &&
|
||||
other.nullable == nullable &&
|
||||
other.typeConverter == typeConverter;
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 result, could either be a [NestedResultTable] or a
|
||||
/// [NestedResultQuery].
|
||||
abstract class NestedResult extends ResultColumn {}
|
||||
|
||||
/// A nested table extracted from a `**` column.
|
||||
///
|
||||
|
@ -549,7 +565,10 @@ class NestedResultTable extends NestedResult {
|
|||
|
||||
NestedResultTable(this.from, this.name, this.table, {this.isNullable = true});
|
||||
|
||||
String get dartFieldName => ReCase(name).camelCase;
|
||||
@override
|
||||
String dartGetterName(Iterable<String> existingNames) {
|
||||
return dartNameForSqlColumn(name, existingNames: existingNames);
|
||||
}
|
||||
|
||||
/// [hashCode] that matches [isCompatibleTo] instead of `==`.
|
||||
@override
|
||||
|
@ -560,7 +579,7 @@ class NestedResultTable extends NestedResult {
|
|||
/// 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) {
|
||||
bool isCompatibleTo(ResultColumn other) {
|
||||
if (other is! NestedResultTable) return false;
|
||||
|
||||
return other.name == name &&
|
||||
|
@ -579,6 +598,11 @@ class NestedResultQuery extends NestedResult {
|
|||
required this.query,
|
||||
});
|
||||
|
||||
@override
|
||||
String dartGetterName(Iterable<String> existingNames) {
|
||||
return dartNameForSqlColumn(filedName(), existingNames: existingNames);
|
||||
}
|
||||
|
||||
String filedName() {
|
||||
if (from.as != null) {
|
||||
return from.as!;
|
||||
|
@ -595,7 +619,7 @@ class NestedResultQuery extends NestedResult {
|
|||
int get compatibilityHashCode => hashCode;
|
||||
|
||||
@override
|
||||
bool isCompatibleTo(NestedResult other) => this == other;
|
||||
bool isCompatibleTo(ResultColumn other) => this == other;
|
||||
}
|
||||
|
||||
/// Something in the query that needs special attention when generating code,
|
||||
|
@ -868,7 +892,7 @@ class _ResultColumnEquality implements Equality<ResultColumn> {
|
|||
const _ResultColumnEquality();
|
||||
|
||||
@override
|
||||
bool equals(ResultColumn e1, ResultColumn e2) => e1.compatibleTo(e2);
|
||||
bool equals(ResultColumn e1, ResultColumn e2) => e1.isCompatibleTo(e2);
|
||||
|
||||
@override
|
||||
int hash(ResultColumn e) => e.compatibilityHashCode;
|
||||
|
@ -876,18 +900,3 @@ class _ResultColumnEquality implements Equality<ResultColumn> {
|
|||
@override
|
||||
bool isValidKey(Object? e) => e is ResultColumn;
|
||||
}
|
||||
|
||||
class _NestedResultEquality implements Equality<NestedResult> {
|
||||
const _NestedResultEquality();
|
||||
|
||||
@override
|
||||
bool equals(NestedResult e1, NestedResult e2) {
|
||||
return e1.isCompatibleTo(e2);
|
||||
}
|
||||
|
||||
@override
|
||||
int hash(NestedResult e) => e.compatibilityHashCode;
|
||||
|
||||
@override
|
||||
bool isValidKey(Object? e) => e is NestedResultTable;
|
||||
}
|
||||
|
|
|
@ -79,7 +79,7 @@ class QueryWriter {
|
|||
final queryRow = _emitter.drift('QueryRow');
|
||||
|
||||
if (resultSet.singleColumn) {
|
||||
final column = resultSet.columns.single;
|
||||
final column = resultSet.scalarColumns.single;
|
||||
_buffer.write('($queryRow row) => '
|
||||
'${readingCode(column, scope.generationOptions, options)}');
|
||||
} else if (resultSet.matchingTable != null) {
|
||||
|
@ -119,31 +119,28 @@ class QueryWriter {
|
|||
|
||||
for (final column in resultSet.columns) {
|
||||
final fieldName = resultSet.dartNameFor(column);
|
||||
_buffer.write('$fieldName: '
|
||||
'${readingCode(column, scope.generationOptions, options)},');
|
||||
}
|
||||
for (final nested in resultSet.nestedResults) {
|
||||
if (nested is NestedResultTable) {
|
||||
final prefix = resultSet.nestedPrefixFor(nested);
|
||||
|
||||
if (column is ScalarResultColumn) {
|
||||
_buffer.write('$fieldName: '
|
||||
'${readingCode(column, scope.generationOptions, options)},');
|
||||
} else if (column is NestedResultTable) {
|
||||
final prefix = resultSet.nestedPrefixFor(column);
|
||||
if (prefix == null) continue;
|
||||
|
||||
final fieldName = nested.dartFieldName;
|
||||
final tableGetter = nested.table.dbGetterName;
|
||||
final tableGetter = column.table.dbGetterName;
|
||||
|
||||
final mappingMethod =
|
||||
nested.isNullable ? 'mapFromRowOrNull' : 'mapFromRow';
|
||||
column.isNullable ? 'mapFromRowOrNull' : 'mapFromRow';
|
||||
|
||||
_buffer.write('$fieldName: await $tableGetter.$mappingMethod(row, '
|
||||
'tablePrefix: ${asDartLiteral(prefix)}),');
|
||||
} else if (nested is NestedResultQuery) {
|
||||
final fieldName = nested.filedName();
|
||||
} else if (column is NestedResultQuery) {
|
||||
_buffer.write('$fieldName: await ');
|
||||
|
||||
_writeCustomSelectStatement(nested.query);
|
||||
|
||||
_writeCustomSelectStatement(column.query);
|
||||
_buffer.write('.get(),');
|
||||
}
|
||||
}
|
||||
|
||||
_buffer.write(');\n}');
|
||||
}
|
||||
}
|
||||
|
@ -151,8 +148,8 @@ class QueryWriter {
|
|||
/// Returns Dart code that, given a variable of type `QueryRow` named `row`
|
||||
/// in the same scope, reads the [column] from that row and brings it into a
|
||||
/// suitable type.
|
||||
String readingCode(ResultColumn column, GenerationOptions generationOptions,
|
||||
DriftOptions moorOptions) {
|
||||
String readingCode(ScalarResultColumn column,
|
||||
GenerationOptions generationOptions, DriftOptions moorOptions) {
|
||||
final specialName = _transformer.newNameFor(column.sqlParserColumn!);
|
||||
|
||||
final dartLiteral = asDartLiteral(specialName ?? column.name);
|
||||
|
|
|
@ -28,39 +28,34 @@ class ResultSetWriter {
|
|||
|
||||
final modifier = scope.options.fieldModifier;
|
||||
|
||||
// write fields
|
||||
// Write fields
|
||||
for (final column in resultSet.columns) {
|
||||
final name = resultSet.dartNameFor(column);
|
||||
final runtimeType = into.dartCode(into.dartType(column));
|
||||
final fieldName = resultSet.dartNameFor(column);
|
||||
|
||||
into.write('$modifier $runtimeType $name\n;');
|
||||
if (column is ScalarResultColumn) {
|
||||
final runtimeType = into.dartCode(into.dartType(column));
|
||||
|
||||
fields.add(EqualityField(name, isList: column.isUint8ListInDart));
|
||||
if (!column.nullable) nonNullableFields.add(name);
|
||||
}
|
||||
|
||||
for (final nested in resultSet.nestedResults) {
|
||||
if (nested is NestedResultTable) {
|
||||
final fieldName = nested.dartFieldName;
|
||||
into.write('$modifier $runtimeType $fieldName\n;');
|
||||
|
||||
fields.add(EqualityField(fieldName, isList: column.isUint8ListInDart));
|
||||
if (!column.nullable) nonNullableFields.add(fieldName);
|
||||
} else if (column is NestedResultTable) {
|
||||
into
|
||||
..write('$modifier ')
|
||||
..writeDart(nested.resultRowType(scope))
|
||||
..write(nested.isNullable ? '? ' : ' ')
|
||||
..writeDart(column.resultRowType(scope))
|
||||
..write(column.isNullable ? '? ' : ' ')
|
||||
..writeln('$fieldName;');
|
||||
|
||||
fields.add(EqualityField(fieldName));
|
||||
if (!nested.isNullable) nonNullableFields.add(fieldName);
|
||||
} else if (nested is NestedResultQuery) {
|
||||
final fieldName = nested.filedName();
|
||||
|
||||
if (nested.query.resultSet.needsOwnClass) {
|
||||
ResultSetWriter(nested.query, scope).write();
|
||||
if (!column.isNullable) nonNullableFields.add(fieldName);
|
||||
} else if (column is NestedResultQuery) {
|
||||
if (column.query.resultSet.needsOwnClass) {
|
||||
ResultSetWriter(column.query, scope).write();
|
||||
}
|
||||
|
||||
into
|
||||
..write('$modifier ')
|
||||
..writeDart(nested.resultRowType(scope))
|
||||
..writeDart(column.resultRowType(scope))
|
||||
..writeln('$fieldName;');
|
||||
|
||||
fields.add(EqualityField(fieldName));
|
||||
|
|
|
@ -58,7 +58,7 @@ extension SqlQueryType on SqlQuery {
|
|||
}
|
||||
|
||||
if (resultSet.singleColumn) {
|
||||
return scope.dartType(resultSet.columns.single);
|
||||
return scope.dartType(resultSet.scalarColumns.single);
|
||||
}
|
||||
|
||||
return AnnotatedDartCode([resultClassName]);
|
||||
|
|
|
@ -33,7 +33,7 @@ WITH RECURSIVE
|
|||
expect(resultSet.singleColumn, isTrue);
|
||||
expect(resultSet.needsOwnClass, isFalse);
|
||||
expect(resultSet.columns.map(resultSet.dartNameFor), ['x']);
|
||||
expect(resultSet.columns.map((c) => c.sqlType), [DriftSqlType.int]);
|
||||
expect(resultSet.scalarColumns.map((c) => c.sqlType), [DriftSqlType.int]);
|
||||
});
|
||||
|
||||
test('finds the underlying table when aliased through CTE', () async {
|
||||
|
|
|
@ -106,8 +106,8 @@ wrongArgs: SELECT sin(oid, foo) FROM numbers;
|
|||
final queryInA =
|
||||
fileA.fileAnalysis!.resolvedQueries.values.single as SqlSelectQuery;
|
||||
expect(
|
||||
queryInA.resultSet.columns.single,
|
||||
const TypeMatcher<ResultColumn>()
|
||||
queryInA.resultSet.scalarColumns.single,
|
||||
const TypeMatcher<ScalarResultColumn>()
|
||||
.having((e) => e.sqlType, 'type', DriftSqlType.double),
|
||||
);
|
||||
|
||||
|
|
|
@ -38,8 +38,8 @@ bar(?1 AS TEXT, :foo AS BOOLEAN): SELECT ?, :foo;
|
|||
|
||||
final resultSet = (query as SqlSelectQuery).resultSet;
|
||||
expect(resultSet.matchingTable, isNull);
|
||||
expect(resultSet.columns.map((c) => c.name), ['?', ':foo']);
|
||||
expect(resultSet.columns.map((c) => c.sqlType),
|
||||
expect(resultSet.scalarColumns.map((c) => c.name), ['?', ':foo']);
|
||||
expect(resultSet.scalarColumns.map((c) => c.sqlType),
|
||||
[DriftSqlType.string, DriftSqlType.bool]);
|
||||
});
|
||||
|
||||
|
@ -161,17 +161,19 @@ q3: SELECT datetime('now');
|
|||
expect(queries, hasLength(3));
|
||||
|
||||
final q1 = queries[0];
|
||||
expect(q1.resultSet!.columns.single.sqlType, DriftSqlType.dateTime);
|
||||
expect(q1.resultSet!.scalarColumns.single.sqlType, DriftSqlType.dateTime);
|
||||
|
||||
final q2 = queries[1];
|
||||
final q3 = queries[2];
|
||||
|
||||
if (dateTimeAsText) {
|
||||
expect(q2.resultSet!.columns.single.sqlType, DriftSqlType.int);
|
||||
expect(q3.resultSet!.columns.single.sqlType, DriftSqlType.dateTime);
|
||||
expect(q2.resultSet!.scalarColumns.single.sqlType, DriftSqlType.int);
|
||||
expect(
|
||||
q3.resultSet!.scalarColumns.single.sqlType, DriftSqlType.dateTime);
|
||||
} else {
|
||||
expect(q2.resultSet!.columns.single.sqlType, DriftSqlType.dateTime);
|
||||
expect(q3.resultSet!.columns.single.sqlType, DriftSqlType.string);
|
||||
expect(
|
||||
q2.resultSet!.scalarColumns.single.sqlType, DriftSqlType.dateTime);
|
||||
expect(q3.resultSet!.scalarColumns.single.sqlType, DriftSqlType.string);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -203,7 +205,7 @@ FROM routes
|
|||
final query = file.fileAnalysis!.resolvedQueries.values.single;
|
||||
final resultSet = (query as SqlSelectQuery).resultSet;
|
||||
|
||||
expect(resultSet.columns.map((e) => e.name), ['id', 'from', 'to']);
|
||||
expect(resultSet.scalarColumns.map((e) => e.name), ['id', 'from', 'to']);
|
||||
expect(resultSet.matchingTable, isNull);
|
||||
expect(
|
||||
resultSet.nestedResults.cast<NestedResultTable>().map((e) => e.name),
|
||||
|
@ -283,7 +285,8 @@ LEFT JOIN tableB1 AS tableB2 -- nullable
|
|||
|
||||
final query = result.fileAnalysis!.resolvedQueries.values.single;
|
||||
expect(query.resultSet!.columns, [
|
||||
isA<ResultColumn>().having((e) => e.sqlType, 'sqlType', DriftSqlType.bool)
|
||||
isA<ScalarResultColumn>()
|
||||
.having((e) => e.sqlType, 'sqlType', DriftSqlType.bool)
|
||||
]);
|
||||
|
||||
final args = query.variables;
|
||||
|
|
|
@ -195,7 +195,7 @@ class _HasInferredColumnTypes extends CustomMatcher {
|
|||
|
||||
final resultSet = actual.resultSet;
|
||||
return {
|
||||
for (final column in resultSet.columns) column.name: column.sqlType
|
||||
for (final column in resultSet.scalarColumns) column.name: column.sqlType
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -283,8 +283,11 @@ class ColumnResolver extends RecursiveVisitor<void, void> {
|
|||
}
|
||||
}
|
||||
|
||||
usedColumns
|
||||
.addAll(visibleColumnsForStar!.where((e) => e.includedInResults));
|
||||
final added =
|
||||
visibleColumnsForStar!.where((e) => e.includedInResults).toList();
|
||||
|
||||
usedColumns.addAll(added);
|
||||
resultColumn.resolvedColumns = added;
|
||||
} else if (resultColumn is ExpressionResultColumn) {
|
||||
final expression = resultColumn.expression;
|
||||
Column column;
|
||||
|
@ -299,6 +302,7 @@ class ColumnResolver extends RecursiveVisitor<void, void> {
|
|||
}
|
||||
|
||||
usedColumns.add(column);
|
||||
resultColumn.resolvedColumns = [column];
|
||||
|
||||
if (resultColumn.as != null) {
|
||||
// make this column available for references if there is no other
|
||||
|
|
|
@ -135,7 +135,18 @@ class ValuesSelectStatement extends BaseSelectStatement
|
|||
Iterable<AstNode> get childNodes => values;
|
||||
}
|
||||
|
||||
abstract class ResultColumn extends AstNode {}
|
||||
abstract class ResultColumn extends AstNode {
|
||||
/// The acutal, schema-level [Column]s that this result column expands to.
|
||||
///
|
||||
/// For a [ExpressionResultColumn], this is usually a singleton list with a
|
||||
/// single [ExpressionColumn]. For star columns, the list may return more
|
||||
/// elements.
|
||||
///
|
||||
/// This list is populated during analysis, it will be `null` after parsing.
|
||||
/// Further, it may be left in its unset state if the column could not be
|
||||
/// analyzed due to an error.
|
||||
List<Column>? resolvedColumns;
|
||||
}
|
||||
|
||||
/// A result column that either yields all columns or all columns from a table
|
||||
/// by using "*" or "table.*".
|
||||
|
|
Loading…
Reference in New Issue