From ff6a17d8254e62ba83d4d0efd5600ed3d13198aa Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Wed, 1 Jul 2026 15:44:50 +0200 Subject: [PATCH 1/4] Allow caching prepared statements --- packages/sqlite_async/CHANGELOG.md | 1 + .../native/database/native_sqlite_database.dart | 3 ++- packages/sqlite_async/lib/src/sqlite_options.dart | 14 ++++++++++++++ .../lib/src/web/web_sqlite_open_factory.dart | 3 ++- packages/sqlite_async/pubspec.yaml | 2 +- .../test/utils/abstract_test_utils.dart | 9 +++++++-- .../sqlite_async/test/utils/web_test_utils.dart | 2 +- 7 files changed, 28 insertions(+), 6 deletions(-) diff --git a/packages/sqlite_async/CHANGELOG.md b/packages/sqlite_async/CHANGELOG.md index c00af79..66305e6 100644 --- a/packages/sqlite_async/CHANGELOG.md +++ b/packages/sqlite_async/CHANGELOG.md @@ -3,6 +3,7 @@ - Native: Add the `NativeSqliteOpenFactory.beforeOpen` method, which can be overridden to configure SQLite asynchronously before opening databases. - Web: Upgrade `package:sqlite3_web` to flush IndexedDB writes automatically, deprecate `flush()`. +- Add `SqliteOptions.preparedStatementCacheSize` to cache prepared statements. ## 0.14.3 diff --git a/packages/sqlite_async/lib/src/native/database/native_sqlite_database.dart b/packages/sqlite_async/lib/src/native/database/native_sqlite_database.dart index e3c1202..ea6f14d 100644 --- a/packages/sqlite_async/lib/src/native/database/native_sqlite_database.dart +++ b/packages/sqlite_async/lib/src/native/database/native_sqlite_database.dart @@ -272,7 +272,8 @@ final class NativeSqliteDatabaseImpl extends SqliteDatabaseImpl { [ for (var i = 0; i < maxReaders; i++) openConnection(isWriter: false) ], - // TODO: Option to enable prepared statement cache. + preparedStatementCacheSize: + openFactory.sqliteOptions.preparedStatementCacheSize, ); }, ); diff --git a/packages/sqlite_async/lib/src/sqlite_options.dart b/packages/sqlite_async/lib/src/sqlite_options.dart index d722f4e..08ee5ac 100644 --- a/packages/sqlite_async/lib/src/sqlite_options.dart +++ b/packages/sqlite_async/lib/src/sqlite_options.dart @@ -43,6 +43,16 @@ final class SqliteOptions { /// additional readers on the web. final int maxReaders; + /// The amount of prepared statements to cache. + /// + /// For each SQLite connection, up to [preparedStatementCacheSize] statements + /// will be cached in an LRU cache. This allows re-using prepared statements + /// instead of parsing and optimizing them again, which improve performance + /// for frequently-used statements like watched queries. + /// + /// This is currently disabled by default (set to `0`). + final int preparedStatementCacheSize; + @Deprecated('Use default SqliteOptions constructor instead') const factory SqliteOptions.defaults() = SqliteOptions; @@ -54,6 +64,7 @@ final class SqliteOptions { this.lockTimeout = const Duration(seconds: 30), this.profileQueries = _profileQueriesByDefault, this.maxReaders = defaultMaxReaders, + this.preparedStatementCacheSize = 0, }); /// Creates a new options instance by applying overrides from parameters. @@ -64,6 +75,7 @@ final class SqliteOptions { WebSqliteOptions? webSqliteOptions, bool? profileQueries, int? maxReaders, + int? preparedStatementCacheSize, }) { return SqliteOptions( journalMode: journalMode, @@ -73,6 +85,8 @@ final class SqliteOptions { lockTimeout: lockTimeout, profileQueries: profileQueries ?? this.profileQueries, maxReaders: maxReaders ?? this.maxReaders, + preparedStatementCacheSize: + preparedStatementCacheSize ?? this.preparedStatementCacheSize, ); } diff --git a/packages/sqlite_async/lib/src/web/web_sqlite_open_factory.dart b/packages/sqlite_async/lib/src/web/web_sqlite_open_factory.dart index bcd6592..eaf875c 100644 --- a/packages/sqlite_async/lib/src/web/web_sqlite_open_factory.dart +++ b/packages/sqlite_async/lib/src/web/web_sqlite_open_factory.dart @@ -64,7 +64,8 @@ base class WebSqliteOpenFactory extends InternalOpenFactory { /// can customize the behavior where needed. Future connectToWorker( WebSqlite sqlite, String name) { - return sqlite.connectToRecommended(name); + return sqlite.connectToRecommended(name, + preparedStatementCacheSize: sqliteOptions.preparedStatementCacheSize); } /// Currently this only uses the SQLite Web WASM implementation. diff --git a/packages/sqlite_async/pubspec.yaml b/packages/sqlite_async/pubspec.yaml index d43e9c6..eaa54a1 100644 --- a/packages/sqlite_async/pubspec.yaml +++ b/packages/sqlite_async/pubspec.yaml @@ -15,7 +15,7 @@ topics: dependencies: sqlite3: ^3.5.0 sqlite3_web: ^0.9.4 - sqlite3_connection_pool: ^0.2.4 + sqlite3_connection_pool: ^0.2.7 async: ^2.10.0 collection: ^1.17.0 meta: ^1.10.0 diff --git a/packages/sqlite_async/test/utils/abstract_test_utils.dart b/packages/sqlite_async/test/utils/abstract_test_utils.dart index 29be184..bf44dff 100644 --- a/packages/sqlite_async/test/utils/abstract_test_utils.dart +++ b/packages/sqlite_async/test/utils/abstract_test_utils.dart @@ -7,14 +7,14 @@ abstract class AbstractTestUtils { Future openDatabaseForSingleConnection(); Future testFactory( - {String? path, SqliteOptions options = const SqliteOptions()}) async { + {String? path, SqliteOptions options = defaultTestOptions}) async { return SqliteOpenFactory(path: path ?? dbPath(), options: options); } /// Creates a SqliteDatabaseConnection Future setupDatabase({ String? path, - SqliteOptions options = const SqliteOptions(), + SqliteOptions options = defaultTestOptions, }) async { final factory = await testFactory(path: path, options: options); return SqliteDatabase.withFactory(factory); @@ -22,4 +22,9 @@ abstract class AbstractTestUtils { /// Deletes any DB data Future cleanDb({required String path}); + +// Enable prepared statement cache in tests, we want to enable that option by +// default eventually. + static const defaultTestOptions = + SqliteOptions(preparedStatementCacheSize: 64); } diff --git a/packages/sqlite_async/test/utils/web_test_utils.dart b/packages/sqlite_async/test/utils/web_test_utils.dart index 34a4469..5542770 100644 --- a/packages/sqlite_async/test/utils/web_test_utils.dart +++ b/packages/sqlite_async/test/utils/web_test_utils.dart @@ -57,7 +57,7 @@ class TestUtils extends AbstractTestUtils { @override Future testFactory({ String? path, - SqliteOptions options = const SqliteOptions(), + SqliteOptions options = AbstractTestUtils.defaultTestOptions, }) async { await _isInitialized; return super.testFactory( From 8fc9df95c43aaa6ac3612071c132f01149c7f52e Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Wed, 22 Jul 2026 11:42:00 +0200 Subject: [PATCH 2/4] Document risks of statement cache --- packages/sqlite_async/CHANGELOG.md | 2 +- packages/sqlite_async/lib/src/sqlite_options.dart | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/sqlite_async/CHANGELOG.md b/packages/sqlite_async/CHANGELOG.md index 66305e6..9d294d4 100644 --- a/packages/sqlite_async/CHANGELOG.md +++ b/packages/sqlite_async/CHANGELOG.md @@ -3,7 +3,7 @@ - Native: Add the `NativeSqliteOpenFactory.beforeOpen` method, which can be overridden to configure SQLite asynchronously before opening databases. - Web: Upgrade `package:sqlite3_web` to flush IndexedDB writes automatically, deprecate `flush()`. -- Add `SqliteOptions.preparedStatementCacheSize` to cache prepared statements. +- Add `SqliteOptions.preparedStatementCacheSize` to cache prepared statements (disabled by default). ## 0.14.3 diff --git a/packages/sqlite_async/lib/src/sqlite_options.dart b/packages/sqlite_async/lib/src/sqlite_options.dart index 08ee5ac..bfd847e 100644 --- a/packages/sqlite_async/lib/src/sqlite_options.dart +++ b/packages/sqlite_async/lib/src/sqlite_options.dart @@ -1,3 +1,6 @@ +/// @docImport 'package:sqlite3/common.dart'; +library; + final class WebSqliteOptions { final String workerUri; final String wasmUri; @@ -50,6 +53,12 @@ final class SqliteOptions { /// instead of parsing and optimizing them again, which improve performance /// for frequently-used statements like watched queries. /// + /// Be aware that active prepared statements may alter the operation of the + /// database, for instance because they might keep some database resources + /// locked. Statements are [CommonPreparedStatement.reset] before being stored + /// in the cache, but enabling a statement cache is still something that + /// should be tested carefully. + /// /// This is currently disabled by default (set to `0`). final int preparedStatementCacheSize; From 7f28dae576a483973349a2d87b2353d106676f04 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Wed, 22 Jul 2026 11:46:00 +0200 Subject: [PATCH 3/4] typo --- packages/sqlite_async/lib/src/sqlite_options.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sqlite_async/lib/src/sqlite_options.dart b/packages/sqlite_async/lib/src/sqlite_options.dart index bfd847e..8785646 100644 --- a/packages/sqlite_async/lib/src/sqlite_options.dart +++ b/packages/sqlite_async/lib/src/sqlite_options.dart @@ -50,7 +50,7 @@ final class SqliteOptions { /// /// For each SQLite connection, up to [preparedStatementCacheSize] statements /// will be cached in an LRU cache. This allows re-using prepared statements - /// instead of parsing and optimizing them again, which improve performance + /// instead of parsing and optimizing them again, which improves performance /// for frequently-used statements like watched queries. /// /// Be aware that active prepared statements may alter the operation of the From b5ee703e9886f824756fd80d9cef61f4608bd2dd Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Wed, 22 Jul 2026 11:49:39 +0200 Subject: [PATCH 4/4] fix indent for comments --- packages/sqlite_async/test/utils/abstract_test_utils.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sqlite_async/test/utils/abstract_test_utils.dart b/packages/sqlite_async/test/utils/abstract_test_utils.dart index bf44dff..de6dc05 100644 --- a/packages/sqlite_async/test/utils/abstract_test_utils.dart +++ b/packages/sqlite_async/test/utils/abstract_test_utils.dart @@ -23,8 +23,8 @@ abstract class AbstractTestUtils { /// Deletes any DB data Future cleanDb({required String path}); -// Enable prepared statement cache in tests, we want to enable that option by -// default eventually. + // Enable prepared statement cache in tests, we want to enable that option by + // default eventually. static const defaultTestOptions = SqliteOptions(preparedStatementCacheSize: 64); }