diff --git a/drift/example/main.dart b/drift/example/main.dart index 0c5ae4d3..a6893780 100644 --- a/drift/example/main.dart +++ b/drift/example/main.dart @@ -12,6 +12,7 @@ class TodoCategories extends Table { TextColumn get name => text()(); } +@TableIndex(name: 'item_title', columns: {#title}) class TodoItems extends Table { IntColumn get id => integer().autoIncrement()(); TextColumn get title => text()(); diff --git a/drift/example/main.g.dart b/drift/example/main.g.dart index 63d7509f..574838c9 100644 --- a/drift/example/main.g.dart +++ b/drift/example/main.g.dart @@ -689,10 +689,17 @@ abstract class _$Database extends GeneratedDatabase { $TodoCategoryItemCountView(this); late final $TodoItemWithCategoryNameViewView customViewName = $TodoItemWithCategoryNameViewView(this); + late final Index itemTitle = + Index('item_title', 'CREATE INDEX item_title ON todo_items (title)'); @override Iterable> get allTables => allSchemaEntities.whereType>(); @override - List get allSchemaEntities => - [todoCategories, todoItems, todoCategoryItemCount, customViewName]; + List get allSchemaEntities => [ + todoCategories, + todoItems, + todoCategoryItemCount, + customViewName, + itemTitle + ]; } diff --git a/drift_dev/lib/src/analysis/resolver/dart/table.dart b/drift_dev/lib/src/analysis/resolver/dart/table.dart index 4d08b5ac..e2cdac0c 100644 --- a/drift_dev/lib/src/analysis/resolver/dart/table.dart +++ b/drift_dev/lib/src/analysis/resolver/dart/table.dart @@ -65,6 +65,9 @@ class DartTableResolver extends LocalElementResolver { ], overrideTableConstraints: tableConstraints, withoutRowId: await _overrideWithoutRowId(element) ?? false, + attachedIndices: [ + for (final id in discovered.attachedIndices) id.name, + ], ); if (primaryKey != null && diff --git a/drift_dev/lib/src/analysis/resolver/discover.dart b/drift_dev/lib/src/analysis/resolver/discover.dart index 6d9bcce2..1d942380 100644 --- a/drift_dev/lib/src/analysis/resolver/discover.dart +++ b/drift_dev/lib/src/analysis/resolver/discover.dart @@ -216,11 +216,14 @@ class _FindDartElements extends RecursiveElementVisitor { _pendingWork.add(Future.sync(() async { final name = await _sqlNameOfTable(element); final id = _discoverStep._id(name); - found.add(DiscoveredDartTable(id, element)); + final attachedIndices = []; for (final (annotation, indexId) in _tableIndexAnnotation(element)) { + attachedIndices.add(indexId); found.add(DiscoveredDartIndex(indexId, element, id, annotation)); } + + found.add(DiscoveredDartTable(id, element, attachedIndices)); })); } } else if (_isDslView(element)) { diff --git a/drift_dev/lib/src/analysis/resolver/file_analysis.dart b/drift_dev/lib/src/analysis/resolver/file_analysis.dart index 63d45206..dc418fed 100644 --- a/drift_dev/lib/src/analysis/resolver/file_analysis.dart +++ b/drift_dev/lib/src/analysis/resolver/file_analysis.dart @@ -42,14 +42,34 @@ class FileAnalyzer { await driver.resolveElements(import.ownUri); } + final availableByDefault = { + ...element.declaredTables, + ...element.declaredViews, + }; + + // For indices added to tables via an annotation, the index should + // also be available. + for (final table in element.declaredTables) { + final fileState = driver.cache.knownFiles[table.id.libraryUri]!; + + for (final attachedIndex in table.attachedIndices) { + final index = + fileState.analysis[fileState.id(attachedIndex)]?.result; + if (index is DriftIndex) { + availableByDefault.add(index); + } + } + } + final availableElements = imported .expand((reachable) { final elementAnalysis = reachable.analysis.values; + return elementAnalysis.map((e) => e.result).where( (e) => e is DefinedSqlQuery || e is DriftSchemaElement); }) .whereType() - .followedBy(element.references) + .followedBy(availableByDefault) .transitiveClosureUnderReferences() .sortTopologicallyOrElse(driver.backend.log.severe); @@ -58,15 +78,11 @@ class FileAnalyzer { // from a Dart file that hasn't been added to `tables`, emit a warning. // https://github.com/simolus3/drift/issues/2462#issuecomment-1620107751 if (element is DriftDatabase) { - final explicitlyAdded = { - ...element.declaredTables, - ...element.declaredViews, - }; final implicitlyAdded = availableElements .whereType() .where((element) => element.declaration.isDartDeclaration && - !explicitlyAdded.contains(element)); + !availableByDefault.contains(element)); if (implicitlyAdded.isNotEmpty) { final names = implicitlyAdded @@ -97,6 +113,9 @@ class FileAnalyzer { result.resolvedDatabases[element.id] = ResolvedDatabaseAccessor(queries, imports, availableElements); + } else if (element is DriftIndex) { + // We need the SQL AST for each index to create them in code + element.createStatementForDartDefinition(); } } } else if (state.extension == '.drift' || state.extension == '.moor') { diff --git a/drift_dev/lib/src/analysis/resolver/intermediate_state.dart b/drift_dev/lib/src/analysis/resolver/intermediate_state.dart index f9c750d9..d6900856 100644 --- a/drift_dev/lib/src/analysis/resolver/intermediate_state.dart +++ b/drift_dev/lib/src/analysis/resolver/intermediate_state.dart @@ -38,7 +38,15 @@ class DiscoveredDartTable extends DiscoveredDartElement { @override DriftElementKind get kind => DriftElementKind.table; - DiscoveredDartTable(super.ownId, super.dartElement); + /// The element ids of [DiscoveredDartIndex] entries that have been added to + /// this table with a `@TableIndex` annotation on the table class. + final List attachedIndices; + + DiscoveredDartTable( + super.ownId, + super.dartElement, + this.attachedIndices, + ); } class DiscoveredDartView extends DiscoveredDartElement { diff --git a/drift_dev/lib/src/analysis/results/index.dart b/drift_dev/lib/src/analysis/results/index.dart index 1f920d83..82bdbca0 100644 --- a/drift_dev/lib/src/analysis/results/index.dart +++ b/drift_dev/lib/src/analysis/results/index.dart @@ -48,6 +48,27 @@ class DriftIndex extends DriftSchemaElement { /// This node is not serialized and only set in the late-state, local file /// analysis. CreateIndexStatement? parsedStatement; + + /// At the moment, the index implementation in the generator writes the + /// `CREATE INDEX` definition as a string into the generated code. This + /// requires [parsedStatement] to be available when generating code. To ensure + /// this for Dart-based index declarations, this method creates a suitable AST + /// for a create index statement based on the information available in our + /// element model. + /// + /// Note that Dart index definitions are less expressive than the ones in SQL, + /// so this method should not be used for indices defined with SQL. + void createStatementForDartDefinition() { + parsedStatement = CreateIndexStatement( + indexName: id.name, + on: TableReference(table?.id.name ?? ''), + unique: unique, + columns: [ + for (final column in indexedColumns) + IndexedColumn(Reference(columnName: column.nameInSql)) + ], + ); + } } sealed class DriftIndexDefintion {} diff --git a/drift_dev/lib/src/analysis/results/table.dart b/drift_dev/lib/src/analysis/results/table.dart index 2f3118bf..f0c11604 100644 --- a/drift_dev/lib/src/analysis/results/table.dart +++ b/drift_dev/lib/src/analysis/results/table.dart @@ -53,6 +53,14 @@ class DriftTable extends DriftElementWithResultSet { /// `customConstraints` getter in the table class with this value. final List overrideTableConstraints; + /// The names of indices that have been attached to this table using the + /// `@TableIndex` annotation in drift. + /// + /// This field only has the purpose of implicitly adding these indices to each + /// database adding this table, so that code for that index will get generated + /// without an explicit reference. + final List attachedIndices; + DriftColumn? _rowIdColumn; DriftTable( @@ -71,6 +79,7 @@ class DriftTable extends DriftElementWithResultSet { this.virtualTableData, this.writeDefaultConstraints = true, this.overrideTableConstraints = const [], + this.attachedIndices = const [], }) { _rowIdColumn = DriftColumn( sqlType: DriftSqlType.int, diff --git a/drift_dev/lib/src/analysis/serializer.dart b/drift_dev/lib/src/analysis/serializer.dart index f6785038..5c3d9d8c 100644 --- a/drift_dev/lib/src/analysis/serializer.dart +++ b/drift_dev/lib/src/analysis/serializer.dart @@ -62,6 +62,7 @@ class ElementSerializer { 'virtual': _serializeVirtualTableData(element.virtualTableData!), 'write_default_constraints': element.writeDefaultConstraints, 'custom_constraints': element.overrideTableConstraints, + 'attached_indices': element.attachedIndices, }; } else if (element is DriftIndex) { additionalInformation = { @@ -516,6 +517,7 @@ class ElementDeserializer { overrideTableConstraints: json['custom_constraints'] != null ? (json['custom_constraints'] as List).cast() : const [], + attachedIndices: (json['attached_indices'] as List).cast(), ); for (final column in columns) { diff --git a/drift_dev/lib/src/services/schema/schema_files.dart b/drift_dev/lib/src/services/schema/schema_files.dart index 67e57905..c31b2e5a 100644 --- a/drift_dev/lib/src/services/schema/schema_files.dart +++ b/drift_dev/lib/src/services/schema/schema_files.dart @@ -311,6 +311,8 @@ class SchemaReader { if (sql != null) { index.parsedStatement = _engine.parse(sql).rootNode as CreateIndexStatement; + } else { + index.createStatementForDartDefinition(); } return index;