Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/multiplatform/db/sqlite3/web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Future<CommonDatabase> openSqliteDb() async {
const name = 'my_app';
// Please download `sqlite3.wasm` from https://github.com/simolus3/sqlite3.dart/releases
// into the `web/` dir of your Flutter app. See `README.md` for details.
final sqlite = await WasmSqlite3.loadFromUrl(Uri.parse('sqlite3.wasm'));
final sqlite = await WasmSqlite3.loadFromUrlString('sqlite3.wasm');
final fileSystem = await IndexedDbFileSystem.open(dbName: name);
sqlite.registerVirtualFileSystem(fileSystem, makeDefault: true);
return sqlite.open(name);
Expand Down
2 changes: 2 additions & 0 deletions sqlite3/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

- Support `native_toolchain_c` versions `0.18.x`.
- Upgrade to SQLite version 3.53.1.
- Allow disabling `BigInt` support via `-Dsqlite3.dartbigints=false`, which can be used to
reduce code size on the web if nothing else uses `BigInt` values.

## 3.3.1

Expand Down
8 changes: 5 additions & 3 deletions sqlite3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ databases. The [hook options page](./doc/hook.md) describe this setup.
When binding parameters to queries, the supported types are `ìnt`,
`double`, `String`, `List<int>` (for `BLOB`) and `null`.
Result sets will use the same set of types.
On the web (but only on the web), `BigInt` is supported as well.

On the web (when compiled with `dart2js`), `BigInt` is supported as well to represent 64bit integers.
Support for this can be disabled with `-Dsqlite3.dartbigints=false`.

## WASM (web support)

Expand Down Expand Up @@ -85,7 +87,7 @@ import 'package:sqlite3/common.dart';
import 'package:sqlite3/wasm.dart';

Future<WasmSqlite3> loadSqlite() async {
final sqlite = await WasmSqlite3.loadFromUrl(Uri.parse('sqlite3.wasm'));
final sqlite = await WasmSqlite3.loadFromUrlString('sqlite3.wasm');
final fileSystem = await IndexedDbFileSystem.open(dbName: 'my_app');
sqlite.registerVirtualFileSystem(fileSystem, makeDefault: true);
return sqlite;
Expand Down Expand Up @@ -123,7 +125,7 @@ To test the encryption integration, download `sqlite3mc.wasm` from the [releases
of this package and use that as a URL to load sqlite3 on the web:

```dart
final sqlite3 = await WasmSqlite3.loadFromUrl(Uri.parse('sqlite3mc.wasm'));
final sqlite3 = await WasmSqlite3.loadFromUrlString('sqlite3mc.wasm');
sqlite3.registerVirtualFileSystem(InMemoryFileSystem(), makeDefault: true);

final database = sqlite3.open('/database')
Expand Down
2 changes: 1 addition & 1 deletion sqlite3/doc/common.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Future<CommonDatabase> openDatabase() async {
import 'package:sqlite3/wasm.dart';

Future<CommonDatabase> openDatabase() async {
final sqlite = await WasmSqlite3.loadFromUrl(Uri.parse('sqlite3.wasm'));
final sqlite = await WasmSqlite3.loadFromUrlString('sqlite3.wasm');
final fs = await IndexedDbFileSystem.open(dbName: 'app.db');
sqlite.registerVirtualFileSystem(fs, makeDefault: true);
return sqlite.open('/app.db');
Expand Down
4 changes: 1 addition & 3 deletions sqlite3/example/web/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ Future<void> main() async {

startIndexedDb.onClick.listen((_) async {
startIndexedDb.remove();
final sqlite3 = await WasmSqlite3.loadFromUrl(
Uri.parse('sqlite3.debug.wasm'),
);
final sqlite3 = await WasmSqlite3.loadFromUrlString('sqlite3.debug.wasm');

print(sqlite3.version);

Expand Down
15 changes: 15 additions & 0 deletions sqlite3/lib/src/compile_options.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/// Whether to support binding [BigInt] values.
///
/// This is enabled by default, and disabling it is experimental. This is mainly
/// relevant for web workers, which don't really benefit from using [BigInt]
/// values internally:
///
/// - Over the wire, we receive values as `JSBigInt`.
/// - To bind values to SQLite, we need to use `JSBigInt` as well.
///
/// By removing support for [BigInt]s and their conversion to `JSBigInt`, we
/// save around 20kb from the default worker with `dart compile js -O4`.
const supportDartBigInts = bool.fromEnvironment(
'sqlite3.dartbigints',
defaultValue: true,
);
3 changes: 0 additions & 3 deletions sqlite3/lib/src/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -544,9 +544,6 @@ const SQLITE_DELETE = 9;
const SQLITE_INSERT = 18;
const SQLITE_UPDATE = 23;

final bigIntMinValue64 = BigInt.parse('-9223372036854775808');
final bigIntMaxValue64 = BigInt.parse('9223372036854775807');

// Connection config options https://www.sqlite.org/c3ref/c_dbconfig_defensive.html
const SQLITE_DBCONFIG_DQS_DML = 1013;
const SQLITE_DBCONFIG_DQS_DDL = 1014;
6 changes: 5 additions & 1 deletion sqlite3/lib/src/implementation/statement.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import '../compile_options.dart';
import '../constants.dart';
import '../result_set.dart';
import '../statement.dart';
Expand Down Expand Up @@ -210,7 +211,10 @@ base class StatementImplementation extends CommonPreparedStatement {
final rc = switch (param) {
null => statement.sqlite3_bind_null(i),
int() => statement.sqlite3_bind_int64(i, param),
BigInt() => statement.sqlite3_bind_int64BigInt(i, param.checkRange),
BigInt() when supportDartBigInts => statement.sqlite3_bind_int64BigInt(
i,
param.checkRange,
),
bool() => statement.sqlite3_bind_int64(i, param ? 1 : 0),
double() => statement.sqlite3_bind_double(i, param),
String() => statement.sqlite3_bind_text(i, param),
Expand Down
3 changes: 3 additions & 0 deletions sqlite3/lib/src/implementation/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ extension BigIntRangeCheck on BigInt {
}
return this;
}

static final bigIntMinValue64 = -(BigInt.one << 63);
static final bigIntMaxValue64 = (BigInt.one << 63) - BigInt.one;
}

int eTextRep(bool deterministic, bool directOnly, bool subtype) {
Expand Down
4 changes: 2 additions & 2 deletions sqlite3/lib/src/in_memory_vfs.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import 'dart:math';
import 'dart:typed_data';

import 'package:path/path.dart' as p;
import 'package:typed_data/typed_buffers.dart';

import 'constants.dart';
import 'platform/platform.dart';
import 'vfs.dart';
import 'utils.dart';

Expand Down Expand Up @@ -32,7 +32,7 @@ final class InMemoryFileSystem extends BaseVirtualFileSystem {

@override
String xFullPathName(String path) {
return p.url.normalize('/$path');
return pathToAbsoluteAndNormalize(path);
}

@override
Expand Down
5 changes: 5 additions & 0 deletions sqlite3/lib/src/platform/fallback.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import 'package:path/path.dart' as p;

String pathToAbsoluteAndNormalize(String source) {
return p.url.normalize('/$source');
}
1 change: 1 addition & 0 deletions sqlite3/lib/src/platform/platform.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export 'fallback.dart' if (dart.library.js_interop) 'web.dart';
36 changes: 36 additions & 0 deletions sqlite3/lib/src/platform/web.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/// Helpers implemented with direct interop to web APIs instead of
/// cross-platform Dart packages or the SDK.
///
/// In compiled web workers, Dart's [Uri.parse] implementation takes up a good
/// chunk of the total file size. By using [URL] directly (which is good enough
/// for our use case), we can avoid that implementation.
library;

import 'dart:js_interop';

import 'package:web/web.dart';

import '../implementation/statement.dart';
import '../statement.dart';
import '../wasm/bindings.dart';

String pathToAbsoluteAndNormalize(String source) {
return URL(source, 'file:///').pathname;
}

Iterable<String> pathComponents(String path) {
return URL(path, 'file:///').pathname.split('/').where((e) => e.isNotEmpty);
}

final class BoxedJavaScriptBigInt implements CustomStatementParameter {
final JSBigInt value;

BoxedJavaScriptBigInt(this.value);

@override
void applyTo(CommonPreparedStatement statement, int index) {
final raw =
(statement as StatementImplementation).statement as WasmStatement;
raw.bindings.sqlite3_bind_int64(raw.stmt, index, value);
}
}
7 changes: 6 additions & 1 deletion sqlite3/lib/src/wasm/bindings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import '../functions.dart';
import '../implementation/bindings.dart';
import '../implementation/exception.dart';
import 'injected_values.dart';
import 'js_interop/core.dart';
import 'wasm_interop.dart' as wasm;
import 'sqlite3_wasm.g.dart';
import 'wasm_interop.dart';
Expand Down Expand Up @@ -473,7 +474,11 @@ final class WasmStatement implements RawSqliteStatement {

@override
int sqlite3_bind_int64BigInt(int index, BigInt value) {
return bindings.sqlite3_bind_int64(stmt, index, value);
return bindings.sqlite3_bind_int64(
stmt,
index,
JsBigInt.fromBigInt(value).jsObject,
);
}

@override
Expand Down
11 changes: 10 additions & 1 deletion sqlite3/lib/src/wasm/js_interop/core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import 'dart:async';
import 'dart:js_interop';
import 'dart:js_interop_unsafe';

import '../../compile_options.dart';
import '../../platform/web.dart';

@JS('BigInt')
external JSBigInt _bigInt(JSAny? s);

Expand All @@ -27,7 +30,13 @@ extension type JsBigInt(JSBigInt _jsBigInt) implements JSBigInt {

int get asDartInt => _number(_jsBigInt).toDartInt;

BigInt get asDartBigInt => BigInt.parse(jsToString());
Object get asDartBigInt {
if (!supportDartBigInts) {
return BoxedJavaScriptBigInt(this);
}

return BigInt.parse(jsToString());
}

JSBigInt get jsObject => _jsBigInt;

Expand Down
21 changes: 18 additions & 3 deletions sqlite3/lib/src/wasm/sqlite3.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,23 @@ final class WasmSqlite3 extends Sqlite3Implementation {
Uri uri, {
Map<String, String>? headers,
WasmModuleLoader? loader,
}) {
return loadFromUrlString(uri.toString());
}

/// Loads a web version of the sqlite3 libraries.
///
/// The native wasm library for sqlite3 is loaded from the [url] with the
/// desired [headers] through a `fetch` request.
///
/// Using this over [loadFromUrl] might reduce compiled JS sizes for apps
/// which otherwise don't use URLs.
///
/// [pgk release]: https://github.com/simolus3/sqlite3.dart/releases
static Future<WasmSqlite3> loadFromUrlString(
String url, {
Map<String, String>? headers,
WasmModuleLoader? loader,
}) async {
web.RequestInit? options;

Expand All @@ -63,9 +80,7 @@ final class WasmSqlite3 extends Sqlite3Implementation {
options = web.RequestInit(headers: headersJs);
}

final jsUri = uri.isAbsolute
? web.URL(uri.toString())
: web.URL(uri.toString(), Uri.base.toString());
final jsUri = web.URL(url, (globalContext['location'] as web.URL).href);
final response = await fetch(jsUri, options).toDart;
return _load(response, loader);
}
Expand Down
11 changes: 5 additions & 6 deletions sqlite3/lib/src/wasm/vfs/async_opfs/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import 'dart:js_interop_unsafe';
import 'dart:math';
import 'dart:typed_data';

import 'package:path/path.dart' as p;
import 'package:web/web.dart' show URL;

import '../../../constants.dart';
import '../../../vfs.dart';
Expand Down Expand Up @@ -42,7 +42,6 @@ final class WasmVfs extends BaseVirtualFileSystem {
final MessageSerializer serializer;

final String chroot;
final p.Context pathContext;

WasmVfs({
super.random,
Expand All @@ -53,7 +52,6 @@ final class WasmVfs extends BaseVirtualFileSystem {
workerOptions.synchronizationBuffer,
),
serializer = MessageSerializer(workerOptions.communicationBuffer),
pathContext = p.Context(style: p.Style.url, current: chroot),
super(name: vfsName);

Res _runInWorker<Req extends Message, Res extends Message>(
Expand Down Expand Up @@ -89,12 +87,13 @@ final class WasmVfs extends BaseVirtualFileSystem {

@override
String xFullPathName(String path) {
final resolved = pathContext.absolute(path);
if (!p.isWithin(chroot, resolved)) {
final normalized = URL(path, 'file://$chroot').pathname;

if (!normalized.startsWith(chroot)) {
throw const VfsException(SqlError.SQLITE_CANTOPEN);
}

return resolved;
return normalized;
}

@override
Expand Down
7 changes: 6 additions & 1 deletion sqlite3/lib/src/wasm/vfs/async_opfs/sync_channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,12 @@ class MessageSerializer {

String _readString(int offset) {
final length = dataView.getInt32(offset);
return utf8.decode(buffer.asUint8ListSlice(offset + 4, length));
// Browsers don't allow TextDecoder.decode on shared array buffers
// (https://github.com/whatwg/encoding/issues/172). So, we copy.
final originalSlice = buffer.asUint8ListSlice(offset + 4, length);
final copy = Uint8List.fromList(originalSlice);

return utf8.decode(copy);
}

void _writeString(int offset, String data) {
Expand Down
Loading
Loading