Skip to content

Add sqflite_watchos: sqflite on the watch's own SQLite - #5

Merged
MAUstaoglu merged 6 commits into
flutterwatch:mainfrom
iamoguzhanarslantas:sqflite-watchos
Oct 24, 2026
Merged

MAUstaoglu merged 6 commits into
flutterwatch:mainfrom
iamoguzhanarslantas:sqflite-watchos

Conversation

@iamoguzhanarslantas

Copy link
Copy Markdown
Contributor

Summary

Adds sqflite_watchos 0.0.1, the watchOS implementation of sqflite, which the README listed under "Feasible but not yet ported".

  • watchOS ships libsqlite3. watchos/Package.swift links it, and Dart calls its C API over dart:ffi (lib/src/sqlite3.dart): no FMDB, no method channel.
  • sqflite's method protocol is answered in Dart (lib/src/handler.dart) with the behaviour of sqflite_common_ffi, the protocol's reference implementation outside a method channel: transaction ids (a call from outside the open transaction waits for it), transactionClosed when SQLite rolls a transaction back, paged cursors, batches with continueOnError, and single-instance databases.
  • registerWith sets sqflite_common's default factory from buildDatabaseFactory, as sqflite_darwin sets its method-channel factory. buildDatabaseFactory and SqfliteDatabaseException come from sqflite_common's src/ libraries, which sqflite_common_ffi imports the same way; sqflite_common: ^2.5.11 pins them.
  • Two native helpers: the Documents directory (copied once with strdup, since fileSystemRepresentation's buffer is only autoreleased) and sqlite3_libversion(), which also keeps libsqlite3 linked.
  • deleteDatabase also removes the -wal, -shm and -journal files, as sqflite_darwin does.
  • The root README gets the table row and drops sqflite from "Feasible but not yet ported"; tool/upstream_versions.yaml gets sqflite.

Verified

On the watchOS 27.0 Simulator (Apple Watch Series 12, 46 mm):

  • Upstream's example/integration_test/sqflite_test.dart passes, all 14 tests, unchanged but for the formatting CI checks. It runs with flutter-watchos test: it uses package:test's test, which flutter-watchos drive does not collect, so drive reports nothing and waits.
  • The example builds with both ffiSymbols in the export table.

Off the watch:

  • 35 host tests run real SQL against macOS's libsqlite3, through sqflite_common's Database API and the protocol below it.
  • Every tool/ check, dart format as CI runs it, and pub publish --dry-run are clean.

Not verified

  • A physical Apple Watch. The SQLite is the system's own, the same library the Simulator runs, but the package has not run on a device.

Notes for review

  • SQL runs on the isolate that calls it, synchronously inside each Future, as the README says; on iOS sqflite runs it on a native thread. That is fine for watch-sized data, but a heavy query holds up the frame it runs in.
  • example/lib/ is a small watch demo, because sqflite's own example is built on sqflite_example_common, which is not published.
  • Three commits: the package; the side-file deletion and the path copy, found in review; and the formatting of upstream's test.

🤖 Generated with Claude Code

iamoguzhanarslantas and others added 3 commits October 21, 2026 01:24
watchOS ships libsqlite3; watchos/Package.swift links it, and Dart calls its
C API over dart:ffi, resolved from the process. sqflite's method protocol
(open, close, execute, insert, update, query with paged cursors, batch with
noResult and continueOnError, transactions with transaction ids, database
files and bytes) is answered in Dart with sqflite_common_ffi's behaviour,
since method channels do not run on watchOS. registerWith makes the factory
sqflite's default, as sqflite_darwin does with its method-channel one.

Two FFI exports: the Documents directory, and sqlite3_libversion(), which
also keeps libsqlite3 linked. buildDatabaseFactory and
SqfliteDatabaseException come from sqflite_common's src/ libraries, as
sqflite_common_ffi imports them.

sqflite's own example app depends on the unpublished sqflite_example_common,
so the example's lib/ is a small watch demo; the official integration test is
upstream's, unchanged. It uses package:test's test, which drive does not
collect, so it runs with flutter-watchos test.

The root README lists the package and drops sqflite from "Feasible but not
yet ported"; tool/upstream_versions.yaml gains sqflite 2.4.4+2.

Checked: 34 host tests running real SQL against macOS's libsqlite3; the
official integration test (14 tests) on the watchOS 27.0 Simulator; the
example there saving and listing rows. Not yet checked on a physical watch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
deleteDatabase removed the database file alone. sqflite_darwin also
removes its -wal, -shm and -journal files, and a write-ahead log left
behind would be replayed into a new database created at the same path.

The databases path is now a copy native owns: fileSystemRepresentation's
buffer is only autoreleased, whatever keeps the string alive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Its content is upstream's; only the layout changes, so that CI's
dart format step passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mehmet-nettle

This comment was marked as duplicate.

@MAUstaoglu MAUstaoglu left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this careful port. The protocol matches sqflite_darwin: open/close, transactions, batch, cursors, readOnly, paths, and the -wal/-shm/-journal cleanup. The Dart tests run real SQL, which is great. Four things before we can merge:

Must-fix

  1. CI: test/sqflite_watchos_test.dart:32 and :479 open /usr/lib/libsqlite3.dylib, but the analyze + test job runs on ubuntu-latest, so every test fails in setUpAll. Please load libsqlite3.so.0 when not on macOS, or fake the bindings (AUTHORING §1).
  2. Licensing: lib/src/handler.dart is adapted from sqflite_common_ffi's sqflite_ffi_impl.dart (same structure, messages and closed-id logic), but its header (:1-13) credits only FlutterWatch. Please:
    • add the Tekartik BSD-2 notice;
    • change BSD-3 to BSD-2 at README.md:69, to match LICENSE;
    • name sqflite_common_ffi in PORTING_REPORT.md:20.
  3. lib/src/sqlite3.dart:432 throws for bool and plain List<int> arguments. sqflite_darwin and sqflite_common_ffi bind them as 0/1 and as a blob.
  4. tool/upstream_versions.yaml:28: sqflite's latest release is 2.4.5 (21 Oct), not 2.4.4+2.

Nice-to-have

  • Set a busy timeout. FMDB waits 2 s on iOS; here a second connection fails at once (lib/src/sqlite3.dart:242).
  • :memory: should never be single-instance, as in sqflite_darwin (lib/src/handler.dart:209).
  • Use sqflite_common: ^2.5.14, which fixes a hang when a cursor is closed inside a transaction, capped at <2.6.0 because of the src/ imports.
  • Extend SqflitePlatform, as AUTHORING §1 asks.
  • All SQL runs on the UI isolate. Consider a background isolate, and a run on a physical watch, before 0.1.0.
  • test/sqflite_watchos_test.dart:52-53 compares a factory with itself, so it can't fail.
  • Small fixes:
    • example/pubspec.yaml:12 should be ^2.4.4;
    • README.md:61 says nothing is unsupported, but the deprecated Sqflite.* helpers still go through the method channel;
    • PORTING_REPORT.md:11 uses ○ for the wrong state.

We'll start the CI run after your update. Thank you!

@MAUstaoglu
MAUstaoglu dismissed mehmet-nettle’s stale review October 23, 2026 20:47

Posted from the wrong account; superseded by the maintainer review.

iamoguzhanarslantas and others added 3 commits October 24, 2026 00:04
From the review of PR flutterwatch#5:

* The host tests open the host's system SQLite: /usr/lib/libsqlite3.dylib
  on macOS, libsqlite3.so.0 on CI's Linux runner, where every test failed
  in setUpAll.
* lib/src/handler.dart is adapted from sqflite_common_ffi's
  sqflite_ffi_impl.dart and now says so, with Alexandre Roux Tekartik's
  BSD-2-Clause notice. The README names BSD-2-Clause, as LICENSE does, and
  PORTING_REPORT names sqflite_common_ffi as a source.
* A bool argument binds as 1 or 0, and a plain List<int> as a blob, as
  sqflite_darwin and sqflite_common_ffi bind them; both threw.
* tool/upstream_versions.yaml: sqflite 2.4.5, the latest release.
* Each connection waits up to 2 seconds for another connection's lock,
  FMDB's default on iOS; a second connection failed at once.
* An in-memory database is never a single instance, as in sqflite_darwin.
* sqflite_common ">=2.5.14 <2.6.0": 2.5.14 fixes a hang when a cursor is
  closed inside a transaction, and the src/ imports stop below the next
  minor.
* SqfliteWatchos extends SqflitePlatform, as AUTHORING asks; its factory
  is databaseFactoryWatchos, since SqflitePlatform has an instance
  databaseFactory.
* The registerWith test compared a factory with itself; it uses a second
  factory now. The example names sqflite ^2.4.4. The README lists the
  deprecated Sqflite.* debug helpers, which call the method channel
  directly, and PORTING_REPORT no longer marks the unified demo with the
  "no upstream test" sign.

Checked: 39 host tests on macOS (5 new; four fail on the old code), and
upstream's integration test, 14 tests, on the watchOS 27.0 Simulator. The
host tests have not run on Linux here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The root README's "Feasible but not yet ported" paragraph: main drops
wakelock_plus from it, this branch drops sqflite; both are dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI's first run on Linux passed 38 of 39 host tests. The one that failed
waited for a locked database: Ubuntu's libsqlite3 failed the second
connection's insert with SQLITE_BUSY after 39 ms instead of waiting out
the timeout, though PRAGMA busy_timeout read 2000 there too. The timeout
is still checked on both; the wait itself only on macOS, whose SQLite is
the watch's.

The README also says that sqflite_common warns once per type, in debug,
about bool and List<int> arguments, as the CI log shows it does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@iamoguzhanarslantas

Copy link
Copy Markdown
Contributor Author

Thanks for the careful review. Pushed bddc6d1, then 2e8fa39 after the first CI run:

Must-fix

  1. The host tests open libsqlite3.so.0 off macOS (/usr/lib/libsqlite3.dylib on macOS). On CI's first run, 38 of 39 passed on Linux. The one that failed was the new busy-timeout test: Ubuntu's libsqlite3 returned SQLITE_BUSY after 39 ms instead of waiting, even though PRAGMA busy_timeout read 2000. 2e8fa39 still checks the timeout value everywhere, but checks the actual wait only on macOS, whose SQLite is the one the watch uses.
  2. lib/src/handler.dart now carries Alexandre Roux Tekartik's BSD-2 notice and says it is adapted from sqflite_common_ffi's sqflite_ffi_impl.dart. The README says BSD-2-Clause, and PORTING_REPORT names sqflite_common_ffi 2.4.4 as a source.
  3. A bool binds as 1/0 and a plain List<int> as a blob, as in sqflite_darwin and sqflite_common_ffi. There are new tests for both.
  4. tool/upstream_versions.yaml now has sqflite 2.4.5.

Nice-to-have

  • Busy timeout: 2 s per connection, FMDB's default. A test checks PRAGMA busy_timeout and that a locked database is waited for.
  • :memory: is never a single instance, as in sqflite_darwin. There is a new protocol-level test.
  • sqflite_common: ">=2.5.14 <2.6.0".
  • SqfliteWatchos extends SqflitePlatform. Its factory is now databaseFactoryWatchos, because SqflitePlatform has an instance databaseFactory.
  • The registerWith test now uses a second factory.
  • Small fixes: example ^2.4.4; the README lists the deprecated Sqflite.* debug helpers, which call the method channel directly; the unified-demo row no longer uses ○.
  • Background isolate and a physical-watch run: agreed for before 0.1.0, not in this PR.

Checked: 39 host tests on macOS, four of which fail on the old code. Upstream's integration test (14 tests) passes on the watchOS 27.0 Simulator.

🤖 Generated with Claude Code

@MAUstaoglu MAUstaoglu left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @iamoguzhanarslantas! Everything is resolved: we verified each fix, and CI is green. Merging as the staged 0.0.1. The follow-ups before 0.1.0 (SQL off the UI isolate, a physical-watch run, the busy-wait README note and the busy test's comment) are tracked in #20.

@MAUstaoglu
MAUstaoglu merged commit bee39eb into flutterwatch:main Oct 24, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants