Skip to content

Commit a2c4c6b

Browse files
committed
drift3: Simplify table extensions
1 parent d8fda9d commit a2c4c6b

11 files changed

Lines changed: 347 additions & 95 deletions

File tree

future/drift3/lib/src/database/batch.dart

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,7 @@ final class Batch {
8585
Insertable<Row> row, {
8686
UpsertClause<Row, RS>? onConflict,
8787
}) {
88-
final stmt = InsertStatement<Row, RS, DatabaseConnectionUser>(
89-
_database,
90-
table,
91-
)..values(row);
88+
final stmt = InsertStatement<Row, RS>(_database, table)..values(row);
9289
if (onConflict != null) {
9390
stmt.onConflict(onConflict);
9491
}
@@ -116,10 +113,8 @@ final class Batch {
116113
required Map<TableColumn, Expression> columns,
117114
UpsertClause<Row, RS>? onConflict,
118115
}) {
119-
final stmt = InsertStatement<Row, RS, DatabaseConnectionUser>(
120-
_database,
121-
table,
122-
)..fromSelect(select, columns: columns);
116+
final stmt = InsertStatement<Row, RS>(_database, table)
117+
..fromSelect(select, columns: columns);
123118
if (onConflict != null) {
124119
stmt.onConflict(onConflict);
125120
}

future/drift3/lib/src/database/connection_user.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -457,11 +457,11 @@ abstract base class DatabaseConnectionUser {
457457

458458
/// Starts an [InsertStatement] for a given table. You can use that statement
459459
/// to write data into the [table] by using [InsertStatement.insert].
460-
InsertStatement<Row, RS, DatabaseConnectionUser> into<
460+
InsertStatement<Row, RS> into<
461461
Row extends Object,
462462
RS extends GeneratedTable<Row, RS>
463463
>(GeneratedTable<Row, RS> table) {
464-
return InsertStatement<Row, RS, DatabaseConnectionUser>(this, table);
464+
return InsertStatement<Row, RS>(this, table);
465465
}
466466

467467
/// Starts an [UpdateStatement] for the given table. You can use that

future/drift3/lib/src/query_builder/compiler.dart

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,6 @@ abstract base class StatementCompiler {
504504
}
505505

506506
if (delete.returning case final returning?) {
507-
statement.resultSetStructure = returning.structure;
508507
statement.space();
509508
returning.compileWith(this);
510509
}
@@ -539,7 +538,6 @@ abstract base class StatementCompiler {
539538
}
540539

541540
if (update.returning case final returning?) {
542-
statement.resultSetStructure = returning.structure;
543541
statement.space();
544542
returning.compileWith(this);
545543
}
@@ -628,6 +626,7 @@ abstract base class StatementCompiler {
628626
void addReturningClause(ReturningClause returning) {
629627
// We currently only support the `RETURNING *` format without arbitrary
630628
// columns.
629+
statement.resultSetStructure = returning.structure;
631630
statement.buffer.write('RETURNING ');
632631
addResultSetExpressions(returning.structure);
633632
}
@@ -765,9 +764,16 @@ abstract base class StatementCompiler {
765764
);
766765
}
767766

767+
// The subquery expression can reference outer columns as well, so we need
768+
// to make table aliases explicit.
769+
final hasMultipleTables = statement.hasMultipleTables;
770+
statement.hasMultipleTables = true;
771+
768772
statement.buffer.write('(');
769773
e.statement.compileWith(this);
770774
statement.buffer.write(')');
775+
776+
statement.hasMultipleTables = hasMultipleTables;
771777
}
772778

773779
void addSubquery(Subquery e) {
Lines changed: 101 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,84 @@
11
import '../connection/connection.dart';
22
import '../database/connection_user.dart';
33
import '../database/data_class.dart';
4+
import '../database/selectable.dart';
5+
import 'expressions/aggregate.dart';
6+
import 'expressions/expression.dart';
7+
import 'schema/result_set.dart';
48
import 'schema/table.dart';
59
import 'statements/insert.dart';
10+
import 'statements/select.dart';
11+
12+
/// Easily-accessible methods to compose common operations or statements on
13+
/// tables or views.
14+
extension type TableOrViewStatements<
15+
Row extends Object,
16+
RS extends ResultSet<Row, RS>
17+
>._((RS, DatabaseConnectionUser) _resultSetWithDatabase) {
18+
/// Selects all rows that are in this table.
19+
///
20+
/// The returned [Selectable] can be run once as a future with [Selectable.get]
21+
/// or as an auto-updating stream with [Selectable.watch].
22+
Selectable<Row> all() {
23+
return select();
24+
}
25+
26+
/// Counts the rows in this table.
27+
///
28+
/// The optional [where] clause can be used to only count rows matching the
29+
/// condition, similar to [SimpleSelectStatement.where].
30+
///
31+
/// The returned [Selectable] can be run once with [Selectable.getSingle] to
32+
/// get the count once, or be watched as a stream with [Selectable.watchSingle].
33+
Selectable<int> count({Expression<bool> Function(RS row)? where}) {
34+
final count = countAll();
35+
final stmt = selectOnly()..addColumns([count]);
36+
if (where != null) {
37+
stmt.where(where(_resultSetWithDatabase.$1));
38+
}
39+
40+
return stmt.map((row) => row.read(count)!);
41+
}
42+
43+
/// Composes a `SELECT` statement on the captured table or view.
44+
///
45+
/// This is equivalent to calling [DatabaseConnectionUser.select].
46+
SingleTableSelectStatement<Row, RS> select({bool distinct = false}) {
47+
final (rs, db) = _resultSetWithDatabase;
48+
return db.select(rs, distinct: distinct);
49+
}
50+
51+
/// Composes a `SELECT` statement only selecting a subset of columns.
52+
///
53+
/// This is equivalent to calling [DatabaseConnectionUser.selectOnly].
54+
SelectStatement selectOnly({bool distinct = false}) {
55+
final (rs, db) = _resultSetWithDatabase;
56+
return db.selectOnly(rs, distinct: distinct);
57+
}
58+
}
659

760
/// Easily-accessible methods to compose common operations or statements on
861
/// tables.
9-
extension TableStatements<
62+
extension type TableStatements<
1063
Row extends Object,
1164
RS extends GeneratedTable<Row, RS>
12-
>
13-
on GeneratedTable<Row, RS> {
65+
>._(TableOrViewStatements<Row, RS> _super)
66+
implements TableOrViewStatements<Row, RS> {
1467
/// Creates an insert statment to be used to compose an insert on the table.
15-
///
16-
/// To later run the statement, first call
17-
/// [InsertStatementWithoutDatabase.onDatabase] followed by methods like
18-
/// [InsertStatementWithDatabase.insert].
19-
InsertStatement<Row, RS, Null> insert() => InsertStatement(null, this);
68+
InsertStatement<Row, RS> insert() {
69+
final (rs, db) = _resultSetWithDatabase;
70+
return InsertStatement(db, rs);
71+
}
2072

2173
/// Inserts one row into this table.
2274
///
23-
/// This is equivalent to calling [InsertStatementWithDatabase.insert] - see
24-
/// that method for more information.
75+
/// This is equivalent to calling [InsertStatement.insert], see that method
76+
/// for more information.
2577
Future<int> insertOne(
2678
Insertable<Row> row, {
27-
required DatabaseConnectionUser database,
2879
UpsertClause<Row, RS>? onConflict,
2980
}) {
30-
return insert().onDatabase(database).insert(row, onConflict: onConflict);
81+
return insert().insert(row, onConflict: onConflict);
3182
}
3283

3384
/// Atomically inserts all [rows] into the table.
@@ -37,14 +88,15 @@ extension TableStatements<
3788
/// the [rows] are in doesn't matter if there are foreign keys between them.
3889
Future<void> insertAll(
3990
Iterable<Insertable<Row>> rows, {
40-
required DatabaseConnectionUser database,
4191
UpsertClause<Row, RS>? onConflict,
4292
}) {
43-
return database.transaction(
93+
final (rs, db) = _resultSetWithDatabase;
94+
95+
return db.transaction(
4496
options: const TransactionOptions(deferForeignKeys: true),
4597
() async {
46-
await database.batch((b) {
47-
b.insertAll(this, rows, onConflict: onConflict);
98+
await db.batch((b) {
99+
b.insertAll(rs, rows, onConflict: onConflict);
48100
});
49101
},
50102
);
@@ -54,16 +106,13 @@ extension TableStatements<
54106
/// exists already.
55107
///
56108
/// Please note that this method is only available on recent sqlite3 versions.
57-
/// See also [InsertStatementWithDatabase.insertOnConflictUpdate].
109+
/// See also [InsertStatement.insertOnConflictUpdate].
58110
/// By default, only the primary key is used for detect uniqueness violations.
59111
/// If you have further uniqueness constraints, please use the general
60112
/// [insertOne] method with a [DoUpdate] including those columns in its
61113
/// [DoUpdate.target].
62-
Future<int> insertOnConflictUpdate(
63-
Insertable<Row> row, {
64-
required DatabaseConnectionUser database,
65-
}) {
66-
return insert().onDatabase(database).insertOnConflictUpdate(row);
114+
Future<int> insertOnConflictUpdate(Insertable<Row> row) {
115+
return insert().insertOnConflictUpdate(row);
67116
}
68117

69118
/// Inserts one row into this table and returns it, along with auto-generated
@@ -75,12 +124,9 @@ extension TableStatements<
75124
/// use [insertReturningOrNull] instead.
76125
Future<Row> insertReturning(
77126
Insertable<Row> row, {
78-
required DatabaseConnectionUser database,
79127
UpsertClause<Row, RS>? onConflict,
80128
}) {
81-
return insert()
82-
.onDatabase(database)
83-
.insertReturning(row, onConflict: onConflict);
129+
return insert().insertReturning(row, onConflict: onConflict);
84130
}
85131

86132
/// Inserts one row into this table and returns it, along with auto-generated
@@ -90,11 +136,36 @@ extension TableStatements<
90136
/// [onConflict] clause with a `where` clause was used), returns `null`.
91137
Future<Row?> insertReturningOrNull(
92138
Insertable<Row> row, {
93-
required DatabaseConnectionUser database,
94139
UpsertClause<Row, RS>? onConflict,
95140
}) {
96-
return insert()
97-
.onDatabase(database)
98-
.insertReturningOrNull(row, onConflict: onConflict);
141+
return insert().insertReturningOrNull(row, onConflict: onConflict);
142+
}
143+
}
144+
145+
/// Extension providing the [statements] method for tables and views.
146+
extension GetTableOrViewStatements<
147+
Row extends Object,
148+
RS extends ResultSet<Row, RS>
149+
>
150+
on ResultSet<Row, RS> {
151+
/// Creates a [TableOrViewStatements] instance that can be used to create
152+
/// common select statements on this table or view.
153+
TableOrViewStatements<Row, RS> statements(DatabaseConnectionUser db) {
154+
return TableOrViewStatements._((asSelfType(), db));
155+
}
156+
}
157+
158+
/// Extension providing the [statements] method for tables specifically.
159+
extension GetTabletatements<
160+
Row extends Object,
161+
RS extends GeneratedTable<Row, RS>
162+
>
163+
on GeneratedTable<Row, RS> {
164+
/// Returns a [TableStatements] instance that can be used to create common
165+
/// select, update, insert and delete statements.
166+
TableStatements<Row, RS> statements(DatabaseConnectionUser db) {
167+
return TableStatements._(
168+
GetTableOrViewStatements<Row, RS>(this).statements(db),
169+
);
99170
}
100171
}

future/drift3/lib/src/query_builder/results.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ final class DriftRow {
215215
/// For result sets that might be absent from some rows (e.g. those added as
216216
/// outer joins), consider using [readTableOrNull] instead.
217217
Row readTable<Row extends Object, RS extends ResultSet<Row, RS>>(
218-
RS resultSet,
218+
ResultSet<Row, RS> resultSet,
219219
) {
220220
final parsed = readTableOrNull<Row, RS>(resultSet);
221221
if (parsed == null) {
@@ -235,7 +235,7 @@ final class DriftRow {
235235
/// This does not allow reading result sets whose columns don't exist in this
236236
/// row at all (e.g. a result set not joined into a select statement).
237237
Row? readTableOrNull<Row extends Object, RS extends ResultSet<Row, RS>>(
238-
RS resultSet,
238+
ResultSet<Row, RS> resultSet,
239239
) {
240240
return this.resultSet._mapperFor(resultSet)(raw) as Row?;
241241
}

future/drift3/lib/src/query_builder/statements/insert.dart

Lines changed: 8 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,14 @@ import 'statement.dart';
1919
/// An `INSERT` statement in SQL.
2020
final class InsertStatement<
2121
Row extends Object,
22-
RS extends GeneratedTable<Row, RS>,
23-
DB extends DatabaseConnectionUser?
22+
RS extends GeneratedTable<Row, RS>
2423
>
2524
extends SqlStatement {
2625
/// The table we're inserting into.
2726
final GeneratedTable<Row, RS> table;
2827

2928
/// The database used to run this insert statement.
30-
final DB _database;
29+
final DatabaseConnectionUser _database;
3130

3231
/// The values to use for the rows being inserted.
3332
InsertSource? source;
@@ -54,7 +53,7 @@ final class InsertStatement<
5453
///
5554
/// Typically, [row] is an instance of the companion class drift generates for
5655
/// tables, which allows specifying which columns to use.
57-
InsertStatement<Row, RS, DB> values(Insertable<Row> row) {
56+
InsertStatement<Row, RS> values(Insertable<Row> row) {
5857
_checkNoSource();
5958

6059
final rawValues = row.toColumns(true);
@@ -101,7 +100,7 @@ final class InsertStatement<
101100
/// target column, and values are expressions added to the select statement.
102101
///
103102
/// For an example, see the [documentation website](https://drift.simonbinder.eu/docs/advanced-features/joins/#using-selects-as-insert)
104-
InsertStatement<Row, RS, DB> fromSelect(
103+
InsertStatement<Row, RS> fromSelect(
105104
BaseSelectStatement select, {
106105
required Map<TableColumn, Expression> columns,
107106
}) {
@@ -128,25 +127,12 @@ final class InsertStatement<
128127
}
129128

130129
/// Adds an [upsert] clause to this insert statement.
131-
InsertStatement<Row, RS, DB> onConflict(UpsertClause<Row, RS> upsert) {
130+
InsertStatement<Row, RS> onConflict(UpsertClause<Row, RS> upsert) {
132131
assert(upsertClause == null, 'upsert clause already set');
133132
upsertClause = upsert;
134133
return this;
135134
}
136135

137-
@override
138-
void compileWith(StatementCompiler compiler) {
139-
compiler.addInsertStatement(this);
140-
}
141-
}
142-
143-
///
144-
extension InsertStatementWithDatabase<
145-
Row extends Object,
146-
RS extends GeneratedTable<Row, RS>,
147-
DB extends DatabaseConnectionUser
148-
>
149-
on InsertStatement<Row, RS, DB> {
150136
Future<QueryResult> _run() async {
151137
final result = await _database.runStatement(this);
152138
if (result.affectedRows case final rows? when rows > 0) {
@@ -327,23 +313,10 @@ extension InsertStatementWithDatabase<
327313
Future<int> insertOnConflictUpdate(Insertable<Row> entity) {
328314
return insert(entity, onConflict: DoUpdate((_) => entity));
329315
}
330-
}
331316

332-
/// Exposes the [onDatabase] method to add a database to this insert statement.
333-
extension InsertStatementWithoutDatabase<
334-
Row extends Object,
335-
RS extends GeneratedTable<Row, RS>
336-
>
337-
on InsertStatement<Row, RS, Null> {
338-
/// Attaches a [DatabaseConnectionUser] to this [InsertStatement] so that it
339-
/// can be executed.
340-
InsertStatement<Row, RS, DatabaseConnectionUser> onDatabase(
341-
DatabaseConnectionUser db,
342-
) {
343-
return InsertStatement(db, table)
344-
..source = source
345-
..upsertClause = upsertClause
346-
..returning = returning;
317+
@override
318+
void compileWith(StatementCompiler compiler) {
319+
compiler.addInsertStatement(this);
347320
}
348321
}
349322

0 commit comments

Comments
 (0)