sql
import "github.com/kitsunium/sdk/pkg/v1/data/sql"
Package sql is the public facade for the SDK's relational-database domain: transaction ownership, a pool policy, a health check and a migration runner, over the standard library's database/sql.
It is NOT an ORM, and it never will be. There is no entity mapping, no query builder, no lazy loading and no repository generation — you write SQL, the SDK owns the transaction around it (ADR 0055).
It also ships no driver. Import the one you want, open the pool yourself, and hand it over:
import _ "github.com/jackc/pgx/v5/stdlib"
pool, err := stdsql.Open("pgx", dsn)
dialect, err := sql.ParseDialect("postgres")
tm, err := sql.NewTransactor(sql.Config{
DB: pool,
Dialect: dialect,
Pool: sql.PoolConfig{MaxOpen: 20},
})
Who is allowed to commit
Nobody but the manager. Transact hands your unit of work an Executor — Exec, Query, QueryRow, and deliberately nothing else. A helper that receives one CANNOT commit or roll back the transaction it was lent, because those methods do not exist on the type it was given:
err := sql.Transact(ctx, tm, func(ctx context.Context, ex sql.Executor) error {
if _, err := ex.ExecContext(ctx, "UPDATE accounts SET cents = cents - $1 WHERE id = $2", n, from); err != nil {
return err
}
return credit(ctx, ex, to, n) // credit cannot commit; it has no way to
})
Returning nil commits. Returning an error rolls back and hands the error back to you unchanged, so errors.Is keeps working.
Nesting is a savepoint
database/sql has no nested transactions. Call Transact with a context that already carries one and you get a SAVEPOINT of that transaction rather than a second connection deadlocking against the first:
_ = sql.Transact(ctx, tm, func(ctx context.Context, ex sql.Executor) error {
if err := mustHappen(ctx, ex); err != nil {
return err
}
// best-effort: if this fails, only ITS work is undone
if err := sql.Transact(ctx, tm, optional); err != nil {
log.Warn("optional step skipped", "err", err)
}
return nil
})
Three consequences worth knowing before you rely on it:
- Only postgres, mysql and sqlite are supported, because those three spell savepoints identically. SQL Server, Oracle and Db2 are refused BY NAME at ParseDialect — their nesting is a different algorithm, not a different string, and generating SQL that fails at the first nested transaction in production is not an option.
- A failed nested scope does NOT condemn the outer transaction. ROLLBACK TO SAVEPOINT is precisely the statement that clears PostgreSQL's aborted state, so catching the inner error leaves you a usable transaction. If that statement itself fails, the transaction is poisoned: every later operation is refused and the commit never happens.
- A nested call must pass the ZERO TxOptions. A savepoint cannot change isolation or read-only mode, and being handed a weaker transaction than you asked for under a nil error is worse than being told no.
Joining the caller's transaction, and waiting for its commit
The transactor NewTransactor returns has three more capabilities, each an interface of its own beside Transactor, found by type assertion (ADR 0139):
ex, inTx := tm.(sql.Joiner).Join(ctx) // where a statement under ctx runs
held := tm.(sql.Deferrer).Defer(ctx, notify) // notify runs once ctx's transaction commits
held = tm.(sql.Aborter).OnAbort(ctx, takeBack) // takeBack runs once ctx's work is undone
Joiner answers the executor of the transaction ctx carries — so a repository called inside its caller's transaction reads what that transaction wrote — or the pool when it carries none. A context that outlived its transaction still names it, and its executor refuses rather than fall back to the pool. Deferrer holds a function until that transaction commits: a rollback drops it, and so does the rollback of the savepoint it was held in. It is how a message, a mail or a store's write hook waits for the work it announces to be committed. The SDK's document store over SQL is built on both. Aborter is the complement of Deferrer: it holds a function until that work is undone — the savepoint it was held in rolls back, or the transaction does not commit — and runs it then, on a context the rollback's cancellation does not reach; a commit drops it. It is how something announced before the commit is taken back when the commit never comes.
Migrations
NewMigrator applies an ordered, versioned set under a lock that excludes every other runner and dies with its holder, so two instances starting together cannot apply the same migration twice and a runner killed mid-run leaves nothing held. On PostgreSQL and MySQL it is the session's advisory lock, which the server drops with the connection. On SQLite it is the database file's write lock (ADR 0140): the run is ONE transaction that takes it with its first statement, each migration a savepoint of it, and the operating system drops a dead process's file locks. A SQLite run needs a single connection, and a migration there cannot run what SQLite refuses inside a transaction — VACUUM, or PRAGMA journal_mode.
Migrations are VALUES you build. The SDK ships no directory, no file format and no naming convention, because the moment it reads a directory it has invented one you did not choose:
m := sql.Migration{
Version: 20260910143000,
Name: "create accounts",
Up: sql.Statements("CREATE TABLE accounts (id BIGINT PRIMARY KEY, cents BIGINT NOT NULL)"),
Down: sql.Statements("DROP TABLE accounts"),
}
Each migration runs in its own transaction together with its version-table row, so on PostgreSQL and SQLite a failed migration rolls back BOTH halves and the database sits exactly at the previous version.
On MySQL and MariaDB that guarantee does not hold, and it is not something this SDK can fix: DDL causes an IMPLICIT COMMIT, so a migration whose second statement fails leaves the first one applied while its version row is rolled back. Write ONE DDL statement per migration on MySQL and the failure is atomic by construction. See ADR 0055 D8.
Migrator.Plan is the dry run: it reports what Migrator.Up would apply, applies nothing and takes no lock. Irreversible is how a migration says out loud that it cannot be rolled back — a nil Down is refused, because "I forgot the reversal" and "there is no reversal" are the same nil.
Errors
Every failure is a typed SDK error joined with the driver's own, side by side, so neither question loses its answer:
errors.Is(err, sql.CommitFailed) // the SDK's verdict
errs.HasCode(err, sql.CommitFailed.Code()) // the same, by code
errors.Is(err, driver.ErrBadConn) // the driver's own
The sentinels above are the matchable values; their Code() is the ADR 0005 dotted quad. No Public message in this domain ever carries a connection string, a query or a bound argument.
Index
- Constants
- Variables
- func ChecksumOf(stmts ...string) string
- func Classify(err error) bool
- func Each[T any](rows *stdsql.Rows, scan func(*stdsql.Rows) (T, error)) iter.Seq2[T, error]
- func InsertRows(ctx context.Context, ex Executor, d Dialect, into InsertSpec, rows [][]any) (inserted int64, err error)
- func Irreversible(ctx context.Context, ex Executor) error
- func NewChecker(cfg Config) (probe Checker, err error)
- func NewMigrator(cfg Config, mig MigrateConfig) (runner Migrator, err error)
- func NewRetrying(tm Transactor, cfg RetryConfig) (manager Transactor, err error)
- func NewTransactor(cfg Config) (manager Transactor, err error)
- func Observe(c driver.Connector, obs StatementObserver) driver.Connector
- func ParseDialect(name string) (dialect Dialect, err error)
- func SlowQueries(threshold time.Duration, log corelogger.Logger, clk clock.Clock) StatementObserver
- func Statements(stmts ...string) Step
- func Traced(tracer coretrace.Tracer) StatementObserver
- func Transact(ctx context.Context, tm Transactor, fn TxFunc) error
- type Aborter
- type Backoff
- type Checker
- type Config
- type Deferrer
- type Dialect
- type Executor
- type InsertSpec
- type Joiner
- type MigrateConfig
- type Migration
- type Migrator
- type PoolConfig
- type Preparer
- type RetryConfig
- type Router
- type StatementKind
- type StatementObserver
- type Step
- type Transactor
- type Transient
- type TxFunc
- type TxOptions
Constants
const DialectMySQL Dialect = 2
DialectMySQL is MySQL 5.7+ / MariaDB 10.x with InnoDB.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.DialectMySQL.
const DialectPostgres Dialect = 1
DialectPostgres is PostgreSQL 9.1+.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.DialectPostgres.
const DialectSQLite Dialect = 3
DialectSQLite is SQLite 3.6.8+. It has no advisory lock, so NewMigrator serialises a run on the database file's write lock instead (ADR 0140).
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.DialectSQLite.
const StatementBegin StatementKind = 4
StatementBegin is a transaction opened.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.StatementBegin.
const StatementCommit StatementKind = 5
StatementCommit is a transaction committed.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.StatementCommit.
const StatementExec StatementKind = 1
StatementExec is a statement that returns no rows.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.StatementExec.
const StatementPrepare StatementKind = 3
StatementPrepare is a statement compiled for repeated execution.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.StatementPrepare.
const StatementQuery StatementKind = 2
StatementQuery is a statement that returns rows.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.StatementQuery.
const StatementRollback StatementKind = 6
StatementRollback is a transaction rolled back.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.StatementRollback.
Variables
var BeginFailed *errs.Error
BeginFailed reports a transaction the driver would not open.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.BeginFailed.
Code 0.3.54.3 · reason BEGIN_FAILED · public "The database refused to open a transaction"
var CommitFailed *errs.Error
CommitFailed reports a transaction that had NO effect.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.CommitFailed.
Code 0.3.54.4 · reason COMMIT_FAILED · public "The transaction could not be committed and had no effect"
var ConfigInvalid *errs.Error
ConfigInvalid is returned by every constructor for a Config that could never produce a working port.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.ConfigInvalid.
Code 0.3.54.1 · reason CONFIG_INVALID · public "The SQL configuration is incomplete and was refused"
var DialectRefused *errs.Error
DialectRefused is returned by ParseDialect for an engine this SDK recognises and declines. The reason travels in a field.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.DialectRefused.
Code 0.2.24.2 · reason DIALECT_REFUSED · public "That SQL engine is recognised and deliberately not supported"
var DuplicateMigration *errs.Error
DuplicateMigration refuses two migrations declaring the same version.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.DuplicateMigration.
Code 0.3.54.17 · reason DUPLICATE_MIGRATION · public "Two migrations declare the same version"
var HealthCheckFailed *errs.Error
HealthCheckFailed reports a database that answered the probe with an error.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.HealthCheckFailed.
Code 0.3.54.9 · reason HEALTH_CHECK_FAILED · public "The database is not reachable"
var HealthCheckTimeout *errs.Error
HealthCheckTimeout reports a database that did not answer in time.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.HealthCheckTimeout.
Code 0.3.54.10 · reason HEALTH_CHECK_TIMEOUT · public "The database did not answer the health check in time"
var InvalidMigration *errs.Error
InvalidMigration is returned for a migration with version 0, an empty name, a nil Up or a nil Down.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.InvalidMigration.
Code 0.2.24.4 · reason INVALID_MIGRATION · public "The migration is not runnable and was refused"
var MigrationChanged *errs.Error
MigrationChanged refuses a run over an applied migration whose statements were edited after they ran. Nothing was applied.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.MigrationChanged.
Code 0.3.54.18 · reason MIGRATION_CHANGED · public "A migration already applied was edited after it ran"
var MigrationFailed *errs.Error
MigrationFailed reports a migration that did not apply; it was rolled back.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.MigrationFailed.
Code 0.3.54.11 · reason MIGRATION_FAILED · public "A migration did not apply and was rolled back"
var MigrationIrreversible *errs.Error
MigrationIrreversible is what the Irreversible step returns.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.MigrationIrreversible.
Code 0.2.24.5 · reason MIGRATION_IRREVERSIBLE · public "That migration was declared irreversible and cannot be rolled back"
var MigrationLockTimeout *errs.Error
MigrationLockTimeout reports another process holding the migration lock for the whole budget. Nothing was applied.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.MigrationLockTimeout.
Code 0.3.54.14 · reason MIGRATION_LOCK_TIMEOUT · public "Another process is running migrations and did not finish in time"
var MigrationLockUnsupported *errs.Error
MigrationLockUnsupported refuses a Migrator on a dialect with no lock that dies with its holder. No dialect this SDK speaks answers it since SQLite's runner serialises on its file's write lock (ADR 0140).
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.MigrationLockUnsupported.
Code 0.3.54.13 · reason MIGRATION_LOCK_UNSUPPORTED · public "That dialect has no advisory lock, so migrations cannot be serialised"
var MigrationOutOfOrder *errs.Error
MigrationOutOfOrder refuses a pending migration older than one already applied.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.MigrationOutOfOrder.
Code 0.3.54.12 · reason MIGRATION_OUT_OF_ORDER · public "A pending migration is older than one already applied"
var MigrationUnknownVersion *errs.Error
MigrationUnknownVersion refuses a Down over a version this build does not carry.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.MigrationUnknownVersion.
Code 0.3.54.15 · reason MIGRATION_UNKNOWN_VERSION · public "The database records a migration this build does not carry"
var NestedIsolation *errs.Error
NestedIsolation is returned when a nested Transact asks for isolation or read-only mode a savepoint cannot provide.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.NestedIsolation.
Code 0.2.24.3 · reason NESTED_ISOLATION · public "A nested transaction cannot change isolation or read-only mode"
var PoolMisconfigured *errs.Error
PoolMisconfigured refuses a pool policy whose zero value database/sql would read as a decision nobody made — a non-positive MaxOpen.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.PoolMisconfigured.
Code 0.3.54.2 · reason POOL_MISCONFIGURED · public "The connection-pool policy is not one this SDK will apply"
var RollbackFailed *errs.Error
RollbackFailed travels alongside the error that triggered the rollback.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.RollbackFailed.
Code 0.3.54.5 · reason ROLLBACK_FAILED · public "Rolling the transaction back did not complete cleanly"
var SavepointFailed *errs.Error
SavepointFailed reports a rejected savepoint statement; the "statement" field names which of the three.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.SavepointFailed.
Code 0.3.54.6 · reason SAVEPOINT_FAILED · public "A savepoint statement was rejected by the database"
var StatementTimedOut *errs.Error
StatementTimedOut is joined with the driver's error when a statement outlived the transactor's statement budget.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.StatementTimedOut.
Code 0.3.54.20 · reason STATEMENT_TIMED_OUT · public "A database statement did not complete within its time budget"
var TxClosed *errs.Error
TxClosed reports an Executor used after the scope that lent it returned.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.TxClosed.
Code 0.3.54.8 · reason TX_CLOSED · public "That transaction handle is no longer valid"
var TxPoisoned *errs.Error
TxPoisoned refuses every operation on a transaction whose savepoint rollback failed. It is never committed.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.TxPoisoned.
Code 0.3.54.7 · reason TX_POISONED · public "The transaction is no longer usable and will not be committed"
var TxRetriesExhausted *errs.Error
TxRetriesExhausted is joined with the last attempt's error when every attempt of a retrying transactor failed on a transient error.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.TxRetriesExhausted.
Code 0.3.54.19 · reason TX_RETRIES_EXHAUSTED · public "The transaction kept conflicting with others and was given up"
var UnknownDialect *errs.Error
UnknownDialect is returned by ParseDialect for a name this SDK has never heard of.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.UnknownDialect.
Code 0.2.24.1 · reason UNKNOWN_DIALECT · public "The SQL dialect is not one this SDK supports"
var VersionTableInvalid *errs.Error
VersionTableInvalid refuses a version-table name that is not a plain SQL identifier.
Initialised with github.com/kitsunium/sdk/internal/core/data/sql.VersionTableInvalid.
Code 0.3.54.16 · reason VERSION_TABLE_INVALID · public "The version-table name is not a plain SQL identifier"
func ChecksumOf(stmts ...string) string
ChecksumOf is the fingerprint a Migration built from stmts records in its Checksum: SHA-256 over the statements, each length-prefixed, in lowercase hex. Set it from the same statements Up runs, and an applied migration edited afterwards is refused with MigrationChanged instead of being called applied by its number alone.
func Classify(err error) bool
Classify reports whether err is a failure the database documents as "run the transaction again": SQLSTATE 40001 or 40P01, or SQLite's BUSY or LOCKED. A context's end never is.
func Each[T any](rows *stdsql.Rows, scan func(*stdsql.Rows) (T, error)) iter.Seq2[T, error]
Each iterates rows with scan and closes them when the iteration ends; a failure is yielded once, last. It is the Next/Scan/Err/Close loop, written once.
func InsertRows(ctx context.Context, ex Executor, d Dialect, into InsertSpec, rows [][]any) (inserted int64, err error)
InsertRows inserts rows into into.Table in multi-row statements of at most into.PerStatement rows, clamped to the engine's bound-parameter ceiling, and returns how many rows were inserted. into.Table and into.Columns are interpolated as given: validate them first.
func Irreversible(ctx context.Context, ex Executor) error
Irreversible is the Step a migration assigns to Down to declare, out loud, that it cannot be reversed.
func NewChecker(cfg Config) (probe Checker, err error)
NewChecker returns a liveness probe bounded by cfg.CheckTimeout.
func NewMigrator(cfg Config, mig MigrateConfig) (runner Migrator, err error)
NewMigrator returns the migration runner. Every migration execution is serialised: on the session's advisory lock where the engine has one (PostgreSQL, MySQL), and on the database file's write lock on SQLite, which has none (ADR 0140). No dialect the SDK speaks is refused for the lock.
func NewRetrying(tm Transactor, cfg RetryConfig) (manager Transactor, err error)
NewRetrying returns a transactor that runs a root transaction of tm again on a failure cfg.Transient calls transient, up to cfg.Attempts runs, on cfg.Backoff's curve. A nested call is never run again on its own. The unit of work must be safe to run more than once.
func NewTransactor(cfg Config) (manager Transactor, err error)
NewTransactor returns the transaction manager for cfg.DB, applying cfg.Pool to it.
func Observe(c driver.Connector, obs StatementObserver) driver.Connector
Observe returns c with obs told about every statement its connections send — exec, query, prepare, BEGIN, COMMIT, ROLLBACK —, the SQL text bounded and never an argument. Open the pool over it: stdsql.OpenDB(sql.Observe(connector, obs)). A nil obs returns c.
func ParseDialect(name string) (dialect Dialect, err error)
ParseDialect resolves a dialect name and never guesses. A recognised but unsupported engine returns DialectRefused carrying why; an unrecognised one returns UnknownDialect.
func SlowQueries(threshold time.Duration, log corelogger.Logger, clk clock.Clock) StatementObserver
SlowQueries returns an observer that logs at Warn each statement that took threshold or longer on clk — its SQL text, duration and rows, never an argument.
func Statements(stmts ...string) Step
Statements returns a Step that runs the given statements in order on the transaction's executor.
It is the smallest useful helper and deliberately not a file loader: it invents no directory layout, no naming convention and no parser. Where the text comes from — a literal, an embed.FS, a generator — stays yours.
func Traced(tracer coretrace.Tracer) StatementObserver
Traced returns an observer that opens one client span per statement, its status following the statement's error.
func Transact(ctx context.Context, tm Transactor, fn TxFunc) error
Transact runs fn inside a transaction with the driver's default isolation.
It is the ergonomic form of Transactor.Transact for the common case. The port itself keeps the options parameter so it never needs a second method (ADR 0039); this helper keeps the call site short.
Aborter is the public alias for the ADR 0039 sibling of Transactor that holds a function until the work a context carries is undone — its savepoint or its transaction rolled back — and drops it with a commit: the complement of Deferrer. Discover it by type assertion.
type Aborter = coresql.Aborter
Aborter is an alias of github.com/kitsunium/sdk/internal/core/data/sql.Aborter, declared as:
type Aborter interface {
// OnAbort holds fn until the work of the scope ctx carries for this
// transactor is undone, and reports true.
//
// The work is undone when the scope's savepoint rolls back, or when any
// scope around it does, the root included: the root's unit of work
// returned an error or panicked, its commit was refused — a poisoned
// transaction — or the COMMIT itself failed, which [CommitFailed] reports
// as a unit of work that had no effect. A savepoint that is released
// hands what it held to the scope around it, which decides in turn; a
// commit that stands drops every function still held, unrun.
//
// The held functions run once, newest first, on the goroutine that ends
// the scope, after its ROLLBACK or ROLLBACK TO SAVEPOINT and before that
// scope's Transact returns — or before its panic continues. Each is
// handed ctx without its cancellation or deadline: a function that
// cleans up after a rollback must not die of the cancellation that
// caused it. It must not panic, and it must not open a transaction on
// ctx, which is over.
//
// For a function held with OnAbort and another with Defer under the same
// scope, exactly one of the two runs, unless a function held with Defer
// before it panics.
//
// OnAbort reports false and holds nothing when ctx carries no open
// transaction of this transactor. There is nothing to undo, so fn is the
// caller's again. A nil fn holds nothing and gets the same answer.
OnAbort(ctx context.Context, fn func(ctx context.Context)) (held bool)
}
func (Aborter) OnAbort
func (Aborter) OnAbort(ctx context.Context, fn func(ctx context.Context)) (held bool)
OnAbort holds fn until the work of the scope ctx carries for this transactor is undone, and reports true.
The work is undone when the scope's savepoint rolls back, or when any scope around it does, the root included: the root's unit of work returned an error or panicked, its commit was refused — a poisoned transaction — or the COMMIT itself failed, which CommitFailed reports as a unit of work that had no effect. A savepoint that is released hands what it held to the scope around it, which decides in turn; a commit that stands drops every function still held, unrun.
The held functions run once, newest first, on the goroutine that ends the scope, after its ROLLBACK or ROLLBACK TO SAVEPOINT and before that scope's Transact returns — or before its panic continues. Each is handed ctx without its cancellation or deadline: a function that cleans up after a rollback must not die of the cancellation that caused it. It must not panic, and it must not open a transaction on ctx, which is over.
For a function held with OnAbort and another with Defer under the same scope, exactly one of the two runs, unless a function held with Defer before it panics.
OnAbort reports false and holds nothing when ctx carries no open transaction of this transactor. There is nothing to undo, so fn is the caller's again. A nil fn holds nothing and gets the same answer.
Backoff is the public alias for the exponential curve a retrying transactor waits on between two attempts (the SDK's one backoff curve, ADR 0103).
type Backoff = kbackoff.Value
Backoff is an alias of github.com/kitsunium/sdk/internal/kernel/backoff.Value, declared as:
type Backoff struct {
// BaseDelay is the wait after the first failure. Zero or negative waits
// nothing.
BaseDelay time.Duration
// MaxDelay is the ceiling on the grown delay. Zero means no ceiling.
MaxDelay time.Duration
// Multiplier is the growth factor between consecutive waits. 1 or below,
// and NaN, mean 2.
Multiplier float64
// Jitter widens each wait by a uniform random fraction of itself, drawn
// from [0, Jitter) and ADDED, after the MaxDelay ceiling — so when a
// ceiling is set a wait may reach MaxDelay × (1 + Jitter). Zero is the
// deterministic backoff.
//
// It exists because a deterministic backoff SYNCHRONISES the very callers
// it is meant to spread: N clients that fail against one dependency at the
// same instant retry at the same instant, N times over. Jitter turns those
// bursts back into a distribution.
//
// It is read only under JitterAdditive.
Jitter float64
// JitterMode is how the randomisation is laid on the curve. The zero,
// JitterAdditive, is the Jitter above — what Value has always done;
// JitterFull and JitterDecorrelated are the Builders' Library's two
// other variants, which randomise the whole wait and ignore Jitter. A
// value outside the three is read as JitterAdditive.
JitterMode JitterMode
}
func (Backoff) Delay
func (Backoff) Delay(attempt int) time.Duration
Delay returns the wait after the attempt-th consecutive failure: [Grow] for the curve, then the randomisation JitterMode names — [Widen] by Jitter for JitterAdditive, [Full] for JitterFull, and for JitterDecorrelated [Decorrelate], reading the curve's wait for the attempt before as the previous wait (a loop that keeps its own calls Decorrelate). attempt counts from 1; a smaller value is read as 1, so a loop that has not failed yet and asks anyway waits BaseDelay rather than nothing.
It never returns a negative duration, and it costs at most as many multiplications as it takes the delay to reach its ceiling, however large attempt is.
Checker is the public alias for the bounded liveness probe.
type Checker = coresql.Checker
Checker is an alias of github.com/kitsunium/sdk/internal/core/data/sql.Checker, declared as:
type Checker interface {
// Check probes the database under a bounded budget and returns nil when
// it answered. A non-nil error is always typed and never carries the
// connection string.
Check(ctx context.Context) error
}
func (Checker) Check
func (Checker) Check(ctx context.Context) error
Check probes the database under a bounded budget and returns nil when it answered. A non-nil error is always typed and never carries the connection string.
Config is the public alias for the parameters every port is built from.
type Config = svcsql.Config
Config is an alias of github.com/kitsunium/sdk/internal/service/data/sql.Config, declared as:
type Config struct {
// DB is the caller's pool. REQUIRED — this package never opens one,
// because opening one means importing a driver (ADR 0055 §D2).
DB *stdsql.DB
// Dialect selects the SQL grammar. REQUIRED and must be a dialect this
// SDK can spell; resolve a configuration string through
// core/data/sql.ParseDialect, which refuses the engines it cannot support BY
// NAME instead of guessing.
Dialect coresql.Dialect
// Pool is the connection-pool policy, applied to DB at construction.
Pool PoolConfig
// Clock is the time source every budget in this package is read from. A
// nil Clock falls back to clock.System.
//
// clock.Timed rather than clock.Clock because this package both stamps
// (the version table's applied_at) and WAITS (the probe budget, the lock
// retry). Reading package time instead would make every budget assertion
// a sleep, and a sleep a tolerance, and a tolerance a flake.
Clock clock.Timed
// CheckTimeout is the budget one [core/data/sql.Checker.Check] gets. A
// non-positive value clamps to [DefaultCheckTimeout].
CheckTimeout time.Duration
// StatementTimeout bounds each statement the transactor's executors run —
// inside a transaction, and through Join or Reader on a pool — measured
// on Clock. Zero, or negative, is none: a statement then runs until the
// caller's context ends, as database/sql runs it.
//
// An Exec or a Prepare is bounded until it returns; a Query until its
// rows are read and closed, or the budget elapses, whichever comes first
// — the cut context lives until then, one timer per statement in
// flight. A cut answered by the call itself is joined with
// [core/data/sql.StatementTimedOut], its "operation" field naming exec,
// query or prepare; a cut met while reading rows surfaces from Next,
// Err or Scan as the context's own end. The migration runner never
// applies it: a migration's DDL runs as long as it takes, under the
// caller's context.
StatementTimeout time.Duration
// ReadReplica is a second pool, on a replica of DB, that a read-only
// transaction and [core/data/sql.Router]'s Reader run on. Nil is none:
// every transaction opens on DB. Pool is applied to it as to DB, and the
// health probe pings both. It is the caller's, opened with its own
// driver, and this package never closes it.
ReadReplica *stdsql.DB
}
Deferrer is the public alias for the ADR 0039 sibling of Transactor that holds a function until the transaction a context carries has committed, and drops it with a rollback. Discover it by type assertion.
type Deferrer = coresql.Deferrer
Deferrer is an alias of github.com/kitsunium/sdk/internal/core/data/sql.Deferrer, declared as:
type Deferrer interface {
// Defer holds fn until the transaction ctx carries for this transactor
// commits, and reports true.
//
// The held functions run once, in the order they were held, on the
// goroutine that commits, after the commit and before the outermost
// Transact returns. A rollback drops them, and so does the rollback of
// the savepoint they were held in; a savepoint that is released hands
// them to the scope around it. A panic in one reaches the caller of the
// outermost Transact after the commit stood, and the functions held after
// it do not run.
//
// Defer reports false and holds nothing when ctx carries no open
// transaction of this transactor. There is no commit to wait for, so fn
// is the caller's again, usually to run at once. A nil fn holds nothing
// and gets the same answer.
Defer(ctx context.Context, fn func()) (held bool)
}
func (Deferrer) Defer
func (Deferrer) Defer(ctx context.Context, fn func()) (held bool)
Defer holds fn until the transaction ctx carries for this transactor commits, and reports true.
The held functions run once, in the order they were held, on the goroutine that commits, after the commit and before the outermost Transact returns. A rollback drops them, and so does the rollback of the savepoint they were held in; a savepoint that is released hands them to the scope around it. A panic in one reaches the caller of the outermost Transact after the commit stood, and the functions held after it do not run.
Defer reports false and holds nothing when ctx carries no open transaction of this transactor. There is no commit to wait for, so fn is the caller's again, usually to run at once. A nil fn holds nothing and gets the same answer.
Dialect is the public alias for the closed set of SQL engines this SDK can spell. For SQL you write yourself, it spells the engine's vocabulary — the tokens the SDK's own statements are spelled with, never a statement: Dialect.Placeholder renders a bind marker ($1 on PostgreSQL, ? on MySQL and SQLite), Dialect.QuoteIdent a delimited name, its delimiter doubled inside, and Dialect.ForUpdate and Dialect.ForUpdateSkipLocked a row-lock clause, which SQLite — whose exclusion is its one write lock — renders as nothing. QuoteIdent validates nothing: check a name before you interpolate it. An unset Dialect renders no marker and no name, so a statement built from it fails rather than passing for another engine's.
type Dialect = coresql.Dialect
Dialect is an alias of github.com/kitsunium/sdk/internal/core/data/sql.Dialect, declared as:
type Dialect uint8
func (Dialect) ForUpdate() string
ForUpdate renders the clause that ends a read by locking the rows it returns until the transaction ends, leading space included: " FOR UPDATE" on PostgreSQL and MySQL. On MySQL it is also a locking read, which reads the latest committed rows rather than a REPEATABLE READ transaction's snapshot.
SQLite has no row lock and no such clause, and renders the empty string: a SQLite transaction excludes every other writer with the database's one write lock, which it takes at its first WRITE, so a transaction that must read under exclusion writes first (ADR 0140). A dialect that is not Dialect.Valid renders the clause: an engine without one refuses the statement, which is louder than a lock dropped in silence.
func (Dialect) ForUpdateSkipLocked() string
ForUpdateSkipLocked renders the clause that ends a read by locking the rows it returns and SKIPPING those another transaction holds, leading space included: " FOR UPDATE SKIP LOCKED" on PostgreSQL and MySQL. Two readers then never wait on each other's rows and never take the same one.
It needs PostgreSQL 9.5, MySQL 8.0.1 or MariaDB 10.6 — later than the floors DialectPostgres and DialectMySQL name — and an older server refuses the statement. SQLite renders the empty string and a dialect that is not Dialect.Valid the clause, for the reasons Dialect.ForUpdate gives: a SQLite transaction that has written holds the database's only write lock, so no row it reads is held by anybody else.
func (Dialect) Placeholder(n int) string
Placeholder renders the bind marker of a statement's n-th argument, counted from 1.
This is the divergence that makes a "portable" hand-written query a fiction: PostgreSQL numbers its markers — $1, $2 — while MySQL and SQLite share the positional ?, whose position is the argument's order. Every statement the SDK binds an argument to takes its markers from here, so a fourth engine is spelled in one place or not at all.
A dialect that is not Dialect.Valid — an unset field — has no marker and renders the empty string, so a statement built from it parses on no engine rather than on whichever one a guess favoured (ADR 0031).
func (Dialect) QuoteIdent(name string) string
QuoteIdent renders name as an identifier of the dialect: delimited, so a table called "order" or "user" is a name rather than a keyword, and with the delimiter doubled wherever the name holds it, which is how every engine spells a delimiter inside a delimited identifier. MySQL delimits with backticks, whatever its ANSI_QUOTES mode says; PostgreSQL and SQLite with the standard's double quotes.
It validates nothing. An identifier cannot be a bound argument, so the SDK interpolates only names it validated at construction, and that check is the whole defence: what a name may hold, and how long it may be — PostgreSQL TRUNCATES a longer one, into what may be another table's name — is the caller's to refuse before quoting it. A dialect that is not Dialect.Valid renders the empty string, for the reason Dialect.Placeholder gives.
func (Dialect) String
func (Dialect) String() string
String returns the dialect's canonical lowercase name.
func (Dialect) SupportsAdvisoryLock() bool
SupportsAdvisoryLock reports whether the engine offers a SESSION-SCOPED advisory lock — one the server releases when the connection holding it drops.
SQLite does not: it has no advisory-lock function, only the file lock that serialises writers. That distinction decides HOW Migrator promises mutual exclusion across processes, so it is a property of the dialect and not a runtime discovery (ADR 0055 §D7). On SQLite the runner holds the database file's write lock for the whole run instead, which the operating system releases when its holder dies, as a server releases an advisory lock (ADR 0140).
func (Dialect) Valid
func (Dialect) Valid() bool
Valid reports whether d is a dialect this domain can actually spell.
Executor is the public alias for the read/write surface a statement runs against. *sql.DB, *sql.Tx and *sql.Conn all satisfy it with no adapter, and it deliberately carries no Commit, Rollback or Begin.
type Executor = coresql.Executor
Executor is an alias of github.com/kitsunium/sdk/internal/core/data/sql.Executor, declared as:
type Executor interface {
// ExecContext runs a statement that returns no rows.
ExecContext(ctx context.Context, query string, args ...any) (stdsql.Result, error)
// QueryContext runs a statement that returns rows. The caller closes them.
QueryContext(ctx context.Context, query string, args ...any) (*stdsql.Rows, error)
// QueryRowContext runs a statement expected to return at most one row.
QueryRowContext(ctx context.Context, query string, args ...any) *stdsql.Row
}
func (Executor) ExecContext(ctx context.Context, query string, args ...any) (stdsql.Result, error)
ExecContext runs a statement that returns no rows.
func (Executor) QueryContext(ctx context.Context, query string, args ...any) (*stdsql.Rows, error)
QueryContext runs a statement that returns rows. The caller closes them.
func (Executor) QueryRowContext(ctx context.Context, query string, args ...any) *stdsql.Row
QueryRowContext runs a statement expected to return at most one row.
InsertSpec is the public alias for the statement InsertRows writes: the table, its columns, and how many rows one statement carries at most.
type InsertSpec = svcsql.InsertSpec
InsertSpec is an alias of github.com/kitsunium/sdk/internal/service/data/sql.InsertSpec, declared as:
type InsertSpec struct {
// Table is the table the rows go into, interpolated as given — bare,
// or already quoted with [coresql.Dialect].QuoteIdent. Validate it
// first.
Table string
// Columns are the table's columns each row gives a value for, in that
// order, interpolated as given like Table.
Columns []string
// PerStatement is how many rows one statement carries at most,
// clamped to the engine's bound-parameter ceiling divided by the
// number of columns; a non-positive one takes that ceiling.
PerStatement int
}
Joiner is the public alias for the ADR 0039 sibling of Transactor that says where a statement issued under a context runs: in the transaction the context carries, or on the pool. Discover it by type assertion.
type Joiner = coresql.Joiner
Joiner is an alias of github.com/kitsunium/sdk/internal/core/data/sql.Joiner, declared as:
type Joiner interface {
// Join returns the executor a statement issued under ctx runs on.
//
// When ctx carries a transaction THIS transactor opened, that is the
// executor of the innermost scope ctx names, and inTx is true. It is
// valid only while that scope runs and refuses afterwards: a context
// that outlived its transaction must not fall back to the pool, where
// its statements would run outside the transaction they were written
// for.
//
// When ctx carries none — no transaction, or only another transactor's —
// it is the pool, outside any transaction, and inTx is false.
Join(ctx context.Context) (ex Executor, inTx bool)
}
func (Joiner) Join
func (Joiner) Join(ctx context.Context) (ex Executor, inTx bool)
Join returns the executor a statement issued under ctx runs on.
When ctx carries a transaction THIS transactor opened, that is the executor of the innermost scope ctx names, and inTx is true. It is valid only while that scope runs and refuses afterwards: a context that outlived its transaction must not fall back to the pool, where its statements would run outside the transaction they were written for.
When ctx carries none — no transaction, or only another transactor's — it is the pool, outside any transaction, and inTx is false.
MigrateConfig is the public alias for the migration runner's parameters.
type MigrateConfig = svcsql.MigrateConfig
MigrateConfig is an alias of github.com/kitsunium/sdk/internal/service/data/sql.MigrateConfig, declared as:
type MigrateConfig struct {
// Migrations is the complete set this binary carries. Order does not
// matter — the runner sorts by Version — but every entry must be valid
// and no two may share a version.
//
// It is COPIED at construction, so a caller appending to their slice
// afterwards cannot change what the runner will apply.
Migrations []coresql.MigrationValue
// VersionTable names the bookkeeping table. Empty clamps to
// [DefaultVersionTable]; anything that is not a bare SQL identifier is
// REFUSED, because it is interpolated rather than bound.
VersionTable string
// LockTimeout bounds the wait for the advisory lock. Non-positive clamps
// to [DefaultLockTimeout].
LockTimeout time.Duration
// LockRetryInterval is how often the non-blocking acquisition is retried
// while another holder has the lock. Non-positive clamps to
// [DefaultLockRetryInterval].
LockRetryInterval time.Duration
}
Migration is the public alias for one versioned schema change and its reversal.
type Migration = coresql.MigrationValue
Migration is an alias of github.com/kitsunium/sdk/internal/core/data/sql.MigrationValue, declared as:
type Migration struct {
// Version orders the migration. It must be non-zero and unique within one
// set: zero is reserved as "before every migration", the value Down takes
// to reverse everything.
//
// Any strictly-increasing scheme works; a UTC timestamp (20260910143000)
// is the conventional one because two branches developed in parallel then
// merge without colliding.
Version uint64
// Name is the human label recorded in the version table. It must be
// non-empty: a schema history whose rows say only "42" is a schema
// history nobody can read during an incident.
Name string
// Up applies the change. Required.
Up Step
// Down reverses it. Required — and [Irreversible] is how a caller says
// "this one cannot be reversed" OUT LOUD.
//
// A nil Down is refused rather than accepted as "irreversible", because
// "I forgot the reversal" and "there is no reversal" are the same nil and
// only one of them is a defect. The same rule core/app/lifecycle applies to a
// nil Stop, for the same reason.
Down Step
// Checksum is the fingerprint of the statements Up runs, recorded in the
// version table beside the version when the migration applies, so a
// later run notices that an applied migration's text was edited — the
// merge that rewrote a column type, the branch that renumbered — and
// refuses with [MigrationChanged] before applying anything, instead of
// calling it applied by its number alone.
//
// Empty means unverified: the migration is never compared, which is what
// a [Step] written as code rather than as statements must be, since a
// func is opaque (ADR 0055 §D12). A migration built with
// internal/service/data/sql's Statements sets it from the same
// statements with ChecksumOf. A version recorded before checksums
// existed is given this one by the next run that declares it.
Checksum string
}
func (Migration) Validate
func (Migration) Validate() error
Validate reports why the migration could never run, or nil.
It is a method on the value rather than a check inside the runner so a caller can validate a set at start-up — before any connection exists — which is where a wiring fault should be found.
Migrator is the public alias for the schema-migration runner.
type Migrator = coresql.Migrator
Migrator is an alias of github.com/kitsunium/sdk/internal/core/data/sql.Migrator, declared as:
type Migrator interface {
// Down reverses every applied migration whose version is STRICTLY GREATER
// than target, in descending order, each in its own transaction, under the
// migration lock. Down(ctx, 0) reverses everything.
Down(ctx context.Context, target uint64) error
// Plan reports what Up would apply, in the order it would apply it,
// WITHOUT applying anything and without taking the migration lock. It is
// the dry run: a read of the version table and a set difference.
Plan(ctx context.Context) ([]MigrationValue, error)
// Up applies every pending migration in ascending version order, each in
// its own transaction, under the migration lock.
Up(ctx context.Context) error
}
func (Migrator) Down
func (Migrator) Down(ctx context.Context, target uint64) error
Down reverses every applied migration whose version is STRICTLY GREATER than target, in descending order, each in its own transaction, under the migration lock. Down(ctx, 0) reverses everything.
func (Migrator) Plan
func (Migrator) Plan(ctx context.Context) ([]MigrationValue, error)
Plan reports what Up would apply, in the order it would apply it, WITHOUT applying anything and without taking the migration lock. It is the dry run: a read of the version table and a set difference.
func (Migrator) Up
func (Migrator) Up(ctx context.Context) error
Up applies every pending migration in ascending version order, each in its own transaction, under the migration lock.
PoolConfig is the public alias for the connection-pool policy.
type PoolConfig = svcsql.PoolConfig
PoolConfig is an alias of github.com/kitsunium/sdk/internal/service/data/sql.PoolConfig, declared as:
type PoolConfig struct {
// MaxOpen is the hard ceiling on open connections. It is REQUIRED and
// must be positive.
//
// database/sql reads 0 as UNLIMITED. "As many connections as the
// application happens to want" is a decision made by forgetting a line,
// and the consequence lands on the database rather than on the process
// that forgot — every replica racing to exhaust one server's
// max_connections. No value the SDK could invent is defensible either,
// because the right one depends on the server's ceiling divided by the
// number of replicas, which the SDK cannot see. So this is the REFUSAL
// side of ADR 0031.
MaxOpen int
// MaxIdle is the number of connections kept warm. A non-positive value
// clamps to [DefaultMaxIdle]; a value above MaxOpen clamps DOWN to
// MaxOpen, because database/sql does that reduction silently and a caller
// reading their own configuration back would otherwise be misled.
MaxIdle int
// MaxLifetime is how long a connection may be reused. A non-positive
// value clamps to [DefaultMaxLifetime]; see that constant for why an
// unbounded lifetime is not an option.
MaxLifetime time.Duration
// MaxIdleTime is how long an unused connection is kept. A non-positive
// value is passed through as "no idle deadline", and that is deliberate
// rather than an oversight: MaxLifetime is now always finite, so an idle
// connection is already retired on a bounded schedule, and a second
// timer would only decide how aggressively to shrink a warm pool — a
// tuning choice with no dangerous zero.
MaxIdleTime time.Duration
}
Preparer is the public alias for the ADR 0039 sibling of Executor: an executor that can also prepare a statement. Discover it by type assertion.
type Preparer = coresql.Preparer
Preparer is an alias of github.com/kitsunium/sdk/internal/core/data/sql.Preparer, declared as:
type Preparer interface {
// PrepareContext compiles a statement. The caller closes it.
PrepareContext(ctx context.Context, query string) (*stdsql.Stmt, error)
}
func (Preparer) PrepareContext(ctx context.Context, query string) (*stdsql.Stmt, error)
PrepareContext compiles a statement. The caller closes it.
RetryConfig is the public alias for a retrying transactor's parameters: its attempts, its backoff curve, its classifier and its clock.
type RetryConfig = svcsql.RetryConfig
RetryConfig is an alias of github.com/kitsunium/sdk/internal/service/data/sql.RetryConfig, declared as:
type RetryConfig struct {
// Attempts is how many times a root transaction may run in all, the
// first included: 1 runs it once and never again. A non-positive value
// clamps to [DefaultRetryAttempts].
Attempts int
// Backoff is the curve of the waits between two attempts: the wait
// after the n-th failed attempt is Backoff.Delay(n). The zero value
// clamps to [DefaultRetryDelay] grown by 2 per attempt with full jitter;
// any other value is the caller's curve, as given — a BaseDelay of zero
// with a ceiling or a jitter is a caller's choice to retry at once.
Backoff kbackoff.Value
// Transient decides which failures earn another attempt. A nil value
// clamps to [Classify]. A context's end never does, whatever Transient
// answers.
Transient coresql.Transient
// Clock is the time source the waits are read from. A nil Clock falls
// back to clock.System; a test hands a ManualClock and advances it
// rather than sleeping.
Clock clock.Timed
}
Router is the public alias for the transactor's ADR 0039 sibling that routes a read — a read-only transaction, or a read outside one — to the read replica Config.ReadReplica names.
type Router = coresql.Router
Router is an alias of github.com/kitsunium/sdk/internal/core/data/sql.Router, declared as:
type Router interface {
// Reader returns the executor a read issued under ctx runs on: the
// innermost scope of the transaction ctx carries for THIS transactor, as
// [Joiner].Join answers it — on the replica when that transaction is
// read-only —, else the replica's pool, else the primary's when no replica
// is configured.
Reader(ctx context.Context) Executor
}
func (Router) Reader
func (Router) Reader(ctx context.Context) Executor
Reader returns the executor a read issued under ctx runs on: the innermost scope of the transaction ctx carries for THIS transactor, as Joiner.Join answers it — on the replica when that transaction is read-only —, else the replica's pool, else the primary's when no replica is configured.
StatementKind is the public alias for what a StatementObserver is told about.
type StatementKind = coresql.StatementKind
StatementKind is an alias of github.com/kitsunium/sdk/internal/core/data/sql.StatementKind, declared as:
type StatementKind uint8
func (StatementKind) String
func (StatementKind) String() string
String returns the kind's lowercase name, "unknown" for none.
StatementObserver is the public alias for what watches the statements a pool sends: told when each begins, it answers the function told when it ends. It sees the SQL text, bounded, and never an argument or a row.
type StatementObserver = coresql.StatementObserver
StatementObserver is an alias of github.com/kitsunium/sdk/internal/core/data/sql.StatementObserver, declared as:
type StatementObserver interface {
// Begin is told that a statement of kind is about to run under ctx, and
// returns end, which is told exactly once when it ended: rows is the
// count a query's rows read before they closed, or an exec's rows
// affected when the driver reports it, else 0; err is the driver's own
// error, nil on success. end is never nil.
Begin(ctx context.Context, kind StatementKind, query string) (end func(rows int64, err error))
}
func (StatementObserver) Begin
func (StatementObserver) Begin(ctx context.Context, kind StatementKind, query string) (end func(rows int64, err error))
Begin is told that a statement of kind is about to run under ctx, and returns end, which is told exactly once when it ended: rows is the count a query's rows read before they closed, or an exec's rows affected when the driver reports it, else 0; err is the driver's own error, nil on success. end is never nil.
Step is the public alias for one direction of a migration.
type Step = coresql.Step
Step is an alias of github.com/kitsunium/sdk/internal/core/data/sql.Step, declared as:
type Step func(ctx context.Context, ex Executor) error
Transactor is the public alias for the transaction manager.
type Transactor = coresql.Transactor
Transactor is an alias of github.com/kitsunium/sdk/internal/core/data/sql.Transactor, declared as:
type Transactor interface {
// Transact runs fn inside a transaction and commits when fn returns nil.
//
// If ctx already carries a transaction opened by THIS Transactor, fn runs
// inside a SAVEPOINT of that transaction instead of a new one, and opts
// must be the zero value — a savepoint cannot change isolation or
// read-only-ness, so asking for either is refused with [NestedIsolation]
// rather than silently ignored (ADR 0055 §D5).
//
// A panic inside fn is NOT recovered: the deferred rollback still runs,
// and the panic reaches the caller with its original stack.
Transact(ctx context.Context, opts TxOptionsValue, fn TxFunc) error
}
func (Transactor) Transact
func (Transactor) Transact(ctx context.Context, opts TxOptionsValue, fn TxFunc) error
Transact runs fn inside a transaction and commits when fn returns nil.
If ctx already carries a transaction opened by THIS Transactor, fn runs inside a SAVEPOINT of that transaction instead of a new one, and opts must be the zero value — a savepoint cannot change isolation or read-only-ness, so asking for either is refused with NestedIsolation rather than silently ignored (ADR 0055 §D5).
A panic inside fn is NOT recovered: the deferred rollback still runs, and the panic reaches the caller with its original stack.
Transient is the public alias for the verdict on a failed transaction: true when the database asks for the whole transaction to run again.
type Transient = coresql.Transient
Transient is an alias of github.com/kitsunium/sdk/internal/core/data/sql.Transient, declared as:
type Transient func(err error) bool
TxFunc is the public alias for a unit of work run inside a transaction.
type TxFunc = coresql.TxFunc
TxFunc is an alias of github.com/kitsunium/sdk/internal/core/data/sql.TxFunc, declared as:
type TxFunc func(ctx context.Context, ex Executor) error
TxOptions is the public alias for one transaction's isolation and read-only-ness. The zero value defers to the driver.
type TxOptions = coresql.TxOptionsValue
TxOptions is an alias of github.com/kitsunium/sdk/internal/core/data/sql.TxOptionsValue, declared as:
type TxOptions struct {
// Isolation is the transaction isolation level. The zero value
// (sql.LevelDefault) defers to the driver.
Isolation stdsql.IsolationLevel
// ReadOnly asks the engine to refuse writes. Not every driver enforces
// it; those that do not report an error at Begin rather than accepting
// writes silently, which is why this is passed through rather than
// emulated.
ReadOnly bool
}
func (TxOptions) IsZero
func (TxOptions) IsZero() bool
IsZero reports whether the options ask for nothing beyond the driver's defaults.
It exists because a NESTED scope is a savepoint, and a savepoint can change neither the isolation level nor the read-only-ness of the transaction it sits inside. Non-zero options on a nested call are therefore refused with NestedIsolation rather than ignored — silently downgrading a caller's explicit `Serializable` to whatever the outer transaction happened to use is the kind of "helpful" behaviour that produces a data race nobody can find (ADR 0055 §D5).
func (TxOptions) StdOptions() *stdsql.TxOptions
StdOptions renders the value as the stdlib's own option struct, ready for (*sql.DB).BeginTx.
Generated from docs/api by tools/genindex.