Connection & Backends topic
Connection & Backends
Connection is the database-agnostic execution surface. The query builder
produces statements; a Connection implementation serializes (via
QueryBuilder/SqlDialect) and runs them against a real driver.
Async-first, sync-compatible
Every method returns a Future; FutureOr appears only on the
transaction callback. This is deliberate: SQLite runs synchronously under
the hood and simply returns already-completed futures, while an async driver
(Postgres) implements the exact same signatures unchanged.
abstract interface class Connection {
Future<List<R>> fetch<R>(SelectQuery<R> statement);
Future<int> execute(WriteStatement statement);
Future<List<R>> executeReturning<R>(ReturningQuery<R> statement);
Future<void> executeSql(String sql, [List<Object?> params]);
Future<List<Map<String, Object?>>> queryRaw(String sql, [List<Object?> params]);
Future<List<IntrospectedTable>> introspect();
Future<T> transaction<T>(FutureOr<T> Function(Connection tx) action);
Future<void> close();
}
executeSql/queryRaware the raw-SQL escape hatches (DDL, migrations, ad-hoc introspection).introspect()reads the live schema into the dialect-neutralIntrospectedTable/IntrospectedColumn/ForeignKeymodel, which thebasalt_cligenerate-schemacommand turns into typed Dart schema code.transactioncommits on success and rolls back on error (see below).
Transactions
transaction hands your callback a distinct, transaction-scoped
Connection — use that tx handle for statements inside the block, not the
original connection:
await db.transaction((tx) async {
final id = await tx.executeReturning(
insertInto(Orders.table).value(Orders.total.set(42)).returning([Orders.id]),
);
await tx.execute(insertInto(LineItems.table).value(LineItems.orderId.set(id)));
// returning normally commits; throwing rolls the whole block back.
});
-
Nesting is decided by the handle, not a counter. Calling
transactionagain on thetxhandle opens a nestedSAVEPOINT, so an inner failure rolls back only the inner work while the outer transaction continues:await db.transaction((tx) async { await tx.execute(/* ... outer write ... */); try { await tx.transaction((inner) async { await inner.execute(/* ... */); throw Exception('bail out of inner only'); }); } on Exception { // outer transaction still commits } }); -
Concurrent top-level transactions are serialized. Two
transactioncalls on one connection never interleave their statements: the second queues until the first commits or rolls back. (On SQLite every operation shares one lock; on Postgres this is the driver's nativerunTxbehaviour, and the raw connection throws if used while a transaction is active.) -
The
txhandle is only valid for the duration of the callback; using it after the block returns — or callingtx.close()— throwsStateError.
Backends
| Package | Driver |
|---|---|
basalt_sqlite |
package:sqlite3 |
basalt_postgres |
package:postgres |
Both implement this same interface, so application code written against
Connection runs unchanged against either backend.
Classes
- Connection Connection & Backends
-
Database-agnostic execution surface. The query builder produces statements;
a
Connectionimplementation serializes and runs them against a driver. - ForeignKey Connection & Backends
- A foreign-key target discovered during introspection.
- IntrospectedColumn Connection & Backends
- IntrospectedTable Connection & Backends
- SerialLock Connection & Backends
- A minimal FIFO async mutex: serializes the run callbacks handed to it so that at most one is in flight at a time, in submission order.