Skip to content

Commit bffb350

Browse files
Support latest version of package:web in drift adapter (#65)
* Support latest version of package:web in drift adapter * Avoid using internal drift APIs * complement test * changelog --------- Co-authored-by: Simon Binder <[email protected]>
1 parent d5bc792 commit bffb350

File tree

5 files changed

+70
-219
lines changed

5 files changed

+70
-219
lines changed

packages/drift_sqlite_async/CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 0.2.0-alpha.1
2+
3+
- Support `drift` version >=2.19 and `web` 1.0.0
4+
- BREAKING CHANGE: Nested transactions through drift no longer create SAVEPOINTs. When nesting a drift `transaction`, the transaction is reused. See https://github.com/powersync-ja/sqlite_async.dart/pull/65
5+
16
## 0.1.0-alpha.7
27

38
- Update a dependency to the latest release.

packages/drift_sqlite_async/lib/src/executor.dart

+54-22
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
import 'dart:async';
22

33
import 'package:drift/backends.dart';
4-
import 'package:drift_sqlite_async/src/transaction_executor.dart';
4+
import 'package:drift/src/runtime/query_builder/query_builder.dart';
55
import 'package:sqlite_async/sqlite3_common.dart';
66
import 'package:sqlite_async/sqlite_async.dart';
77

8-
class _SqliteAsyncDelegate extends DatabaseDelegate {
8+
// Ends with " RETURNING *", or starts with insert/update/delete.
9+
// Drift-generated queries will always have the RETURNING *.
10+
// The INSERT/UPDATE/DELETE check is for custom queries, and is not exhaustive.
11+
final _returningCheck = RegExp(r'( RETURNING \*;?$)|(^(INSERT|UPDATE|DELETE))',
12+
caseSensitive: false);
13+
14+
class _SqliteAsyncDelegate extends _SqliteAsyncQueryDelegate
15+
implements DatabaseDelegate {
916
final SqliteConnection db;
1017
bool _closed = false;
1118

12-
_SqliteAsyncDelegate(this.db);
19+
_SqliteAsyncDelegate(this.db) : super(db, db.writeLock);
20+
21+
bool isInTransaction = false; // unused
1322

1423
@override
1524
late final DbVersionDelegate versionDelegate =
@@ -18,18 +27,11 @@ class _SqliteAsyncDelegate extends DatabaseDelegate {
1827
// Not used - we override beginTransaction() with SqliteAsyncTransactionExecutor for more control.
1928
@override
2029
late final TransactionDelegate transactionDelegate =
21-
const NoTransactionDelegate();
30+
_SqliteAsyncTransactionDelegate(db);
2231

2332
@override
2433
bool get isOpen => !db.closed && !_closed;
2534

26-
// Ends with " RETURNING *", or starts with insert/update/delete.
27-
// Drift-generated queries will always have the RETURNING *.
28-
// The INSERT/UPDATE/DELETE check is for custom queries, and is not exhaustive.
29-
final _returningCheck = RegExp(
30-
r'( RETURNING \*;?$)|(^(INSERT|UPDATE|DELETE))',
31-
caseSensitive: false);
32-
3335
@override
3436
Future<void> open(QueryExecutorUser user) async {
3537
// Workaround - this ensures the db is open
@@ -42,9 +44,30 @@ class _SqliteAsyncDelegate extends DatabaseDelegate {
4244
_closed = true;
4345
}
4446

47+
@override
48+
void notifyDatabaseOpened(OpeningDetails details) {
49+
// Unused
50+
}
51+
}
52+
53+
class _SqliteAsyncQueryDelegate extends QueryDelegate {
54+
final SqliteWriteContext _context;
55+
final Future<T> Function<T>(
56+
Future<T> Function(SqliteWriteContext tx) callback)? _writeLock;
57+
58+
_SqliteAsyncQueryDelegate(this._context, this._writeLock);
59+
60+
Future<T> writeLock<T>(Future<T> Function(SqliteWriteContext tx) callback) {
61+
if (_writeLock case var writeLock?) {
62+
return writeLock.call(callback);
63+
} else {
64+
return callback(_context);
65+
}
66+
}
67+
4568
@override
4669
Future<void> runBatched(BatchedStatements statements) async {
47-
return db.writeLock((tx) async {
70+
return writeLock((tx) async {
4871
// sqlite_async's batch functionality doesn't have enough flexibility to support
4972
// this with prepared statements yet.
5073
for (final arg in statements.arguments) {
@@ -56,12 +79,12 @@ class _SqliteAsyncDelegate extends DatabaseDelegate {
5679

5780
@override
5881
Future<void> runCustom(String statement, List<Object?> args) {
59-
return db.execute(statement, args);
82+
return _context.execute(statement, args);
6083
}
6184

6285
@override
6386
Future<int> runInsert(String statement, List<Object?> args) async {
64-
return db.writeLock((tx) async {
87+
return writeLock((tx) async {
6588
await tx.execute(statement, args);
6689
final row = await tx.get('SELECT last_insert_rowid() as row_id');
6790
return row['row_id'];
@@ -77,24 +100,38 @@ class _SqliteAsyncDelegate extends DatabaseDelegate {
77100
// This takes write lock, so we want to avoid it for plain select statements.
78101
// This is not an exhaustive check, but should cover all Drift-generated queries using
79102
// `runSelect()`.
80-
result = await db.execute(statement, args);
103+
result = await _context.execute(statement, args);
81104
} else {
82105
// Plain SELECT statement - use getAll() to avoid using a write lock.
83-
result = await db.getAll(statement, args);
106+
result = await _context.getAll(statement, args);
84107
}
85108
return QueryResult(result.columnNames, result.rows);
86109
}
87110

88111
@override
89112
Future<int> runUpdate(String statement, List<Object?> args) {
90-
return db.writeLock((tx) async {
113+
return writeLock((tx) async {
91114
await tx.execute(statement, args);
92115
final row = await tx.get('SELECT changes() as changes');
93116
return row['changes'];
94117
});
95118
}
96119
}
97120

121+
class _SqliteAsyncTransactionDelegate extends SupportedTransactionDelegate {
122+
final SqliteConnection _db;
123+
124+
_SqliteAsyncTransactionDelegate(this._db);
125+
126+
@override
127+
Future<void> startTransaction(Future Function(QueryDelegate p1) run) async {
128+
await _db.writeTransaction((context) async {
129+
final delegate = _SqliteAsyncQueryDelegate(context, null);
130+
return run(delegate);
131+
});
132+
}
133+
}
134+
98135
class _SqliteAsyncVersionDelegate extends DynamicVersionDelegate {
99136
final SqliteConnection _db;
100137

@@ -137,9 +174,4 @@ class SqliteAsyncQueryExecutor extends DelegatedDatabase {
137174

138175
@override
139176
bool get isSequential => false;
140-
141-
@override
142-
TransactionExecutor beginTransaction() {
143-
return SqliteAsyncTransactionExecutor(db);
144-
}
145177
}

packages/drift_sqlite_async/lib/src/transaction_executor.dart

-193
This file was deleted.

packages/drift_sqlite_async/pubspec.yaml

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: drift_sqlite_async
2-
version: 0.1.0-alpha.7
2+
version: 0.2.0-alpha.1
33
homepage: https://github.com/powersync-ja/sqlite_async.dart
44
repository: https://github.com/powersync-ja/sqlite_async.dart
55
description: Use Drift with a sqlite_async database, allowing both to be used in the same application.
@@ -14,11 +14,11 @@ topics:
1414
environment:
1515
sdk: ">=3.0.0 <4.0.0"
1616
dependencies:
17-
drift: ">=2.15.0 <2.19.0"
17+
drift: ">=2.19.0 <3.0.0"
1818
sqlite_async: ^0.9.0
1919
dev_dependencies:
2020
build_runner: ^2.4.8
21-
drift_dev: ">=2.15.0 <2.19.0"
21+
drift_dev: ">=2.19.0 <3.0.0"
2222
glob: ^2.1.2
2323
sqlite3: ^2.4.0
2424
test: ^1.25.2

packages/drift_sqlite_async/test/basic_test.dart

+8-1
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,13 @@ void main() {
129129
// This runs outside the transaction
130130
expect(await db.get('select count(*) as count from test_data'),
131131
equals({'count': 0}));
132+
133+
// This runs in the transaction
134+
final countInTransaction = (await dbu
135+
.customSelect('select count(*) as count from test_data')
136+
.getSingle())
137+
.data;
138+
expect(countInTransaction, equals({'count': 2}));
132139
});
133140

134141
expect(await db.get('select count(*) as count from test_data'),
@@ -172,7 +179,7 @@ void main() {
172179
{'description': 'Test 1'},
173180
{'description': 'Test 3'}
174181
]));
175-
});
182+
}, skip: 'sqlite_async does not support nested transactions');
176183

177184
test('Concurrent select', () async {
178185
var completer1 = Completer<void>();

0 commit comments

Comments
 (0)