Skip to content

Commit 1645ed4

Browse files
committed
Remove connection mixins
1 parent b66827a commit 1645ed4

File tree

9 files changed

+312
-329
lines changed

9 files changed

+312
-329
lines changed

packages/drift_sqlite_async/lib/src/connection.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class SqliteAsyncDriftConnection extends DatabaseConnection {
2222
bool logStatements = false,
2323
Set<TableUpdate> Function(UpdateNotification)? transformTableUpdates,
2424
}) : super(SqliteAsyncQueryExecutor(db, logStatements: logStatements)) {
25-
_updateSubscription = (db as SqliteQueries).updates!.listen((event) {
25+
_updateSubscription = db.updates.listen((event) {
2626
final Set<TableUpdate> setUpdates;
2727
if (transformTableUpdates != null) {
2828
setUpdates = transformTableUpdates(event);

packages/sqlite_async/lib/sqlite_async.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,5 @@ export 'src/common/sqlite_database.dart' hide SqliteDatabaseImpl;
1010
export 'src/sqlite_connection.dart';
1111
export 'src/sqlite_migrations.dart';
1212
export 'src/sqlite_options.dart';
13-
export 'src/sqlite_queries.dart';
1413
export 'src/update_notification.dart';
1514
export 'src/utils.dart';

packages/sqlite_async/lib/src/common/connection/sync_sqlite_connection.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,14 @@ import 'package:sqlite3/common.dart';
44
import 'package:sqlite_async/src/common/mutex.dart';
55
import 'package:sqlite_async/src/sqlite_connection.dart';
66
import 'package:sqlite_async/src/sqlite_options.dart';
7-
import 'package:sqlite_async/src/sqlite_queries.dart';
87
import 'package:sqlite_async/src/update_notification.dart';
98
import 'package:sqlite_async/src/utils/profiler.dart';
109

1110
import '../../impl/context.dart';
1211

1312
/// A simple "synchronous" connection which provides the async SqliteConnection
1413
/// implementation using a synchronous SQLite connection
15-
class SyncSqliteConnection with SqliteQueries implements SqliteConnection {
14+
final class SyncSqliteConnection extends SqliteConnection {
1615
final CommonDatabase db;
1716
final Mutex mutex;
1817
@override

packages/sqlite_async/lib/src/common/sqlite_database.dart

Lines changed: 29 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,53 +4,15 @@ import 'package:meta/meta.dart';
44
import 'package:sqlite_async/src/common/abstract_open_factory.dart';
55
import 'package:sqlite_async/src/impl/single_connection_database.dart';
66
import 'package:sqlite_async/src/sqlite_options.dart';
7-
import 'package:sqlite_async/src/sqlite_queries.dart';
8-
import 'package:sqlite_async/src/update_notification.dart';
97
import 'package:sqlite_async/src/sqlite_connection.dart';
108

119
import '../impl/platform.dart' as platform;
1210

13-
mixin SqliteDatabaseMixin implements SqliteConnection, SqliteQueries {
14-
/// Maximum number of concurrent read transactions.
15-
int get maxReaders;
16-
17-
/// Factory that opens a raw database connection in each isolate.
18-
///
19-
/// This must be safe to pass to different isolates.
20-
///
21-
/// Use a custom class for this to customize the open process.
22-
SqliteOpenFactory get openFactory;
23-
24-
/// Use this stream to subscribe to notifications of updates to tables.
25-
@override
26-
Stream<UpdateNotification> get updates;
27-
28-
@protected
29-
Future<void> get isInitialized;
30-
31-
/// Wait for initialization to complete.
32-
///
33-
/// While initializing is automatic, this helps to catch and report initialization errors.
34-
Future<void> initialize() async {
35-
await isInitialized;
36-
}
37-
38-
/// Locks all underlying connections making up this database, and gives [block] access to all of them at once.
39-
/// This can be useful to run the same statement on all connections. For instance,
40-
/// ATTACHing a database, that is expected to be available in all connections.
41-
Future<T> withAllConnections<T>(
42-
Future<T> Function(
43-
SqliteWriteContext writer, List<SqliteReadContext> readers)
44-
block);
45-
}
46-
4711
/// A SQLite database instance.
4812
///
4913
/// Use one instance per database file. If multiple instances are used, update
5014
/// notifications may not trigger, and calls may fail with "SQLITE_BUSY" errors.
51-
abstract base class SqliteDatabase
52-
with SqliteQueries, SqliteDatabaseMixin
53-
implements SqliteConnection {
15+
abstract base class SqliteDatabase extends SqliteConnection {
5416
SqliteDatabase._();
5517

5618
/// Open a SqliteDatabase.
@@ -100,6 +62,34 @@ abstract base class SqliteDatabase
10062
factory SqliteDatabase.singleConnection(SqliteConnection connection) {
10163
return SingleConnectionDatabase(connection);
10264
}
65+
66+
/// Maximum number of concurrent read transactions.
67+
int get maxReaders;
68+
69+
/// Factory that opens a raw database connection in each isolate.
70+
///
71+
/// This must be safe to pass to different isolates.
72+
///
73+
/// Use a custom class for this to customize the open process.
74+
SqliteOpenFactory get openFactory;
75+
76+
@protected
77+
Future<void> get isInitialized;
78+
79+
/// Wait for initialization to complete.
80+
///
81+
/// While initializing is automatic, this helps to catch and report initialization errors.
82+
Future<void> initialize() async {
83+
await isInitialized;
84+
}
85+
86+
/// Locks all underlying connections making up this database, and gives [block] access to all of them at once.
87+
/// This can be useful to run the same statement on all connections. For instance,
88+
/// ATTACHing a database, that is expected to be available in all connections.
89+
Future<T> withAllConnections<T>(
90+
Future<T> Function(
91+
SqliteWriteContext writer, List<SqliteReadContext> readers)
92+
block);
10393
}
10494

10595
/// Internal superclass for all [SqliteDatabase] implementations.

packages/sqlite_async/lib/src/impl/single_connection_database.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,7 @@ final class SingleConnectionDatabase extends SqliteDatabaseImpl {
3939
}
4040

4141
@override
42-
Stream<UpdateNotification> get updates =>
43-
connection.updates ?? const Stream.empty();
42+
Stream<UpdateNotification> get updates => connection.updates;
4443

4544
@override
4645
Future<T> writeLock<T>(Future<T> Function(SqliteWriteContext tx) callback,

packages/sqlite_async/lib/src/sqlite_connection.dart

Lines changed: 150 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:sqlite_async/src/update_notification.dart';
99

1010
import 'common/connection/sync_sqlite_connection.dart';
1111
import 'common/mutex.dart';
12+
import 'utils/shared_utils.dart';
1213

1314
/// Abstract class representing calls available in a read-only or read-write context.
1415
abstract interface class SqliteReadContext {
@@ -98,11 +99,35 @@ abstract interface class SqliteWriteContext extends SqliteReadContext {
9899
Future<T> Function(SqliteWriteContext tx) callback);
99100
}
100101

102+
/// Interface representing a SQLite connection or pool from which inner
103+
/// connection contexts can be obtained.
104+
abstract interface class SqliteLocks {
105+
/// Takes a read lock, without starting a transaction.
106+
///
107+
/// The lock only applies to a single [SqliteConnection], and multiple
108+
/// connections may hold read locks at the same time.
109+
///
110+
/// In most cases, [SqliteConnection.readTransaction] should be used instead.
111+
Future<T> readLock<T>(Future<T> Function(SqliteReadContext tx) callback,
112+
{Duration? lockTimeout, String? debugContext});
113+
114+
/// Takes a global lock, without starting a transaction.
115+
///
116+
/// In most cases, [SqliteConnection.writeTransaction] should be used instead.
117+
///
118+
/// The lock applies to all [SqliteConnection] instances for a [SqliteDatabase].
119+
/// Locks for separate [SqliteDatabase] instances on the same database file
120+
/// may be held concurrently.
121+
Future<T> writeLock<T>(Future<T> Function(SqliteWriteContext tx) callback,
122+
{Duration? lockTimeout, String? debugContext});
123+
}
124+
101125
/// Abstract class representing a connection to the SQLite database.
102126
///
103127
/// This package typically pools multiple [SqliteConnection] instances into a
104128
/// managed [SqliteDatabase] automatically.
105-
abstract interface class SqliteConnection extends SqliteWriteContext {
129+
abstract base class SqliteConnection
130+
implements SqliteWriteContext, SqliteLocks {
106131
/// Default constructor for subclasses.
107132
SqliteConnection();
108133

@@ -124,7 +149,7 @@ abstract interface class SqliteConnection extends SqliteWriteContext {
124149
}
125150

126151
/// Reports table change update notifications
127-
Stream<UpdateNotification>? get updates;
152+
Stream<UpdateNotification> get updates;
128153

129154
/// Open a read-only transaction.
130155
///
@@ -133,7 +158,11 @@ abstract interface class SqliteConnection extends SqliteWriteContext {
133158
/// instance will error.
134159
Future<T> readTransaction<T>(
135160
Future<T> Function(SqliteReadContext tx) callback,
136-
{Duration? lockTimeout});
161+
{Duration? lockTimeout}) {
162+
return readLock((ctx) async {
163+
return await internalReadTransaction(ctx, callback);
164+
}, lockTimeout: lockTimeout, debugContext: 'readTransaction()');
165+
}
137166

138167
/// Open a read-write transaction.
139168
///
@@ -147,43 +176,138 @@ abstract interface class SqliteConnection extends SqliteWriteContext {
147176
@override
148177
Future<T> writeTransaction<T>(
149178
Future<T> Function(SqliteWriteContext tx) callback,
150-
{Duration? lockTimeout});
179+
{Duration? lockTimeout}) {
180+
return writeLock((ctx) async {
181+
return ctx.writeTransaction(callback);
182+
}, lockTimeout: lockTimeout, debugContext: 'writeTransaction()');
183+
}
151184

152-
/// Execute a read query every time the source tables are modified.
185+
/// Create a Stream of changes to any of the specified tables.
153186
///
154-
/// Use [throttle] to specify the minimum interval between queries.
155-
///
156-
/// Source tables are automatically detected using `EXPLAIN QUERY PLAN`.
157-
Stream<sqlite.ResultSet> watch(String sql,
158-
{List<Object?> parameters = const [],
159-
Duration throttle = const Duration(milliseconds: 30)});
160-
161-
/// Takes a read lock, without starting a transaction.
187+
/// This is preferred over [watch] when multiple queries need to be performed
188+
/// together when data is changed, e.g. like this:
162189
///
163-
/// The lock only applies to a single [SqliteConnection], and multiple
164-
/// connections may hold read locks at the same time.
190+
/// ```dart
191+
/// var subscription = db.onChange({'users', 'groups'}).asyncMap((event) async {
192+
/// await db.readTransaction((tx) async {
193+
/// var data = await tx.getAll('SELECT * ROM users');
194+
/// var moreData = await tx.getAll('SELECT * ROM groups');
165195
///
166-
/// In most cases, [readTransaction] should be used instead.
167-
Future<T> readLock<T>(Future<T> Function(SqliteReadContext tx) callback,
168-
{Duration? lockTimeout, String? debugContext});
196+
/// // Handle data here...
197+
/// });
198+
/// });
199+
/// ```
200+
Stream<UpdateNotification> onChange(Iterable<String>? tables,
201+
{Duration throttle = const Duration(milliseconds: 30),
202+
bool triggerImmediately = true}) {
203+
final filteredStream = tables != null
204+
? updates.transform(UpdateNotification.filterTablesTransformer(tables))
205+
: updates;
206+
final throttledStream = UpdateNotification.throttleStream(
207+
filteredStream, throttle,
208+
addOne: triggerImmediately ? UpdateNotification.empty() : null);
209+
return throttledStream;
210+
}
169211

170-
/// Takes a global lock, without starting a transaction.
212+
/// Execute a read query every time the source tables are modified.
171213
///
172-
/// In most cases, [writeTransaction] should be used instead.
214+
/// Use [throttle] to specify the minimum interval between queries.
173215
///
174-
/// The lock applies to all [SqliteConnection] instances for a [SqliteDatabase].
175-
/// Locks for separate [SqliteDatabase] instances on the same database file
176-
/// may be held concurrently.
177-
Future<T> writeLock<T>(Future<T> Function(SqliteWriteContext tx) callback,
178-
{Duration? lockTimeout, String? debugContext});
216+
/// Source tables are automatically detected using `EXPLAIN QUERY PLAN`.
217+
Stream<sqlite.ResultSet> watch(
218+
String sql, {
219+
List<Object?> parameters = const [],
220+
Duration throttle = const Duration(milliseconds: 30),
221+
Iterable<String>? triggerOnTables,
222+
}) {
223+
Stream<sqlite.ResultSet> watchInner(Iterable<String> trigger) {
224+
return onChange(
225+
trigger,
226+
throttle: throttle,
227+
triggerImmediately: true,
228+
).asyncMap((_) => getAll(sql, parameters));
229+
}
230+
231+
if (triggerOnTables case final knownTrigger?) {
232+
return watchInner(knownTrigger);
233+
} else {
234+
return Stream.fromFuture(getSourceTables(this, sql, parameters))
235+
.asyncExpand(watchInner);
236+
}
237+
}
179238

180239
Future<void> close();
181240

182241
/// Ensures that all connections are aware of the latest schema changes applied (if any).
183242
/// Queries and watch calls can potentially use outdated schema information after a schema update.
184-
Future<void> refreshSchema();
243+
Future<void> refreshSchema() {
244+
return getAll("PRAGMA table_info('sqlite_master')");
245+
}
185246

186247
/// Returns true if the connection is closed
187248
@override
188249
bool get closed;
250+
251+
@override
252+
Future<sqlite.ResultSet> execute(String sql,
253+
[List<Object?> parameters = const []]) async {
254+
return writeLock((ctx) async {
255+
return ctx.execute(sql, parameters);
256+
}, debugContext: 'execute()');
257+
}
258+
259+
@override
260+
Future<sqlite.ResultSet> getAll(String sql,
261+
[List<Object?> parameters = const []]) {
262+
return readLock((ctx) async {
263+
return ctx.getAll(sql, parameters);
264+
}, debugContext: 'getAll()');
265+
}
266+
267+
@override
268+
Future<sqlite.Row> get(String sql, [List<Object?> parameters = const []]) {
269+
return readLock((ctx) async {
270+
return ctx.get(sql, parameters);
271+
}, debugContext: 'get()');
272+
}
273+
274+
@override
275+
Future<sqlite.Row?> getOptional(String sql,
276+
[List<Object?> parameters = const []]) {
277+
return readLock((ctx) async {
278+
return ctx.getOptional(sql, parameters);
279+
}, debugContext: 'getOptional()');
280+
}
281+
282+
/// See [SqliteReadContext.computeWithDatabase].
283+
///
284+
/// When called here directly on the connection, the call is wrapped in a
285+
/// write transaction.
286+
@override
287+
Future<T> computeWithDatabase<T>(
288+
Future<T> Function(sqlite.CommonDatabase db) compute) {
289+
return writeTransaction((tx) async {
290+
return tx.computeWithDatabase(compute);
291+
});
292+
}
293+
294+
/// Execute a write query (INSERT, UPDATE, DELETE) multiple times with each
295+
/// parameter set. This is more faster than executing separately with each
296+
/// parameter set.
297+
///
298+
/// When called here directly on the connection, the batch is wrapped in a
299+
/// write transaction.
300+
@override
301+
Future<void> executeBatch(String sql, List<List<Object?>> parameterSets) {
302+
return writeTransaction((tx) async {
303+
return tx.executeBatch(sql, parameterSets);
304+
});
305+
}
306+
307+
@override
308+
Future<void> executeMultiple(String sql) {
309+
return writeTransaction((tx) async {
310+
return tx.executeMultiple(sql);
311+
});
312+
}
189313
}

0 commit comments

Comments
 (0)