Skip to content

Commit c137dce

Browse files
authored
Prepare release, optimize executeBatch on the web (#138)
1 parent c5f4eaf commit c137dce

7 files changed

Lines changed: 155 additions & 41 deletions

File tree

packages/sqlite_async/CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
## 0.14.0-wip.0
1+
## 0.14.0
22

33
__Note__: This version of `sqlite_async` is still in development and there might be additional
44
API changes between this release and the final `0.14.0` version. This release is mostly meant for
55
internal testing.
66

7-
- Support versions 3.x of the `sqlite3` package and 0.6.0 of `sqlite3_web`.
7+
- Support versions 3.x of the `sqlite3` package and 0.7.x of `sqlite3_web`.
88
- Remove the `sqlite3_open.dart` library, SQLite libraries are no longer loaded through Dart.
99
- __Breaking__: Rewrite the native connection pool implementation.
1010
- Remove isolate connection factories. Simply open the same database on another isolate, it's safe to do so now.

packages/sqlite_async/lib/src/web/database.dart

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ final class WebDatabase extends SqliteDatabaseImpl
5252

5353
@override
5454
Future<bool> getAutoCommit() async {
55-
final response = await _database.customRequest(
56-
CustomDatabaseMessage(CustomDatabaseMessageKind.getAutoCommit));
55+
final response = await _database
56+
.customRequest(BaseCustomDatabaseMessage.getAutoCommit());
5757
return (response as JSBoolean?)?.toDart ?? false;
5858
}
5959

@@ -274,16 +274,14 @@ final class _UnscopedContext extends UnscopedContext {
274274
Future<void> executeBatch(String sql, List<List<Object?>> parameterSets) {
275275
return _task.timeAsync('executeBatch', sql: sql, () {
276276
return wrapSqliteException(() async {
277-
for (final set in parameterSets) {
278-
// use execute instead of select to avoid transferring rows from the
279-
// worker to this context.
280-
await _database._database.execute(
281-
sql,
282-
parameters: set,
283-
token: _lock,
284-
checkInTransaction: _checkInTransaction,
285-
);
286-
}
277+
await _database._database.customRequest(
278+
RunBatchRequest(
279+
sql: sql,
280+
parameters: parameterSets,
281+
requireTransaction: _checkInTransaction,
282+
),
283+
token: _lock,
284+
);
287285
});
288286
});
289287
}

packages/sqlite_async/lib/src/web/protocol.dart

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,35 @@ import 'package:sqlite3_web/protocol_utils.dart' as proto;
88
enum CustomDatabaseMessageKind {
99
ok,
1010
getAutoCommit,
11-
executeBatchInTransaction,
11+
executeBatch,
1212
updateSubscriptionManagement,
1313
notifyUpdates,
1414
}
1515

16-
extension type CustomDatabaseMessage._raw(JSObject _) implements JSObject {
16+
extension type BaseCustomDatabaseMessage._raw(JSObject _) implements JSObject {
17+
external JSString get rawKind;
18+
19+
external factory BaseCustomDatabaseMessage({required JSString rawKind});
20+
21+
factory BaseCustomDatabaseMessage.getAutoCommit() {
22+
return BaseCustomDatabaseMessage(
23+
rawKind: CustomDatabaseMessageKind.getAutoCommit.name.toJS,
24+
);
25+
}
26+
27+
factory BaseCustomDatabaseMessage.okResponse() {
28+
return BaseCustomDatabaseMessage(
29+
rawKind: CustomDatabaseMessageKind.ok.name.toJS,
30+
);
31+
}
32+
33+
CustomDatabaseMessageKind get kind {
34+
return CustomDatabaseMessageKind.values.byName(rawKind.toDart);
35+
}
36+
}
37+
38+
extension type CustomDatabaseMessage._raw(JSObject _)
39+
implements BaseCustomDatabaseMessage {
1740
external factory CustomDatabaseMessage._({
1841
required JSString rawKind,
1942
JSString rawSql,
@@ -38,16 +61,55 @@ extension type CustomDatabaseMessage._raw(JSObject _) implements JSObject {
3861
);
3962
}
4063

41-
external JSString get rawKind;
42-
4364
external JSString get rawSql;
4465

4566
external JSArray get rawParameters;
4667

4768
/// Not set in earlier versions of this package.
4869
external JSArrayBuffer? get typeInfo;
70+
}
4971

50-
CustomDatabaseMessageKind get kind {
51-
return CustomDatabaseMessageKind.values.byName(rawKind.toDart);
72+
extension type RunBatchRequest._raw(JSObject _)
73+
implements BaseCustomDatabaseMessage {
74+
external factory RunBatchRequest._({
75+
required JSString rawKind,
76+
required JSString rawSql,
77+
required JSArray<BatchParameters> parameters,
78+
required JSBoolean requireTransaction,
79+
});
80+
81+
factory RunBatchRequest({
82+
required String sql,
83+
required List<List<Object?>> parameters,
84+
required bool requireTransaction,
85+
}) {
86+
return RunBatchRequest._(
87+
rawKind: CustomDatabaseMessageKind.executeBatch.name.toJS,
88+
rawSql: sql.toJS,
89+
parameters: parameters.map(BatchParameters.new).toList().toJS,
90+
requireTransaction: requireTransaction.toJS,
91+
);
92+
}
93+
94+
external JSString get rawSql;
95+
external JSArray<BatchParameters> get parameters;
96+
external JSBoolean get requireTransaction;
97+
}
98+
99+
extension type BatchParameters._raw(JSObject _) implements JSObject {
100+
external JSArray get parameters;
101+
external JSArrayBuffer get parameterTypes;
102+
103+
external factory BatchParameters._({
104+
required JSArray parameters,
105+
required JSArrayBuffer parameterTypes,
106+
});
107+
108+
factory BatchParameters(List<Object?> parameters) {
109+
final (params, types) = proto.serializeParameters(parameters);
110+
return BatchParameters._(parameters: params, parameterTypes: types);
52111
}
112+
113+
List<Object?> get decodedParameters =>
114+
proto.deserializeParameters(parameters, parameterTypes);
53115
}

packages/sqlite_async/lib/src/web/update_notifications.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ final class UpdateNotificationStreams {
2020
final Map<String, StreamController<UpdateNotification>> _updates = {};
2121

2222
Future<JSAny?> handleRequest(JSAny? request) async {
23-
final customRequest = request as CustomDatabaseMessage;
23+
final customRequest = request as BaseCustomDatabaseMessage;
2424
if (customRequest.kind == CustomDatabaseMessageKind.notifyUpdates) {
25+
customRequest as CustomDatabaseMessage;
2526
final notification = UpdateNotification(customRequest.rawParameters.toDart
2627
.map((e) => (e as JSString).toDart)
2728
.toSet());

packages/sqlite_async/lib/src/web/worker/worker_utils.dart

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import 'dart:js_interop';
44
import 'package:meta/meta.dart';
55
import 'package:sqlite3/wasm.dart';
66
import 'package:sqlite3_web/sqlite3_web.dart';
7-
import 'package:sqlite3_web/protocol_utils.dart' as proto;
87
import 'package:sqlite_async/src/utils/shared_utils.dart';
98

109
import '../protocol.dart';
@@ -35,7 +34,7 @@ base class AsyncSqliteController extends DatabaseController {
3534

3635
@override
3736
Future<JSAny?> handleCustomRequest(
38-
ClientConnection connection, JSAny? request) {
37+
ClientConnection connection, CustomClientRequest request) {
3938
throw UnimplementedError();
4039
}
4140
}
@@ -68,29 +67,39 @@ class AsyncSqliteDatabase extends WorkerDatabase {
6867

6968
@override
7069
Future<JSAny?> handleCustomRequest(
71-
ClientConnection connection, JSAny? request) async {
72-
final message = request as CustomDatabaseMessage;
70+
ClientConnection connection, CustomClientDatabaseRequest request) async {
71+
final message = request.request as BaseCustomDatabaseMessage;
7372

7473
switch (message.kind) {
7574
case CustomDatabaseMessageKind.ok:
7675
case CustomDatabaseMessageKind.notifyUpdates:
7776
throw UnsupportedError('This is a response, not a request');
7877
case CustomDatabaseMessageKind.getAutoCommit:
7978
return database.autocommit.toJS;
80-
case CustomDatabaseMessageKind.executeBatchInTransaction:
81-
final sql = message.rawSql.toDart;
82-
final parameters = proto.deserializeParameters(
83-
message.rawParameters, message.typeInfo);
84-
if (database.autocommit) {
85-
throw SqliteException(
86-
extendedResultCode: 0,
87-
message:
88-
'Transaction rolled back by earlier statement. Cannot execute',
89-
causingStatement: sql,
90-
);
91-
}
92-
database.execute(sql, parameters);
79+
case CustomDatabaseMessageKind.executeBatch:
80+
final data = message as RunBatchRequest;
81+
82+
await request.useLock(() {
83+
if (data.requireTransaction.toDart && database.autocommit) {
84+
throw SqliteException(
85+
extendedResultCode: 0,
86+
message:
87+
'Transaction rolled back by earlier statement. Cannot execute',
88+
causingStatement: data.rawSql.toDart,
89+
);
90+
}
91+
92+
final stmt = database.prepare(data.rawSql.toDart);
93+
try {
94+
for (final parameter in data.parameters.toDart) {
95+
stmt.execute(parameter.decodedParameters);
96+
}
97+
} finally {
98+
stmt.close();
99+
}
100+
});
93101
case CustomDatabaseMessageKind.updateSubscriptionManagement:
102+
message as CustomDatabaseMessage;
94103
final shouldSubscribe =
95104
(message.rawParameters.toDart[0] as JSBoolean).toDart;
96105
final id = message.rawSql.toDart;
@@ -113,7 +122,7 @@ class AsyncSqliteDatabase extends WorkerDatabase {
113122
}
114123
}
115124

116-
return CustomDatabaseMessage(CustomDatabaseMessageKind.ok);
125+
return BaseCustomDatabaseMessage.okResponse();
117126
}
118127

119128
Map<String, dynamic> resultSetToMap(ResultSet resultSet) {

packages/sqlite_async/pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: sqlite_async
22
description: High-performance asynchronous interface for SQLite on Dart and Flutter.
3-
version: 0.14.0-wip.0
3+
version: 0.14.0
44
resolution: workspace
55
repository: https://github.com/powersync-ja/sqlite_async.dart
66
environment:
@@ -14,7 +14,7 @@ topics:
1414

1515
dependencies:
1616
sqlite3: ^3.2.0
17-
sqlite3_web: ^0.6.0
17+
sqlite3_web: ^0.7.0
1818
sqlite3_connection_pool: ^0.2.3
1919
async: ^2.10.0
2020
collection: ^1.17.0

packages/sqlite_async/test/basic_test.dart

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,50 @@ void main() {
348348
expect(result.rows[0][1], equals('test returning without params'));
349349
});
350350

351+
group('executeBatch', () {
352+
late SqliteDatabase db;
353+
354+
setUp(() async {
355+
db = await testUtils.setupDatabase(path: path);
356+
});
357+
358+
tearDown(() => db.close());
359+
360+
test('can execute multiple times', () async {
361+
await createTables(db);
362+
363+
await db
364+
.executeBatch('INSERT INTO test_data (description) VALUES (?)', [
365+
['foo'],
366+
['bar']
367+
]);
368+
369+
final results =
370+
await db.getAll('SELECT description FROM test_data ORDER BY id');
371+
expect(results.length, equals(2));
372+
expect(results.rows[0], equals(['foo']));
373+
expect(results.rows[1], equals(['bar']));
374+
});
375+
376+
test('can execute in transaction', () async {
377+
await createTables(db);
378+
const exception = 'exception thrown for rollback';
379+
380+
await expectLater(db.writeTransaction((tx) async {
381+
await tx
382+
.executeBatch('INSERT INTO test_data (description) VALUES (?)', [
383+
['foo'],
384+
['bar']
385+
]);
386+
387+
expect(await tx.getAll('SELECT * FROM test_data'), hasLength(2));
388+
throw exception;
389+
}), throwsA(exception));
390+
391+
expect(await db.getAll('SELECT * FROM test_data'), isEmpty);
392+
});
393+
});
394+
351395
test('executeMultiple handles multiple statements', () async {
352396
final db = await testUtils.setupDatabase(path: path);
353397
await createTables(db);

0 commit comments

Comments
 (0)