sqflite_common: the pure-Dart sqflite API
package:sqflite_common defines the whole sqflite API (DatabaseFactory,
Database, Transaction, Batch, OpenDatabaseOptions, ...) and the shared
implementation, with no Flutter dependency. It contains no SQLite engine: an
implementation package supplies a DatabaseFactory (sqflite on
Android/iOS/macOS, sqflite_common_ffi on desktop/VM/tests,
sqflite_common_ffi_web on the web). Depend on sqflite_common in packages
that must stay Flutter-free and accept a factory from the caller.
import 'package:sqflite_common/sqlite_api.dart';
class NoteStore {
NoteStore(this.factory, this.path);
final DatabaseFactory factory;
final String path;
Future<Database> open() => factory.openDatabase(
path,
options: OpenDatabaseOptions(
version: 1,
onCreate: (db, _) => db.execute(
'CREATE TABLE Note (id INTEGER PRIMARY KEY, content TEXT)',
),
),
);
}
Guidelines
Which import gives what
package:sqflite_common/sqlite_api.dart: the types only. DatabaseFactory
(openDatabase(path, {options}), getDatabasesPath(),
setDatabasesPath(), deleteDatabase(), databaseExists(),
readDatabaseBytes(), writeDatabaseBytes()), DatabaseExecutor
(execute, rawInsert/insert, rawQuery/query,
rawQueryCursor/queryCursor, rawUpdate/update,
rawDelete/delete, batch(), database), Database (path,
isOpen, close(), transaction(), readTransaction()), Transaction,
Batch (commit, apply, length, same mutators as the executor),
QueryCursor, OpenDatabaseOptions, ConflictAlgorithm,
DatabaseException, inMemoryDatabasePath, the callback typedefs
(OnDatabaseCreateFn, OnDatabaseVersionChangeFn, OnDatabaseOpenFn,
OnDatabaseConfigureFn), onDatabaseDowngradeDelete,
onDatabaseVersionChangeError, and the extensions
SqfliteDatabaseExecutorExt (getVersion, setVersion),
SqfliteDatabaseExt (setJournalMode),
SqfliteDatabaseExecutorIterateExt (queryIterate, rawQueryIterate),
SqfliteDatabaseFactorySandboxExtension (sandbox),
SqfliteSqlCommand + SqfliteSqlCommandExecutorExt, sqfliteLogLevel*
constants. Use this import in libraries.
package:sqflite_common/sqflite.dart: everything above plus the global
databaseFactory getter/setter, databaseFactoryOrNull, and the global
functions openDatabase, openReadOnlyDatabase, getDatabasesPath,
deleteDatabase, databaseExists that forward to databaseFactory. Only
applications should rely on the global; a library that reads it forces the
app to have set it. package:sqflite/sqflite.dart and
package:sqflite_common_ffi/sqflite_ffi.dart both re-export this library,
so do not import it next to them (unnecessary_import).
package:sqflite_common/sql.dart: ConflictAlgorithm, escapeName,
unescapeName.
package:sqflite_common/utils/utils.dart: firstIntValue(rows),
firstStringValue(rows), hex(bytes), setLockWarningInfo(duration:, callback:), sqlCountColumn ('COUNT(*)'). There is no Sqflite class
here; that class lives in package:sqflite.
package:sqflite_common/sqflite_logger.dart: SqfliteDatabaseFactoryLogger
(its constructor is @experimental, expect an experimental_member_use
analyzer warning), SqfliteLoggerOptions, SqfliteDatabaseFactoryLoggerType (all,
invoke), the event classes (SqfliteLoggerSqlEvent,
SqfliteLoggerBatchEvent, SqfliteLoggerDatabaseOpenEvent,
SqfliteLoggerDatabaseCloseEvent, SqfliteLoggerDatabaseDeleteEvent,
SqfliteLoggerInvokeEvent), SqfliteLoggerEventExt.dump() and
DatabaseFactoryLoggerDebugExt.debugQuickLoggerWrapper().
package:sqflite_common/sqflite_dev.dart: deprecated dev-only
setLogLevel / setOptions (SqfliteDatabaseFactoryDev) and
SqfliteOptions. Do not ship code that uses it.
- Never import
package:sqflite_common/src/... from user code.
Designing a Flutter-free package
- Take a
DatabaseFactory (or a Database) as a constructor or function
parameter. The Flutter app passes databaseFactory from package:sqflite,
the CLI or test passes databaseFactoryFfi from sqflite_common_ffi.
- Take the database
path as a parameter too: getDatabasesPath() is only
meaningful for the sqflite plugin; on other implementations the app should
compute a location (path_provider, a CLI argument, a temp dir).
inMemoryDatabasePath (':memory:') works everywhere.
- Type your SQL-facing functions on
DatabaseExecutor so they work with both
a Database and a Transaction.
- Opening returns the same instance for the same path while
singleInstance
is true (default); it is forced to false for in-memory databases.
databaseFactory throws StateError('databaseFactory not initialized')
until an implementation sets it; the setter rejects factories that are not
sqflite implementations (ArgumentError) and prints a warning when
changing an already set factory.
factory.sandbox(path: root) returns a DatabaseFactory restricted to
root (relative paths resolved under it, absolute paths must be inside,
getDatabasesPath() returns root); it works over any implementation and
is never nested twice.
Statements, transactions, batches (shared semantics)
- One statement per call; bind with
? and an argument list; values are
int, num, String, Uint8List or null (bool, DateTime, List,
Map are not supported). Results are read-only maps.
db.transaction((txn) async {...}) commits when the callback returns and
rolls back (and rethrows) when it throws. Use only txn inside. Callbacks
onCreate / onUpgrade / onDowngrade are already in a transaction.
db.batch() collects operations and commit() runs them in one call in a
transaction (noResult: true, continueOnError: true); a batch created
from a Transaction is committed with it.
queryIterate / rawQueryIterate (or queryCursor + moveNext() +
close()) stream large results with a bufferSize (default 100).
DatabaseException helpers: isNoSuchTableError, isSyntaxError,
isUniqueConstraintError, isNotNullConstraintError,
isDuplicateColumnError, isOpenFailedError, isDatabaseClosedError,
isReadOnlyError, getResultCode().
Examples
Shared repository, used from Flutter and from a Dart test
// package my_data (depends on sqflite_common only)
import 'package:sqflite_common/sqlite_api.dart';
import 'package:sqflite_common/utils/utils.dart';
class TodoRepository {
TodoRepository({required this.factory, required this.path});
final DatabaseFactory factory;
final String path;
Future<Database>? _db;
Future<Database> get db => _db ??= factory.openDatabase(
path,
options: OpenDatabaseOptions(
version: 1,
onConfigure: (db) => db.execute('PRAGMA foreign_keys = ON'),
onCreate: (db, _) => db.execute(
'CREATE TABLE Todo (id INTEGER PRIMARY KEY, title TEXT NOT NULL, done INTEGER NOT NULL DEFAULT 0)',
),
),
);
Future<int> add(String title) async =>
(await db).insert('Todo', {'title': title});
Future<int> count(DatabaseExecutor executor) async =>
firstIntValue(await executor.query('Todo', columns: [sqlCountColumn])) ?? 0;
Future<void> markAllDone() async {
final database = await db;
await database.transaction((txn) async {
final pending = await count(txn);
if (pending > 0) {
await txn.update('Todo', {'done': 1});
}
});
}
Future<void> close() async {
await (await db).close();
_db = null;
}
}
// test/todo_repository_test.dart (dev_dependency: sqflite_common_ffi)
// sqflite_ffi.dart re-exports the sqflite_common API (inMemoryDatabasePath...).
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
import 'package:test/test.dart';
import 'package:my_data/todo_repository.dart';
void main() {
setUpAll(sqfliteFfiInit);
test('count and markAllDone', () async {
final repo = TodoRepository(
factory: databaseFactoryFfi,
path: inMemoryDatabasePath,
);
await repo.add('a');
expect(await repo.count(await repo.db), 1);
await repo.markAllDone();
expect(await (await repo.db).query('Todo', where: 'done = 1'), hasLength(1));
await repo.close();
});
}
In the Flutter app: TodoRepository(factory: databaseFactory, path: join(await getDatabasesPath(), 'todo.db')) with package:sqflite/sqflite.dart.
Using the global factory in an application
import 'package:sqflite_common/sqflite.dart';
/// The app (not a library) sets databaseFactory first, e.g.
/// `databaseFactory = databaseFactoryFfi;` from sqflite_common_ffi.
Future<int> countRows(String table) async {
final db = await openDatabase(inMemoryDatabasePath);
try {
await db.execute('CREATE TABLE $table (id INTEGER PRIMARY KEY)');
final rows = await db.rawQuery('SELECT COUNT(*) FROM $table');
return rows.first.values.first as int;
} finally {
await db.close();
}
}
Sandboxed factory for tests or per-user data
import 'package:sqflite_common/sqlite_api.dart';
Future<Database> openUserDb(DatabaseFactory factory, String userRoot) async {
final userFactory = factory.sandbox(path: userRoot);
// Relative to userRoot; '/etc/x.db' would throw ArgumentError.
return userFactory.openDatabase('cache.db');
}
Logging every statement of any factory
import 'package:sqflite_common/sqflite_logger.dart';
import 'package:sqflite_common/sqlite_api.dart';
// ignore: experimental_member_use
DatabaseFactory withLogs(DatabaseFactory factory) => SqfliteDatabaseFactoryLogger(
factory,
options: SqfliteLoggerOptions(
type: SqfliteDatabaseFactoryLoggerType.all,
log: (event) {
if (event is SqfliteLoggerSqlEvent) {
print('${event.type.name}: ${event.sql} ${event.arguments ?? ''}'
'${event.error != null ? ' error: ${event.error}' : ''}');
} else if (event is SqfliteLoggerBatchEvent) {
for (final op in event.operations) {
print('batch ${op.type.name}: ${op.sql} ${op.arguments ?? ''}');
}
} else {
event.dump();
}
},
),
);
Prepared commands and iteration
import 'package:sqflite_common/sqlite_api.dart';
final _byDone = SqfliteSqlCommand.query('Todo', where: 'done = ?', whereArgs: [0]);
final _insert = SqfliteSqlCommand.insert('Todo', {'title': 'x', 'done': 0});
Future<List<String>> pendingTitles(DatabaseExecutor executor) async {
final titles = <String>[];
await _byDone.iterate(executor, bufferSize: 50, onRow: (row) {
titles.add(row['title'] as String);
return true;
});
return titles;
}
Future<int> insertOne(DatabaseExecutor executor) => _insert.insert(executor);
Future<void> walk(DatabaseExecutor executor) => executor.queryIterate(
'Todo',
orderBy: 'id',
onRow: (row) async {
print(row);
return row['id'] != 100; // stop at id 100
},
);
Reading the schema version and enabling WAL
import 'package:sqflite_common/sqlite_api.dart';
Future<Database> openWithWal(DatabaseFactory factory, String path) =>
factory.openDatabase(
path,
options: OpenDatabaseOptions(
version: 2,
onConfigure: (db) async {
await db.setJournalMode('WAL');
if (await db.getVersion() == 0) {
// New file: settings that must precede table creation.
await db.execute('PRAGMA auto_vacuum = 2');
}
},
onCreate: (db, v) => db.execute('CREATE TABLE T (id INTEGER PRIMARY KEY)'),
onUpgrade: (db, old, _) async {
if (old < 2) await db.execute('ALTER TABLE T ADD name TEXT');
},
onDowngrade: onDatabaseDowngradeDelete,
),
);
Common mistakes
- Depending on
package:sqflite in a package that also runs on the Dart VM
or in dart test; depend on sqflite_common and inject the factory.
- Importing
package:sqflite_common/sqflite.dart in a library only for the
types; use sqlite_api.dart so the library does not read the global.
- Calling
openDatabase(...) (global) before the app set databaseFactory
(StateError: databaseFactory not initialized).
- Assigning a hand-written
DatabaseFactory implementation to
databaseFactory: the setter only accepts sqflite implementations. Wrap
with SqfliteDatabaseFactoryLogger or use sandbox() instead.
- Using
db inside db.transaction((txn) ...), or db.transaction() inside
onCreate / onUpgrade.
- Relying on
getDatabasesPath() from a non-plugin factory for real data;
pass an explicit path.
More
Implementation packages: sqflite (Flutter, Android/iOS/macOS),
sqflite_common_ffi (desktop, VM, tests), sqflite_common_ffi_web (web);
each ships its own skills. Docs in the repository: doc/sqflite_iterate.md,
doc/sqflite_sql_command.md, doc/sqflite_logger.md,
doc/method_call_protocol.md.
1---2name: sqflite-common-api3description: Use when writing Dart code against the sqflite API without depending on Flutter, or when a shared package must work with sqflite (mobile), sqflite_common_ffi (desktop/VM/tests) and sqflite_common_ffi_web: the DatabaseFactory, Database, Transaction, Batch, DatabaseExecutor, OpenDatabaseOptions, ConflictAlgorithm, DatabaseException, QueryCursor types from package:sqflite_common/sqlite_api.dart, the global databaseFactory / openDatabase from sqflite_common/sqflite.dart, sql.dart escapeName, utils/utils.dart firstIntValue, sqflite_logger.dart SqfliteDatabaseFactoryLogger, sandbox(), SqfliteSqlCommand, queryIterate.4---56# sqflite_common: the pure-Dart sqflite API78`package:sqflite_common` defines the whole sqflite API (`DatabaseFactory`,9`Database`, `Transaction`, `Batch`, `OpenDatabaseOptions`, ...) and the shared10implementation, with no Flutter dependency. It contains no SQLite engine: an11implementation package supplies a `DatabaseFactory` (`sqflite` on12Android/iOS/macOS, `sqflite_common_ffi` on desktop/VM/tests,13`sqflite_common_ffi_web` on the web). Depend on `sqflite_common` in packages14that must stay Flutter-free and accept a factory from the caller.1516```dart17import 'package:sqflite_common/sqlite_api.dart';1819class NoteStore {20 NoteStore(this.factory, this.path);2122 final DatabaseFactory factory;23 final String path;2425 Future<Database> open() => factory.openDatabase(26 path,27 options: OpenDatabaseOptions(28 version: 1,29 onCreate: (db, _) => db.execute(30 'CREATE TABLE Note (id INTEGER PRIMARY KEY, content TEXT)',31 ),32 ),33 );34}35```3637## Guidelines3839### Which import gives what4041* `package:sqflite_common/sqlite_api.dart`: the types only. `DatabaseFactory`42 (`openDatabase(path, {options})`, `getDatabasesPath()`,43 `setDatabasesPath()`, `deleteDatabase()`, `databaseExists()`,44 `readDatabaseBytes()`, `writeDatabaseBytes()`), `DatabaseExecutor`45 (`execute`, `rawInsert`/`insert`, `rawQuery`/`query`,46 `rawQueryCursor`/`queryCursor`, `rawUpdate`/`update`,47 `rawDelete`/`delete`, `batch()`, `database`), `Database` (`path`,48 `isOpen`, `close()`, `transaction()`, `readTransaction()`), `Transaction`,49 `Batch` (`commit`, `apply`, `length`, same mutators as the executor),50 `QueryCursor`, `OpenDatabaseOptions`, `ConflictAlgorithm`,51 `DatabaseException`, `inMemoryDatabasePath`, the callback typedefs52 (`OnDatabaseCreateFn`, `OnDatabaseVersionChangeFn`, `OnDatabaseOpenFn`,53 `OnDatabaseConfigureFn`), `onDatabaseDowngradeDelete`,54 `onDatabaseVersionChangeError`, and the extensions55 `SqfliteDatabaseExecutorExt` (`getVersion`, `setVersion`),56 `SqfliteDatabaseExt` (`setJournalMode`),57 `SqfliteDatabaseExecutorIterateExt` (`queryIterate`, `rawQueryIterate`),58 `SqfliteDatabaseFactorySandboxExtension` (`sandbox`),59 `SqfliteSqlCommand` + `SqfliteSqlCommandExecutorExt`, `sqfliteLogLevel*`60 constants. Use this import in libraries.61* `package:sqflite_common/sqflite.dart`: everything above plus the global62 `databaseFactory` getter/setter, `databaseFactoryOrNull`, and the global63 functions `openDatabase`, `openReadOnlyDatabase`, `getDatabasesPath`,64 `deleteDatabase`, `databaseExists` that forward to `databaseFactory`. Only65 applications should rely on the global; a library that reads it forces the66 app to have set it. `package:sqflite/sqflite.dart` and67 `package:sqflite_common_ffi/sqflite_ffi.dart` both re-export this library,68 so do not import it next to them (`unnecessary_import`).69* `package:sqflite_common/sql.dart`: `ConflictAlgorithm`, `escapeName`,70 `unescapeName`.71* `package:sqflite_common/utils/utils.dart`: `firstIntValue(rows)`,72 `firstStringValue(rows)`, `hex(bytes)`, `setLockWarningInfo(duration:,73 callback:)`, `sqlCountColumn` (`'COUNT(*)'`). There is no `Sqflite` class74 here; that class lives in `package:sqflite`.75* `package:sqflite_common/sqflite_logger.dart`: `SqfliteDatabaseFactoryLogger`76 (its constructor is `@experimental`, expect an `experimental_member_use`77 analyzer warning), `SqfliteLoggerOptions`, `SqfliteDatabaseFactoryLoggerType` (`all`,78 `invoke`), the event classes (`SqfliteLoggerSqlEvent`,79 `SqfliteLoggerBatchEvent`, `SqfliteLoggerDatabaseOpenEvent`,80 `SqfliteLoggerDatabaseCloseEvent`, `SqfliteLoggerDatabaseDeleteEvent`,81 `SqfliteLoggerInvokeEvent`), `SqfliteLoggerEventExt.dump()` and82 `DatabaseFactoryLoggerDebugExt.debugQuickLoggerWrapper()`.83* `package:sqflite_common/sqflite_dev.dart`: deprecated dev-only84 `setLogLevel` / `setOptions` (`SqfliteDatabaseFactoryDev`) and85 `SqfliteOptions`. Do not ship code that uses it.86* Never import `package:sqflite_common/src/...` from user code.8788### Designing a Flutter-free package8990* Take a `DatabaseFactory` (or a `Database`) as a constructor or function91 parameter. The Flutter app passes `databaseFactory` from `package:sqflite`,92 the CLI or test passes `databaseFactoryFfi` from `sqflite_common_ffi`.93* Take the database `path` as a parameter too: `getDatabasesPath()` is only94 meaningful for the sqflite plugin; on other implementations the app should95 compute a location (`path_provider`, a CLI argument, a temp dir).96 `inMemoryDatabasePath` (`':memory:'`) works everywhere.97* Type your SQL-facing functions on `DatabaseExecutor` so they work with both98 a `Database` and a `Transaction`.99* Opening returns the same instance for the same path while `singleInstance`100 is true (default); it is forced to false for in-memory databases.101* `databaseFactory` throws `StateError('databaseFactory not initialized')`102 until an implementation sets it; the setter rejects factories that are not103 sqflite implementations (`ArgumentError`) and prints a warning when104 changing an already set factory.105* `factory.sandbox(path: root)` returns a `DatabaseFactory` restricted to106 `root` (relative paths resolved under it, absolute paths must be inside,107 `getDatabasesPath()` returns `root`); it works over any implementation and108 is never nested twice.109110### Statements, transactions, batches (shared semantics)111112* One statement per call; bind with `?` and an argument list; values are113 `int`, `num`, `String`, `Uint8List` or `null` (`bool`, `DateTime`, `List`,114 `Map` are not supported). Results are read-only maps.115* `db.transaction((txn) async {...})` commits when the callback returns and116 rolls back (and rethrows) when it throws. Use only `txn` inside. Callbacks117 `onCreate` / `onUpgrade` / `onDowngrade` are already in a transaction.118* `db.batch()` collects operations and `commit()` runs them in one call in a119 transaction (`noResult: true`, `continueOnError: true`); a batch created120 from a `Transaction` is committed with it.121* `queryIterate` / `rawQueryIterate` (or `queryCursor` + `moveNext()` +122 `close()`) stream large results with a `bufferSize` (default 100).123* `DatabaseException` helpers: `isNoSuchTableError`, `isSyntaxError`,124 `isUniqueConstraintError`, `isNotNullConstraintError`,125 `isDuplicateColumnError`, `isOpenFailedError`, `isDatabaseClosedError`,126 `isReadOnlyError`, `getResultCode()`.127128## Examples129130### Shared repository, used from Flutter and from a Dart test131132```dart133// package my_data (depends on sqflite_common only)134import 'package:sqflite_common/sqlite_api.dart';135import 'package:sqflite_common/utils/utils.dart';136137class TodoRepository {138 TodoRepository({required this.factory, required this.path});139140 final DatabaseFactory factory;141 final String path;142 Future<Database>? _db;143144 Future<Database> get db => _db ??= factory.openDatabase(145 path,146 options: OpenDatabaseOptions(147 version: 1,148 onConfigure: (db) => db.execute('PRAGMA foreign_keys = ON'),149 onCreate: (db, _) => db.execute(150 'CREATE TABLE Todo (id INTEGER PRIMARY KEY, title TEXT NOT NULL, done INTEGER NOT NULL DEFAULT 0)',151 ),152 ),153 );154155 Future<int> add(String title) async =>156 (await db).insert('Todo', {'title': title});157158 Future<int> count(DatabaseExecutor executor) async =>159 firstIntValue(await executor.query('Todo', columns: [sqlCountColumn])) ?? 0;160161 Future<void> markAllDone() async {162 final database = await db;163 await database.transaction((txn) async {164 final pending = await count(txn);165 if (pending > 0) {166 await txn.update('Todo', {'done': 1});167 }168 });169 }170171 Future<void> close() async {172 await (await db).close();173 _db = null;174 }175}176```177178```dart179// test/todo_repository_test.dart (dev_dependency: sqflite_common_ffi)180// sqflite_ffi.dart re-exports the sqflite_common API (inMemoryDatabasePath...).181import 'package:sqflite_common_ffi/sqflite_ffi.dart';182import 'package:test/test.dart';183184import 'package:my_data/todo_repository.dart';185186void main() {187 setUpAll(sqfliteFfiInit);188189 test('count and markAllDone', () async {190 final repo = TodoRepository(191 factory: databaseFactoryFfi,192 path: inMemoryDatabasePath,193 );194 await repo.add('a');195 expect(await repo.count(await repo.db), 1);196 await repo.markAllDone();197 expect(await (await repo.db).query('Todo', where: 'done = 1'), hasLength(1));198 await repo.close();199 });200}201```202203In the Flutter app: `TodoRepository(factory: databaseFactory, path: join(await204getDatabasesPath(), 'todo.db'))` with `package:sqflite/sqflite.dart`.205206### Using the global factory in an application207208```dart209import 'package:sqflite_common/sqflite.dart';210211/// The app (not a library) sets databaseFactory first, e.g.212/// `databaseFactory = databaseFactoryFfi;` from sqflite_common_ffi.213Future<int> countRows(String table) async {214 final db = await openDatabase(inMemoryDatabasePath);215 try {216 await db.execute('CREATE TABLE $table (id INTEGER PRIMARY KEY)');217 final rows = await db.rawQuery('SELECT COUNT(*) FROM $table');218 return rows.first.values.first as int;219 } finally {220 await db.close();221 }222}223```224225### Sandboxed factory for tests or per-user data226227```dart228import 'package:sqflite_common/sqlite_api.dart';229230Future<Database> openUserDb(DatabaseFactory factory, String userRoot) async {231 final userFactory = factory.sandbox(path: userRoot);232 // Relative to userRoot; '/etc/x.db' would throw ArgumentError.233 return userFactory.openDatabase('cache.db');234}235```236237### Logging every statement of any factory238239```dart240import 'package:sqflite_common/sqflite_logger.dart';241import 'package:sqflite_common/sqlite_api.dart';242243// ignore: experimental_member_use244DatabaseFactory withLogs(DatabaseFactory factory) => SqfliteDatabaseFactoryLogger(245 factory,246 options: SqfliteLoggerOptions(247 type: SqfliteDatabaseFactoryLoggerType.all,248 log: (event) {249 if (event is SqfliteLoggerSqlEvent) {250 print('${event.type.name}: ${event.sql} ${event.arguments ?? ''}'251 '${event.error != null ? ' error: ${event.error}' : ''}');252 } else if (event is SqfliteLoggerBatchEvent) {253 for (final op in event.operations) {254 print('batch ${op.type.name}: ${op.sql} ${op.arguments ?? ''}');255 }256 } else {257 event.dump();258 }259 },260 ),261 );262```263264### Prepared commands and iteration265266```dart267import 'package:sqflite_common/sqlite_api.dart';268269final _byDone = SqfliteSqlCommand.query('Todo', where: 'done = ?', whereArgs: [0]);270final _insert = SqfliteSqlCommand.insert('Todo', {'title': 'x', 'done': 0});271272Future<List<String>> pendingTitles(DatabaseExecutor executor) async {273 final titles = <String>[];274 await _byDone.iterate(executor, bufferSize: 50, onRow: (row) {275 titles.add(row['title'] as String);276 return true;277 });278 return titles;279}280281Future<int> insertOne(DatabaseExecutor executor) => _insert.insert(executor);282283Future<void> walk(DatabaseExecutor executor) => executor.queryIterate(284 'Todo',285 orderBy: 'id',286 onRow: (row) async {287 print(row);288 return row['id'] != 100; // stop at id 100289 },290 );291```292293### Reading the schema version and enabling WAL294295```dart296import 'package:sqflite_common/sqlite_api.dart';297298Future<Database> openWithWal(DatabaseFactory factory, String path) =>299 factory.openDatabase(300 path,301 options: OpenDatabaseOptions(302 version: 2,303 onConfigure: (db) async {304 await db.setJournalMode('WAL');305 if (await db.getVersion() == 0) {306 // New file: settings that must precede table creation.307 await db.execute('PRAGMA auto_vacuum = 2');308 }309 },310 onCreate: (db, v) => db.execute('CREATE TABLE T (id INTEGER PRIMARY KEY)'),311 onUpgrade: (db, old, _) async {312 if (old < 2) await db.execute('ALTER TABLE T ADD name TEXT');313 },314 onDowngrade: onDatabaseDowngradeDelete,315 ),316 );317```318319## Common mistakes320321* Depending on `package:sqflite` in a package that also runs on the Dart VM322 or in `dart test`; depend on `sqflite_common` and inject the factory.323* Importing `package:sqflite_common/sqflite.dart` in a library only for the324 types; use `sqlite_api.dart` so the library does not read the global.325* Calling `openDatabase(...)` (global) before the app set `databaseFactory`326 (`StateError: databaseFactory not initialized`).327* Assigning a hand-written `DatabaseFactory` implementation to328 `databaseFactory`: the setter only accepts sqflite implementations. Wrap329 with `SqfliteDatabaseFactoryLogger` or use `sandbox()` instead.330* Using `db` inside `db.transaction((txn) ...)`, or `db.transaction()` inside331 `onCreate` / `onUpgrade`.332* Relying on `getDatabasesPath()` from a non-plugin factory for real data;333 pass an explicit path.334335## More336337Implementation packages: `sqflite` (Flutter, Android/iOS/macOS),338`sqflite_common_ffi` (desktop, VM, tests), `sqflite_common_ffi_web` (web);339each ships its own skills. Docs in the repository: `doc/sqflite_iterate.md`,340`doc/sqflite_sql_command.md`, `doc/sqflite_logger.md`,341`doc/method_call_protocol.md`.