Skip to content

Commit f1afdab

Browse files
committed
[feat] start sqflite_common_ffi_async based on sqlite_async
1 parent 4252869 commit f1afdab

23 files changed

+802
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# https://dart.dev/guides/libraries/private-files
2+
# Created by `dart pub`
3+
.dart_tool/
4+
5+
# Avoid committing pubspec.lock for library packages; see
6+
# https://dart.dev/guides/libraries/private-files#pubspeclock.
7+
pubspec.lock
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## 1.0.0
2+
3+
- Initial version.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# sqflite ffi async
2+
3+
[sqflite](https://pub.dev/packages/sqflite) based ffi implementation. Based
4+
on [`sqlite_async`](https://pub.dev/packages/sqlite_async). Thanks to [PowerSync](https://github.com/powersync-ja)
5+
6+
* Works on Linux, MacOS and Windows on both Flutter and Dart VM.
7+
* Works on iOS and Android (using [sqlite3_flutter_libs](https://pub.dev/packages/sqlite3_flutter_libs) - Thanks
8+
to [Simon Binder](https://github.com/simolus3))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# This file configures the static analysis results for your project (errors,
2+
# warnings, and lints).
3+
#
4+
# This enables the 'recommended' set of lints from `package:lints`.
5+
# This set helps identify many issues that may lead to problems when running
6+
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
7+
# style and format.
8+
#
9+
# If you want a smaller set of lints you can change this to specify
10+
# 'package:lints/core.yaml'. These are just the most critical lints
11+
# (the recommended set includes the core lints).
12+
# The core lints are also what is used by pub.dev for scoring packages.
13+
14+
include: package:lints/recommended.yaml
15+
16+
# Uncomment the following section to specify additional rules.
17+
18+
# linter:
19+
# rules:
20+
# - camel_case_types
21+
22+
# analyzer:
23+
# exclude:
24+
# - path/to/excluded/files/**
25+
26+
# For more information about the core and recommended set of lints, see
27+
# https://dart.dev/go/core-lints
28+
29+
# For additional information about configuring this file, see
30+
# https://dart.dev/guides/language/analysis-options
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/// Support for doing something awesome.
2+
///
3+
/// More dartdocs go here.
4+
library;
5+
6+
export 'src/platform.dart'
7+
show databaseFactoryFfiAsync, databaseFactoryFfiAsyncTest;
8+
export 'package:sqflite_common/sqflite.dart';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// ignore_for_file: depend_on_referenced_packages
2+
3+
export 'package:sqflite_common/src/database.dart';
4+
export 'package:sqflite_common/src/database_mixin.dart';
5+
export 'package:sqflite_common/src/factory_mixin.dart';
6+
export 'package:sqflite_common/src/exception.dart';
7+
export 'package:sqflite_common/src/constant.dart';
8+
export 'package:sqflite_common/src/transaction.dart';
9+
export 'package:sqflite_common_ffi/src/mixin/handler_mixin.dart';
10+
export 'package:sqflite_common/src/dev_utils.dart';
11+
export 'package:sqflite_common/src/batch.dart';
12+
export 'package:sqflite_common/src/cursor.dart';
13+
export 'package:sqflite_common/src/sql_command.dart';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export 'platform_io.dart' if (dart.library.js) 'platform_web.dart';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
2+
import 'package:sqflite_common_ffi_async/src/sqflite_ffi_async_factory.dart';
3+
4+
/// The Ffi database factory.
5+
DatabaseFactory get databaseFactoryFfiAsync => databaseFactoryFfiAsyncImpl;
6+
DatabaseFactory get databaseFactoryFfiAsyncTest =>
7+
databaseFactoryFfiAsyncTestImpl;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
2+
3+
/// The Ffi database factory.
4+
DatabaseFactory get databaseFactoryFfiAsync => throw UnsupportedError(
5+
'Unsupported on the web, use sqflite_common_ffi_web');
6+
7+
/// The Ffi database factory.
8+
DatabaseFactory get databaseFactoryFfiAsyncTest => throw UnsupportedError(
9+
'Unsupported on the web, use sqflite_common_ffi_web');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
import 'dart:collection';
2+
import 'dart:io';
3+
4+
import 'package:path/path.dart';
5+
6+
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
7+
import 'package:sqflite_common_ffi_async/src/sqflite_ffi_async_factory.dart';
8+
import 'package:sqflite_common_ffi_async/src/sqflite_ffi_async_transaction.dart';
9+
import 'package:sqlite_async/sqlite3.dart' as sqlite3;
10+
import 'package:sqlite_async/sqlite_async.dart' as sqlite_async;
11+
12+
import 'import.dart';
13+
14+
class SqfliteDatabaseAsync extends SqfliteDatabaseBase {
15+
static int _id = 0;
16+
late final asyncId = ++_id;
17+
late sqlite_async.SqliteDatabase _database;
18+
19+
SqfliteDatabaseAsync(super.openHelper, super.path);
20+
21+
@override
22+
Future<T> transaction<T>(Future<T> Function(Transaction txn) action,
23+
{bool? exclusive}) async {
24+
return _wrapFfiAsyncCall(() async {
25+
if (openTransaction is SqfliteFfiAsyncTransaction) {
26+
var sqfliteTxn = openTransaction as SqfliteFfiAsyncTransaction;
27+
var result = await action(sqfliteTxn);
28+
return result;
29+
}
30+
return await _database.writeTransaction((wc) async {
31+
var txn = SqfliteFfiAsyncTransaction(this, wc);
32+
var result = await action(txn);
33+
return result;
34+
});
35+
});
36+
}
37+
38+
@override
39+
Future<int> openDatabase() async {
40+
sqlite_async.SqliteOptions sqliteOptions;
41+
int maxReaders = sqlite_async.SqliteDatabase.defaultMaxReaders;
42+
var path = this.path;
43+
if (path == inMemoryDatabasePath) {
44+
var dir = await Directory.systemTemp.createTemp();
45+
path = this.path = join(dir.path, 'in_memory_$asyncId.db');
46+
}
47+
if (options?.readOnly ?? false) {
48+
if (!await factoryFfi.databaseExists(path)) {
49+
throw SqfliteDatabaseException(
50+
'read-only Database not found: $path', null);
51+
}
52+
sqliteOptions = sqlite_async.SqliteOptions(
53+
journalMode: null, journalSizeLimit: null, synchronous: null);
54+
} else {
55+
var dir = Directory(dirname(path));
56+
try {
57+
if (!dir.existsSync()) {
58+
// Create the directory if needed
59+
await dir.create(recursive: true);
60+
}
61+
} catch (e) {
62+
print('error checking directory $dir: $e');
63+
}
64+
sqliteOptions = sqlite_async.SqliteOptions.defaults();
65+
}
66+
final factory = sqlite_async.DefaultSqliteOpenFactory(
67+
path: path, sqliteOptions: sqliteOptions);
68+
69+
_database = sqlite_async.SqliteDatabase.withFactory(factory,
70+
maxReaders: maxReaders);
71+
//_database = sqlite_async.SqliteDatabase(path: path);
72+
return asyncId;
73+
}
74+
75+
Future<List<Map<String, Object?>>> _select(sqlite_async.SqliteReadContext wc,
76+
String sql, List<Object?>? arguments) async {
77+
var resultSet = await wc.getAll(sql, _fixArguments(arguments));
78+
return SqfliteResultSet(resultSet);
79+
}
80+
81+
@override
82+
Future<List<Map<String, Object?>>> txnRawQuery(
83+
SqfliteTransaction? txn, String sql, List<Object?>? arguments) async {
84+
return _wrapFfiAsyncCall(() {
85+
return _select(_readContext(txn), sql, arguments);
86+
});
87+
}
88+
89+
/// Execute a raw SELECT command by page.
90+
/// TODO not supported yet
91+
@override
92+
Future<SqfliteQueryCursor> txnRawQueryCursor(SqfliteTransaction? txn,
93+
String sql, List<Object?>? arguments, int pageSize) async {
94+
var results = await _select(_readContext(txn), sql, arguments);
95+
return SqfliteQueryCursor(this, txn, null, results);
96+
}
97+
98+
List<Object?> _fixArguments(List<Object?>? arguments) {
99+
return arguments ?? const <Object?>[];
100+
}
101+
102+
Future<T> _wrapFfiAsyncCall<T>(Future<T> Function() action) async {
103+
try {
104+
return await action();
105+
} catch (e) {
106+
throw ffiWrapAnyException(e);
107+
}
108+
}
109+
110+
sqlite_async.SqliteWriteContext _writeContext(SqfliteTransaction? txn) {
111+
return (txn as SqfliteFfiAsyncTransaction?)?.writeContext ?? _database;
112+
}
113+
114+
sqlite_async.SqliteReadContext _readContext(SqfliteTransaction? txn) =>
115+
_writeContext(txn);
116+
117+
Future<T> _writeTransaction<T>(SqfliteTransaction? txn,
118+
Future<T> Function(sqlite_async.SqliteWriteContext writeContext) action) {
119+
if (txn == null) {
120+
return _wrapFfiAsyncCall(() async {
121+
return await _database.writeTransaction(action);
122+
});
123+
} else {
124+
return _wrapFfiAsyncCall(() async {
125+
return await action((txn as SqfliteFfiAsyncTransaction).writeContext);
126+
});
127+
}
128+
}
129+
130+
@override
131+
Future<T> txnExecute<T>(
132+
SqfliteTransaction? txn, String sql, List<Object?>? arguments,
133+
{bool? beginTransaction}) {
134+
// devPrint('txnExecute $sql $arguments');
135+
return _writeTransaction<T>(txn, (wc) async {
136+
var result = await wc.execute(sql, _fixArguments(arguments));
137+
return result as T;
138+
});
139+
}
140+
141+
//}
142+
143+
Future<int> _insert(sqlite_async.SqliteWriteContext wc, String sql,
144+
List<Object?>? arguments) async {
145+
// Result is empty list
146+
await wc.execute(sql, _fixArguments(arguments));
147+
148+
var result =
149+
await wc.get('SELECT last_insert_rowid() as rowid, changes() as count');
150+
// devPrint('insert $result');
151+
var count = result['count'] as int;
152+
if (count > 0) {
153+
return result['rowid'] as int;
154+
} else {
155+
return 0;
156+
}
157+
}
158+
159+
@override
160+
Future<int> txnRawInsert(
161+
SqfliteTransaction? txn, String sql, List<Object?>? arguments) async {
162+
// devPrint('txnRawInsert $sql $arguments');
163+
return _writeTransaction<int>(txn, (wc) async {
164+
return _insert(wc, sql, arguments);
165+
});
166+
}
167+
168+
@override
169+
Future<List<Object?>> txnApplyBatch(
170+
SqfliteTransaction? txn, SqfliteBatch batch,
171+
{bool? noResult, bool? continueOnError}) {
172+
return _writeTransaction(txn, (wc) async {
173+
var results = <Object?>[];
174+
175+
void addResult(Object? result) {
176+
if (noResult != true) {
177+
results.add(result);
178+
}
179+
}
180+
181+
for (var operation in batch.operations) {
182+
try {
183+
switch (operation.type) {
184+
case SqliteSqlCommandType.insert:
185+
addResult(await _insert(wc, operation.sql, operation.arguments));
186+
break;
187+
case SqliteSqlCommandType.update:
188+
addResult(await _updateOrDelete(
189+
wc, operation.sql, operation.arguments));
190+
break;
191+
192+
case SqliteSqlCommandType.delete:
193+
addResult(await _updateOrDelete(
194+
wc, operation.sql, operation.arguments));
195+
break;
196+
case SqliteSqlCommandType.execute:
197+
await wc.execute(
198+
operation.sql, _fixArguments(operation.arguments));
199+
addResult(null);
200+
case SqliteSqlCommandType.query:
201+
addResult(await _select(wc, operation.sql, operation.arguments));
202+
break;
203+
}
204+
} catch (e) {
205+
if (continueOnError ?? false) {
206+
continue;
207+
} else {
208+
rethrow;
209+
}
210+
}
211+
}
212+
return results;
213+
});
214+
}
215+
216+
/// for Update sql query
217+
/// returns the update count
218+
@override
219+
Future<int> txnRawUpdate(
220+
SqfliteTransaction? txn, String sql, List<Object?>? arguments) =>
221+
_txnRawUpdateOrDelete(txn, sql, arguments);
222+
223+
/// for Delete sql query
224+
/// returns the delete count
225+
@override
226+
Future<int> txnRawDelete(
227+
SqfliteTransaction? txn, String sql, List<Object?>? arguments) =>
228+
_txnRawUpdateOrDelete(txn, sql, arguments);
229+
230+
Future<int> _txnRawUpdateOrDelete(
231+
SqfliteTransaction? txn, String sql, List<Object?>? arguments) {
232+
return _writeTransaction<int>(txn, (wc) async {
233+
// Result is empty list
234+
return await _updateOrDelete(wc, sql, arguments);
235+
});
236+
}
237+
238+
Future<int> _updateOrDelete(sqlite_async.SqliteWriteContext wc, String sql,
239+
List<Object?>? arguments) async {
240+
// Result is empty list
241+
await wc.execute(sql, _fixArguments(arguments));
242+
243+
var result = await wc.get('SELECT changes() as count');
244+
// devPrint('insert $result');
245+
return result['count'] as int;
246+
}
247+
248+
@override
249+
Future<void> close() async {
250+
await super.close();
251+
}
252+
253+
Future<void> _closeSqfliteAsyncDatabase() {
254+
return _database.close();
255+
}
256+
257+
@override
258+
Future<void> closeDatabase() {
259+
return _closeSqfliteAsyncDatabase();
260+
}
261+
}
262+
263+
class SqfliteResultSet extends ListBase<Map<String, Object?>> {
264+
final sqlite3.ResultSet resultSet;
265+
266+
@override
267+
int get length => resultSet.length;
268+
269+
SqfliteResultSet(this.resultSet);
270+
271+
@override
272+
Map<String, Object?> operator [](int index) {
273+
return resultSet[index];
274+
}
275+
276+
@override
277+
void operator []=(int index, Map<String, Object?> value) {
278+
throw UnsupportedError('Read-only');
279+
}
280+
281+
@override
282+
set length(int newLength) {
283+
throw UnsupportedError('Read-only');
284+
}
285+
}

0 commit comments

Comments
 (0)