diff --git a/packages/sqlite_async/CHANGELOG.md b/packages/sqlite_async/CHANGELOG.md index c00af79..9d294d4 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 (disabled by default). ## 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..8785646 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; @@ -43,6 +46,22 @@ 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 improves 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; + @Deprecated('Use default SqliteOptions constructor instead') const factory SqliteOptions.defaults() = SqliteOptions; @@ -54,6 +73,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 +84,7 @@ final class SqliteOptions { WebSqliteOptions? webSqliteOptions, bool? profileQueries, int? maxReaders, + int? preparedStatementCacheSize, }) { return SqliteOptions( journalMode: journalMode, @@ -73,6 +94,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..de6dc05 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(