sqflite-common-api
GitHub提供纯 Dart 的 sqflite API,支持跨平台 SQLite 操作。用于编写不依赖 Flutter 的代码或需兼容多端的共享包,涵盖数据库工厂、事务及 SQL 执行等核心类型与工具。
Trigger Scenarios
Install
npx skills add tekartik/sqflite --skill sqflite-common-api -g -y
SKILL.md
Frontmatter
{
"name": "sqflite-common-api",
"description": "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."
}
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 extensionsSqfliteDatabaseExecutorExt(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 globaldatabaseFactorygetter/setter,databaseFactoryOrNull, and the global functionsopenDatabase,openReadOnlyDatabase,getDatabasesPath,deleteDatabase,databaseExiststhat forward todatabaseFactory. Only applications should rely on the global; a library that reads it forces the app to have set it.package:sqflite/sqflite.dartandpackage:sqflite_common_ffi/sqflite_ffi.dartboth 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 noSqfliteclass here; that class lives inpackage:sqflite.package:sqflite_common/sqflite_logger.dart:SqfliteDatabaseFactoryLogger(its constructor is@experimental, expect anexperimental_member_useanalyzer warning),SqfliteLoggerOptions,SqfliteDatabaseFactoryLoggerType(all,invoke), the event classes (SqfliteLoggerSqlEvent,SqfliteLoggerBatchEvent,SqfliteLoggerDatabaseOpenEvent,SqfliteLoggerDatabaseCloseEvent,SqfliteLoggerDatabaseDeleteEvent,SqfliteLoggerInvokeEvent),SqfliteLoggerEventExt.dump()andDatabaseFactoryLoggerDebugExt.debugQuickLoggerWrapper().package:sqflite_common/sqflite_dev.dart: deprecated dev-onlysetLogLevel/setOptions(SqfliteDatabaseFactoryDev) andSqfliteOptions. 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 aDatabase) as a constructor or function parameter. The Flutter app passesdatabaseFactoryfrompackage:sqflite, the CLI or test passesdatabaseFactoryFfifromsqflite_common_ffi. - Take the database
pathas 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
DatabaseExecutorso they work with both aDatabaseand aTransaction. - Opening returns the same instance for the same path while
singleInstanceis true (default); it is forced to false for in-memory databases. databaseFactorythrowsStateError('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 aDatabaseFactoryrestricted toroot(relative paths resolved under it, absolute paths must be inside,getDatabasesPath()returnsroot); 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 areint,num,String,Uint8Listornull(bool,DateTime,List,Mapare 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 onlytxninside. CallbacksonCreate/onUpgrade/onDowngradeare already in a transaction.db.batch()collects operations andcommit()runs them in one call in a transaction (noResult: true,continueOnError: true); a batch created from aTransactionis committed with it.queryIterate/rawQueryIterate(orqueryCursor+moveNext()+close()) stream large results with abufferSize(default 100).DatabaseExceptionhelpers: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:sqflitein a package that also runs on the Dart VM or indart test; depend onsqflite_commonand inject the factory. - Importing
package:sqflite_common/sqflite.dartin a library only for the types; usesqlite_api.dartso the library does not read the global. - Calling
openDatabase(...)(global) before the app setdatabaseFactory(StateError: databaseFactory not initialized). - Assigning a hand-written
DatabaseFactoryimplementation todatabaseFactory: the setter only accepts sqflite implementations. Wrap withSqfliteDatabaseFactoryLoggeror usesandbox()instead. - Using
dbinsidedb.transaction((txn) ...), ordb.transaction()insideonCreate/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.
Version History
- aaabf90 Current 2026-09-22 03:38


