Generate queries with new analyzer

This commit is contained in:
Simon Binder 2022-10-13 22:05:42 +02:00
parent b16d8f5e3a
commit 81ba7686c7
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
10 changed files with 857 additions and 1629 deletions

File diff suppressed because it is too large Load Diff

View File

@ -40,9 +40,20 @@ class DriftAnalysisCache {
///
/// This assumes that pre-analysis has already happened for all transitive
/// imports, meaning that [knownFiles] contains an entry for every import URI.
Iterable<FileState> crawl(FileState entrypoint) sync* {
final seenUris = <Uri>{entrypoint.ownUri};
final pending = [entrypoint];
Iterable<FileState> crawl(FileState entrypoint) {
return crawlMulti([entrypoint]);
}
/// Crawls all dependencies from a set of [entrypoints].
Iterable<FileState> crawlMulti(Iterable<FileState> entrypoints) sync* {
final seenUris = <Uri>{};
final pending = <FileState>[];
for (final initial in entrypoints) {
if (seenUris.add(initial.ownUri)) {
pending.add(initial);
}
}
while (pending.isNotEmpty) {
final found = pending.removeLast();

View File

@ -37,6 +37,17 @@ class FileAnalyzer {
error.span, 'Error in ${query.name}: ${error.message}'));
}
}
final imports = <FileState>[];
for (final include in element.declaredIncludes) {
final imported = driver.cache.knownFiles[include];
if (imported != null) {
imports.add(imported);
}
}
result.resolvedDatabases[element.id] =
ResolvedDatabaseAccessor(queries, imports);
}
}
} else if (state.extension == '.drift' || state.extension == '.moor') {

View File

@ -13,6 +13,8 @@ import 'package:json_annotation/json_annotation.dart';
part '../../generated/analysis/results/dart.g.dart';
class AnnotatedDartCode {
static final Uri dartAsync = Uri.parse('dart:async');
static final Uri dartCore = Uri.parse('dart:core');
static final Uri drift = Uri.parse('package:drift/drift.dart');
final List<dynamic /* String|DartTopLevelSymbol */ > elements;
@ -85,11 +87,21 @@ class AnnotatedDartCodeBuilder {
void addText(String lexeme) => _pendingText.write(lexeme);
void addCode(AnnotatedDartCode code) {
_addPendingText();
_elements.addAll(code.elements);
}
void addSymbol(String lexeme, Uri? importUri) {
_addPendingText();
_elements.add(DartTopLevelSymbol(lexeme, importUri));
}
void addTopLevel(DartTopLevelSymbol symbol) {
_addPendingText();
_elements.add(symbol);
}
void addTopLevelElement(Element element) {
_addPendingText();
_elements.add(DartTopLevelSymbol.topLevelElement(element));

View File

@ -443,7 +443,9 @@ class ElementDeserializer {
? VirtualTableData.fromJson(json['virtual'] as Map)
: null,
writeDefaultConstraints: json['write_default_constraints'] as bool,
overrideTableConstraints: (json['custom_constraints'] as List).cast(),
overrideTableConstraints: json['custom_constraints'] != null
? (json['custom_constraints'] as List).cast()
: null,
);
case 'index':
return DriftIndex(
@ -490,7 +492,7 @@ class ElementDeserializer {
await _readColumn(rawColumn as Map),
];
final serializedSource = json['serializedSource'] as Map;
final serializedSource = json['source'] as Map;
final sourceKind = serializedSource['kind'];
DriftViewSource source;
@ -505,12 +507,12 @@ class ElementDeserializer {
}
source = DartViewSource(
AnnotatedDartCode.fromJson(json['query'] as Map),
json['primaryFrom'] != null
? readReference(json['primaryFrom'] as Map)
AnnotatedDartCode.fromJson(serializedSource['query'] as Map),
serializedSource['primaryFrom'] != null
? readReference(serializedSource['primaryFrom'] as Map)
: null,
[
for (final element in json['staticReferences'])
for (final element in serializedSource['staticReferences'])
readReference(element as Map)
],
);
@ -549,7 +551,7 @@ class ElementDeserializer {
];
final includes =
(json['includes'] as List).cast<String>().map(Uri.parse).toList();
final queries = (json['views'] as List)
final queries = (json['queries'] as List)
.cast<Map>()
.map(QueryOnAccessor.fromJson)
.toList();

View File

@ -78,18 +78,32 @@ class DriftBuilder extends Builder {
return;
}
final result = await driver.resolveElements(buildStep.inputId.uri);
final fileResult = await driver.fullyAnalyze(buildStep.inputId.uri);
final generationOptions = GenerationOptions(
imports: ImportManagerForPartFiles(),
);
final writer = Writer(options, generationOptions: generationOptions);
for (final element in result.analysis.values) {
for (final element in fileResult.analysis.values) {
final result = element.result;
if (result is DriftDatabase) {
DatabaseWriter(result, writer.child()).write();
final importedQueries = <String, SqlQuery>{};
final resolved = fileResult.fileAnalysis!.resolvedDatabases[result.id]!;
// Crawl queries
for (final imported in driver.cache.crawlMulti(resolved.knownImports)) {
final resolved = await driver.fullyAnalyze(imported.ownUri);
importedQueries.addAll({
for (final entry in resolved.fileAnalysis!.resolvedQueries.entries)
entry.key.name: entry.value,
});
}
final input = DatabaseGenerationInput(
result, resolved, importedQueries.values.toList());
DatabaseWriter(input, writer.child()).write();
} else {
writer.leaf().writeln('// ${element.ownId}');
}

View File

@ -4,9 +4,11 @@ import 'package:drift/src/runtime/executor/stream_queries.dart';
import 'package:drift_dev/src/writer/utils/memoized_getter.dart';
import 'package:recase/recase.dart';
import '../analysis/results/file_results.dart';
import '../analysis/results/results.dart';
import '../services/find_stream_update_rules.dart';
import '../utils/string_escaper.dart';
import 'queries/query_writer.dart';
import 'tables/table_writer.dart';
import 'tables/view_writer.dart';
import 'writer.dart';
@ -14,10 +16,12 @@ import 'writer.dart';
/// Generates the Dart code put into a `.g.dart` file when running the
/// generator.
class DatabaseWriter {
final DriftDatabase db;
DatabaseGenerationInput input;
final Scope scope;
DatabaseWriter(this.db, this.scope);
DriftDatabase get db => input.db;
DatabaseWriter(this.input, this.scope);
String get dbClassName {
if (scope.generationOptions.isGeneratingForSchema) {
@ -118,7 +122,11 @@ class DatabaseWriter {
}
// Write implementation for query methods
// db.queries?.forEach((query) => QueryWriter(dbScope.child()).write(query));
final queries = input.resolvedAccessor.definedQueries.values
.followedBy(input.importedQueries);
for (final query in queries) {
QueryWriter(dbScope.child()).write(query);
}
// Write List of tables
final schemaScope = dbScope.leaf();
@ -180,6 +188,15 @@ class DatabaseWriter {
}
}
class DatabaseGenerationInput {
final DriftDatabase db;
final ResolvedDatabaseAccessor resolvedAccessor;
final List<SqlQuery> importedQueries;
DatabaseGenerationInput(this.db, this.resolvedAccessor, this.importedQueries);
}
extension on drift.UpdateRule {
void writeConstructor(TextEmitter emitter) {
if (this is drift.WritePropagation) {

View File

@ -1,13 +1,15 @@
import 'package:drift_dev/moor_generator.dart';
import 'package:drift_dev/src/analyzer/options.dart';
import 'package:drift_dev/src/analyzer/sql_queries/explicit_alias_transformer.dart';
import 'package:drift_dev/src/analyzer/sql_queries/nested_queries.dart';
import 'package:drift_dev/src/utils/string_escaper.dart';
import 'package:drift_dev/writer.dart';
import 'package:recase/recase.dart';
import 'package:sqlparser/sqlparser.dart' hide ResultColumn;
import '../../analysis/resolver/queries/nested_queries.dart';
import '../../analysis/results/results.dart';
import '../../analyzer/options.dart';
import '../../analyzer/sql_queries/explicit_alias_transformer.dart';
import '../../utils/string_escaper.dart';
import '../writer.dart';
import 'result_set_writer.dart';
import 'sql_writer.dart';
import 'utils.dart';
const highestAssignedIndexVar = '\$arrayStartIndex';
@ -17,18 +19,19 @@ class QueryWriter {
final Scope scope;
late final ExplicitAliasTransformer _transformer;
final StringBuffer _buffer;
final TextEmitter _emitter;
StringBuffer get _buffer => _emitter.buffer;
DriftOptions get options => scope.writer.options;
QueryWriter(this.scope) : _buffer = scope.leaf();
QueryWriter(this.scope) : _emitter = scope.leaf();
void write(SqlQuery query) {
// Note that writing queries can have a result set if they use a RETURNING
// clause.
final resultSet = query.resultSet;
if (resultSet?.needsOwnClass == true) {
final resultSetScope = scope.findScopeOfLevel(DartScope.library);
final resultSetScope = scope.root.child();
ResultSetWriter(query, resultSetScope).write();
}
@ -96,7 +99,7 @@ class QueryWriter {
_buffer
..write(asDartLiteral(alias.key))
..write(': ')
..write(asDartLiteral(alias.value.name.name))
..write(asDartLiteral(alias.value.nameInSql))
..write(', ');
}
@ -153,7 +156,7 @@ class QueryWriter {
final dartLiteral = asDartLiteral(specialName ?? column.name);
final method = column.nullable ? 'readNullable' : 'read';
final rawDartType = dartTypeNames[column.type];
final rawDartType = dartTypeNames[column.sqlType];
var code = 'row.$method<$rawDartType>($dartLiteral)';
final converter = column.typeConverter;
@ -163,10 +166,10 @@ class QueryWriter {
// nullable in SQL => just map null to null and only invoke the type
// converter for non-null values.
code = 'NullAwareTypeConverter.wrapFromSql'
'(${_converter(converter)}, $code)';
'(${_converter(_emitter, converter)}, $code)';
} else {
// Just apply the type converter directly.
code = '${_converter(converter)}.fromSql($code)';
code = '${_converter(_emitter, converter)}.fromSql($code)';
}
}
return code;
@ -175,11 +178,19 @@ class QueryWriter {
/// Writes a method returning a `Selectable<T>`, where `T` is the return type
/// of the custom query.
void _writeSelectStatementCreator(SqlSelectQuery select) {
final returnType =
'Selectable<${select.resultTypeCode(scope.generationOptions)}>';
final returnType = AnnotatedDartCode.build((builder) {
builder
..addSymbol('Selectable', AnnotatedDartCode.drift)
..addText('<')
..addCode(select.resultRowType(scope))
..addText('>');
});
final methodName = _nameOfCreationMethod(select);
_buffer.write('$returnType $methodName(');
_emitter
..writeDart(returnType)
..write(' $methodName(');
_writeParameters(select);
_buffer.write(') {\n');
@ -205,8 +216,19 @@ class QueryWriter {
}
void _writeUpdatingQueryWithReturning(UpdatingQuery update) {
final type = update.resultTypeCode(scope.generationOptions);
_buffer.write('Future<List<$type>> ${update.name}(');
final type = AnnotatedDartCode.build((builder) {
builder
..addSymbol('Future', AnnotatedDartCode.dartAsync)
..addText('<')
..addSymbol('List', AnnotatedDartCode.dartCore)
..addText('<')
..addCode(update.resultRowType(scope))
..addText('>>');
});
_emitter
..writeDart(type)
..write(' ${update.name}(');
_writeParameters(update);
_buffer.write(') {\n');
@ -260,7 +282,7 @@ class QueryWriter {
}
String typeFor(FoundElement element) {
return element.dartTypeCode();
return _emitter.dartCode(element.dartType(scope));
}
String writeScopedTypeFor(FoundDartPlaceholder element) {
@ -362,7 +384,7 @@ class QueryWriter {
}
void _writeVariables(SqlQuery query) {
_ExpandedVariableWriter(query, scope, _buffer).writeVariables();
_ExpandedVariableWriter(query, _emitter).writeVariables();
}
/// Returns a Dart string literal representing the query after variables have
@ -406,11 +428,8 @@ class QueryWriter {
}
/// Returns code to load an instance of the [converter] at runtime.
String _converter(UsedTypeConverter converter) {
final infoName = converter.table!.entityInfoName;
final field = '$infoName.${converter.fieldName}';
return field;
String _converter(TextEmitter emitter, AppliedTypeConverter converter) {
return emitter.dartCode(emitter.readConverter(converter));
}
class _ExpandedDeclarationWriter {
@ -501,7 +520,7 @@ class _ExpandedDeclarationWriter {
// The parameter is a function type that needs to be evaluated first
final args = element.availableResultSets.map((e) {
final table = 'this.${e.entity.dbGetterName}';
final needsAlias = e.name != e.entity.displayName;
final needsAlias = e.name != e.entity.schemaName;
if (needsAlias) {
return 'alias($table, ${asDartLiteral(e.name)})';
@ -585,10 +604,10 @@ class _ExpandedDeclarationWriter {
class _ExpandedVariableWriter {
final SqlQuery query;
final Scope scope;
final StringBuffer _buffer;
final TextEmitter _emitter;
StringBuffer get _buffer => _emitter.buffer;
_ExpandedVariableWriter(this.query, this.scope, this._buffer);
_ExpandedVariableWriter(this.query, this._emitter);
void writeVariables() {
_buffer.write('variables: [');
@ -652,10 +671,10 @@ class _ExpandedVariableWriter {
// Apply the converter.
if (element.nullable && converter.canBeSkippedForNulls) {
buffer.write('NullAwareTypeConverter.wrapToSql('
'${_converter(element.typeConverter!)}, $dartExpr)');
'${_converter(_emitter, element.typeConverter!)}, $dartExpr)');
} else {
buffer
.write('${_converter(element.typeConverter!)}.toSql($dartExpr)');
buffer.write(
'${_converter(_emitter, element.typeConverter!)}.toSql($dartExpr)');
}
} else if (capture != null) {
buffer.write('row.read(${asDartLiteral(capture.helperColumn)})');

View File

@ -1,7 +1,8 @@
import 'package:drift_dev/src/model/sql_query.dart';
import 'package:drift_dev/src/model/types.dart';
import 'package:drift_dev/src/writer/utils/override_toString.dart';
import 'package:drift_dev/writer.dart';
import '../../analysis/results/results.dart';
import '../utils/hash_and_equals.dart';
import '../utils/override_toString.dart';
import '../writer.dart';
import 'utils.dart';
/// Writes a class holding the result of an sql query into Dart.
class ResultSetWriter {
@ -30,7 +31,7 @@ class ResultSetWriter {
// write fields
for (final column in resultSet.columns) {
final name = resultSet.dartNameFor(column);
final runtimeType = column.dartTypeCode();
final runtimeType = into.dartCode(into.dartType(column));
into.write('$modifier $runtimeType $name\n;');
@ -40,26 +41,27 @@ class ResultSetWriter {
for (final nested in resultSet.nestedResults) {
if (nested is NestedResultTable) {
var typeName = nested.table.dartTypeCode();
final fieldName = nested.dartFieldName;
if (nested.isNullable) {
typeName += '?';
}
into.write('$modifier $typeName $fieldName;\n');
into
..write('$modifier ')
..writeDart(nested.resultRowType(scope))
..write(nested.isNullable ? '? ' : ' ')
..writeln('$fieldName;');
fields.add(EqualityField(fieldName));
if (!nested.isNullable) nonNullableFields.add(fieldName);
} else if (nested is NestedResultQuery) {
final fieldName = nested.filedName();
final typeName = nested.resultTypeCode();
if (nested.query.resultSet.needsOwnClass) {
ResultSetWriter(nested.query, scope).write();
}
into.write('$modifier List<$typeName> $fieldName;\n');
into
..write(modifier)
..writeDart(nested.resultRowType(scope))
..writeln('$fieldName;');
fields.add(EqualityField(fieldName));
nonNullableFields.add(fieldName);
@ -89,11 +91,12 @@ class ResultSetWriter {
// if requested, override hashCode and equals
if (scope.writer.options.overrideHashAndEqualsInResultSets) {
into.write('@override int get hashCode => ');
writeHashCode(fields, into);
writeHashCode(fields, into.buffer);
into.write(';\n');
overrideEquals(fields, className, into);
overrideToString(className, fields.map((f) => f.lexeme).toList(), into);
overrideEquals(fields, className, into.buffer);
overrideToString(
className, fields.map((f) => f.lexeme).toList(), into.buffer);
}
into.write('}\n');

View File

@ -0,0 +1,85 @@
import '../../analysis/results/results.dart';
import '../writer.dart';
extension FoundElementType on FoundElement {
AnnotatedDartCode dartType(Scope scope) {
final $this = this;
if ($this is FoundVariable) {
return scope.dartType($this);
} else if ($this is FoundDartPlaceholder) {
return AnnotatedDartCode.build((builder) {
final kind = $this.type;
if (kind is SimpleDartPlaceholderType) {
switch (kind.kind) {
case SimpleDartPlaceholderKind.limit:
builder.addSymbol('Limit', AnnotatedDartCode.drift);
break;
case SimpleDartPlaceholderKind.orderByTerm:
builder.addSymbol('OrderingTerm', AnnotatedDartCode.drift);
break;
case SimpleDartPlaceholderKind.orderBy:
builder.addSymbol('OrderBy', AnnotatedDartCode.drift);
break;
}
} else if (kind is ExpressionDartPlaceholderType) {
builder
..addSymbol('Expression', AnnotatedDartCode.drift)
..addText('<')
..addTopLevel(dartTypeNames[kind.columnType]!)
..addText('>');
} else if (kind is InsertableDartPlaceholderType) {
final table = kind.table;
builder.addSymbol('Insertable', AnnotatedDartCode.drift);
if (table != null) {
builder
..addText('<')
..addCode(scope.rowType(table))
..addText('>');
}
}
});
} else {
throw ArgumentError.value(this, 'this', 'Unknown query element');
}
}
}
extension SqlQueryType on SqlQuery {
AnnotatedDartCode resultRowType(Scope scope) {
final resultSet = this.resultSet;
if (resultSet == null) {
throw StateError('This query ($name) does not have a result set');
}
if (resultSet.matchingTable != null) {
return scope.rowType(resultSet.matchingTable!.table);
}
if (resultSet.singleColumn) {
return scope.dartType(resultSet.columns.single);
}
return AnnotatedDartCode([resultClassName]);
}
}
extension NestedResultType on NestedResult {
AnnotatedDartCode resultRowType(Scope scope) {
final $this = this;
if ($this is NestedResultTable) {
return scope.rowType($this.table);
} else if ($this is NestedResultQuery) {
return AnnotatedDartCode.build((builder) {
builder
..addSymbol('List', AnnotatedDartCode.dartCore)
..addText('<')
..addCode($this.query.resultRowType(scope))
..addText('>');
});
} else {
throw ArgumentError.value($this, 'this', 'Unknown nested type');
}
}
}