diff --git a/drift/CHANGELOG.md b/drift/CHANGELOG.md index 7e21c232..342fb759 100644 --- a/drift/CHANGELOG.md +++ b/drift/CHANGELOG.md @@ -1,3 +1,11 @@ +## 1.4.0 + +- Most methods to compose statements are now available as an extension on + tables. As an alternative to `update(todos).replace(newEntry)`, you can + now write `todos.replaceOne(newEntry)`. +- Deprecate the `from(table)` API introduced in 1.3.0. Having the methods on + the table instances turned out to be even easier! + ## 1.3.0 - Add the `from(table)` method to generated databases. It can be used to write diff --git a/drift/example/main.g.dart b/drift/example/main.g.dart index c79a2b1a..193359d1 100644 --- a/drift/example/main.g.dart +++ b/drift/example/main.g.dart @@ -535,10 +535,11 @@ class $TodoCategoryItemCountView extends ViewInfo<$TodoCategoryItemCountView, TodoCategoryItemCountData> implements HasResultSet { final String? _alias; - $TodoCategoryItemCountView(DatabaseConnectionUser db, [this._alias]) - : super(db); - $TodoItemsTable get todoItems => _db.todoItems; - $TodoCategoriesTable get todoCategories => _db.todoCategories; + @override + final _$Database attachedDatabase; + $TodoCategoryItemCountView(this.attachedDatabase, [this._alias]); + $TodoItemsTable get todoItems => attachedDatabase.todoItems; + $TodoCategoriesTable get todoCategories => attachedDatabase.todoCategories; @override List get $columns => [todoCategories.name, itemCount]; @override @@ -643,10 +644,11 @@ class $TodoItemWithCategoryNameViewView extends ViewInfo< $TodoItemWithCategoryNameViewView, TodoItemWithCategoryNameViewData> implements HasResultSet { final String? _alias; - $TodoItemWithCategoryNameViewView(DatabaseConnectionUser db, [this._alias]) - : super(db); - $TodoItemsTable get todoItems => _db.todoItems; - $TodoCategoriesTable get todoCategories => _db.todoCategories; + @override + final _$Database attachedDatabase; + $TodoItemWithCategoryNameViewView(this.attachedDatabase, [this._alias]); + $TodoItemsTable get todoItems => attachedDatabase.todoItems; + $TodoCategoriesTable get todoCategories => attachedDatabase.todoCategories; @override List get $columns => [todoItems.id, title]; @override diff --git a/drift/lib/src/runtime/api/connection_user.dart b/drift/lib/src/runtime/api/connection_user.dart index e40272fc..71037607 100644 --- a/drift/lib/src/runtime/api/connection_user.dart +++ b/drift/lib/src/runtime/api/connection_user.dart @@ -159,7 +159,11 @@ abstract class DatabaseConnectionUser { /// The [TableOrViewOperations] class (or the [TableOperations] extension /// for tables) provides convenience methods that make common operations /// easier to write than using the methods from this class directly. - @experimental + /// + /// This method is deprecated. For an even easier access, the methods that + /// were made available here are now available on the [table] or view instance + /// directly. + @Deprecated('Experiment ended - use the methods on the [table] direclty') TableOrViewOperations from( ResultSetImplementation table) { return TableOrViewOperations._(this, table); diff --git a/drift/lib/src/runtime/query_builder/on_table.dart b/drift/lib/src/runtime/query_builder/on_table.dart new file mode 100644 index 00000000..1d2ceaf5 --- /dev/null +++ b/drift/lib/src/runtime/query_builder/on_table.dart @@ -0,0 +1,102 @@ +import 'package:drift/drift.dart'; + +/// Easily-accessible methods to compose common operations or statements on +/// tables or views. +extension TableOrViewStatements + on ResultSetImplementation { + /// Composes a `SELECT` statement on the captured table or view. + /// + /// This is equivalent to calling [DatabaseConnectionUser.select]. + SimpleSelectStatement select({bool distinct = false}) { + return attachedDatabase.select(this, distinct: distinct); + } + + /// Composes a `SELECT` statement only selecting a subset of columns. + /// + /// This is equivalent to calling [DatabaseConnectionUser.selectOnly]. + JoinedSelectStatement selectOnly( + {bool distinct = false, bool includeJoinedTableColumns = true}) { + return attachedDatabase.selectOnly(this, + distinct: distinct, + includeJoinedTableColumns: includeJoinedTableColumns); + } +} + +/// Easily-accessible methods to compose common operations or statements on +/// tables. +extension TableStatements on TableInfo { + /// Creates an insert statment to be used to compose an insert on the table. + /// + /// This is equivalent to calling [DatabaseConnectionUser.into] on the + /// captured table. See that method for more information. + InsertStatement insert() => attachedDatabase.into(this); + + /// Inserts one row into this table. + /// + /// This is equivalent to calling [InsertStatement.insert] - see that method + /// for more information. + Future insertOne( + Insertable row, { + InsertMode? mode, + UpsertClause? onConflict, + }) { + return insert().insert(row, mode: mode, onConflict: onConflict); + } + + /// Inserts one row into this table table, replacing an existing row if it + /// exists already. + /// + /// Please note that this method is only available on recent sqlite3 versions. + /// See also [InsertStatement.insertOnConflictUpdate]. + Future insertOnConflictUpdate(Insertable row) { + return insert().insertOnConflictUpdate(row); + } + + /// Inserts one row into this table and returns it, along with auto- + /// generated fields. + /// + /// Please note that this method is only available on recent sqlite3 versions. + /// See also [InsertStatement.insertReturning]. + Future insertReturning( + Insertable row, { + InsertMode? mode, + UpsertClause? onConflict, + }) { + return insert().insertReturning( + row, + mode: mode, + onConflict: onConflict, + ); + } + + /// Creates a statement to compose an `UPDATE` into the database. + /// + /// This is equivalent to calling [DatabaseConnectionUser.update] with the + /// captured table. + UpdateStatement update() => attachedDatabase.update(this); + + /// Replaces a single row with an update statement. + /// + /// See also [UpdateStatement.replace]. + Future replaceOne(Insertable row) { + return update().replace(row); + } + + /// Creates a statement to compose a `DELETE` from the database. + /// + /// This is equivalent to calling [DatabaseConnectionUser.delete] with the + /// captured table. + DeleteStatement delete() => attachedDatabase.delete(this); + + /// Deletes the [row] from the captured table. + Future deleteOne(Insertable row) async { + return await (delete()..whereSamePrimaryKey(row)).go() != 0; + } + + /// Deletes all rows matching the [filter] from the table. + /// + /// See also [SingleTableQueryMixin.where]. + Future deleteWhere(Expression Function(Tbl tbl) filter) { + return (delete()..where(filter)).go(); + } +} diff --git a/drift/lib/src/runtime/query_builder/query_builder.dart b/drift/lib/src/runtime/query_builder/query_builder.dart index 1acaf0a6..4f2e5fee 100644 --- a/drift/lib/src/runtime/query_builder/query_builder.dart +++ b/drift/lib/src/runtime/query_builder/query_builder.dart @@ -12,6 +12,8 @@ import 'package:meta/meta.dart'; // split up. import 'expressions/case_when.dart'; +export 'on_table.dart'; + part 'components/group_by.dart'; part 'components/join.dart'; part 'components/limit.dart'; diff --git a/drift/lib/src/runtime/query_builder/schema/view_info.dart b/drift/lib/src/runtime/query_builder/schema/view_info.dart index 947d82e1..e44a6954 100644 --- a/drift/lib/src/runtime/query_builder/schema/view_info.dart +++ b/drift/lib/src/runtime/query_builder/schema/view_info.dart @@ -11,13 +11,6 @@ part of '../query_builder.dart'; /// [sql-tut]: https://www.sqlitetutorial.net/sqlite-create-view/ abstract class ViewInfo implements ResultSetImplementation { - @override - final DatabaseConnectionUser attachedDatabase; - - /// Constructor for a view implementation, used by drift-generated code. - @internal - ViewInfo(this.attachedDatabase); - @override String get entityName; diff --git a/drift/test/data/tables/custom_tables.g.dart b/drift/test/data/tables/custom_tables.g.dart index b3140c60..7c042110 100644 --- a/drift/test/data/tables/custom_tables.g.dart +++ b/drift/test/data/tables/custom_tables.g.dart @@ -1547,7 +1547,9 @@ class MyViewData extends DataClass { class MyView extends ViewInfo implements HasResultSet { final String? _alias; - MyView(DatabaseConnectionUser db, [this._alias]) : super(db); + @override + final _$CustomTablesDb attachedDatabase; + MyView(this.attachedDatabase, [this._alias]); @override List get $columns => [configKey, configValue, syncState, syncStateImplicit]; diff --git a/drift/test/data/tables/todos.g.dart b/drift/test/data/tables/todos.g.dart index 52c07e49..c7cb9b82 100644 --- a/drift/test/data/tables/todos.g.dart +++ b/drift/test/data/tables/todos.g.dart @@ -1417,10 +1417,11 @@ class $CategoryTodoCountViewView extends ViewInfo<$CategoryTodoCountViewView, CategoryTodoCountViewData> implements HasResultSet { final String? _alias; - $CategoryTodoCountViewView(DatabaseConnectionUser db, [this._alias]) - : super(db); - $TodosTableTable get todos => _db.todosTable; - $CategoriesTable get categories => _db.categories; + @override + final _$TodoDb attachedDatabase; + $CategoryTodoCountViewView(this.attachedDatabase, [this._alias]); + $TodosTableTable get todos => attachedDatabase.todosTable; + $CategoriesTable get categories => attachedDatabase.categories; @override List get $columns => [categories.description, itemCount]; @override @@ -1523,10 +1524,11 @@ class $TodoWithCategoryViewView extends ViewInfo<$TodoWithCategoryViewView, TodoWithCategoryViewData> implements HasResultSet { final String? _alias; - $TodoWithCategoryViewView(DatabaseConnectionUser db, [this._alias]) - : super(db); - $TodosTableTable get todos => _db.todosTable; - $CategoriesTable get categories => _db.categories; + @override + final _$TodoDb attachedDatabase; + $TodoWithCategoryViewView(this.attachedDatabase, [this._alias]); + $TodosTableTable get todos => attachedDatabase.todosTable; + $CategoriesTable get categories => attachedDatabase.categories; @override List get $columns => [todos.title, categories.description]; @override diff --git a/drift/test/delete_test.dart b/drift/test/delete_test.dart index 2508d155..0a9e46c9 100644 --- a/drift/test/delete_test.dart +++ b/drift/test/delete_test.dart @@ -71,23 +71,21 @@ void main() { }); }); - group('delete with from()', () { + group('delete on table instances', () { test('delete()', () async { - await db.from(db.users).delete().go(); + await db.users.delete().go(); verify(executor.runDelete('DELETE FROM users;', [])); }); test('deleteOne()', () async { - await db.from(db.users).deleteOne(const UsersCompanion(id: Value(3))); + await db.users.deleteOne(const UsersCompanion(id: Value(3))); verify(executor.runDelete('DELETE FROM users WHERE id = ?;', [3])); }); test('deleteWhere', () async { - await db - .from(db.users) - .deleteWhere((tbl) => tbl.id.isSmallerThanValue(3)); + await db.users.deleteWhere((tbl) => tbl.id.isSmallerThanValue(3)); verify(executor.runDelete('DELETE FROM users WHERE id < ?;', [3])); }); diff --git a/drift/test/insert_test.dart b/drift/test/insert_test.dart index 019c23af..06c07fdb 100644 --- a/drift/test/insert_test.dart +++ b/drift/test/insert_test.dart @@ -454,4 +454,55 @@ void main() { )); }); }); + + group('on table instances', () { + test('insert', () async { + await db.categories + .insert() + .insert(CategoriesCompanion.insert(description: 'description')); + + verify(executor.runInsert( + 'INSERT INTO categories ("desc") VALUES (?)', ['description'])); + }); + + test('insertOne', () async { + await db.categories.insertOne( + CategoriesCompanion.insert(description: 'description'), + mode: InsertMode.insertOrReplace); + + verify(executor.runInsert( + 'INSERT OR REPLACE INTO categories ("desc") VALUES (?)', + ['description'])); + }); + + test('insertOnConflictUpdate', () async { + when(executor.runSelect(any, any)).thenAnswer( + (_) => Future.value([ + { + 'id': 1, + 'desc': 'description', + 'description_in_upper_case': 'DESCRIPTION', + 'priority': 1, + }, + ]), + ); + + final row = await db.categories.insertReturning( + CategoriesCompanion.insert(description: 'description')); + expect( + row, + Category( + id: 1, + description: 'description', + descriptionInUpperCase: 'DESCRIPTION', + priority: CategoryPriority.medium, + ), + ); + + verify(executor.runSelect( + 'INSERT INTO categories ("desc") VALUES (?) RETURNING *', + ['description'], + )); + }); + }); } diff --git a/drift/test/update_test.dart b/drift/test/update_test.dart index 7d85b0ff..d34bd2d2 100644 --- a/drift/test/update_test.dart +++ b/drift/test/update_test.dart @@ -180,4 +180,21 @@ void main() { ['new name', 3])); }); }); + + group('update on table instances', () { + test('update()', () async { + await db.users.update().write(const UsersCompanion(id: Value(3))); + + verify(executor.runUpdate('UPDATE users SET id = ?;', [3])); + }); + + test('replace', () async { + await db.categories.replace(const CategoriesCompanion( + id: Value(3), description: Value('new name'))); + + verify(executor.runUpdate( + 'UPDATE categories SET "desc" = ?, priority = 0 WHERE id = ?;', + ['new name', 3])); + }); + }); } diff --git a/drift_dev/lib/src/analyzer/dart/view_parser.dart b/drift_dev/lib/src/analyzer/dart/view_parser.dart index f46744d2..7fe40021 100644 --- a/drift_dev/lib/src/analyzer/dart/view_parser.dart +++ b/drift_dev/lib/src/analyzer/dart/view_parser.dart @@ -187,9 +187,7 @@ class ViewParser { (tbl) => tbl.fromClass!.name == node.returnType.toString()); if (type != null) { final name = node.name.toString(); - final declaration = '${type.entityInfoName} get $name => ' - '_db.${type.dbGetterName};'; - return TableReferenceInDartView(type, name, declaration); + return TableReferenceInDartView(type, name); } } } catch (_) {} diff --git a/drift_dev/lib/src/model/declarations/views.dart b/drift_dev/lib/src/model/declarations/views.dart index 3607bf3c..4c2f0958 100644 --- a/drift_dev/lib/src/model/declarations/views.dart +++ b/drift_dev/lib/src/model/declarations/views.dart @@ -34,9 +34,8 @@ class DartViewDeclaration implements ViewDeclaration, DartDeclaration { class TableReferenceInDartView { final MoorTable table; final String name; - final String declaration; - TableReferenceInDartView(this.table, this.name, this.declaration); + TableReferenceInDartView(this.table, this.name); } class MoorViewDeclaration diff --git a/drift_dev/lib/src/writer/tables/view_writer.dart b/drift_dev/lib/src/writer/tables/view_writer.dart index 9299584c..71f33196 100644 --- a/drift_dev/lib/src/writer/tables/view_writer.dart +++ b/drift_dev/lib/src/writer/tables/view_writer.dart @@ -38,19 +38,21 @@ class ViewWriter extends TableOrViewWriter { } else { buffer.write('<${view.entityInfoName}, Never>'); } - buffer.write(' implements HasResultSet'); + buffer.writeln(' implements HasResultSet {'); buffer - ..write('{\n') - // write the generated database reference that is set in the constructor - ..write('final ${scope.nullableType('String')} _alias;\n') - ..write('${view.entityInfoName}(DatabaseConnectionUser db, ' - '[this._alias]): super(db);\n'); + ..writeln('final ${scope.nullableType('String')} _alias;') + ..writeln( + '@override final ${databaseWriter.dbClassName} attachedDatabase;') + ..writeln('${view.entityInfoName}(this.attachedDatabase, ' + '[this._alias]);'); final declaration = view.declaration; if (declaration is DartViewDeclaration) { for (final ref in declaration.staticReferences) { - buffer.write('${ref.declaration}\n'); + final declaration = '${ref.table.entityInfoName} get ${ref.name} => ' + 'attachedDatabase.${ref.table.dbGetterName};'; + buffer.writeln(declaration); } }