Support ordering mode for Dart indexes

https://github.com/simolus3/drift/discussions/3676
This commit is contained in:
Simon Binder
2025-09-30 20:51:13 +02:00
parent e8a5c79411
commit 979b700d4c
18 changed files with 410 additions and 126 deletions
+4
View File
@@ -312,6 +312,10 @@ users table contains a lot of rows:
{{ load_snippet('index','lib/snippets/dart_api/tables.dart.excerpt.json') }}
To specify the ordering mode for a column, you can use an `IndexedColumn` instance:
{{ load_snippet('index_ordering','lib/snippets/dart_api/tables.dart.excerpt.json') }}
#### SQL-based index
If you need more options in your index, for instance to define partial indexes,
+11
View File
@@ -203,6 +203,17 @@ class Users extends Table {
}
// #enddocregion index
// #docregion index_ordering
@TableIndex(
name: 'log_entries_at',
columns: {IndexedColumn(#loggedAt, orderBy: OrderingMode.desc)},
)
class LogEntries extends Table {
IntColumn get id => integer().autoIncrement()();
DateTimeColumn get loggedAt => dateTime()();
}
// #enddocregion index_ordering
// #docregion indexsql
@TableIndex.sql('''
CREATE INDEX pending_orders ON orders (creation_time)
+4
View File
@@ -1,3 +1,7 @@
## 2.29.0-dev
- Allow specifying ordering for columns in `@TableIndex` annotation.
## 2.28.2
- Legacy wasm database: Fix cast issue when compiling with `dart2wasm`.
+1 -1
View File
@@ -31,7 +31,7 @@ targets:
named_parameters: true
fatal_warnings: true
sql:
dialect: sqlite
dialects: [sqlite, postgres]
options:
version: "3.37"
modules:
+4 -2
View File
@@ -699,8 +699,10 @@ 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)');
late final Index itemTitle = Index.byDialect('item_title', {
SqlDialect.sqlite: 'CREATE INDEX item_title ON todo_items (title)',
SqlDialect.postgres: 'CREATE INDEX item_title ON todo_items (title)',
});
@override
Iterable<TableInfo<Table, Object?>> get allTables =>
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
+24 -1
View File
@@ -289,11 +289,16 @@ final class TableIndex {
/// The columns of the table that should be part of the index.
///
/// This can either be a [Symbol] for the default options or an
/// [IndexedColumn] instance.
///
/// Columns are referenced with a [Symbol] of their getter name used in the
/// column definition. For instance, a table declaring a column as
/// `IntColumn get nextUpdateSnapshot => ...()` could reference this column
/// using `#nextUpdateSnapshot`.
final Set<Symbol> columns;
///
/// To further control options of the index, use an [IndexedColumn] instance.
final Set<Object /*IndexedColumn | Symbol*/ > columns;
/// As an alternative to [name], [unique] and [columns], a `CREATE INDEX` SQL
/// statement defining the index.
@@ -324,6 +329,24 @@ final class TableIndex {
columns = const {};
}
/// A column that can appear in a [TableIndex].
final class IndexedColumn {
/// The columns of the table that should be part of the index.
///
/// Columns are referenced with a [Symbol] of their getter name used in the
/// column definition. For instance, a table declaring a column as
/// `IntColumn get nextUpdateSnapshot => ...()` could reference this column
/// using `#nextUpdateSnapshot`.
final Symbol columnName;
/// The [OrderingMode] to use for the index, controlling the order of values
/// in the b-tree.
final OrderingMode? orderBy;
/// Creates an indexed column from a [Symbol] and index options.
const IndexedColumn(this.columnName, {required this.orderBy});
}
/// A class to be used as an annotation on [Table] classes to customize the
/// name for the data class that will be generated for the table class. The data
/// class is a dart object that will be used to represent a row in the table.
@@ -34,7 +34,7 @@ extension WriteDefinition on GenerationContext {
String pickForDialect(Map<SqlDialect, String> sql) {
assert(
sql.containsKey(dialect),
'Tried running SQL optimized for the following dialects: ${sql.keys.join}. '
'Tried running SQL optimized for the following dialects: ${sql.keys.join(', ')}. '
'However, the database is running $dialect. Has that dialect been added '
'to the `dialects` drift builder option?',
);
@@ -92,6 +92,11 @@ void main() {
'INNER JOIN "categories" "t1" '
'ON "t1"."id" = "t0"."category"',
[]));
verify(mockExecutor.runCustom(
'CREATE INDEX categories_desc ON categories ("desc" DESC, priority)',
[],
));
});
test('creates individual tables', () async {
@@ -218,7 +218,13 @@ abstract class _$_GeopolyTestDatabase extends GeneratedDatabase {
late final GeopolyTest geopolyTest = GeopolyTest(this);
Selectable<double?> area(int var1) {
return customSelect(
'SELECT geopoly_area(_shape) AS _c0 FROM geopoly_test WHERE "rowid" = ?1',
switch (executor.dialect) {
SqlDialect.sqlite =>
'SELECT geopoly_area(_shape) AS _c0 FROM geopoly_test WHERE "rowid" = ?1',
SqlDialect.postgres ||
_ =>
'SELECT geopoly_area(_shape) AS _c0 FROM geopoly_test WHERE "rowid" = \$1',
},
variables: [
Variable<int>(var1)
],
+7
View File
@@ -57,6 +57,13 @@ class Users extends Table with AutoIncrement {
}
@DataClassName('Category')
@TableIndex(
name: 'categories_desc',
columns: {
IndexedColumn(#description, orderBy: OrderingMode.desc),
#priority,
},
)
class Categories extends Table with AutoIncrement {
late final description =
text().named('desc').customConstraint('NOT NULL UNIQUE')();
+50 -18
View File
@@ -659,13 +659,15 @@ class $UsersTable extends Users with TableInfo<$UsersTable, User> {
static const VerificationMeta _isAwesomeMeta =
const VerificationMeta('isAwesome');
@override
late final GeneratedColumn<bool> isAwesome = GeneratedColumn<bool>(
'is_awesome', aliasedName, false,
type: DriftSqlType.bool,
requiredDuringInsert: false,
defaultConstraints:
GeneratedColumn.constraintIsAlways('CHECK ("is_awesome" IN (0, 1))'),
defaultValue: const Constant(true));
late final GeneratedColumn<bool> isAwesome =
GeneratedColumn<bool>('is_awesome', aliasedName, false,
type: DriftSqlType.bool,
requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintsDependsOnDialect({
SqlDialect.sqlite: 'CHECK ("is_awesome" IN (0, 1))',
SqlDialect.postgres: '',
}),
defaultValue: const Constant(true));
static const VerificationMeta _profilePictureMeta =
const VerificationMeta('profilePicture');
@override
@@ -1687,12 +1689,14 @@ class $TableWithEveryColumnTypeTable extends TableWithEveryColumnType
.withConverter<RowId>($TableWithEveryColumnTypeTable.$converterid);
static const VerificationMeta _aBoolMeta = const VerificationMeta('aBool');
@override
late final GeneratedColumn<bool> aBool = GeneratedColumn<bool>(
'a_bool', aliasedName, true,
type: DriftSqlType.bool,
requiredDuringInsert: false,
defaultConstraints:
GeneratedColumn.constraintIsAlways('CHECK ("a_bool" IN (0, 1))'));
late final GeneratedColumn<bool> aBool =
GeneratedColumn<bool>('a_bool', aliasedName, true,
type: DriftSqlType.bool,
requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintsDependsOnDialect({
SqlDialect.sqlite: 'CHECK ("a_bool" IN (0, 1))',
SqlDialect.postgres: '',
}));
static const VerificationMeta _aDateTimeMeta =
const VerificationMeta('aDateTime');
@override
@@ -3327,6 +3331,12 @@ abstract class _$TodoDb extends GeneratedDatabase {
$CategoryTodoCountViewView(this);
late final $TodoWithCategoryViewView todoWithCategoryView =
$TodoWithCategoryViewView(this);
late final Index categoriesDesc = Index.byDialect('categories_desc', {
SqlDialect.sqlite:
'CREATE INDEX categories_desc ON categories ("desc" DESC, priority)',
SqlDialect.postgres:
'CREATE INDEX categories_desc ON categories ("desc" DESC, priority)',
});
late final SomeDao someDao = SomeDao(this as TodoDb);
Selectable<AllTodosWithCategoryResult> allTodosWithCategory() {
return customSelect(
@@ -3354,7 +3364,10 @@ abstract class _$TodoDb extends GeneratedDatabase {
Future<int> deleteTodoById(RowId var1) {
return customUpdate(
'DELETE FROM todos WHERE id = ?1',
switch (executor.dialect) {
SqlDialect.sqlite => 'DELETE FROM todos WHERE id = ?1',
SqlDialect.postgres || _ => 'DELETE FROM todos WHERE id = \$1',
},
variables: [Variable<int>($TodosTableTable.$converterid.toSql(var1))],
updates: {todosTable},
updateKind: UpdateKind.delete,
@@ -3366,7 +3379,13 @@ abstract class _$TodoDb extends GeneratedDatabase {
final expandedvar3 = $expandVar($arrayStartIndex, var3.length);
$arrayStartIndex += var3.length;
return customSelect(
'SELECT * FROM todos WHERE title = ?2 OR id IN ($expandedvar3) OR title = ?1',
switch (executor.dialect) {
SqlDialect.sqlite =>
'SELECT * FROM todos WHERE title = ?2 OR id IN ($expandedvar3) OR title = ?1',
SqlDialect.postgres ||
_ =>
'SELECT * FROM todos WHERE title = \$2 OR id IN ($expandedvar3) OR title = \$1',
},
variables: [
Variable<String>(var1),
Variable<String>(var2),
@@ -3380,7 +3399,13 @@ abstract class _$TodoDb extends GeneratedDatabase {
Selectable<TodoEntry> search({required RowId id}) {
return customSelect(
'SELECT * FROM todos WHERE CASE WHEN -1 = ?1 THEN 1 ELSE id = ?1 END',
switch (executor.dialect) {
SqlDialect.sqlite =>
'SELECT * FROM todos WHERE CASE WHEN -1 = ?1 THEN 1 ELSE id = ?1 END',
SqlDialect.postgres ||
_ =>
'SELECT * FROM todos WHERE CASE WHEN -1 = \$1 THEN 1 ELSE id = \$1 END',
},
variables: [
Variable<int>($TodosTableTable.$converterid.toSql(id))
],
@@ -3417,7 +3442,8 @@ abstract class _$TodoDb extends GeneratedDatabase {
store,
listing,
categoryTodoCountView,
todoWithCategoryView
todoWithCategoryView,
categoriesDesc
];
}
@@ -5977,7 +6003,13 @@ mixin _$SomeDaoMixin on DatabaseAccessor<TodoDb> {
attachedDatabase.todoWithCategoryView;
Selectable<TodoEntry> todosForUser({required RowId user}) {
return customSelect(
'SELECT t.* FROM todos AS t INNER JOIN shared_todos AS st ON st.todo = t.id INNER JOIN users AS u ON u.id = st.user WHERE u.id = ?1',
switch (executor.dialect) {
SqlDialect.sqlite =>
'SELECT t.* FROM todos AS t INNER JOIN shared_todos AS st ON st.todo = t.id INNER JOIN users AS u ON u.id = st.user WHERE u.id = ?1',
SqlDialect.postgres ||
_ =>
'SELECT t.* FROM todos AS t INNER JOIN shared_todos AS st ON st.todo = t.id INNER JOIN users AS u ON u.id = st."user" WHERE u.id = \$1',
},
variables: [
Variable<int>($UsersTable.$converterid.toSql(user))
],
+109 -86
View File
@@ -210,8 +210,8 @@ class _Fake$TodoWithCategoryViewView_16 extends _i1.SmartFake
);
}
class _FakeSomeDao_17 extends _i1.SmartFake implements _i3.SomeDao {
_FakeSomeDao_17(
class _FakeIndex_17 extends _i1.SmartFake implements _i2.Index {
_FakeIndex_17(
Object parent,
Invocation parentInvocation,
) : super(
@@ -220,9 +220,19 @@ class _FakeSomeDao_17 extends _i1.SmartFake implements _i3.SomeDao {
);
}
class _FakeGeneratedDatabase_18 extends _i1.SmartFake
class _FakeSomeDao_18 extends _i1.SmartFake implements _i3.SomeDao {
_FakeSomeDao_18(
Object parent,
Invocation parentInvocation,
) : super(
parent,
parentInvocation,
);
}
class _FakeGeneratedDatabase_19 extends _i1.SmartFake
implements _i2.GeneratedDatabase {
_FakeGeneratedDatabase_18(
_FakeGeneratedDatabase_19(
Object parent,
Invocation parentInvocation,
) : super(
@@ -231,9 +241,9 @@ class _FakeGeneratedDatabase_18 extends _i1.SmartFake
);
}
class _FakeStreamQueryUpdateRules_19 extends _i1.SmartFake
class _FakeStreamQueryUpdateRules_20 extends _i1.SmartFake
implements _i2.StreamQueryUpdateRules {
_FakeStreamQueryUpdateRules_19(
_FakeStreamQueryUpdateRules_20(
Object parent,
Invocation parentInvocation,
) : super(
@@ -242,9 +252,9 @@ class _FakeStreamQueryUpdateRules_19 extends _i1.SmartFake
);
}
class _FakeDatabaseConnection_20 extends _i1.SmartFake
class _FakeDatabaseConnection_21 extends _i1.SmartFake
implements _i2.DatabaseConnection {
_FakeDatabaseConnection_20(
_FakeDatabaseConnection_21(
Object parent,
Invocation parentInvocation,
) : super(
@@ -253,8 +263,8 @@ class _FakeDatabaseConnection_20 extends _i1.SmartFake
);
}
class _FakeQueryExecutor_21 extends _i1.SmartFake implements _i2.QueryExecutor {
_FakeQueryExecutor_21(
class _FakeQueryExecutor_22 extends _i1.SmartFake implements _i2.QueryExecutor {
_FakeQueryExecutor_22(
Object parent,
Invocation parentInvocation,
) : super(
@@ -263,9 +273,9 @@ class _FakeQueryExecutor_21 extends _i1.SmartFake implements _i2.QueryExecutor {
);
}
class _FakeStreamQueryStore_22 extends _i1.SmartFake
class _FakeStreamQueryStore_23 extends _i1.SmartFake
implements _i4.StreamQueryStore {
_FakeStreamQueryStore_22(
_FakeStreamQueryStore_23(
Object parent,
Invocation parentInvocation,
) : super(
@@ -274,9 +284,9 @@ class _FakeStreamQueryStore_22 extends _i1.SmartFake
);
}
class _FakeDatabaseConnectionUser_23 extends _i1.SmartFake
class _FakeDatabaseConnectionUser_24 extends _i1.SmartFake
implements _i2.DatabaseConnectionUser {
_FakeDatabaseConnectionUser_23(
_FakeDatabaseConnectionUser_24(
Object parent,
Invocation parentInvocation,
) : super(
@@ -285,8 +295,8 @@ class _FakeDatabaseConnectionUser_23 extends _i1.SmartFake
);
}
class _FakeSelectable_24<T> extends _i1.SmartFake implements _i2.Selectable<T> {
_FakeSelectable_24(
class _FakeSelectable_25<T> extends _i1.SmartFake implements _i2.Selectable<T> {
_FakeSelectable_25(
Object parent,
Invocation parentInvocation,
) : super(
@@ -295,8 +305,8 @@ class _FakeSelectable_24<T> extends _i1.SmartFake implements _i2.Selectable<T> {
);
}
class _FakeMigrator_25 extends _i1.SmartFake implements _i2.Migrator {
_FakeMigrator_25(
class _FakeMigrator_26 extends _i1.SmartFake implements _i2.Migrator {
_FakeMigrator_26(
Object parent,
Invocation parentInvocation,
) : super(
@@ -305,8 +315,8 @@ class _FakeMigrator_25 extends _i1.SmartFake implements _i2.Migrator {
);
}
class _FakeFuture_26<T1> extends _i1.SmartFake implements _i5.Future<T1> {
_FakeFuture_26(
class _FakeFuture_27<T1> extends _i1.SmartFake implements _i5.Future<T1> {
_FakeFuture_27(
Object parent,
Invocation parentInvocation,
) : super(
@@ -315,9 +325,9 @@ class _FakeFuture_26<T1> extends _i1.SmartFake implements _i5.Future<T1> {
);
}
class _FakeInsertStatement_27<T1 extends _i2.Table, D1> extends _i1.SmartFake
class _FakeInsertStatement_28<T1 extends _i2.Table, D1> extends _i1.SmartFake
implements _i2.InsertStatement<T1, D1> {
_FakeInsertStatement_27(
_FakeInsertStatement_28(
Object parent,
Invocation parentInvocation,
) : super(
@@ -326,9 +336,9 @@ class _FakeInsertStatement_27<T1 extends _i2.Table, D1> extends _i1.SmartFake
);
}
class _FakeUpdateStatement_28<T extends _i2.Table, D> extends _i1.SmartFake
class _FakeUpdateStatement_29<T extends _i2.Table, D> extends _i1.SmartFake
implements _i2.UpdateStatement<T, D> {
_FakeUpdateStatement_28(
_FakeUpdateStatement_29(
Object parent,
Invocation parentInvocation,
) : super(
@@ -337,9 +347,9 @@ class _FakeUpdateStatement_28<T extends _i2.Table, D> extends _i1.SmartFake
);
}
class _FakeSimpleSelectStatement_29<T1 extends _i2.HasResultSet, D>
class _FakeSimpleSelectStatement_30<T1 extends _i2.HasResultSet, D>
extends _i1.SmartFake implements _i2.SimpleSelectStatement<T1, D> {
_FakeSimpleSelectStatement_29(
_FakeSimpleSelectStatement_30(
Object parent,
Invocation parentInvocation,
) : super(
@@ -348,9 +358,9 @@ class _FakeSimpleSelectStatement_29<T1 extends _i2.HasResultSet, D>
);
}
class _FakeJoinedSelectStatement_30<FirstT extends _i2.HasResultSet, FirstD>
class _FakeJoinedSelectStatement_31<FirstT extends _i2.HasResultSet, FirstD>
extends _i1.SmartFake implements _i2.JoinedSelectStatement<FirstT, FirstD> {
_FakeJoinedSelectStatement_30(
_FakeJoinedSelectStatement_31(
Object parent,
Invocation parentInvocation,
) : super(
@@ -359,9 +369,9 @@ class _FakeJoinedSelectStatement_30<FirstT extends _i2.HasResultSet, FirstD>
);
}
class _FakeBaseSelectStatement_31<Row> extends _i1.SmartFake
class _FakeBaseSelectStatement_32<Row> extends _i1.SmartFake
implements _i2.BaseSelectStatement<Row> {
_FakeBaseSelectStatement_31(
_FakeBaseSelectStatement_32(
Object parent,
Invocation parentInvocation,
) : super(
@@ -370,9 +380,9 @@ class _FakeBaseSelectStatement_31<Row> extends _i1.SmartFake
);
}
class _FakeDeleteStatement_32<T1 extends _i2.Table, D1> extends _i1.SmartFake
class _FakeDeleteStatement_33<T1 extends _i2.Table, D1> extends _i1.SmartFake
implements _i2.DeleteStatement<T1, D1> {
_FakeDeleteStatement_32(
_FakeDeleteStatement_33(
Object parent,
Invocation parentInvocation,
) : super(
@@ -381,9 +391,9 @@ class _FakeDeleteStatement_32<T1 extends _i2.Table, D1> extends _i1.SmartFake
);
}
class _FakeGenerationContext_33 extends _i1.SmartFake
class _FakeGenerationContext_34 extends _i1.SmartFake
implements _i2.GenerationContext {
_FakeGenerationContext_33(
_FakeGenerationContext_34(
Object parent,
Invocation parentInvocation,
) : super(
@@ -653,14 +663,27 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
),
) as _i3.$TodoWithCategoryViewView);
@override
_i2.Index get categoriesDesc => (super.noSuchMethod(
Invocation.getter(#categoriesDesc),
returnValue: _FakeIndex_17(
this,
Invocation.getter(#categoriesDesc),
),
returnValueForMissingStub: _FakeIndex_17(
this,
Invocation.getter(#categoriesDesc),
),
) as _i2.Index);
@override
_i3.SomeDao get someDao => (super.noSuchMethod(
Invocation.getter(#someDao),
returnValue: _FakeSomeDao_17(
returnValue: _FakeSomeDao_18(
this,
Invocation.getter(#someDao),
),
returnValueForMissingStub: _FakeSomeDao_17(
returnValueForMissingStub: _FakeSomeDao_18(
this,
Invocation.getter(#someDao),
),
@@ -684,11 +707,11 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
@override
_i2.GeneratedDatabase get attachedDatabase => (super.noSuchMethod(
Invocation.getter(#attachedDatabase),
returnValue: _FakeGeneratedDatabase_18(
returnValue: _FakeGeneratedDatabase_19(
this,
Invocation.getter(#attachedDatabase),
),
returnValueForMissingStub: _FakeGeneratedDatabase_18(
returnValueForMissingStub: _FakeGeneratedDatabase_19(
this,
Invocation.getter(#attachedDatabase),
),
@@ -697,11 +720,11 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
@override
_i2.StreamQueryUpdateRules get streamUpdateRules => (super.noSuchMethod(
Invocation.getter(#streamUpdateRules),
returnValue: _FakeStreamQueryUpdateRules_19(
returnValue: _FakeStreamQueryUpdateRules_20(
this,
Invocation.getter(#streamUpdateRules),
),
returnValueForMissingStub: _FakeStreamQueryUpdateRules_19(
returnValueForMissingStub: _FakeStreamQueryUpdateRules_20(
this,
Invocation.getter(#streamUpdateRules),
),
@@ -710,11 +733,11 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
@override
_i2.DatabaseConnection get connection => (super.noSuchMethod(
Invocation.getter(#connection),
returnValue: _FakeDatabaseConnection_20(
returnValue: _FakeDatabaseConnection_21(
this,
Invocation.getter(#connection),
),
returnValueForMissingStub: _FakeDatabaseConnection_20(
returnValueForMissingStub: _FakeDatabaseConnection_21(
this,
Invocation.getter(#connection),
),
@@ -736,11 +759,11 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
@override
_i2.QueryExecutor get executor => (super.noSuchMethod(
Invocation.getter(#executor),
returnValue: _FakeQueryExecutor_21(
returnValue: _FakeQueryExecutor_22(
this,
Invocation.getter(#executor),
),
returnValueForMissingStub: _FakeQueryExecutor_21(
returnValueForMissingStub: _FakeQueryExecutor_22(
this,
Invocation.getter(#executor),
),
@@ -749,11 +772,11 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
@override
_i4.StreamQueryStore get streamQueries => (super.noSuchMethod(
Invocation.getter(#streamQueries),
returnValue: _FakeStreamQueryStore_22(
returnValue: _FakeStreamQueryStore_23(
this,
Invocation.getter(#streamQueries),
),
returnValueForMissingStub: _FakeStreamQueryStore_22(
returnValueForMissingStub: _FakeStreamQueryStore_23(
this,
Invocation.getter(#streamQueries),
),
@@ -762,11 +785,11 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
@override
_i2.DatabaseConnectionUser get resolvedEngine => (super.noSuchMethod(
Invocation.getter(#resolvedEngine),
returnValue: _FakeDatabaseConnectionUser_23(
returnValue: _FakeDatabaseConnectionUser_24(
this,
Invocation.getter(#resolvedEngine),
),
returnValueForMissingStub: _FakeDatabaseConnectionUser_23(
returnValueForMissingStub: _FakeDatabaseConnectionUser_24(
this,
Invocation.getter(#resolvedEngine),
),
@@ -779,7 +802,7 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
#allTodosWithCategory,
[],
),
returnValue: _FakeSelectable_24<_i3.AllTodosWithCategoryResult>(
returnValue: _FakeSelectable_25<_i3.AllTodosWithCategoryResult>(
this,
Invocation.method(
#allTodosWithCategory,
@@ -787,7 +810,7 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
),
),
returnValueForMissingStub:
_FakeSelectable_24<_i3.AllTodosWithCategoryResult>(
_FakeSelectable_25<_i3.AllTodosWithCategoryResult>(
this,
Invocation.method(
#allTodosWithCategory,
@@ -821,7 +844,7 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
var3,
],
),
returnValue: _FakeSelectable_24<_i3.TodoEntry>(
returnValue: _FakeSelectable_25<_i3.TodoEntry>(
this,
Invocation.method(
#withIn,
@@ -832,7 +855,7 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
],
),
),
returnValueForMissingStub: _FakeSelectable_24<_i3.TodoEntry>(
returnValueForMissingStub: _FakeSelectable_25<_i3.TodoEntry>(
this,
Invocation.method(
#withIn,
@@ -853,7 +876,7 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
[],
{#id: id},
),
returnValue: _FakeSelectable_24<_i3.TodoEntry>(
returnValue: _FakeSelectable_25<_i3.TodoEntry>(
this,
Invocation.method(
#search,
@@ -861,7 +884,7 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
{#id: id},
),
),
returnValueForMissingStub: _FakeSelectable_24<_i3.TodoEntry>(
returnValueForMissingStub: _FakeSelectable_25<_i3.TodoEntry>(
this,
Invocation.method(
#search,
@@ -877,14 +900,14 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
#findCustom,
[],
),
returnValue: _FakeSelectable_24<_i3.MyCustomObject>(
returnValue: _FakeSelectable_25<_i3.MyCustomObject>(
this,
Invocation.method(
#findCustom,
[],
),
),
returnValueForMissingStub: _FakeSelectable_24<_i3.MyCustomObject>(
returnValueForMissingStub: _FakeSelectable_25<_i3.MyCustomObject>(
this,
Invocation.method(
#findCustom,
@@ -899,14 +922,14 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
#createMigrator,
[],
),
returnValue: _FakeMigrator_25(
returnValue: _FakeMigrator_26(
this,
Invocation.method(
#createMigrator,
[],
),
),
returnValueForMissingStub: _FakeMigrator_25(
returnValueForMissingStub: _FakeMigrator_26(
this,
Invocation.method(
#createMigrator,
@@ -1038,7 +1061,7 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
),
(T v) => _i5.Future<T>.value(v),
) ??
_FakeFuture_26<T>(
_FakeFuture_27<T>(
this,
Invocation.method(
#doWhenOpened,
@@ -1055,7 +1078,7 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
),
(T v) => _i5.Future<T>.value(v),
) ??
_FakeFuture_26<T>(
_FakeFuture_27<T>(
this,
Invocation.method(
#doWhenOpened,
@@ -1072,14 +1095,14 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
#into,
[table],
),
returnValue: _FakeInsertStatement_27<T, D>(
returnValue: _FakeInsertStatement_28<T, D>(
this,
Invocation.method(
#into,
[table],
),
),
returnValueForMissingStub: _FakeInsertStatement_27<T, D>(
returnValueForMissingStub: _FakeInsertStatement_28<T, D>(
this,
Invocation.method(
#into,
@@ -1096,14 +1119,14 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
#update,
[table],
),
returnValue: _FakeUpdateStatement_28<Tbl, R>(
returnValue: _FakeUpdateStatement_29<Tbl, R>(
this,
Invocation.method(
#update,
[table],
),
),
returnValueForMissingStub: _FakeUpdateStatement_28<Tbl, R>(
returnValueForMissingStub: _FakeUpdateStatement_29<Tbl, R>(
this,
Invocation.method(
#update,
@@ -1123,7 +1146,7 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
[table],
{#distinct: distinct},
),
returnValue: _FakeSimpleSelectStatement_29<T, R>(
returnValue: _FakeSimpleSelectStatement_30<T, R>(
this,
Invocation.method(
#select,
@@ -1131,7 +1154,7 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
{#distinct: distinct},
),
),
returnValueForMissingStub: _FakeSimpleSelectStatement_29<T, R>(
returnValueForMissingStub: _FakeSimpleSelectStatement_30<T, R>(
this,
Invocation.method(
#select,
@@ -1152,7 +1175,7 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
[table],
{#distinct: distinct},
),
returnValue: _FakeJoinedSelectStatement_30<T, R>(
returnValue: _FakeJoinedSelectStatement_31<T, R>(
this,
Invocation.method(
#selectOnly,
@@ -1160,7 +1183,7 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
{#distinct: distinct},
),
),
returnValueForMissingStub: _FakeJoinedSelectStatement_30<T, R>(
returnValueForMissingStub: _FakeJoinedSelectStatement_31<T, R>(
this,
Invocation.method(
#selectOnly,
@@ -1178,14 +1201,14 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
#selectExpressions,
[columns],
),
returnValue: _FakeBaseSelectStatement_31<_i2.TypedResult>(
returnValue: _FakeBaseSelectStatement_32<_i2.TypedResult>(
this,
Invocation.method(
#selectExpressions,
[columns],
),
),
returnValueForMissingStub: _FakeBaseSelectStatement_31<_i2.TypedResult>(
returnValueForMissingStub: _FakeBaseSelectStatement_32<_i2.TypedResult>(
this,
Invocation.method(
#selectExpressions,
@@ -1202,14 +1225,14 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
#delete,
[table],
),
returnValue: _FakeDeleteStatement_32<T, D>(
returnValue: _FakeDeleteStatement_33<T, D>(
this,
Invocation.method(
#delete,
[table],
),
),
returnValueForMissingStub: _FakeDeleteStatement_32<T, D>(
returnValueForMissingStub: _FakeDeleteStatement_33<T, D>(
this,
Invocation.method(
#delete,
@@ -1295,7 +1318,7 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
#readsFrom: readsFrom,
},
),
returnValue: _FakeSelectable_24<_i2.QueryRow>(
returnValue: _FakeSelectable_25<_i2.QueryRow>(
this,
Invocation.method(
#customSelect,
@@ -1306,7 +1329,7 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
},
),
),
returnValueForMissingStub: _FakeSelectable_24<_i2.QueryRow>(
returnValueForMissingStub: _FakeSelectable_25<_i2.QueryRow>(
this,
Invocation.method(
#customSelect,
@@ -1334,7 +1357,7 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
#readsFrom: readsFrom,
},
),
returnValue: _FakeSelectable_24<_i2.QueryRow>(
returnValue: _FakeSelectable_25<_i2.QueryRow>(
this,
Invocation.method(
#customSelectQuery,
@@ -1345,7 +1368,7 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
},
),
),
returnValueForMissingStub: _FakeSelectable_24<_i2.QueryRow>(
returnValueForMissingStub: _FakeSelectable_25<_i2.QueryRow>(
this,
Invocation.method(
#customSelectQuery,
@@ -1397,7 +1420,7 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
),
(T v) => _i5.Future<T>.value(v),
) ??
_FakeFuture_26<T>(
_FakeFuture_27<T>(
this,
Invocation.method(
#transaction,
@@ -1416,7 +1439,7 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
),
(T v) => _i5.Future<T>.value(v),
) ??
_FakeFuture_26<T>(
_FakeFuture_27<T>(
this,
Invocation.method(
#transaction,
@@ -1443,7 +1466,7 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
),
(T v) => _i5.Future<T>.value(v),
) ??
_FakeFuture_26<T>(
_FakeFuture_27<T>(
this,
Invocation.method(
#exclusively,
@@ -1460,7 +1483,7 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
),
(T v) => _i5.Future<T>.value(v),
) ??
_FakeFuture_26<T>(
_FakeFuture_27<T>(
this,
Invocation.method(
#exclusively,
@@ -1502,7 +1525,7 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
),
(T v) => _i5.Future<T>.value(v),
) ??
_FakeFuture_26<T>(
_FakeFuture_27<T>(
this,
Invocation.method(
#runWithInterceptor,
@@ -1521,7 +1544,7 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
),
(T v) => _i5.Future<T>.value(v),
) ??
_FakeFuture_26<T>(
_FakeFuture_27<T>(
this,
Invocation.method(
#runWithInterceptor,
@@ -1546,7 +1569,7 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
#startIndex: startIndex,
},
),
returnValue: _FakeGenerationContext_33(
returnValue: _FakeGenerationContext_34(
this,
Invocation.method(
#$write,
@@ -1557,7 +1580,7 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
},
),
),
returnValueForMissingStub: _FakeGenerationContext_33(
returnValueForMissingStub: _FakeGenerationContext_34(
this,
Invocation.method(
#$write,
@@ -1585,7 +1608,7 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
],
{#startIndex: startIndex},
),
returnValue: _FakeGenerationContext_33(
returnValue: _FakeGenerationContext_34(
this,
Invocation.method(
#$writeInsertable,
@@ -1596,7 +1619,7 @@ class MockTodoDb extends _i1.Mock implements _i3.TodoDb {
{#startIndex: startIndex},
),
),
returnValueForMissingStub: _FakeGenerationContext_33(
returnValueForMissingStub: _FakeGenerationContext_34(
this,
Invocation.method(
#$writeInsertable,
@@ -30,16 +30,39 @@ class DartIndexResolver extends LocalElementResolver<DiscoveredDartIndex> {
final unique = computed?.getField('unique')?.toBoolValue() ?? false;
final columns = <DriftColumn>[];
final columns = <DriftIndexedColumn>[];
final referencedColumns = computed?.getField('columns')?.toSetValue();
for (final column in referencedColumns ?? const <DartObject>{}) {
final columnName = column.toSymbolValue();
// Column can either be a Symbol or an IndexedColumn instance.
String? columnName;
OrderingMode? orderBy;
if (column.toSymbolValue() case final symbol?) {
columnName = symbol;
} else {
columnName = column.getField('columnName')?.toSymbolValue();
if (column.getField('orderBy')?.getField('_name')?.toStringValue()
case final orderByName?) {
orderBy = switch (orderByName) {
'asc' => OrderingMode.ascending,
'desc' => OrderingMode.descending,
_ => null,
};
}
}
if (columnName == null) {
reportError(DriftAnalysisError.forDartElement(
discovered.dartElement,
'Unknown entry in columns array, should be a symbols or an IndexedColumn instance.',
));
}
final tableColumn =
table?.columns.firstWhereOrNull((c) => c.nameInDart == columnName);
if (tableColumn != null) {
columns.add(tableColumn);
columns.add(DriftIndexedColumn(column: tableColumn, orderBy: orderBy));
} else {
reportError(DriftAnalysisError.forDartElement(
discovered.dartElement,
+13 -3
View File
@@ -17,7 +17,7 @@ class DriftIndex extends DriftSchemaElement {
/// This list is empty for indices created in SQL because it can't represent
/// all expressions being indexed. It is useful for Dart-defined indices to
/// implement [createStatementForDartDefinition].
List<DriftColumn> indexedColumns;
List<DriftIndexedColumn> indexedColumns;
/// Whethet the index has been declared to be unique.
final bool unique;
@@ -69,10 +69,20 @@ class DriftIndex extends DriftSchemaElement {
unique: unique,
columns: [
for (final column in indexedColumns)
IndexedColumn(Reference(columnName: column.nameInSql))
IndexedColumn(
Reference(columnName: column.column.nameInSql),
column.orderBy,
)
],
);
}
}
sealed class DriftIndexDefintion {}
final class DriftIndexedColumn {
final DriftColumn column;
final OrderingMode? orderBy;
DriftIndexedColumn({required this.column, required this.orderBy});
}
sealed class DriftIndexDefinition {}
+16 -4
View File
@@ -2,7 +2,7 @@ import 'package:analyzer/dart/element/element2.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:collection/collection.dart';
import 'package:drift/drift.dart' show DriftSqlType, UpdateKind;
import 'package:sqlparser/sqlparser.dart' show ReferenceAction;
import 'package:sqlparser/sqlparser.dart' show OrderingMode, ReferenceAction;
import 'driver/driver.dart';
import 'driver/state.dart';
@@ -75,7 +75,11 @@ class ElementSerializer {
'type': 'index',
'sql': element.createStmt,
'columns': [
for (final column in element.indexedColumns) column.nameInSql,
for (final column in element.indexedColumns)
{
'column': column.column.nameInSql,
'order_by': column.orderBy?.name,
},
],
'unique': element.unique,
};
@@ -594,8 +598,16 @@ class ElementDeserializer {
table: onTable,
createStmt: json['sql'] as String?,
indexedColumns: [
for (final entry in json['columns'] as List)
onTable!.columnBySqlName[entry as String]!,
for (final entry
in (json['columns'] as List).cast<Map<String, Object?>>())
DriftIndexedColumn(
column: onTable!.columnBySqlName[entry['column'] as String]!,
orderBy: switch (entry['order_by']) {
null => null,
final orderBy =>
OrderingMode.values.byName(orderBy as String),
},
),
],
unique: json['unique'] as bool,
);
@@ -162,7 +162,11 @@ class SchemaWriter {
'sql': entity.createStmt,
'unique': entity.unique,
'columns': [
for (final column in entity.indexedColumns) column.nameInSql,
for (final column in entity.indexedColumns)
{
'column': column.column.nameInSql,
'order_by': column.orderBy?.name,
},
],
};
} else if (entity is DriftView) {
@@ -446,13 +450,33 @@ class SchemaReader {
final sql = content['sql'] as String?;
if (_version.supportsDartIndex) {
DriftIndexedColumn readColumn(Object serialized) {
if (serialized case final String name) {
// Older versions used to write index columns by name.
return DriftIndexedColumn(
column: on.columnBySqlName[name]!,
orderBy: null,
);
} else {
// Newer schemas encode {name, order_by}.
serialized as Map<String, Object?>;
return DriftIndexedColumn(
column: on.columnBySqlName[serialized['column'] as String]!,
orderBy: switch (serialized['order_by']) {
null => null,
final ordering => OrderingMode.values.byName(ordering as String),
},
);
}
}
final index = DriftIndex(
_id(name),
_declaration,
table: on,
indexedColumns: [
for (final col in content['columns'] as List)
on.columnBySqlName[col]!,
readColumn(col as Object),
],
unique: content['unique'] as bool,
createStmt: sql,
@@ -478,7 +502,11 @@ class SchemaReader {
unique: stmt.unique,
indexedColumns: [
for (final column in stmt.columns)
on.columnBySqlName[(column.expression as Reference).columnName]!,
DriftIndexedColumn(
column: on.columnBySqlName[
(column.expression as Reference).columnName]!,
orderBy: column.ordering,
)
],
)..parsedStatement = stmt;
}
@@ -1,5 +1,6 @@
import 'package:build_test/build_test.dart';
import 'package:drift_dev/src/analysis/results/results.dart';
import 'package:sqlparser/sqlparser.dart';
import 'package:test/test.dart';
import '../../../utils.dart';
@@ -32,13 +33,13 @@ class MyTable extends Table {
expect(indexA.table, table);
expect(indexA.unique, false);
expect(indexA.indexedColumns, [table.columnBySqlName['a']]);
expect(indexA.indexedColumns, [indexedColumn('a')]);
expect(indexBC.table, table);
expect(indexBC.unique, true);
expect(indexBC.indexedColumns, [
table.columnBySqlName['b'],
table.columnBySqlName['c'],
indexedColumn('b'),
indexedColumn('c'),
]);
});
@@ -61,6 +62,28 @@ class MyTable extends Table {
]);
});
test('reads ordering mode', () async {
final backend = await TestBackend.inTest({
'a|lib/a.dart': '''
import 'package:drift/drift.dart';
@TableIndex(columns: {IndexedColumn(#a, orderBy: OrderingMode.desc)}, name: 'tbl_a')
class MyTable extends Table {
IntColumn get a => integer()();
TextColumn get b => text()();
TextColumn get c => text()();
}
''',
});
final file = await backend.analyze('package:a/a.dart');
backend.expectNoErrors();
final indexA = file.analysis[file.id('tbl_a')]!.result as DriftIndex;
expect(indexA.indexedColumns,
[indexedColumn('a', ordering: OrderingMode.descending)]);
});
group('SQL', () {
test('can create index', () async {
final results = await emulateDriftBuild(
@@ -124,3 +147,10 @@ class MyTable extends Table {
});
});
}
TypeMatcher<DriftIndexedColumn> indexedColumn(String name,
{OrderingMode? ordering}) {
return isA<DriftIndexedColumn>()
.having((e) => e.column.nameInSql, 'nameInSql', name)
.having((e) => e.orderBy, 'orderBy', ordering);
}
@@ -222,6 +222,70 @@ class Database {}
expect(generated, isNot(contains('GeneratedAs')));
});
test('can export Dart-defined index', () async {
final backend = await TestBackend.inTest({
'a|lib/main.dart': '''
import 'package:drift/drift.dart';
@TableIndex(name: 'tbl_name', columns: {IndexedColumn(#name, orderBy: OrderingMode.desc)})
class MyTable extends Table {
IntColumn get id => integer()();
TextColumn get name => text()();
}
@DriftDatabase(tables: [MyTable])
class Database {}
''',
});
final file = await backend.analyze('package:a/main.dart');
backend.expectNoErrors();
final db = file.fileAnalysis!.resolvedDatabases.values.single;
final schemaJson =
await SchemaWriter(db.availableElements).createSchemaJson();
final serializedIndex = (schemaJson['entities'] as List)[1];
expect(serializedIndex['data'], {
'on': 0,
'name': 'tbl_name',
'sql': null,
'unique': false,
'columns': [
{'column': 'name', 'order_by': 'descending'}
]
});
final newFormat = schemaJson;
// Older versions of drift_dev used to only include the column name in the
// schema and did not support specifying an ordering mode.
final oldFormat = json.decode(json.encode(newFormat));
oldFormat['entities'][1]['data']['columns'] = ['name'];
for (final format in [oldFormat, newFormat]) {
final reader = SchemaReader.readJson(format as Map<String, Object?>);
final fakeBuildConfig = runInBuildConfigZone(() {
return BuildConfig(buildTargets: {});
}, 'drift_dev', []);
final generated = await GenerateUtils.generateSchemaCode(
DriftDevCli()
..project = DriftProject(fakeBuildConfig, Directory(sandbox)),
1,
ExportedSchema(reader.entities.toList(), {}),
false,
false,
);
if (format == newFormat) {
expect(generated,
contains('CREATE INDEX tbl_name ON my_table (name DESC)'));
} else {
expect(generated, contains('CREATE INDEX tbl_name ON my_table (name)'));
}
}
});
group('generates correct datetime mode', () {
Future<void> runTest(bool storeAsText, String expectedDefault) async {
final options =