Skip to content

Commit 1447293

Browse files
committed
drift3: Support LIST() queries
1 parent 3eb0786 commit 1447293

10 files changed

Lines changed: 245 additions & 75 deletions

File tree

drift_dev/lib/src/analysis/resolver/queries/nested_queries.dart

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -126,15 +126,15 @@ class _AnalyzerState {
126126
void _process() {
127127
// Add necessary columns to select variables read by inner nested queries.
128128
for (final variable in container.variablesCapturedByChildren) {
129-
container.addedColumns.add(
130-
ExpressionResultColumn(
131-
expression: Reference(
132-
entityName: variable.reference.entityName,
133-
columnName: variable.reference.columnName,
134-
),
135-
as: AliasClause(variable.helperColumn),
129+
final column = ExpressionResultColumn(
130+
expression: Reference(
131+
entityName: variable.reference.entityName,
132+
columnName: variable.reference.columnName,
136133
),
134+
as: AliasClause(variable.helperColumn),
137135
);
136+
container.addedColumns.add(column);
137+
variable.columnIndex = container.nextColumnIndex++;
138138
}
139139

140140
// Re-index variables. This is necessary for two reasons:

drift_dev/lib/src/analysis/results/query.dart

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ class SqlSelectQuery extends SqlQuery {
332332
class NestedQueriesContainer {
333333
final SelectStatement select;
334334
final Map<NestedQueryColumn, NestedQuery> nestedQueries = {};
335+
int nextColumnIndex = 0;
335336

336337
/// The nested queries transformation may change the index of variables used
337338
/// in a query.
@@ -347,7 +348,22 @@ class NestedQueriesContainer {
347348
/// variables with different indexes are considered different variables.
348349
final Map<Variable, int> originalIndexForVariable = {};
349350

350-
NestedQueriesContainer(this.select);
351+
NestedQueriesContainer(this.select) {
352+
for (final column in select.columns) {
353+
if (column case NestedStarResultColumn(:final resultSet?)) {
354+
// Nested star columns aren't resolved by the sqlparser package, so we
355+
// need to count columns here.
356+
nextColumnIndex += resultSet.resolvedColumns
357+
?.where((c) => c.includedInResults)
358+
.length ??
359+
0;
360+
} else if (column.resolvedColumns case final columns?) {
361+
// All other result columns should have columns set on them if they
362+
// appear in the final query.
363+
nextColumnIndex += columns.length;
364+
}
365+
}
366+
}
351367

352368
/// Columns that should be added to the [select] statement to read variables
353369
/// captured by children.
@@ -390,9 +406,14 @@ class CapturedVariable {
390406
/// This variable is not mounted to the same syntax tree as [reference], it
391407
/// will be mounted into the tree returned by [addHelperNodes].
392408
final NamedVariable introducedVariable;
409+
FoundVariable? resolvedVariable;
393410

394411
String get helperColumn => '\$n_$queryGlobalId';
395412

413+
/// The index at which [helperColumn] has been inserted into the outer select
414+
/// statement.
415+
int? columnIndex;
416+
396417
CapturedVariable(this.reference, this.queryGlobalId)
397418
: introducedVariable = NamedVariable.synthetic(':', 'r$queryGlobalId') {
398419
introducedVariable.setMeta<CapturedVariable>(this);
@@ -1039,14 +1060,16 @@ class FoundVariable extends FoundElement implements HasType {
10391060
required this.name,
10401061
required this.sqlType,
10411062
required Variable variable,
1042-
required this.forCaptured,
1063+
required CapturedVariable this.forCaptured,
10431064
}) : originalIndex = index,
10441065
typeConverter = null,
10451066
nullable = false,
10461067
isArray = false,
10471068
isRequired = true,
10481069
hidden = true,
1049-
syntacticOrigin = variable;
1070+
syntacticOrigin = variable {
1071+
forCaptured!.resolvedVariable = this;
1072+
}
10501073

10511074
@override
10521075
String get dartParameterName {

drift_dev/lib/src/writer/queries/query_writer.dart

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ typedef _ArgumentContext = ({
2828
/// should be included in a generated database or dao class.
2929
class QueryWriter {
3030
final Scope scope;
31+
final Map<CapturedVariable, String> _outerVariables = {};
3132

3233
late final ExplicitAliasTransformer _transformer;
3334
final TextEmitter _emitter;
@@ -40,6 +41,8 @@ class QueryWriter {
4041

4142
QueryWriter(this.scope) : _emitter = scope.leaf();
4243

44+
QueryWriter._existingEmitter(this.scope, this._emitter);
45+
4346
void write(SqlQuery query) {
4447
// Note that writing queries can have a result set if they use a RETURNING
4548
// clause.
@@ -88,9 +91,10 @@ class QueryWriter {
8891

8992
/// Writes the function literal that turns a "QueryRow" into the desired
9093
/// custom return type of a query.
91-
void _writeMappingLambda(InferredResultSet resultSet, QueryRowType rowClass) {
94+
void _writeMappingLambda(InferredResultSet resultSet,
95+
NestedQueriesContainer? nestedQueries, QueryRowType rowClass) {
9296
if (drift3) {
93-
return _Drift3MappingCodeWriter(this)
97+
return _Drift3MappingCodeWriter(this, nestedQueries)
9498
.write(_emitter, resultSet, rowClass);
9599
}
96100

@@ -317,17 +321,20 @@ class QueryWriter {
317321
[QueryRowType? resultType]) {
318322
final resultSet = select.resultSet;
319323
resultType ??= select.queryRowType(options);
324+
final needsAsyncMapping = resultType.requiresAsynchronousContext(options);
320325

321326
if (drift3) {
327+
final methodName =
328+
needsAsyncMapping ? 'customSelectMappedAsync' : 'customSelectMapped';
322329
_emitter
323-
..write(' customSelectMapped<')
330+
..write(' $methodName<')
324331
..writeDart(resultType.rowType)
325332
..write('>(query: ${_queryCode(select)},');
326333
_writeVariables(select);
327334
_emitter.write(', ');
328335
_writeReadsFrom(select);
329336
_emitter.write(', createMapper: ');
330-
_writeMappingLambda(resultSet, resultType);
337+
_writeMappingLambda(resultSet, select.nestedContainer, resultType);
331338
_emitter.write(')');
332339
return;
333340
}
@@ -337,13 +344,13 @@ class QueryWriter {
337344
_buffer.write(', ');
338345
_writeReadsFrom(select);
339346

340-
if (resultType.requiresAsynchronousContext(options)) {
347+
if (needsAsyncMapping) {
341348
_buffer.write(').asyncMap(');
342349
} else {
343350
_buffer.write(').map(');
344351
}
345352

346-
_writeMappingLambda(resultSet, resultType);
353+
_writeMappingLambda(resultSet, select.nestedContainer, resultType);
347354
_buffer.write(')');
348355
}
349356

@@ -374,7 +381,7 @@ class QueryWriter {
374381
if (drift3) {
375382
_buffer.writeln(').then((rows) {');
376383

377-
final mappingWriter = _Drift3MappingCodeWriter(this)
384+
final mappingWriter = _Drift3MappingCodeWriter(this, null)
378385
.._writeArgumentExpression(
379386
rowType, resultSet, (isNullable: false, sqlPrefix: null));
380387
_buffer
@@ -398,11 +405,11 @@ class QueryWriter {
398405

399406
if (rowType.requiresAsynchronousContext(options)) {
400407
_buffer.write('Future.wait(rows.map(');
401-
_writeMappingLambda(resultSet, rowType);
408+
_writeMappingLambda(resultSet, null, rowType);
402409
_buffer.write('))');
403410
} else {
404411
_buffer.write('rows.map(');
405-
_writeMappingLambda(resultSet, rowType);
412+
_writeMappingLambda(resultSet, null, rowType);
406413
_buffer.write(').toList()');
407414
}
408415
_buffer.write(');\n}');
@@ -545,7 +552,7 @@ class QueryWriter {
545552
}
546553

547554
void _writeVariables(SqlQuery query) {
548-
_ExpandedVariableWriter(query, _emitter).writeVariables();
555+
_ExpandedVariableWriter(query, this).writeVariables();
549556
}
550557

551558
/// Returns a Dart string literal representing the query after variables have
@@ -640,6 +647,7 @@ String readConverter(TextEmitter emitter, AppliedTypeConverter converter) {
640647
/// query.
641648
class _Drift3MappingCodeWriter {
642649
final QueryWriter _writer;
650+
final NestedQueriesContainer? nestedQueries;
643651

644652
final StringBuffer _outerSetup = StringBuffer();
645653
final StringBuffer _innerMapper = StringBuffer();
@@ -649,7 +657,7 @@ class _Drift3MappingCodeWriter {
649657

650658
TextEmitter get _emitter => _writer._emitter;
651659

652-
_Drift3MappingCodeWriter(this._writer);
660+
_Drift3MappingCodeWriter(this._writer, this.nestedQueries);
653661

654662
String referenceBuiltinType(DriftSqlType type) {
655663
return _obtainedTypes.putIfAbsent(type, () {
@@ -690,7 +698,10 @@ class _Drift3MappingCodeWriter {
690698
..writeln(_outerSetup)
691699
..write('return (')
692700
..writeDriftRef('RawRow')
693-
..writeln(' row) => $_innerMapper;')
701+
..write(' row) ')
702+
..write(
703+
rowClass.requiresAsynchronousContext(_writer.options) ? 'async' : '')
704+
..writeln('=> $_innerMapper;')
694705
..writeln('}');
695706
}
696707

@@ -715,12 +726,23 @@ class _Drift3MappingCodeWriter {
715726
resultSet,
716727
(sqlPrefix: prefix, isNullable: argument.nullable),
717728
);
718-
case MappedNestedListQuery():
719-
_innerMapper.write("throw 'todo'");
720-
// _innerMapper.write('await ');
721-
// final query = argument.column.query;
722-
// _writeCustomSelectStatement(query, argument.nestedType);
723-
// _innerMapper.write('.get()');
729+
case MappedNestedListQuery(:final column):
730+
final knownVariableTypes = <CapturedVariable, String>{};
731+
if (nestedQueries?.nestedQueries[column.from] case final nested?) {
732+
for (final variable in nested.capturedVariables.values) {
733+
if (variable.resolvedVariable case final resolved?) {
734+
knownVariableTypes[variable] = referenceType(resolved.sqlType);
735+
}
736+
}
737+
}
738+
739+
_innerMapper.write('await ');
740+
final query = argument.column.query;
741+
final innerWriter = QueryWriter._existingEmitter(
742+
_writer.scope, TextEmitter(_writer.scope, buffer: _innerMapper));
743+
innerWriter._outerVariables.addAll(knownVariableTypes);
744+
innerWriter._writeCustomSelectStatement(query, argument.nestedType);
745+
_innerMapper.write('.get()');
724746
case QueryRowType():
725747
final singleValue = argument.singleValue;
726748
if (singleValue != null) {
@@ -1007,10 +1029,12 @@ class _ExpandedDeclarationWriter {
10071029
class _ExpandedVariableWriter {
10081030
final SqlQuery query;
10091031
final TextEmitter _emitter;
1032+
final QueryWriter _queryWriter;
10101033

10111034
StringBuffer get _buffer => _emitter.buffer;
10121035

1013-
_ExpandedVariableWriter(this.query, this._emitter);
1036+
_ExpandedVariableWriter(this.query, this._queryWriter)
1037+
: _emitter = _queryWriter._emitter;
10141038

10151039
void writeVariables() {
10161040
_buffer.write('variables: ');
@@ -1097,6 +1121,16 @@ class _ExpandedVariableWriter {
10971121
String constructVar(String dartExpr) {
10981122
final capture = element.forCaptured;
10991123
if (capture != null) {
1124+
if (_emitter.writer.options.drift3Preview) {
1125+
return _emitter.dartCode(AnnotatedDartCode.build((b) {
1126+
b.addSymbol('MappedValue', AnnotatedDartCode.drift);
1127+
b.addText('.raw(');
1128+
b.addText('${_queryWriter._outerVariables[capture]},');
1129+
b.addText('row[${capture.columnIndex}]');
1130+
b.addText(')');
1131+
}));
1132+
}
1133+
11001134
dartExpr = ('row.read(${asDartLiteral(capture.helperColumn)})');
11011135
}
11021136

drift_dev/lib/src/writer/writer.dart

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -277,24 +277,20 @@ abstract class _NodeOrWriter {
277277
return AnnotatedDartCode.build((b) {
278278
final converter = column.typeConverter;
279279

280-
if (writer.options.drift3Preview) {
281-
b.addText('mapValue(');
282-
b.addCode(_drift3SqlType(column.sqlType));
283-
b.addText(', ');
284-
if (converter != null) {
285-
// apply type converter before writing the variable
286-
b
287-
..addCode(readConverter(converter, forNullable: column.nullable))
288-
..addText('.toSql(')
289-
..addCode(expression)
290-
..addText(')');
291-
} else {
292-
b.addCode(expression);
293-
}
294-
b.addText(')');
295-
296-
return;
280+
b.addText('mapValue(');
281+
b.addCode(_drift3SqlType(column.sqlType));
282+
b.addText(', ');
283+
if (converter != null) {
284+
// apply type converter before writing the variable
285+
b
286+
..addCode(readConverter(converter, forNullable: column.nullable))
287+
..addText('.toSql(')
288+
..addCode(expression)
289+
..addText(')');
290+
} else {
291+
b.addCode(expression);
297292
}
293+
b.addText(')');
298294
});
299295
}
300296

@@ -568,14 +564,16 @@ class Scope extends _Node {
568564
}
569565

570566
class TextEmitter extends _Node {
571-
final StringBuffer buffer = StringBuffer();
567+
final StringBuffer buffer;
572568
final void Function(TaggedDartLexeme, StringBuffer)? writeTaggedDartCode;
573569

574570
@override
575571
final Writer writer;
576572

577-
TextEmitter(Scope super.parent, {this.writeTaggedDartCode})
578-
: writer = parent.writer;
573+
TextEmitter(Scope super.parent,
574+
{this.writeTaggedDartCode, StringBuffer? buffer})
575+
: writer = parent.writer,
576+
buffer = buffer ?? StringBuffer();
579577

580578
@override
581579
void _writeTagged(StringBuffer buffer, TaggedDartLexeme lexeme) {

drift_dev/test/writer/queries/query_writer_test.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,17 @@ FROM tbl AS parent WHERE parent.a = :a;
268268
);
269269
});
270270

271+
test('drift3 preview', () {
272+
return runTest(
273+
const DriftOptions.defaults(drift3Preview: true),
274+
[
275+
contains(
276+
r"variables: [mapValue(BuiltinDriftType.text, a), MappedValue.raw(type$0,row[1]), mapValue(BuiltinDriftType.text, b)]",
277+
),
278+
],
279+
);
280+
});
281+
271282
test('should generate correct data class', () {
272283
return runTest(
273284
const DriftOptions.defaults(),

future/drift3/lib/src/database/connection_user.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,25 @@ abstract base class DatabaseConnectionUser {
539539
);
540540
}
541541

542+
/// Like [customSelectMapped], but allows [createMapper] to be asynchronous
543+
/// and to fire off additional queries.
544+
Selectable<T> customSelectMappedAsync<T>({
545+
required String query,
546+
required Future<T> Function(RawRow) Function(RawResultSet) createMapper,
547+
List<MappedValue> variables = const [],
548+
Set<ResultSet> readsFrom = const {},
549+
bool isReadOnly = true,
550+
}) {
551+
return AsyncCustomSelectStatement(
552+
query,
553+
variables,
554+
readsFrom,
555+
createMapper,
556+
this,
557+
isReadOnly,
558+
);
559+
}
560+
542561
/// Map a Dart [value] into a typed [MappedValue] understood by the database
543562
/// driver.
544563
MappedValue mapValue<T extends Object>(SqlType<T> type, T? value) {

0 commit comments

Comments
 (0)