mirror of https://github.com/AMT-Cheif/drift.git
Begin optional modular code generation
This commit is contained in:
parent
f41cff5fc6
commit
f4e45584ec
|
@ -0,0 +1,66 @@
|
|||
/// Internal library used by generated code when drift's modular code generation
|
||||
/// is enabled.
|
||||
///
|
||||
/// This library is not part of drift's public API and should not be imported
|
||||
/// manually.
|
||||
library drift.internal.modules;
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
|
||||
final _databaseElementCache = Expando<_DatabaseElementCache>();
|
||||
|
||||
/// A database accessor implicitly created by a `.drift` file.
|
||||
///
|
||||
/// When modular code generation is enabled, drift will emit a file with a
|
||||
/// [ModularAccessor] for each drift file instead of generating all the code for
|
||||
/// a database into a single file.
|
||||
class ModularAccessor extends DatabaseAccessor<GeneratedDatabase> {
|
||||
/// Default constructor - create an accessor from the [attachedDatabase].
|
||||
ModularAccessor(super.attachedDatabase);
|
||||
|
||||
/// Find a result set by its [name] in the database. The result is cached.
|
||||
T resultSet<T extends ResultSetImplementation>(String name) {
|
||||
return attachedDatabase.resultSet(name);
|
||||
}
|
||||
|
||||
/// Find an accessor by its [name] in the database, or create it with
|
||||
/// [create]. The result will be cached.
|
||||
T accessor<T extends DatabaseAccessor>(
|
||||
String name, T Function(GeneratedDatabase) create) {
|
||||
return attachedDatabase.accessor<T>(name, create);
|
||||
}
|
||||
}
|
||||
|
||||
/// Look up cached elements or accessors from a database.
|
||||
/// This extension is meant to be used by drift-generated code.
|
||||
extension ReadDatabaseContainer on GeneratedDatabase {
|
||||
_DatabaseElementCache get _cache {
|
||||
return _databaseElementCache[attachedDatabase] ??=
|
||||
_DatabaseElementCache(attachedDatabase);
|
||||
}
|
||||
|
||||
/// Find a result set by its [name] in the database. The result is cached.
|
||||
T resultSet<T extends ResultSetImplementation>(String name) {
|
||||
return _cache.knownEntities[name]! as T;
|
||||
}
|
||||
|
||||
/// Find an accessor by its [name] in the database, or create it with
|
||||
/// [create]. The result will be cached.
|
||||
T accessor<T extends DatabaseAccessor>(
|
||||
String name, T Function(GeneratedDatabase) create) {
|
||||
final cache = _cache.knownAccessors;
|
||||
|
||||
return cache.putIfAbsent(name, () => create(attachedDatabase)) as T;
|
||||
}
|
||||
}
|
||||
|
||||
class _DatabaseElementCache {
|
||||
final Map<String, DatabaseSchemaEntity> knownEntities;
|
||||
final Map<String, DatabaseAccessor> knownAccessors = {};
|
||||
|
||||
_DatabaseElementCache(GeneratedDatabase database)
|
||||
: knownEntities = {
|
||||
for (final entity in database.allSchemaEntities)
|
||||
entity.entityName: entity
|
||||
};
|
||||
}
|
|
@ -20,6 +20,7 @@ builders:
|
|||
build_to: cache
|
||||
applies_builders: ["drift_dev:cleanup"]
|
||||
|
||||
# Regular build flow, emitting a shared part file for source_gen to pick up
|
||||
drift_dev:
|
||||
import: "package:drift_dev/integrations/build.dart"
|
||||
builder_factories: ["analyzer", "driftBuilder"]
|
||||
|
@ -31,6 +32,8 @@ builders:
|
|||
required_inputs: [".drift_prep.json"]
|
||||
applies_builders: ["source_gen:combining_builder", ":preparing_builder"]
|
||||
|
||||
# Similar to the regular builder, but emitting a direct part file instead of a
|
||||
# shared part file.
|
||||
not_shared:
|
||||
import: "package:drift_dev/integrations/build.dart"
|
||||
builder_factories: ["driftBuilderNotShared"]
|
||||
|
@ -39,6 +42,26 @@ builders:
|
|||
auto_apply: none
|
||||
required_inputs: [".drift_prep.json"]
|
||||
|
||||
# A work-in-progress builder that emits standalone Dart libraries for each
|
||||
# .drift file.
|
||||
modular:
|
||||
import: "package:drift_dev/integrations/build.dart"
|
||||
builder_factories: ["modular"]
|
||||
build_extensions: {".dart": [".drift.dart"]}
|
||||
build_to: source
|
||||
auto_apply: none
|
||||
required_inputs: [".drift.drift_module.json"]
|
||||
applies_builders: [":analyzer"]
|
||||
analyzer:
|
||||
import: "package:drift_dev/integrations/build.dart"
|
||||
builder_factories: ["analyzer"]
|
||||
build_extensions:
|
||||
".dart": [".dart.drift_module.json"]
|
||||
".drift": [".drift.drift_module.json"]
|
||||
build_to: cache
|
||||
auto_apply: none
|
||||
required_inputs: [".drift_prep.json"]
|
||||
|
||||
post_process_builders:
|
||||
cleanup:
|
||||
import: "package:drift_dev/integrations/build.dart"
|
||||
|
|
|
@ -13,6 +13,9 @@ Builder driftBuilder(BuilderOptions options) =>
|
|||
Builder driftBuilderNotShared(BuilderOptions options) =>
|
||||
DriftBuilder(DriftGenerationMode.monolithicPart, options);
|
||||
|
||||
Builder modular(BuilderOptions options) =>
|
||||
DriftBuilder(DriftGenerationMode.modular, options);
|
||||
|
||||
PostProcessBuilder driftCleanup(BuilderOptions options) {
|
||||
return const FileDeletingBuilder(['.temp.dart']);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:sqlparser/sqlparser.dart' hide AnalysisError;
|
|||
import '../results/database.dart';
|
||||
import '../results/element.dart';
|
||||
import '../results/file_results.dart';
|
||||
import '../results/query.dart';
|
||||
import 'error.dart';
|
||||
|
||||
class FileState {
|
||||
|
@ -26,6 +27,15 @@ class FileState {
|
|||
return analyzedElements.any((e) => e is BaseDriftAccessor);
|
||||
}
|
||||
|
||||
/// Whether an accessor class making queries and imports available should be
|
||||
/// written for this file if modular analysis is enabled.
|
||||
bool get hasModularDriftAccessor {
|
||||
final hasImports = discovery?.importDependencies.isNotEmpty == true;
|
||||
final hasQuery = analyzedElements.any((e) => e is DefinedSqlQuery);
|
||||
|
||||
return hasImports || hasQuery;
|
||||
}
|
||||
|
||||
/// All analyzed [DriftElement]s found in this library.
|
||||
@visibleForTesting
|
||||
Iterable<DriftElement> get analyzedElements {
|
||||
|
|
|
@ -24,6 +24,12 @@ class DriftElementId {
|
|||
|
||||
Map<String, Object?> toJson() => _$DriftElementIdToJson(this);
|
||||
|
||||
Uri get modularImportUri {
|
||||
final path = url.withoutExtension(libraryUri.path);
|
||||
|
||||
return libraryUri.replace(path: '$path.drift.dart');
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(DriftElementId, libraryUri, name);
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:build/build.dart';
|
||||
import 'package:dart_style/dart_style.dart';
|
||||
import 'package:drift_dev/src/writer/tables/table_writer.dart';
|
||||
import 'package:pub_semver/pub_semver.dart';
|
||||
|
||||
import '../../analysis/custom_result_class.dart';
|
||||
|
@ -11,6 +12,8 @@ import '../../utils/string_escaper.dart';
|
|||
import '../../writer/database_writer.dart';
|
||||
import '../../writer/drift_accessor_writer.dart';
|
||||
import '../../writer/import_manager.dart';
|
||||
import '../../writer/modules.dart';
|
||||
import '../../writer/tables/view_writer.dart';
|
||||
import '../../writer/writer.dart';
|
||||
import 'backend.dart';
|
||||
|
||||
|
@ -26,16 +29,29 @@ enum DriftGenerationMode {
|
|||
///
|
||||
/// Drift will generate a single part file for the main database file and each
|
||||
/// DAO-defining file.
|
||||
monolithicSharedPart,
|
||||
monolithicSharedPart(true, true),
|
||||
|
||||
/// Like [monolithicSharedPart], except that drift will generate a single
|
||||
/// part file on its own instead of generating a part file for `source_gen`
|
||||
/// to process later.
|
||||
monolithicPart;
|
||||
monolithicPart(true, true),
|
||||
|
||||
bool get isMonolithic => true;
|
||||
/// Generates a separate Dart library (no `part of` directive) for each input
|
||||
/// (.drift file or .dart file with databases / tables).
|
||||
modular(false, false);
|
||||
|
||||
bool get isPartFile => true;
|
||||
/// Whether this mode defines a "monolithic" build.
|
||||
///
|
||||
/// In a monolithic build, drift will generate all code into a single file,
|
||||
/// even if tables and queries are defined across multiple `.drift` files.
|
||||
/// In modular (non-monolithic) builds, files are generated for each input
|
||||
/// defining drift elements instead.
|
||||
final bool isMonolithic;
|
||||
|
||||
/// Whether this build mode generates a part file.
|
||||
final bool isPartFile;
|
||||
|
||||
const DriftGenerationMode(this.isMonolithic, this.isPartFile);
|
||||
|
||||
/// Whether the analysis happens in the generating build step.
|
||||
///
|
||||
|
@ -47,8 +63,6 @@ enum DriftGenerationMode {
|
|||
}
|
||||
|
||||
class DriftBuilder extends Builder {
|
||||
static final Version _minimalDartLanguageVersion = Version(2, 12, 0);
|
||||
|
||||
final DriftOptions options;
|
||||
final DriftGenerationMode generationMode;
|
||||
|
||||
|
@ -71,11 +85,93 @@ class DriftBuilder extends Builder {
|
|||
return {
|
||||
'.dart': ['.drift.dart']
|
||||
};
|
||||
case DriftGenerationMode.modular:
|
||||
return {
|
||||
'.dart': ['.drift.dart'],
|
||||
'.drift': ['.drift.dart'],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> build(BuildStep buildStep) async {
|
||||
final run = _DriftBuildRun(options, generationMode, buildStep);
|
||||
await run.run();
|
||||
}
|
||||
}
|
||||
|
||||
extension on Version {
|
||||
String get majorMinor => '$major.$minor';
|
||||
}
|
||||
|
||||
class _DriftBuildRun {
|
||||
final DriftOptions options;
|
||||
final DriftGenerationMode mode;
|
||||
final BuildStep buildStep;
|
||||
|
||||
final DriftAnalysisDriver driver;
|
||||
|
||||
/// When emitting a direct part file, contains the `// @dart` language version
|
||||
/// comment from the main library. We need to apply it to the part file as
|
||||
/// well.
|
||||
Version? overriddenLanguageVersion;
|
||||
|
||||
/// The Dart language version from the package. When it's too old and we're
|
||||
/// generating libraries, we need to apply a `// @dart` version comment to get
|
||||
/// a suitable version.
|
||||
Version? packageLanguageVersion;
|
||||
|
||||
late Writer writer;
|
||||
|
||||
Set<Uri> analyzedUris = {};
|
||||
|
||||
_DriftBuildRun(this.options, this.mode, this.buildStep)
|
||||
: driver = DriftAnalysisDriver(DriftBuildBackend(buildStep), options)
|
||||
..cacheReader = BuildCacheReader(buildStep);
|
||||
|
||||
Future<void> run() async {
|
||||
await _warnAboutDeprecatedOptions();
|
||||
if (!await _checkForElementsToBuild()) return;
|
||||
|
||||
await _checkForLanguageVersions();
|
||||
|
||||
final fileResult =
|
||||
await _analyze(buildStep.inputId.uri, isEntrypoint: true);
|
||||
|
||||
// For the monolithic build modes, we only generate code for databases and
|
||||
// crawl the tables from there.
|
||||
if (mode.isMonolithic && !fileResult.containsDatabaseAccessor) {
|
||||
return;
|
||||
}
|
||||
|
||||
_createWriter();
|
||||
if (mode.isMonolithic) {
|
||||
await _generateMonolithic(fileResult);
|
||||
} else {
|
||||
await _generateModular(fileResult);
|
||||
}
|
||||
await _emitCode();
|
||||
}
|
||||
|
||||
Future<FileState> _analyze(Uri uri, {bool isEntrypoint = false}) async {
|
||||
final result = await driver.fullyAnalyze(uri);
|
||||
|
||||
// If we're doing a monolithic build, we need to warn about errors in
|
||||
// imports too.
|
||||
final printErrors =
|
||||
isEntrypoint || (mode.isMonolithic && analyzedUris.add(result.ownUri));
|
||||
if (printErrors) {
|
||||
for (final error in result.fileAnalysis?.analysisErrors ?? const []) {
|
||||
log.warning(error);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Once per build, prints a warning about deprecated build options if they
|
||||
/// are applied to this builder.
|
||||
Future<void> _warnAboutDeprecatedOptions() async {
|
||||
final flags = await buildStep.fetchResource(_flags);
|
||||
if (!flags.didWarnAboutDeprecatedOptions &&
|
||||
options.enabledDeprecatedOption) {
|
||||
|
@ -84,11 +180,11 @@ class DriftBuilder extends Builder {
|
|||
'Consider removing the option from your build.yaml.');
|
||||
flags.didWarnAboutDeprecatedOptions = true;
|
||||
}
|
||||
}
|
||||
|
||||
final driver = DriftAnalysisDriver(DriftBuildBackend(buildStep), options)
|
||||
..cacheReader = BuildCacheReader(buildStep);
|
||||
|
||||
if (!generationMode.embeddedAnalyzer) {
|
||||
/// Checks if the input file contains elements drift should generate code for.
|
||||
Future<bool> _checkForElementsToBuild() async {
|
||||
if (!mode.embeddedAnalyzer) {
|
||||
// An analysis step should have already run for this asset. If we can't
|
||||
// pick up results from that, there is no code for drift to generate.
|
||||
final fromCache =
|
||||
|
@ -97,15 +193,17 @@ class DriftBuilder extends Builder {
|
|||
if (fromCache == null) {
|
||||
// Don't do anything! There are no analysis results for this file, so
|
||||
// there's nothing for drift to generate code for.
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Ok, we actually have something to generate. We're generating code
|
||||
// needing version 2.12 (or later) of the Dart _language_. This property is
|
||||
// inherited from the main file, so let's check that.
|
||||
Version? overriddenLanguageVersion;
|
||||
if (generationMode.isPartFile) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Prints a warning if the used Dart version is incompatible with drift's
|
||||
/// minimal version constraints.
|
||||
Future<void> _checkForLanguageVersions() async {
|
||||
if (mode.isPartFile) {
|
||||
final library = await buildStep.inputLibrary;
|
||||
overriddenLanguageVersion = library.languageVersion.override;
|
||||
|
||||
|
@ -123,43 +221,41 @@ class DriftBuilder extends Builder {
|
|||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Set<Uri> analyzedUris = {};
|
||||
Future<FileState> analyze(Uri uri) async {
|
||||
final fileResult = await driver.fullyAnalyze(uri);
|
||||
if (analyzedUris.add(fileResult.ownUri)) {
|
||||
for (final error
|
||||
in fileResult.fileAnalysis?.analysisErrors ?? const []) {
|
||||
log.warning(error);
|
||||
}
|
||||
Future<void> _generateModular(FileState entrypointState) async {
|
||||
for (final element in entrypointState.analysis.values) {
|
||||
final result = element.result;
|
||||
|
||||
if (result is DriftTable) {
|
||||
TableWriter(result, writer.child()).writeInto();
|
||||
} else if (result is DriftView) {
|
||||
ViewWriter(result, writer.child(), null).write();
|
||||
} else if (result is DriftDatabase) {
|
||||
final resolved =
|
||||
entrypointState.fileAnalysis!.resolvedDatabases[result.id]!;
|
||||
final input = DatabaseGenerationInput(result, resolved, const {});
|
||||
DatabaseWriter(input, writer.child()).write();
|
||||
}
|
||||
|
||||
return fileResult;
|
||||
}
|
||||
|
||||
final fileResult = await analyze(buildStep.inputId.uri);
|
||||
|
||||
// For the monolithic build modes, we only generate code for databases and
|
||||
// crawl the tables from there.
|
||||
if (generationMode.isMonolithic && !fileResult.containsDatabaseAccessor) {
|
||||
return;
|
||||
if (entrypointState.hasModularDriftAccessor) {
|
||||
ModularAccessorWriter(writer.child(), entrypointState).write();
|
||||
}
|
||||
}
|
||||
|
||||
final generationOptions = GenerationOptions(
|
||||
imports: ImportManagerForPartFiles(),
|
||||
);
|
||||
final writer = Writer(options, generationOptions: generationOptions);
|
||||
|
||||
for (final element in fileResult.analysis.values) {
|
||||
Future<void> _generateMonolithic(FileState entrypointState) async {
|
||||
for (final element in entrypointState.analysis.values) {
|
||||
final result = element.result;
|
||||
|
||||
if (result is BaseDriftAccessor) {
|
||||
final resolved = fileResult.fileAnalysis!.resolvedDatabases[result.id]!;
|
||||
final resolved =
|
||||
entrypointState.fileAnalysis!.resolvedDatabases[result.id]!;
|
||||
var importedQueries = <DefinedSqlQuery, SqlQuery>{};
|
||||
|
||||
for (final query
|
||||
in resolved.availableElements.whereType<DefinedSqlQuery>()) {
|
||||
final resolvedFile = await analyze(query.id.libraryUri);
|
||||
final resolvedFile = await _analyze(query.id.libraryUri);
|
||||
final resolvedQuery =
|
||||
resolvedFile.fileAnalysis?.resolvedQueries[query.id];
|
||||
|
||||
|
@ -189,34 +285,51 @@ class DriftBuilder extends Builder {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _createWriter() {
|
||||
if (mode.isMonolithic) {
|
||||
final generationOptions = GenerationOptions(
|
||||
imports: ImportManagerForPartFiles(),
|
||||
);
|
||||
writer = Writer(options, generationOptions: generationOptions);
|
||||
} else {
|
||||
final imports = LibraryInputManager();
|
||||
final generationOptions = GenerationOptions(
|
||||
imports: imports,
|
||||
isModular: true,
|
||||
);
|
||||
writer = Writer(options, generationOptions: generationOptions);
|
||||
imports.linkToWriter(writer);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _emitCode() {
|
||||
final output = StringBuffer();
|
||||
output.writeln('// ignore_for_file: type=lint');
|
||||
|
||||
if (generationMode == DriftGenerationMode.monolithicPart) {
|
||||
if (mode == DriftGenerationMode.monolithicPart) {
|
||||
final originalFile = buildStep.inputId.pathSegments.last;
|
||||
|
||||
if (overriddenLanguageVersion != null) {
|
||||
// Part files need to have the same version as the main library.
|
||||
output.writeln('// @dart=${overriddenLanguageVersion.majorMinor}');
|
||||
output.writeln('// @dart=${overriddenLanguageVersion!.majorMinor}');
|
||||
}
|
||||
|
||||
output.writeln('part of ${asDartLiteral(originalFile)};');
|
||||
}
|
||||
output.write(writer.writeGenerated());
|
||||
|
||||
var generated = output.toString();
|
||||
var code = output.toString();
|
||||
try {
|
||||
generated = DartFormatter().format(generated);
|
||||
code = DartFormatter().format(code);
|
||||
} on FormatterException {
|
||||
log.warning('Could not format generated source. The generated code is '
|
||||
'probably invalid, and this is most likely a bug in drift_dev.');
|
||||
}
|
||||
|
||||
await buildStep.writeAsString(buildStep.allowedOutputs.single, generated);
|
||||
return buildStep.writeAsString(buildStep.allowedOutputs.single, code);
|
||||
}
|
||||
}
|
||||
|
||||
extension on Version {
|
||||
String get majorMinor => '$major.$minor';
|
||||
static final Version _minimalDartLanguageVersion = Version(2, 12, 0);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import '../analysis/results/file_results.dart';
|
|||
import '../analysis/results/results.dart';
|
||||
import '../services/find_stream_update_rules.dart';
|
||||
import '../utils/string_escaper.dart';
|
||||
import 'modules.dart';
|
||||
import 'queries/query_writer.dart';
|
||||
import 'tables/table_writer.dart';
|
||||
import 'tables/view_writer.dart';
|
||||
|
@ -28,18 +29,22 @@ class DatabaseWriter {
|
|||
return 'DatabaseAtV${scope.generationOptions.forSchema}';
|
||||
}
|
||||
|
||||
return '_\$${db.id.name}';
|
||||
final prefix = scope.generationOptions.isModular ? '' : r'_';
|
||||
|
||||
return '$prefix\$${db.id.name}';
|
||||
}
|
||||
|
||||
void write() {
|
||||
final elements = input.resolvedAccessor.availableElements;
|
||||
|
||||
// Write data classes, companions and info classes
|
||||
for (final reference in elements) {
|
||||
if (reference is DriftTable) {
|
||||
TableWriter(reference, scope.child()).writeInto();
|
||||
} else if (reference is DriftView) {
|
||||
ViewWriter(reference, scope.child(), this).write();
|
||||
if (!scope.generationOptions.isModular) {
|
||||
for (final reference in elements) {
|
||||
if (reference is DriftTable) {
|
||||
TableWriter(reference, scope.child()).writeInto();
|
||||
} else if (reference is DriftView) {
|
||||
ViewWriter(reference, scope.child(), this).write();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,7 +79,7 @@ class DatabaseWriter {
|
|||
}
|
||||
|
||||
if (entity is DriftTable) {
|
||||
final tableClassName = entity.entityInfoName;
|
||||
final tableClassName = dbScope.dartCode(dbScope.entityInfoType(entity));
|
||||
|
||||
writeMemoizedGetter(
|
||||
buffer: dbScope.leaf().buffer,
|
||||
|
@ -126,6 +131,27 @@ class DatabaseWriter {
|
|||
);
|
||||
}
|
||||
|
||||
// Also write implicit DAOs for modular imports
|
||||
if (scope.generationOptions.isModular) {
|
||||
for (final import in input.resolvedAccessor.knownImports) {
|
||||
if (import.hasModularDriftAccessor) {
|
||||
final type = dbScope.modularAccessor(import.ownUri);
|
||||
final getter = ReCase(type.toString()).camelCase;
|
||||
|
||||
dbScope.leaf()
|
||||
..writeDart(type)
|
||||
..write(' get $getter => ')
|
||||
..writeUriRef(
|
||||
ModularAccessorWriter.modularSupport, 'ReadDatabaseContainer')
|
||||
..writeln('(this).accessor<')
|
||||
..writeDart(type)
|
||||
..write('>(${asDartLiteral(import.ownUri.toString())}, ')
|
||||
..writeDart(type)
|
||||
..writeln('.new);');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write implementation for query methods
|
||||
for (final query in input.availableRegularQueries) {
|
||||
QueryWriter(dbScope.child()).write(query);
|
||||
|
@ -134,11 +160,14 @@ class DatabaseWriter {
|
|||
// Write List of tables
|
||||
final schemaScope = dbScope.leaf();
|
||||
|
||||
final tableInfoType =
|
||||
'${dbScope.drift('TableInfo')}<${dbScope.drift('Table')}, Object?>';
|
||||
final schemaEntity = dbScope.drift('DatabaseSchemaEntity');
|
||||
|
||||
schemaScope
|
||||
..write(
|
||||
'@override\nIterable<TableInfo<Table, dynamic>> get allTables => ')
|
||||
..write('allSchemaEntities.whereType<TableInfo<Table, Object?>>();\n')
|
||||
..write('@override\nList<DatabaseSchemaEntity> get allSchemaEntities ')
|
||||
..write('@override\nIterable<$tableInfoType> get allTables => ')
|
||||
..write('allSchemaEntities.whereType<$tableInfoType>();\n')
|
||||
..write('@override\nList<$schemaEntity> get allSchemaEntities ')
|
||||
..write('=> [');
|
||||
|
||||
schemaScope
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import '../utils/string_escaper.dart';
|
||||
import 'writer.dart';
|
||||
|
||||
abstract class ImportManager {
|
||||
String? prefixFor(Uri definitionUri, String elementName);
|
||||
}
|
||||
|
@ -8,3 +11,31 @@ class ImportManagerForPartFiles extends ImportManager {
|
|||
return null; // todo: Find import alias from existing imports?
|
||||
}
|
||||
}
|
||||
|
||||
class LibraryInputManager extends ImportManager {
|
||||
static final _dartCore = Uri.parse('dart:core');
|
||||
|
||||
final Map<Uri, String> _importAliases = {};
|
||||
TextEmitter? emitter;
|
||||
|
||||
LibraryInputManager();
|
||||
|
||||
void linkToWriter(Writer writer) {
|
||||
emitter = writer.leaf();
|
||||
}
|
||||
|
||||
@override
|
||||
String? prefixFor(Uri definitionUri, String elementName) {
|
||||
if (definitionUri == _dartCore) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return _importAliases.putIfAbsent(definitionUri, () {
|
||||
final alias = 'i${_importAliases.length}';
|
||||
|
||||
emitter?.writeln(
|
||||
'import ${asDartLiteral(definitionUri.toString())} as $alias;');
|
||||
return alias;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
import '../analysis/driver/state.dart';
|
||||
import '../analysis/results/results.dart';
|
||||
import '../utils/string_escaper.dart';
|
||||
import 'queries/query_writer.dart';
|
||||
import 'writer.dart';
|
||||
|
||||
/// Write a modular database accessor for a drift file.
|
||||
///
|
||||
/// In drift's (opt-in) modular build set, a `.drift.dart` file is generated for
|
||||
/// each `.drift` file. This file defines elements defined in that drift file
|
||||
/// (like tables, data classes, companions, fields for indexes and triggers).
|
||||
///
|
||||
/// If queries are defined in that drift file, a "modular accessor" is generated
|
||||
/// as well. This accessor contains generated methods for all queries. The main
|
||||
/// database will make these accessor available as getters.
|
||||
class ModularAccessorWriter {
|
||||
final Scope scope;
|
||||
final FileState file;
|
||||
|
||||
ModularAccessorWriter(this.scope, this.file);
|
||||
|
||||
void write() {
|
||||
if (!file.hasModularDriftAccessor) return;
|
||||
|
||||
final className = scope.modularAccessor(file.ownUri);
|
||||
final generatedDatabase = scope.drift('GeneratedDatabase');
|
||||
|
||||
scope.leaf()
|
||||
..write('class $className extends ')
|
||||
..write(_modular('ModularAccessor'))
|
||||
..writeln('{ $className($generatedDatabase db): super(db);');
|
||||
|
||||
final referencedElements = <DriftElement>{};
|
||||
|
||||
final queries = file.fileAnalysis?.resolvedQueries ?? const {};
|
||||
for (final query in queries.entries) {
|
||||
final queryElement = file.analysis[query.key]?.result;
|
||||
if (queryElement != null) {
|
||||
referencedElements.addAll(queryElement.references);
|
||||
}
|
||||
|
||||
QueryWriter(scope.child()).write(query.value);
|
||||
}
|
||||
|
||||
final restOfClass = scope.leaf();
|
||||
|
||||
for (final reference in referencedElements) {
|
||||
// This element is referenced in a query, and the query writer expects it
|
||||
// to be available as a getter. So, let's generate that getter:
|
||||
|
||||
if (reference is DriftElementWithResultSet) {
|
||||
final infoType = restOfClass.entityInfoType(reference);
|
||||
|
||||
restOfClass
|
||||
..writeDart(infoType)
|
||||
..write(' get ${reference.dbGetterName} => this.resultSet<')
|
||||
..writeDart(infoType)
|
||||
..write('>(${asDartLiteral(reference.schemaName)});');
|
||||
}
|
||||
}
|
||||
|
||||
restOfClass.writeln('}');
|
||||
}
|
||||
|
||||
String _modular(String element) {
|
||||
return scope.refUri(modularSupport, element);
|
||||
}
|
||||
|
||||
static final Uri modularSupport =
|
||||
Uri.parse('package:drift/internal/modular.dart');
|
||||
}
|
|
@ -710,10 +710,10 @@ String? _defaultForDartPlaceholder(
|
|||
// the surrounding precedence in SQL.
|
||||
final sql = SqlWriter(scope.options)
|
||||
.writeNodeIntoStringLiteral(Parentheses(kind.defaultValue!));
|
||||
return 'const CustomExpression($sql)';
|
||||
return 'const ${scope.drift('CustomExpression')}($sql)';
|
||||
} else if (kind is SimpleDartPlaceholderType &&
|
||||
kind.kind == SimpleDartPlaceholderKind.orderBy) {
|
||||
return 'const OrderBy.nothing()';
|
||||
return 'const ${scope.drift('OrderBy')}.nothing()';
|
||||
} else {
|
||||
assert(!placeholder.hasDefaultOrImplicitFallback);
|
||||
return null;
|
||||
|
|
|
@ -17,7 +17,7 @@ class DataClassWriter {
|
|||
|
||||
DataClassWriter(this.table, this.scope) : _emitter = scope.leaf();
|
||||
|
||||
String get serializerType => 'ValueSerializer?';
|
||||
String get serializerType => _emitter.drift('ValueSerializer?');
|
||||
|
||||
String _columnType(DriftColumn column) {
|
||||
return _emitter.dartCode(_emitter.dartType(column));
|
||||
|
@ -44,14 +44,15 @@ class DataClassWriter {
|
|||
}
|
||||
|
||||
void write() {
|
||||
final parentClass = table.customParentClass ?? 'DataClass';
|
||||
final parentClass = table.customParentClass ?? _emitter.drift('DataClass');
|
||||
_buffer.write('class ${table.nameOfRowClass} extends $parentClass ');
|
||||
|
||||
if (isInsertable) {
|
||||
// The data class is only an insertable if we can actually insert rows
|
||||
// into the target entity.
|
||||
final type = _emitter.dartCode(_emitter.writer.rowType(table));
|
||||
_buffer.writeln('implements Insertable<$type> {');
|
||||
|
||||
_buffer.writeln('implements ${_emitter.drift('Insertable')}<$type> {');
|
||||
} else {
|
||||
_buffer.writeln('{');
|
||||
}
|
||||
|
@ -121,7 +122,8 @@ class DataClassWriter {
|
|||
..write('factory $dataClassName.fromJson('
|
||||
'Map<String, dynamic> json, {$serializerType serializer}'
|
||||
') {\n')
|
||||
..write('serializer ??= driftRuntimeOptions.defaultSerializer;\n')
|
||||
..write('serializer ??= ${_emitter.drift('driftRuntimeOptions')}'
|
||||
'.defaultSerializer;\n')
|
||||
..write('return $dataClassName(');
|
||||
|
||||
for (final column in columns) {
|
||||
|
@ -161,7 +163,8 @@ class DataClassWriter {
|
|||
void _writeToJson() {
|
||||
_buffer.write('@override Map<String, dynamic> toJson('
|
||||
'{$serializerType serializer}) {\n'
|
||||
'serializer ??= driftRuntimeOptions.defaultSerializer;\n'
|
||||
'serializer ??= ${_emitter.drift('driftRuntimeOptions')}'
|
||||
'.defaultSerializer;\n'
|
||||
'return <String, dynamic>{\n');
|
||||
|
||||
for (final column in columns) {
|
||||
|
@ -187,6 +190,7 @@ class DataClassWriter {
|
|||
void _writeCopyWith() {
|
||||
final dataClassName = _emitter.dartCode(_emitter.writer.rowClass(table));
|
||||
final wrapNullableInValue = scope.options.generateValuesInCopyWith;
|
||||
final valueType = _emitter.drift('Value');
|
||||
|
||||
_buffer.write('$dataClassName copyWith({');
|
||||
for (var i = 0; i < columns.length; i++) {
|
||||
|
@ -197,8 +201,8 @@ class DataClassWriter {
|
|||
final typeName = _columnType(column);
|
||||
if (wrapNullableInValue && isNullable) {
|
||||
_buffer
|
||||
..write('Value<$typeName> ${column.nameInDart} ')
|
||||
..write('= const Value.absent()');
|
||||
..write('$valueType<$typeName> ${column.nameInDart} ')
|
||||
..write('= const $valueType.absent()');
|
||||
} else if (!isNullable) {
|
||||
// We always use nullable parameters in copyWith, since all parameters
|
||||
// are optional. The !isNullable check is there to avoid a duplicate
|
||||
|
@ -233,10 +237,13 @@ class DataClassWriter {
|
|||
}
|
||||
|
||||
void _writeToColumnsOverride() {
|
||||
final expression = _emitter.drift('Expression');
|
||||
final variable = _emitter.drift('Variable');
|
||||
|
||||
_buffer
|
||||
..write('@override\nMap<String, Expression> toColumns'
|
||||
..write('@override\nMap<String, $expression> toColumns'
|
||||
'(bool nullToAbsent) {\n')
|
||||
..write('final map = <String, Expression> {};');
|
||||
..write('final map = <String, $expression> {};');
|
||||
|
||||
for (final column in columns) {
|
||||
// Generated column - cannot be used for inserts or updates
|
||||
|
@ -255,7 +262,7 @@ class DataClassWriter {
|
|||
|
||||
final typeName = column.variableTypeCode(nullable: false);
|
||||
final mapSetter = 'map[${asDartLiteral(column.nameInSql)}] = '
|
||||
'Variable<$typeName>';
|
||||
'$variable<$typeName>';
|
||||
|
||||
if (column.typeConverter != null) {
|
||||
// apply type converter before writing the variable
|
||||
|
@ -309,12 +316,14 @@ class DataClassWriter {
|
|||
if (needsNullCheck) {
|
||||
_buffer
|
||||
..write(dartName)
|
||||
..write(' == null && nullToAbsent ? const Value.absent() : ');
|
||||
..write(' == null && nullToAbsent ? '
|
||||
'const ${_emitter.drift('Value')}.absent() : ');
|
||||
// We'll write the non-null case afterwards
|
||||
}
|
||||
|
||||
_buffer
|
||||
..write('Value (')
|
||||
..write(_emitter.drift('Value'))
|
||||
..write('(')
|
||||
..write(dartName)
|
||||
..write('),');
|
||||
}
|
||||
|
@ -369,7 +378,7 @@ class RowMappingWriter {
|
|||
final columnName = column.nameInSql;
|
||||
final rawData = "data['\${effectivePrefix}$columnName']";
|
||||
|
||||
final sqlType = column.sqlType.toString();
|
||||
final sqlType = writer.drift(column.sqlType.toString());
|
||||
var loadType = '$databaseGetter.typeMapping.read($sqlType, $rawData)';
|
||||
|
||||
if (!column.nullable) {
|
||||
|
|
|
@ -23,7 +23,8 @@ abstract class TableOrViewWriter {
|
|||
|
||||
for (final constraint in column.constraints) {
|
||||
if (constraint is LimitingTextLength) {
|
||||
final buffer = StringBuffer('GeneratedColumn.checkTextLength(');
|
||||
final buffer =
|
||||
StringBuffer(emitter.drift('GeneratedColumn.checkTextLength('));
|
||||
|
||||
if (constraint.minLength != null) {
|
||||
buffer.write('minTextLength: ${constraint.minLength},');
|
||||
|
@ -45,7 +46,7 @@ abstract class TableOrViewWriter {
|
|||
final dartCode = emitter.dartCode(constraint.dartExpression);
|
||||
|
||||
additionalParams['generatedAs'] =
|
||||
'GeneratedAs($dartCode, ${constraint.stored})';
|
||||
'${emitter.drift('GeneratedAs')}($dartCode, ${constraint.stored})';
|
||||
}
|
||||
|
||||
if (constraint is PrimaryKeyColumn && constraint.isAutoIncrement) {
|
||||
|
@ -53,7 +54,7 @@ abstract class TableOrViewWriter {
|
|||
}
|
||||
}
|
||||
|
||||
additionalParams['type'] = column.sqlType.toString();
|
||||
additionalParams['type'] = emitter.drift(column.sqlType.toString());
|
||||
|
||||
if (tableOrView is DriftTable) {
|
||||
additionalParams['requiredDuringInsert'] = (tableOrView as DriftTable)
|
||||
|
@ -102,7 +103,7 @@ abstract class TableOrViewWriter {
|
|||
}
|
||||
|
||||
final innerType = column.innerColumnType();
|
||||
var type = 'GeneratedColumn<$innerType>';
|
||||
var type = '${emitter.drift('GeneratedColumn')}<$innerType>';
|
||||
expressionBuffer
|
||||
..write(type)
|
||||
..write(
|
||||
|
@ -133,7 +134,8 @@ abstract class TableOrViewWriter {
|
|||
final converterCode = emitter.dartCode(emitter.writer
|
||||
.readConverter(converter, forNullable: column.nullable));
|
||||
|
||||
type = 'GeneratedColumnWithTypeConverter<$mappedType, $innerType>';
|
||||
type = '${emitter.drift('GeneratedColumnWithTypeConverter')}'
|
||||
'<$mappedType, $innerType>';
|
||||
expressionBuffer
|
||||
..write('.withConverter<')
|
||||
..write(mappedType)
|
||||
|
@ -155,7 +157,7 @@ abstract class TableOrViewWriter {
|
|||
if (!scope.generationOptions.writeDataClasses) {
|
||||
buffer.writeln('''
|
||||
@override
|
||||
Never map(Map<String, dynamic> data, {$String? tablePrefix}) {
|
||||
Never map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
throw UnsupportedError('TableInfo.map in schema verification code');
|
||||
}
|
||||
''');
|
||||
|
@ -229,7 +231,8 @@ abstract class TableOrViewWriter {
|
|||
void writeGetColumnsOverride() {
|
||||
final columnsWithGetters =
|
||||
tableOrView.columns.map((c) => c.nameInDart).join(', ');
|
||||
buffer.write('@override\nList<GeneratedColumn> get \$columns => '
|
||||
buffer.write(
|
||||
'@override\nList<${emitter.drift('GeneratedColumn')}> get \$columns => '
|
||||
'[$columnsWithGetters];\n');
|
||||
}
|
||||
|
||||
|
@ -276,30 +279,37 @@ class TableWriter extends TableOrViewWriter {
|
|||
|
||||
if (!scope.generationOptions.writeDataClasses) {
|
||||
// Write a small table header without data class
|
||||
buffer
|
||||
..write('class ${table.entityInfoName} extends ')
|
||||
..write(emitter.drift('Table'))
|
||||
..write(' with ')
|
||||
..write(emitter.drift('TableInfo'));
|
||||
buffer.write('class ${table.entityInfoName} extends Table with '
|
||||
'TableInfo');
|
||||
if (table.isVirtual) {
|
||||
buffer.write(', VirtualTableInfo');
|
||||
buffer.write(', ${emitter.drift('VirtualTableInfo')}');
|
||||
}
|
||||
} else {
|
||||
// Regular generation, write full table class
|
||||
final dataClass = emitter.dartCode(emitter.writer.rowClass(table));
|
||||
final tableDslName = table.definingDartClass ?? 'Table';
|
||||
final tableDslName = table.definingDartClass ?? emitter.drift('Table');
|
||||
|
||||
// class UsersTable extends Users implements TableInfo<Users, User> {
|
||||
final typeArgs = '<${table.entityInfoName}, $dataClass>';
|
||||
|
||||
buffer.write('class ${table.entityInfoName} extends $tableDslName with '
|
||||
'TableInfo$typeArgs ');
|
||||
'${emitter.drift('TableInfo')}$typeArgs ');
|
||||
|
||||
if (table.isVirtual) {
|
||||
buffer.write(', VirtualTableInfo$typeArgs ');
|
||||
buffer.write(', ${emitter.drift('VirtualTableInfo')}$typeArgs ');
|
||||
}
|
||||
}
|
||||
|
||||
buffer
|
||||
..writeln('{')
|
||||
// write a GeneratedDatabase reference that is set in the constructor
|
||||
..writeln('@override final GeneratedDatabase attachedDatabase;')
|
||||
..writeln(
|
||||
'@override final ${emitter.drift('GeneratedDatabase')} attachedDatabase;')
|
||||
..writeln('final String? _alias;')
|
||||
..writeln(
|
||||
'${table.entityInfoName}(this.attachedDatabase, [this._alias]);');
|
||||
|
@ -366,9 +376,11 @@ class TableWriter extends TableOrViewWriter {
|
|||
|
||||
void _writeColumnVerificationMeta(DriftColumn column) {
|
||||
if (!_skipVerification) {
|
||||
final meta = emitter.drift('VerificationMeta');
|
||||
|
||||
buffer
|
||||
..write('final VerificationMeta ${_fieldNameForColumnMeta(column)} = ')
|
||||
..write("const VerificationMeta('${column.nameInDart}');\n");
|
||||
..write('static const $meta ${_fieldNameForColumnMeta(column)} = ')
|
||||
..writeln("const $meta('${column.nameInDart}');");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -376,11 +388,13 @@ class TableWriter extends TableOrViewWriter {
|
|||
if (_skipVerification) return;
|
||||
|
||||
final innerType = emitter.dartCode(emitter.writer.rowType(table));
|
||||
buffer
|
||||
..write('@override\nVerificationContext validateIntegrity'
|
||||
'(Insertable<$innerType> instance, '
|
||||
'{bool isInserting = false}) {\n')
|
||||
..write('final context = VerificationContext();\n')
|
||||
emitter
|
||||
..writeln('@override')
|
||||
..writeDriftRef('VerificationContext')
|
||||
..write(' validateIntegrity(')
|
||||
..writeDriftRef('Insertable')
|
||||
..writeln('<$innerType> instance, {bool isInserting = false}) {')
|
||||
..write('final context = ${emitter.drift('VerificationContext')}();\n')
|
||||
..write('final data = instance.toColumns(true);\n');
|
||||
|
||||
const locals = {'instance', 'isInserting', 'context', 'data'};
|
||||
|
@ -392,8 +406,8 @@ class TableWriter extends TableOrViewWriter {
|
|||
if (column.typeConverter != null) {
|
||||
// dont't verify custom columns, we assume that the user knows what
|
||||
// they're doing
|
||||
buffer.write(
|
||||
'context.handle($metaName, const VerificationResult.success());');
|
||||
buffer.write('context.handle($metaName, '
|
||||
'const ${emitter.drift('VerificationResult')}.success());');
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -421,11 +435,12 @@ class TableWriter extends TableOrViewWriter {
|
|||
}
|
||||
|
||||
void _writePrimaryKeyOverride() {
|
||||
buffer.write('@override\nSet<GeneratedColumn> get \$primaryKey => ');
|
||||
buffer.write(
|
||||
'@override\nSet<${emitter.drift('GeneratedColumn')}> get \$primaryKey => ');
|
||||
final primaryKey = table.fullPrimaryKey;
|
||||
|
||||
if (primaryKey.isEmpty) {
|
||||
buffer.write('const <GeneratedColumn>{};');
|
||||
buffer.write('const {};');
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -452,7 +467,8 @@ class TableWriter extends TableOrViewWriter {
|
|||
return;
|
||||
}
|
||||
|
||||
buffer.write('@override\nList<Set<GeneratedColumn>> get uniqueKeys => [');
|
||||
buffer.write('@override\nList<Set<${emitter.drift('GeneratedColumn')}>> '
|
||||
'get uniqueKeys => [');
|
||||
|
||||
for (final uniqueKey in uniqueKeys) {
|
||||
buffer.write('{');
|
||||
|
|
|
@ -13,6 +13,8 @@ class UpdateCompanionWriter {
|
|||
|
||||
StringBuffer get _buffer => _emitter.buffer;
|
||||
|
||||
String get _value => _emitter.drift('Value');
|
||||
|
||||
late final List<DriftColumn> columns = [
|
||||
for (final column in table.columns)
|
||||
if (!column.isGenerated) column,
|
||||
|
@ -20,15 +22,15 @@ class UpdateCompanionWriter {
|
|||
|
||||
UpdateCompanionWriter(this.table, this.scope) : _emitter = scope.leaf();
|
||||
|
||||
String get _companionClass =>
|
||||
_emitter.dartCode(_emitter.companionType(table));
|
||||
String get _companionClass => _emitter.companionType(table).toString();
|
||||
String get _companionType => _emitter.dartCode(_emitter.companionType(table));
|
||||
|
||||
void write() {
|
||||
final rowClass = _emitter.dartCode(_emitter.rowType(table));
|
||||
|
||||
_buffer.write('class $_companionClass '
|
||||
'extends '
|
||||
'UpdateCompanion<$rowClass> {\n');
|
||||
'${_emitter.drift('UpdateCompanion')}<$rowClass> {\n');
|
||||
_writeFields();
|
||||
|
||||
_writeConstructor();
|
||||
|
@ -50,7 +52,7 @@ class UpdateCompanionWriter {
|
|||
for (final column in columns) {
|
||||
final modifier = scope.options.fieldModifier;
|
||||
final type = _emitter.dartCode(scope.writer.dartType(column));
|
||||
_buffer.write('$modifier Value<$type> ${column.nameInDart};\n');
|
||||
_buffer.write('$modifier $_value<$type> ${column.nameInDart};\n');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,7 +63,7 @@ class UpdateCompanionWriter {
|
|||
_buffer.write('$_companionClass({');
|
||||
|
||||
for (final column in columns) {
|
||||
_buffer.write('this.${column.nameInDart} = const Value.absent(),');
|
||||
_buffer.write('this.${column.nameInDart} = const $_value.absent(),');
|
||||
}
|
||||
|
||||
_buffer.write('});\n');
|
||||
|
@ -93,7 +95,7 @@ class UpdateCompanionWriter {
|
|||
|
||||
_buffer.write('required $typeName $param,');
|
||||
} else {
|
||||
_buffer.write('this.$param = const Value.absent(),');
|
||||
_buffer.write('this.$param = const $_value.absent(),');
|
||||
}
|
||||
}
|
||||
_buffer.write('})');
|
||||
|
@ -108,7 +110,7 @@ class UpdateCompanionWriter {
|
|||
}
|
||||
|
||||
final param = required.nameInDart;
|
||||
_buffer.write('$param = Value($param)');
|
||||
_buffer.write('$param = $_value($param)');
|
||||
}
|
||||
|
||||
_buffer.write(';\n');
|
||||
|
@ -124,17 +126,19 @@ class UpdateCompanionWriter {
|
|||
|
||||
final rowType = _emitter.dartCode(_emitter.rowType(table));
|
||||
_buffer
|
||||
..write('static Insertable<$rowType> $constructorName')
|
||||
..write(
|
||||
'static ${_emitter.drift('Insertable')}<$rowType> $constructorName')
|
||||
..write('({');
|
||||
|
||||
final expression = _emitter.drift('Expression');
|
||||
for (final column in columns) {
|
||||
final typeName = column.innerColumnType();
|
||||
_buffer.write('Expression<$typeName>? ${column.nameInDart}, \n');
|
||||
_buffer.write('$expression<$typeName>? ${column.nameInDart}, \n');
|
||||
}
|
||||
|
||||
_buffer
|
||||
..write('}) {\n')
|
||||
..write('return RawValuesInsertable({');
|
||||
..write('return ${_emitter.drift('RawValuesInsertable')}({');
|
||||
|
||||
for (final column in columns) {
|
||||
_buffer
|
||||
|
@ -148,7 +152,7 @@ class UpdateCompanionWriter {
|
|||
|
||||
void _writeCopyWith() {
|
||||
_buffer
|
||||
..write(_companionClass)
|
||||
..write(_companionType)
|
||||
..write(' copyWith({');
|
||||
var first = true;
|
||||
for (final column in columns) {
|
||||
|
@ -158,12 +162,12 @@ class UpdateCompanionWriter {
|
|||
first = false;
|
||||
|
||||
final typeName = _emitter.dartCode(_emitter.dartType(column));
|
||||
_buffer.write('Value<$typeName>? ${column.nameInDart}');
|
||||
_buffer.write('$_value<$typeName>? ${column.nameInDart}');
|
||||
}
|
||||
|
||||
_buffer
|
||||
..writeln('}) {')
|
||||
..write('return $_companionClass(');
|
||||
..write('return $_companionType(');
|
||||
for (final column in columns) {
|
||||
final name = column.nameInDart;
|
||||
_buffer.write('$name: $name ?? this.$name,');
|
||||
|
@ -172,11 +176,11 @@ class UpdateCompanionWriter {
|
|||
}
|
||||
|
||||
void _writeToColumnsOverride() {
|
||||
// Map<String, Variable> entityToSql(covariant UpdateCompanion<D> instance)
|
||||
final expression = _emitter.drift('Expression');
|
||||
_buffer
|
||||
..write('@override\nMap<String, Expression> toColumns'
|
||||
..write('@override\nMap<String, $expression> toColumns'
|
||||
'(bool nullToAbsent) {\n')
|
||||
..write('final map = <String, Expression> {};');
|
||||
..write('final map = <String, $expression> {};');
|
||||
|
||||
const locals = {'map', 'nullToAbsent', 'converter'};
|
||||
|
||||
|
@ -186,7 +190,7 @@ class UpdateCompanionWriter {
|
|||
_buffer.write('if ($getterName.present) {');
|
||||
final typeName = column.variableTypeCode(nullable: false);
|
||||
final mapSetter = 'map[${asDartLiteral(column.nameInSql)}] = '
|
||||
'Variable<$typeName>';
|
||||
'${_emitter.drift('Variable')}<$typeName>';
|
||||
|
||||
final converter = column.typeConverter;
|
||||
if (converter != null) {
|
||||
|
@ -237,7 +241,7 @@ class UpdateCompanionWriter {
|
|||
'$insertableClass(this._object);\n\n'
|
||||
'@override\n'
|
||||
'Map<String, Expression> toColumns(bool nullToAbsent) {\n'
|
||||
'return $_companionClass(\n');
|
||||
'return $_companionType(\n');
|
||||
|
||||
final columns = info.positionalColumns.followedBy(info.namedColumns.values);
|
||||
for (final columnName in columns) {
|
||||
|
|
|
@ -8,7 +8,7 @@ import 'table_writer.dart';
|
|||
class ViewWriter extends TableOrViewWriter {
|
||||
final DriftView view;
|
||||
final Scope scope;
|
||||
final DatabaseWriter databaseWriter;
|
||||
final DatabaseWriter? databaseWriter;
|
||||
|
||||
@override
|
||||
late TextEmitter emitter;
|
||||
|
@ -41,10 +41,10 @@ class ViewWriter extends TableOrViewWriter {
|
|||
}
|
||||
buffer.writeln(' implements HasResultSet {');
|
||||
|
||||
final dbClassName = databaseWriter?.dbClassName ?? 'GeneratedDatabase';
|
||||
buffer
|
||||
..writeln('final String? _alias;')
|
||||
..writeln(
|
||||
'@override final ${databaseWriter.dbClassName} attachedDatabase;')
|
||||
..writeln('@override final $dbClassName attachedDatabase;')
|
||||
..writeln('${view.entityInfoName}(this.attachedDatabase, '
|
||||
'[this._alias]);');
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import 'package:recase/recase.dart';
|
||||
import 'package:sqlparser/sqlparser.dart' as sql;
|
||||
import 'package:path/path.dart' show url;
|
||||
|
||||
import '../analysis/results/results.dart';
|
||||
import '../analysis/options.dart';
|
||||
|
@ -55,14 +57,53 @@ class Writer extends _NodeOrWriter {
|
|||
abstract class _NodeOrWriter {
|
||||
Writer get writer;
|
||||
|
||||
AnnotatedDartCode _generatedElement(DriftElement element, String dartName) {
|
||||
if (writer.generationOptions.isModular) {
|
||||
return AnnotatedDartCode([
|
||||
DartTopLevelSymbol(dartName, element.id.modularImportUri),
|
||||
]);
|
||||
} else {
|
||||
return AnnotatedDartCode([dartName]);
|
||||
}
|
||||
}
|
||||
|
||||
AnnotatedDartCode modularAccessor(Uri driftFile) {
|
||||
final id = DriftElementId(driftFile, '(file)');
|
||||
|
||||
return AnnotatedDartCode([
|
||||
DartTopLevelSymbol(
|
||||
ReCase(url.basename(driftFile.path)).pascalCase, id.modularImportUri),
|
||||
]);
|
||||
}
|
||||
|
||||
AnnotatedDartCode companionType(DriftTable table) {
|
||||
final baseName = writer.options.useDataClassNameForCompanions
|
||||
? table.nameOfRowClass
|
||||
: table.baseDartName;
|
||||
|
||||
return AnnotatedDartCode([
|
||||
DartTopLevelSymbol('${baseName}Companion', table.id.libraryUri),
|
||||
]);
|
||||
return _generatedElement(table, '${baseName}Companion');
|
||||
}
|
||||
|
||||
AnnotatedDartCode entityInfoType(DriftElementWithResultSet element) {
|
||||
return _generatedElement(element, element.entityInfoName);
|
||||
}
|
||||
|
||||
AnnotatedDartCode rowType(DriftElementWithResultSet element) {
|
||||
final existing = element.existingRowClass;
|
||||
if (existing != null) {
|
||||
return existing.targetType;
|
||||
} else {
|
||||
return _generatedElement(element, element.nameOfRowClass);
|
||||
}
|
||||
}
|
||||
|
||||
AnnotatedDartCode rowClass(DriftElementWithResultSet element) {
|
||||
final existing = element.existingRowClass;
|
||||
if (existing != null) {
|
||||
return existing.targetClass;
|
||||
} else {
|
||||
return _generatedElement(element, element.nameOfRowClass);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a Dart expression evaluating to the [converter].
|
||||
|
@ -139,24 +180,6 @@ abstract class _NodeOrWriter {
|
|||
}
|
||||
}
|
||||
|
||||
AnnotatedDartCode rowType(DriftElementWithResultSet element) {
|
||||
final existing = element.existingRowClass;
|
||||
if (existing != null) {
|
||||
return existing.targetType;
|
||||
} else {
|
||||
return AnnotatedDartCode([element.nameOfRowClass]);
|
||||
}
|
||||
}
|
||||
|
||||
AnnotatedDartCode rowClass(DriftElementWithResultSet element) {
|
||||
final existing = element.existingRowClass;
|
||||
if (existing != null) {
|
||||
return existing.targetClass;
|
||||
} else {
|
||||
return AnnotatedDartCode([element.nameOfRowClass]);
|
||||
}
|
||||
}
|
||||
|
||||
String refUri(Uri definition, String element) {
|
||||
final prefix =
|
||||
writer.generationOptions.imports.prefixFor(definition, element);
|
||||
|
@ -168,6 +191,12 @@ abstract class _NodeOrWriter {
|
|||
}
|
||||
}
|
||||
|
||||
/// References a top-level symbol exposed by the core `package:drift/drift.dart`
|
||||
/// library.
|
||||
String drift(String element) {
|
||||
return refUri(AnnotatedDartCode.drift, element);
|
||||
}
|
||||
|
||||
String dartCode(AnnotatedDartCode code) {
|
||||
final buffer = StringBuffer();
|
||||
|
||||
|
@ -291,6 +320,10 @@ class GenerationOptions {
|
|||
/// Whether companions should be generated.
|
||||
final bool writeCompanions;
|
||||
|
||||
/// Whether multiple files are generated, instead of just generating one file
|
||||
/// for each database.
|
||||
final bool isModular;
|
||||
|
||||
final ImportManager imports;
|
||||
|
||||
const GenerationOptions({
|
||||
|
@ -298,6 +331,7 @@ class GenerationOptions {
|
|||
this.forSchema,
|
||||
this.writeDataClasses = true,
|
||||
this.writeCompanions = true,
|
||||
this.isModular = false,
|
||||
});
|
||||
|
||||
/// Whether, instead of generating the full database code, we're only
|
||||
|
|
|
@ -6,5 +6,6 @@ This collection of examples demonstrates how to use some advanced drift features
|
|||
- `encryption`: A very simple Flutter app running an encrypted drift database.
|
||||
- `flutter_web_worker_example`: Asynchronously run a drift database through a web worker with Fluter.
|
||||
- `migrations_example`: Example showing to how to generate test utilities to verify schema migration.
|
||||
- `modular`: Example using drift's upcoming modular generation mode.
|
||||
- `web_worker_-_example`: Asynchronously run a drift database through a web worker, without Flutter.
|
||||
- `with_built_value`: Configure `build_runner` so that drift-generated classes can be used by `build_runner`.
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
# Files and directories created by pub.
|
||||
.dart_tool/
|
||||
.packages
|
|
@ -0,0 +1 @@
|
|||
include: package:lints/recommended.yaml
|
|
@ -0,0 +1,8 @@
|
|||
import 'package:drift/native.dart';
|
||||
import 'package:modular/database.dart';
|
||||
|
||||
void main() async {
|
||||
final database = Database(NativeDatabase.memory(logStatements: true));
|
||||
|
||||
database.usersDrift.findUsers().watch().listen(print);
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
targets:
|
||||
$default:
|
||||
builders:
|
||||
drift_dev:
|
||||
enabled: false
|
||||
|
||||
drift_dev:analyzer:
|
||||
enabled: true
|
||||
drift_dev:modular:
|
||||
enabled: true
|
|
@ -0,0 +1,11 @@
|
|||
import 'package:drift/drift.dart';
|
||||
|
||||
import 'database.drift.dart';
|
||||
|
||||
@DriftDatabase(include: {'src/users.drift'})
|
||||
class Database extends $Database {
|
||||
Database(super.e);
|
||||
|
||||
@override
|
||||
int get schemaVersion => 1;
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
// ignore_for_file: type=lint
|
||||
import 'package:drift/drift.dart' as i0;
|
||||
import 'package:modular/src/users.drift.dart' as i1;
|
||||
import 'package:drift/internal/modular.dart' as i2;
|
||||
|
||||
abstract class $Database extends i0.GeneratedDatabase {
|
||||
$Database(i0.QueryExecutor e) : super(e);
|
||||
late final i1.Users users = i1.Users(this);
|
||||
i1.UsersDrift get usersDrift =>
|
||||
i2.ReadDatabaseContainer(this).accessor<i1.UsersDrift>(
|
||||
'package:modular/src/users.drift', i1.UsersDrift.new);
|
||||
@override
|
||||
Iterable<i0.TableInfo<i0.Table, Object?>> get allTables =>
|
||||
allSchemaEntities.whereType<i0.TableInfo<i0.Table, Object?>>();
|
||||
@override
|
||||
List<i0.DatabaseSchemaEntity> get allSchemaEntities => [users];
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
CREATE TABLE users (
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
biography TEXT
|
||||
);
|
||||
|
||||
findUsers($predicate = TRUE): SELECT * FROM users WHERE $predicate;
|
|
@ -0,0 +1,243 @@
|
|||
// ignore_for_file: type=lint
|
||||
import 'package:drift/drift.dart' as i0;
|
||||
import 'package:modular/src/users.drift.dart' as i1;
|
||||
import 'package:drift/internal/modular.dart' as i2;
|
||||
|
||||
class User extends i0.DataClass implements i0.Insertable<i1.User> {
|
||||
final int id;
|
||||
final String name;
|
||||
final String? biography;
|
||||
const User({required this.id, required this.name, this.biography});
|
||||
@override
|
||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, i0.Expression>{};
|
||||
map['id'] = i0.Variable<int>(id);
|
||||
map['name'] = i0.Variable<String>(name);
|
||||
if (!nullToAbsent || biography != null) {
|
||||
map['biography'] = i0.Variable<String>(biography);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
i1.UsersCompanion toCompanion(bool nullToAbsent) {
|
||||
return i1.UsersCompanion(
|
||||
id: i0.Value(id),
|
||||
name: i0.Value(name),
|
||||
biography: biography == null && nullToAbsent
|
||||
? const i0.Value.absent()
|
||||
: i0.Value(biography),
|
||||
);
|
||||
}
|
||||
|
||||
factory User.fromJson(Map<String, dynamic> json,
|
||||
{i0.ValueSerializer? serializer}) {
|
||||
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||
return User(
|
||||
id: serializer.fromJson<int>(json['id']),
|
||||
name: serializer.fromJson<String>(json['name']),
|
||||
biography: serializer.fromJson<String?>(json['biography']),
|
||||
);
|
||||
}
|
||||
@override
|
||||
Map<String, dynamic> toJson({i0.ValueSerializer? serializer}) {
|
||||
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'id': serializer.toJson<int>(id),
|
||||
'name': serializer.toJson<String>(name),
|
||||
'biography': serializer.toJson<String?>(biography),
|
||||
};
|
||||
}
|
||||
|
||||
i1.User copyWith(
|
||||
{int? id,
|
||||
String? name,
|
||||
i0.Value<String?> biography = const i0.Value.absent()}) =>
|
||||
i1.User(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
biography: biography.present ? biography.value : this.biography,
|
||||
);
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('User(')
|
||||
..write('id: $id, ')
|
||||
..write('name: $name, ')
|
||||
..write('biography: $biography')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(id, name, biography);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is i1.User &&
|
||||
other.id == this.id &&
|
||||
other.name == this.name &&
|
||||
other.biography == this.biography);
|
||||
}
|
||||
|
||||
class UsersCompanion extends i0.UpdateCompanion<i1.User> {
|
||||
final i0.Value<int> id;
|
||||
final i0.Value<String> name;
|
||||
final i0.Value<String?> biography;
|
||||
const UsersCompanion({
|
||||
this.id = const i0.Value.absent(),
|
||||
this.name = const i0.Value.absent(),
|
||||
this.biography = const i0.Value.absent(),
|
||||
});
|
||||
UsersCompanion.insert({
|
||||
this.id = const i0.Value.absent(),
|
||||
required String name,
|
||||
this.biography = const i0.Value.absent(),
|
||||
}) : name = i0.Value(name);
|
||||
static i0.Insertable<i1.User> custom({
|
||||
i0.Expression<int>? id,
|
||||
i0.Expression<String>? name,
|
||||
i0.Expression<String>? biography,
|
||||
}) {
|
||||
return i0.RawValuesInsertable({
|
||||
if (id != null) 'id': id,
|
||||
if (name != null) 'name': name,
|
||||
if (biography != null) 'biography': biography,
|
||||
});
|
||||
}
|
||||
|
||||
i1.UsersCompanion copyWith(
|
||||
{i0.Value<int>? id,
|
||||
i0.Value<String>? name,
|
||||
i0.Value<String?>? biography}) {
|
||||
return i1.UsersCompanion(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
biography: biography ?? this.biography,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, i0.Expression>{};
|
||||
if (id.present) {
|
||||
map['id'] = i0.Variable<int>(id.value);
|
||||
}
|
||||
if (name.present) {
|
||||
map['name'] = i0.Variable<String>(name.value);
|
||||
}
|
||||
if (biography.present) {
|
||||
map['biography'] = i0.Variable<String>(biography.value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('i1.UsersCompanion(')
|
||||
..write('id: $id, ')
|
||||
..write('name: $name, ')
|
||||
..write('biography: $biography')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
class Users extends i0.Table with i0.TableInfo<Users, i1.User> {
|
||||
@override
|
||||
final i0.GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
Users(this.attachedDatabase, [this._alias]);
|
||||
static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id');
|
||||
late final i0.GeneratedColumn<int> id = i0.GeneratedColumn<int>(
|
||||
'id', aliasedName, false,
|
||||
type: i0.DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints: 'NOT NULL PRIMARY KEY');
|
||||
static const i0.VerificationMeta _nameMeta =
|
||||
const i0.VerificationMeta('name');
|
||||
late final i0.GeneratedColumn<String> name = i0.GeneratedColumn<String>(
|
||||
'name', aliasedName, false,
|
||||
type: i0.DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL');
|
||||
static const i0.VerificationMeta _biographyMeta =
|
||||
const i0.VerificationMeta('biography');
|
||||
late final i0.GeneratedColumn<String> biography = i0.GeneratedColumn<String>(
|
||||
'biography', aliasedName, true,
|
||||
type: i0.DriftSqlType.string,
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints: '');
|
||||
@override
|
||||
List<i0.GeneratedColumn> get $columns => [id, name, biography];
|
||||
@override
|
||||
String get aliasedName => _alias ?? 'users';
|
||||
@override
|
||||
String get actualTableName => 'users';
|
||||
@override
|
||||
i0.VerificationContext validateIntegrity(i0.Insertable<i1.User> instance,
|
||||
{bool isInserting = false}) {
|
||||
final context = i0.VerificationContext();
|
||||
final data = instance.toColumns(true);
|
||||
if (data.containsKey('id')) {
|
||||
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
|
||||
}
|
||||
if (data.containsKey('name')) {
|
||||
context.handle(
|
||||
_nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta));
|
||||
} else if (isInserting) {
|
||||
context.missing(_nameMeta);
|
||||
}
|
||||
if (data.containsKey('biography')) {
|
||||
context.handle(_biographyMeta,
|
||||
biography.isAcceptableOrUnknown(data['biography']!, _biographyMeta));
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
@override
|
||||
Set<i0.GeneratedColumn> get $primaryKey => {id};
|
||||
@override
|
||||
i1.User map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
return i1.User(
|
||||
id: attachedDatabase.typeMapping
|
||||
.read(i0.DriftSqlType.int, data['${effectivePrefix}id'])!,
|
||||
name: attachedDatabase.typeMapping
|
||||
.read(i0.DriftSqlType.string, data['${effectivePrefix}name'])!,
|
||||
biography: attachedDatabase.typeMapping
|
||||
.read(i0.DriftSqlType.string, data['${effectivePrefix}biography']),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Users createAlias(String alias) {
|
||||
return Users(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
@override
|
||||
List<String> get customConstraints => const [];
|
||||
@override
|
||||
bool get dontWriteConstraints => true;
|
||||
}
|
||||
|
||||
class UsersDrift extends i2.ModularAccessor {
|
||||
UsersDrift(i0.GeneratedDatabase db) : super(db);
|
||||
i0.Selectable<i1.User> findUsers({FindUsers$predicate? predicate}) {
|
||||
var $arrayStartIndex = 1;
|
||||
final generatedpredicate = $write(
|
||||
predicate?.call(this.users) ?? const i0.CustomExpression('(TRUE)'),
|
||||
startIndex: $arrayStartIndex);
|
||||
$arrayStartIndex += generatedpredicate.amountOfVariables;
|
||||
return customSelect('SELECT * FROM users WHERE ${generatedpredicate.sql}',
|
||||
variables: [
|
||||
...generatedpredicate.introducedVariables
|
||||
],
|
||||
readsFrom: {
|
||||
users,
|
||||
...generatedpredicate.watchedTables,
|
||||
}).asyncMap(users.mapFromRow);
|
||||
}
|
||||
|
||||
i1.Users get users => this.resultSet<i1.Users>('users');
|
||||
}
|
||||
|
||||
typedef FindUsers$predicate = i0.Expression<bool> Function(Users users);
|
|
@ -0,0 +1,15 @@
|
|||
name: modular
|
||||
version: 1.0.0
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: '>=2.18.0 <3.0.0'
|
||||
|
||||
dependencies:
|
||||
drift:
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: ^2.3.2
|
||||
drift_dev:
|
||||
lints: ^2.0.0
|
||||
test: ^1.16.0
|
Loading…
Reference in New Issue