sqlite

package
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 25, 2026 License: MIT Imports: 31 Imported by: 0

Documentation

Overview

Package sqlite is the SQLite dialect for drops. It mirrors drops/pg's API surface — Table / Column / DB / DDL / query builders — over the same swappable drops.Driver connector, but emits SQLite-flavoured SQL: "?" placeholders, SQLite type affinities, and constraints declared inline in CREATE TABLE (SQLite has no ALTER TABLE ADD CONSTRAINT).

Because both pg and sqlite build on drops.Dialect, pointing the same schema at SQLite instead of PostgreSQL is a matter of swapping the package (and the underlying driver) — the builder chain is otherwise identical.

Index

Constants

View Source
const (
	AttrStatement    = "db.statement"
	AttrArgsCount    = "db.args.count"
	AttrSystem       = "db.system"
	AttrOperation    = "db.operation"
	AttrSystemSQLite = "sqlite"
)

Attribute keys used by drops spans — named constants so downstream dashboards can match them reliably.

View Source
const DefaultMigrationsTable = "_drops_sqlite_migrations"

DefaultMigrationsTable is the table used to track applied migrations when no override is set on the Migrator.

View Source
const DrizzleTable = "__drizzle_migrations"

DrizzleTable is the migration history table. SQLite has no schemas, so (unlike drops/pg) the table is unqualified — matching how drizzle-orm's sqlite driver stores its history.

View Source
const StatementBreakpoint = "--> statement-breakpoint"

StatementBreakpoint is the delimiter drizzle-kit emits between statements when breakpoints are enabled.

Variables

View Source
var (
	// ErrNoRows is returned by ScanOne when the result set is empty.
	ErrNoRows = errors.New("drops/sqlite: no rows in result set")

	// ErrNoRowsToInsert is returned when an INSERT has no rows to write.
	ErrNoRowsToInsert = errors.New("drops/sqlite: INSERT with no rows")

	// ErrInvalidIdentifier is returned (or panicked, at declaration
	// time) for an identifier that cannot be safely quoted.
	ErrInvalidIdentifier = errors.New("drops/sqlite: invalid identifier")

	// ErrBusy / ErrLocked are the retryable-contention sentinels
	// matching SQLITE_BUSY and SQLITE_LOCKED. A RetryPolicy listing
	// them (as DefaultRetryPolicy does) retries a transaction when the
	// underlying driver reports either — matched by errors.Is or by the
	// driver error's message (drivers rarely wrap a comparable
	// sentinel).
	ErrBusy   = errors.New("drops/sqlite: database is busy (SQLITE_BUSY)")
	ErrLocked = errors.New("drops/sqlite: database is locked (SQLITE_LOCKED)")
)

Sentinel errors for assertable failure modes. They mirror drops/pg's error surface so code that switches dialects keeps the same errors.Is checks.

View Source
var Dialect drops.Dialect = sqliteDialect{}

Dialect is the SQLite dialect value. Pass it to drops.WithDialect (or rely on the sqlite.DB, which installs it on every builder).

View Source
var ErrAuditTableMissing = errors.New("drops/sqlite: audit table not present; create it via NewAuditTable + migration")

ErrAuditTableMissing is returned when an audit operation fails because the configured table does not exist.

View Source
var ErrConcurrencyConflict = errors.New("drops/sqlite: event store concurrency conflict")

ErrConcurrencyConflict is returned by Append when expectedVersion no longer matches the stream's head — another writer beat us to it.

View Source
var ErrEmptyKey = errors.New("drops/sqlite: idempotency key cannot be empty")

ErrEmptyKey is returned by Run for an empty key — it would collapse every operation onto one row.

View Source
var ErrNoHandler = errors.New("drops/sqlite: OutboxWorker has no handler")

ErrNoHandler is returned by Run when neither OnEvent nor OnBatch was set.

View Source
var ErrNoMigrationsApplied = errors.New("drops/sqlite: no migrations applied")

ErrNoMigrationsApplied is returned by Down when the history table is empty.

View Source
var ErrSchemaRequired = errors.New("drops/sqlite: Push requires a non-nil schema")

ErrSchemaRequired is returned by Push when schema is nil.

View Source
var ErrSubjectMissing = errors.New("drops/sqlite: entity is guarded but ctx has no subject")

ErrSubjectMissing is returned when a guarded entity is used with a ctx lacking WithSubject.

View Source
var ErrTenantMismatch = errors.New("drops/sqlite: row tenant disagrees with ctx tenant")

ErrTenantMismatch is returned by Create when the row carries a tenant value disagreeing with the ctx tenant.

View Source
var ErrTenantMissing = errors.New("drops/sqlite: entity is tenant-scoped but ctx has no tenant")

ErrTenantMissing is returned when a tenant-scoped entity is used with a ctx that carries no tenant.

View Source
var ErrUnauthorized = errors.New("drops/sqlite: unauthorized")

ErrUnauthorized signals a guard explicitly denied the request.

View Source
var Placeholder = drops.WithDialect(Dialect)

Placeholder is the SQLite placeholder BuilderOption, provided for symmetry with clickhouse.Placeholder. Prefer drops.WithDialect(Dialect).

Functions

func Abs added in v0.5.0

func Abs(e any) drops.Expression

func Acos added in v0.5.0

func Acos(e any) drops.Expression

func ActorFrom added in v0.5.0

func ActorFrom(ctx context.Context) string

ActorFrom returns the actor stored on ctx, or "" when absent.

func And

func And(preds ...drops.Expression) drops.Expression

And / Or combine predicates.

func As added in v0.5.0

func As(e drops.Expression, alias string) drops.Expression

As renames an arbitrary expression: "<expr> AS <alias>".

func Asin added in v0.5.0

func Asin(e any) drops.Expression

func Atan added in v0.5.0

func Atan(e any) drops.Expression

func Avg added in v0.5.0

func AvgDistinct added in v0.5.0

func AvgDistinct(e drops.Expression) drops.Expression

func Between added in v0.5.0

func Between(left, low, high any) drops.Expression

Between renders "left BETWEEN low AND high".

func Cast added in v0.5.0

func Cast(e any, typeSQL string) drops.Expression

Cast is an alias for CastAs, provided for source-level parity with drops/pg (whose Cast renders the PostgreSQL "::type" shorthand).

func CastAs added in v0.5.0

func CastAs(e any, typeSQL string) drops.Expression

CastAs renders CAST(<e> AS <type>) — the standard-SQL type conversion. SQLite has no "::type" shorthand; the CAST form is the only spelling. typeSQL is a SQLite type name such as "INTEGER", "REAL" or "TEXT".

func Ceil added in v0.5.0

func Ceil(e any) drops.Expression

func Chr added in v0.5.0

func Chr(args ...any) drops.Expression

Chr renders char(<codepoints...>) — a string from Unicode code points. Named Chr (not Char) to avoid colliding with the Char column constructor in types.go.

func Coalesce added in v0.5.0

func Coalesce(args ...any) drops.Expression

Coalesce renders coalesce(<args...>).

func ColumnExists added in v0.5.0

func ColumnExists(ctx context.Context, db *DB, table, column string) (bool, error)

ColumnExists reports whether column exists on table. It uses pragma_table_info, so it correctly reflects the live column set even after ALTER TABLE ADD/DROP COLUMN.

func Concat added in v0.5.0

func Concat(args ...any) drops.Expression

Concat renders concat(args...) (SQLite 3.44+). Prefer ConcatOp for portability across older SQLite builds.

func ConcatOp added in v0.5.0

func ConcatOp(left, right any) drops.Expression

ConcatOp renders the SQL "||" concatenation operator: (a || b). This is the portable form on every SQLite version; the concat() function only arrived in 3.44.

func ConcatWS added in v0.5.0

func ConcatWS(sep any, args ...any) drops.Expression

ConcatWS renders concat_ws(sep, args...) (SQLite 3.44+).

func Cos added in v0.5.0

func Cos(e any) drops.Expression

func Count added in v0.5.0

Count renders count(<e>).

func CountAll added in v0.5.0

func CountAll() drops.Expression

CountAll renders count(*).

func CountDistinct added in v0.5.0

func CountDistinct(e drops.Expression) drops.Expression

CountDistinct renders count(DISTINCT <e>).

func CreateIndex

func CreateIndex(name string, t *Table, cols ...ColRef) drops.Expression

CreateIndex returns a CREATE INDEX statement over the given columns.

func CreateIndexIfNotExists

func CreateIndexIfNotExists(name string, t *Table, cols ...ColRef) drops.Expression

CreateIndexIfNotExists is the IF NOT EXISTS variant of CreateIndex.

func CreateTable

func CreateTable(t *Table) drops.Expression

CreateTable returns a CREATE TABLE statement for t.

Unlike PostgreSQL, SQLite cannot add constraints after the fact (there is no ALTER TABLE ADD CONSTRAINT), so EVERY constraint — composite primary key, UNIQUE, CHECK and single- or multi-column foreign key — is emitted inline inside the CREATE TABLE body. This is the deliberate dialect difference from drops/pg, whose migration generator emits those as separate ALTER statements.

func CreateTableIfNotExists

func CreateTableIfNotExists(t *Table) drops.Expression

CreateTableIfNotExists is the IF NOT EXISTS variant.

func CreateUniqueIndex

func CreateUniqueIndex(name string, t *Table, cols ...ColRef) drops.Expression

CreateUniqueIndex returns a CREATE UNIQUE INDEX statement.

func CumeDist added in v0.5.0

func CumeDist() drops.Expression

func CurrentDate added in v0.5.0

func CurrentDate() drops.Expression

CurrentDate / CurrentTime / CurrentTimestamp render the SQL standard keywords, which SQLite supports directly.

func CurrentTime added in v0.5.0

func CurrentTime() drops.Expression

func CurrentTimestamp added in v0.5.0

func CurrentTimestamp() drops.Expression

func DateOf added in v0.5.0

func DateOf(timeval any, modifiers ...any) drops.Expression

DateOf renders date(<timeval>, modifiers...) — the date portion.

func DateTime added in v0.5.0

func DateTime(timeval any, modifiers ...any) drops.Expression

DateTime renders datetime(<timeval>, modifiers...).

func DenseRank added in v0.5.0

func DenseRank() drops.Expression

func Diff

func Diff(prev, cur *Snapshot) []string

Diff returns the ordered list of SQL statements (and inline comments) that evolve a SQLite database from prev's schema to cur's. Output is deterministic for a given (prev, cur): every map is walked in sorted key order.

SQLite has no ALTER TABLE ADD CONSTRAINT and no ALTER COLUMN, so the rules differ sharply from drops/pg:

  • New table → a full CREATE TABLE with every constraint (composite PK, UNIQUE, CHECK, single- and multi-column FK) rendered INLINE.
  • Dropped table → DROP TABLE.
  • Added column that is nullable or has a default (and is not PRIMARY KEY / UNIQUE / a foreign key) → ALTER TABLE ADD COLUMN, which SQLite does support.
  • Dropped column, a column whose type / NOT NULL / default / PK / autoincrement changed, an added column that ALTER cannot add, or any table-level constraint change → the standard SQLite table-rebuild sequence: create "t_new" with the new shape, copy the shared columns across with INSERT … SELECT, DROP the old table and RENAME "t_new" into place. The sequence is prefixed by a `-- rebuild "t": <reason>` comment.

Operation order:

  1. DROP TABLE for tables removed entirely
  2. CREATE TABLE for new tables (all constraints inline)
  3. per surviving table: ADD COLUMN statements, or a rebuild sequence

func DiffDown

func DiffDown(prev, cur *Snapshot) []string

DiffDown returns the SQL that reverses the migration from cur back to prev — applying these statements after the corresponding Diff(prev, cur) restores the original schema. It is simply Diff(cur, prev).

func Div added in v0.5.0

func Div(left, right any) drops.Expression

func DropIndex

func DropIndex(name string) drops.Expression

DropIndex returns DROP INDEX name.

func DropIndexIfExists

func DropIndexIfExists(name string) drops.Expression

DropIndexIfExists is the IF EXISTS variant.

func DropTable

func DropTable(t *Table) drops.Expression

DropTable returns DROP TABLE t.

func DropTableIfExists

func DropTableIfExists(t *Table) drops.Expression

DropTableIfExists is the IF EXISTS variant.

func Eq added in v0.5.0

func Eq(left, right any) drops.Expression

func Exists added in v0.5.0

func Exists(q drops.Expression) drops.Expression

Exists renders EXISTS (<subquery>).

func Exp added in v0.5.0

func Exp(e any) drops.Expression

func ExponentialJitter added in v0.5.0

func ExponentialJitter(base, maxN time.Duration) func(attempt int) time.Duration

ExponentialJitter returns a backoff that doubles each attempt from base, caps at maxN, and adds [0, base) jitter so concurrent retries don't synchronise into thundering herds.

func Filter added in v0.5.0

func Filter(agg, pred drops.Expression) drops.Expression

Filter wraps an aggregate with a FILTER (WHERE ...) clause. Supported by SQLite 3.30+:

sqlite.Filter(sqlite.Count(UserID), UserStatus.Eq("active"))

func FirstValue added in v0.5.0

func FirstValue(expr any) drops.Expression

func Floor added in v0.5.0

func Floor(e any) drops.Expression

func Format added in v0.5.0

func Format(format any, args ...any) drops.Expression

Format renders format(<fmt>, args...) — SQLite's printf-style formatter (also spelled printf()).

func Func added in v0.5.0

func Func(name string, args ...any) drops.Expression

Func renders an arbitrary function call <name>(<args...>). Escape hatch for SQLite functions without a dedicated helper.

func Glob added in v0.5.0

func Glob(left, pattern any) drops.Expression

Glob renders "<left> GLOB <pattern>" — SQLite's case-sensitive, Unix-shell-style wildcard match.

func Greatest added in v0.5.0

func Greatest(args ...any) drops.Expression

Greatest / Least map to SQLite's multi-argument max() / min(), which act as scalar (not aggregate) functions when given two or more arguments.

func GroupConcat added in v0.5.0

func GroupConcat(e any, sep ...any) drops.Expression

GroupConcat renders group_concat(<e>) or group_concat(<e>, <sep>) — SQLite's equivalent of PostgreSQL string_agg.

func Gt added in v0.5.0

func Gt(left, right any) drops.Expression

func Gte added in v0.5.0

func Gte(left, right any) drops.Expression

func Hex added in v0.5.0

func Hex(e any) drops.Expression

Hex / Unhex render hex(<e>) / unhex(<e>) (unhex is SQLite 3.41+).

func IfNull added in v0.5.0

func IfNull(a, b any) drops.Expression

IfNull renders ifnull(<a>, <b>) — SQLite's two-argument coalesce.

func In added in v0.5.0

func In(left any, values ...any) drops.Expression

In renders "left IN (...)". A single slice argument is expanded so In(col, []int{1, 2, 3}) is equivalent to In(col, 1, 2, 3). A subquery Expression is rendered inline: In(col, subSelect).

func InSub added in v0.5.0

func InSub(value any, sub drops.Expression) drops.Expression

InSub renders "<value> IN (<subquery>)".

func IndexExists added in v0.5.0

func IndexExists(ctx context.Context, db *DB, index string) (bool, error)

IndexExists reports whether an index named index exists.

func Instr added in v0.5.0

func Instr(e, sub any) drops.Expression

Instr renders instr(<e>, <sub>) — the 1-based position of sub in e, or 0 when absent. SQLite's counterpart to PostgreSQL strpos.

func IsDistinctFrom added in v0.5.0

func IsDistinctFrom(left, right any) drops.Expression

IsDistinctFrom / IsNotDistinctFrom render NULL-safe comparison via SQLite's IS / IS NOT operators (SQLite's IS treats NULLs as equal).

func IsNotDistinctFrom added in v0.5.0

func IsNotDistinctFrom(left, right any) drops.Expression

func IsNotNull added in v0.5.0

func IsNotNull(e any) drops.Expression

func IsNull added in v0.5.0

func IsNull(e any) drops.Expression

func IsPII added in v0.5.0

func IsPII(v any) bool

IsPII returns true when v carries the redaction marker.

func IsSagaError added in v0.5.0

func IsSagaError(err error) bool

IsSagaError reports whether err is a *SagaError.

func JSONArray added in v0.5.0

func JSONArray(args ...any) drops.Expression

JSONArray renders json_array(args...).

func JSONArrayLength added in v0.5.0

func JSONArrayLength(e any, path ...any) drops.Expression

JSONArrayLength renders json_array_length(<e>) or, with a path, json_array_length(<e>, <path>).

func JSONExtract added in v0.5.0

func JSONExtract(e, path any) drops.Expression

JSONExtract renders json_extract(<e>, <path>) — path is a JSON path string such as "$.name" or "$.items[0]".

func JSONGet added in v0.5.0

func JSONGet(e, path any) drops.Expression

JSONGet renders <e> -> <path> (returns a JSON representation).

func JSONGetText added in v0.5.0

func JSONGetText(e, path any) drops.Expression

JSONGetText renders <e> ->> <path> (returns a SQL text/numeric value).

func JSONGroupArray added in v0.5.0

func JSONGroupArray(e any) drops.Expression

JSONGroupArray / JSONGroupObject are the JSON aggregate functions.

func JSONGroupObject added in v0.5.0

func JSONGroupObject(k, v any) drops.Expression

func JSONHasKey added in v0.5.0

func JSONHasKey(col ColRef, path ...string) drops.Expression

JSONHasKey renders "json_type(<col>, '<path>') IS NOT NULL" — true when the key exists in the document (even if its value is JSON null, json_type returns 'null', which is still non-NULL SQL).

func JSONInsert added in v0.5.0

func JSONInsert(e any, args ...any) drops.Expression

func JSONObject added in v0.5.0

func JSONObject(args ...any) drops.Expression

JSONObject renders json_object(k1, v1, k2, v2, ...).

func JSONPatch added in v0.5.0

func JSONPatch(target, patch any) drops.Expression

JSONPatch renders json_patch(<target>, <patch>) — RFC 7396 merge.

func JSONQuote added in v0.5.0

func JSONQuote(e any) drops.Expression

JSONQuote renders json_quote(<e>) — the JSON representation of a value.

func JSONRemove added in v0.5.0

func JSONRemove(e any, paths ...any) drops.Expression

JSONRemove renders json_remove(<e>, paths...).

func JSONReplace added in v0.5.0

func JSONReplace(e any, args ...any) drops.Expression

func JSONSet added in v0.5.0

func JSONSet(e any, args ...any) drops.Expression

JSONSet / JSONInsert / JSONReplace render the corresponding mutating functions: json_set(<e>, path, value, ...) and friends. Arguments after e are path/value pairs.

func JSONType added in v0.5.0

func JSONType(e any, path ...any) drops.Expression

JSONType renders json_type(<e>) or json_type(<e>, <path>).

func JSONValid added in v0.5.0

func JSONValid(e any) drops.Expression

JSONValid renders json_valid(<e>) — 1 when e is well-formed JSON.

func JulianDay added in v0.5.0

func JulianDay(timeval any, modifiers ...any) drops.Expression

JulianDay renders julianday(<timeval>, modifiers...).

func LTrim added in v0.5.0

func LTrim(e any, chars ...any) drops.Expression

func Lag added in v0.5.0

func Lag(expr any, args ...any) drops.Expression

Lag renders lag(expr [, offset [, default]]).

func LastValue added in v0.5.0

func LastValue(expr any) drops.Expression

func Lead added in v0.5.0

func Lead(expr any, args ...any) drops.Expression

Lead renders lead(expr [, offset [, default]]).

func Least added in v0.5.0

func Least(args ...any) drops.Expression

func Length added in v0.5.0

func Length(e any) drops.Expression

Length renders length(<e>).

func Like added in v0.5.0

func Like(left, pattern any) drops.Expression

Like renders "<left> LIKE <pattern>". SQLite's LIKE is case-insensitive for ASCII by default.

func LikeEscape added in v0.5.0

func LikeEscape(left, pattern, esc any) drops.Expression

LikeEscape renders "<left> LIKE <pattern> ESCAPE <esc>".

func Ln added in v0.5.0

func Ln(e any) drops.Expression

func Log added in v0.5.0

func Log(e any, base ...any) drops.Expression

Log renders log(<e>) — base-10 logarithm in SQLite — or log(<base>, <e>) when a base is supplied.

func LoggerHook added in v0.5.0

func LoggerHook(log LoggerFunc, opts ...LoggerOptions) drops.Hook

LoggerHook re-exports drops.LoggerHook. See its documentation for behaviour and tuning options. Attach it with db.WithHook.

func Lower added in v0.5.0

Lower / Upper case-folding helpers.

func Lt added in v0.5.0

func Lt(left, right any) drops.Expression

func Lte added in v0.5.0

func Lte(left, right any) drops.Expression

func Max added in v0.5.0

func MermaidDiagram added in v0.5.0

func MermaidDiagram(s *Schema) string

MermaidDiagram renders the schema as a Mermaid `erDiagram` block, driven directly by the table declarations and their registered relations.

fmt.Println(sqlite.MermaidDiagram(sqlite.NewSchema(Users, Posts)))
// erDiagram
//   USERS {
//     INTEGER id PK
//     TEXT name
//   }
//   USERS ||--o{ POSTS : posts

Cardinality marks come from the Relation metadata:

HasMany     →  ||--o{
HasOne      →  ||--o|
ManyToMany  →  }o--o{   (junction omitted for clarity)
BelongsTo   →  rendered from the parent side as the inverse

func Min added in v0.5.0

func Minus added in v0.5.0

func Minus(left, right any) drops.Expression

func Mod added in v0.5.0

func Mod(a, b any) drops.Expression

Mod renders the SQL "%" modulo operator: (a % b). SQLite has no mod() function; the operator is the portable form.

func Mul added in v0.5.0

func Mul(left, right any) drops.Expression

func N1Hook added in v0.5.0

func N1Hook(ctx context.Context, e drops.QueryEvent)

N1Hook is a drops.Hook that records every query into the tracker attached to ctx by WithN1Detector. Without a tracker the hook is a no-op, so attaching it globally to the DB is free for untracked traffic.

func Ne added in v0.5.0

func Ne(left, right any) drops.Expression

func Not added in v0.5.0

Not negates a predicate.

func NotBetween added in v0.5.0

func NotBetween(left, low, high any) drops.Expression

NotBetween renders "left NOT BETWEEN low AND high".

func NotExists added in v0.5.0

func NotExists(q drops.Expression) drops.Expression

NotExists renders NOT EXISTS (<subquery>).

func NotIn added in v0.5.0

func NotIn(left any, values ...any) drops.Expression

NotIn renders "left NOT IN (...)".

func NotInSub added in v0.5.0

func NotInSub(value any, sub drops.Expression) drops.Expression

NotInSub renders "<value> NOT IN (<subquery>)".

func NotLike added in v0.5.0

func NotLike(left, pattern any) drops.Expression

NotLike renders "<left> NOT LIKE <pattern>".

func Now added in v0.5.0

func Now() drops.Expression

Now renders CURRENT_TIMESTAMP — "YYYY-MM-DD HH:MM:SS" in UTC.

func NthValue added in v0.5.0

func NthValue(expr, n any) drops.Expression

func Ntile added in v0.5.0

func Ntile(n any) drops.Expression

func NullIf added in v0.5.0

func NullIf(a, b any) drops.Expression

NullIf renders nullif(<a>, <b>).

func OctetLength added in v0.5.0

func OctetLength(e any) drops.Expression

OctetLength renders octet_length(<e>) (SQLite 3.43+).

func OnDelete

func OnDelete(action string) func(*FK)

OnDelete / OnUpdate configure the referential actions on a FK.

func OnUpdate

func OnUpdate(action string) func(*FK)

func Or

func Or(preds ...drops.Expression) drops.Expression

func Over added in v0.5.0

func Over(fn drops.Expression, win *Window) drops.Expression

Over wraps an aggregate or window function with an OVER clause.

sqlite.Over(sqlite.RowNumber(),
    sqlite.WindowSpec().PartitionBy(UserID).OrderBy(PostCreatedAt.Desc()))

func PII added in v0.5.0

func PII(value any) any

PII wraps value so it travels through drops as a redaction marker.

func ParseMigrationName

func ParseMigrationName(filename string) (version, name, kind string, ok bool)

ParseMigrationName recognises "<version>_<name>.{up,down}.sql" and returns the version, name and kind ("up" or "down"). It is exposed so callers can validate filenames before adding them.

func PercentRank added in v0.5.0

func PercentRank() drops.Expression

func Plus added in v0.5.0

func Plus(left, right any) drops.Expression

Arithmetic operators as parenthesised binary expressions.

func Power added in v0.5.0

func Power(a, b any) drops.Expression

func Printf added in v0.5.0

func Printf(format any, args ...any) drops.Expression

Printf is the SQLite-native spelling of Format.

func Quote added in v0.5.0

func Quote(e any) drops.Expression

Quote renders quote(<e>) — an SQL-literal-safe rendering of the value.

func RTrim added in v0.5.0

func RTrim(e any, chars ...any) drops.Expression

func Random added in v0.5.0

func Random() drops.Expression

Random renders random() — a signed 64-bit integer in SQLite.

func Rank added in v0.5.0

func Rank() drops.Expression

func Regexp added in v0.5.0

func Regexp(left, pattern any) drops.Expression

Regexp renders "<left> REGEXP <pattern>". SQLite has no built-in REGEXP implementation; a user-defined function must be registered on the connection for this to execute.

func Replace added in v0.5.0

func Replace(e, from, to any) drops.Expression

Replace renders replace(<e>, <from>, <to>).

func Round added in v0.5.0

func Round(e any, digits ...int) drops.Expression

Round renders round(<e>) or round(<e>, <digits>) when digits is given.

func RowNumber added in v0.5.0

func RowNumber() drops.Expression

func RunJSON added in v0.5.0

func RunJSON[T any](s *IdempotencyStore, ctx context.Context, key string, fn func(tx *DB) (T, error)) (T, error)

RunJSON wraps Run with JSON marshalling on both sides.

func SagaStateGet added in v0.5.0

func SagaStateGet[T any](s *SagaState, key string) (T, bool)

SagaStateGet is the type-safe getter — zero value + ok=false when the key is missing or the stored value is the wrong type.

func Sign added in v0.5.0

func Sign(e any) drops.Expression

func Sin added in v0.5.0

func Sin(e any) drops.Expression

Trigonometric helpers — pass radians (require the math extension).

func Sqrt added in v0.5.0

func Sqrt(e any) drops.Expression

func StrfTime added in v0.5.0

func StrfTime(format, timeval any, modifiers ...any) drops.Expression

StrfTime renders strftime(<format>, <timeval>, modifiers...) — the general-purpose formatter, e.g. StrfTime("%Y-%m", col).

func SubjectFrom added in v0.5.0

func SubjectFrom(ctx context.Context) (any, bool)

SubjectFrom returns the subject on ctx and ok=true when set.

func Subquery added in v0.5.0

func Subquery(q drops.Expression) drops.Expression

Subquery wraps an expression (typically a SELECT) in parentheses for use as a scalar subquery in a SELECT list or predicate.

func Substr added in v0.5.0

func Substr(e, from any, count ...any) drops.Expression

Substr renders substr(<e>, <from>) or substr(<e>, <from>, <count>). SQLite indexes are 1-based; a negative from counts from the end.

func Sum added in v0.5.0

Sum / Avg / Min / Max aggregates.

func SumDistinct added in v0.5.0

func SumDistinct(e drops.Expression) drops.Expression

SumDistinct / AvgDistinct apply DISTINCT to the aggregate.

func TableExists added in v0.5.0

func TableExists(ctx context.Context, db *DB, table string) (bool, error)

TableExists reports whether a table (or view) named table exists.

func Tan added in v0.5.0

func Tan(e any) drops.Expression

func TenantFrom added in v0.5.0

func TenantFrom(ctx context.Context) (any, bool)

TenantFrom returns the tenant on ctx (ok=false when absent).

func TestTx added in v0.5.0

func TestTx(t TB, db *DB, ctx context.Context, fn func(tx *DB))

TestTx runs fn inside a freshly-opened transaction that is unconditionally rolled back at the end of the test. The handle passed to fn is a *DB scoped to the transaction; every Exec / Query / Entity operation against it lives or dies with the rollback — the underlying schema and rows are untouched after the test returns.

sqlite.TestTx(t, db, ctx, func(tx *sqlite.DB) {
    if err := UserEntity.Create(tx, ctx, &u); err != nil {
        t.Fatalf("create: %v", err)
    }
    ...
})

Use it to keep test cases hermetic without writing manual setup / teardown. Tests that need to verify post-commit behaviour (triggers that look at xact id, etc.) should open a transaction directly via db.Begin instead.

func TimeOf added in v0.5.0

func TimeOf(timeval any, modifiers ...any) drops.Expression

TimeOf renders time(<timeval>, modifiers...) — the time portion.

func ToSQL

func ToSQL(e drops.Expression) (sql string, args []any)

ToSQL renders e with the SQLite dialect. Exposed for tests and logging (mirrors clickhouse.ToSQL).

func Total added in v0.5.0

Total is SQLite's sum() variant that returns 0.0 (never NULL) over an empty or all-NULL set, always as a floating-point value.

func TriggerExists added in v0.5.0

func TriggerExists(ctx context.Context, db *DB, trigger string) (bool, error)

TriggerExists reports whether a trigger named trigger exists.

func Trim added in v0.5.0

func Trim(e any, chars ...any) drops.Expression

Trim / LTrim / RTrim render trim/ltrim/rtrim(<e>) or the two-argument form trim(<e>, <chars>) that strips a custom character set.

func Trunc added in v0.5.0

func Trunc(e any) drops.Expression

func Unhex added in v0.5.0

func Unhex(e any) drops.Expression

func Unicode added in v0.5.0

func Unicode(e any) drops.Expression

Unicode renders unicode(<e>) — the code point of the first character.

func UnixEpoch added in v0.5.0

func UnixEpoch(timeval any, modifiers ...any) drops.Expression

UnixEpoch renders unixepoch(<timeval>, modifiers...) (SQLite 3.38+).

func Upper added in v0.5.0

func WithActor added in v0.5.0

func WithActor(ctx context.Context, actor any) context.Context

WithActor annotates ctx with an actor identifier.

func WithN1Detector added in v0.5.0

func WithN1Detector(ctx context.Context) (derived context.Context, finish func(threshold int) N1Report)

WithN1Detector returns a derived context that records every SQL statement issued through sqlite.DB. Call the returned finisher with a threshold to produce the report; typically wired up in a `defer` so it runs once at the end of the request / job.

func WithSubject added in v0.5.0

func WithSubject(ctx context.Context, subject any) context.Context

WithSubject annotates ctx with the acting subject (typically the caller's user id). Distinct from WithActor, which records who performed the mutation for audit.

func WithTenant added in v0.5.0

func WithTenant(ctx context.Context, tenant any) context.Context

WithTenant returns a context carrying tenant. Pass anything the driver can bind — a string id, int64, UUID string, etc.

Types

type AggregateSnapshot added in v0.5.0

type AggregateSnapshot struct {
	AggregateType string
	AggregateID   string
	Version       int64
	State         json.RawMessage
	CreatedAt     time.Time
}

AggregateSnapshot persists the materialised state of an aggregate at a version so reads avoid replaying every event.

type AuditCols added in v0.5.0

type AuditCols[T any] struct {
	CreatedBy *Col[T]
	UpdatedBy *Col[T]
}

AuditCols holds the typed handles created by Audit.

func Audit added in v0.5.0

func Audit[T any](t *Table, target *Col[T]) AuditCols[T]

Audit appends nullable "createdBy" / "updatedBy" columns to t and declares foreign keys against target — typically a users.id PK. The referencing columns adopt the target column's declared type.

type AuditEvent added in v0.5.0

type AuditEvent struct {
	Entity  string
	Op      string
	PK      json.RawMessage
	Payload json.RawMessage
	Actor   string
}

AuditEvent is the row written per mutation.

type AuditLog added in v0.5.0

type AuditLog struct {
	// contains filtered or unexported fields
}

AuditLog records who-changed-what-when for every Create / Update / Delete on the entities attached to it. The audit row is written in the SAME transaction as the business mutation, so a rollback rolls back both.

audit := sqlite.NewAuditLog(db, "audit_events")
sqlite.WithAudit(UserEntity, audit)
ctx = sqlite.WithActor(ctx, currentUserID)
UserEntity.Update(db, ctx, &u) // also writes an audit row

func NewAuditLog added in v0.5.0

func NewAuditLog(db *DB, table string) *AuditLog

NewAuditLog binds the log to db and a destination table.

func (*AuditLog) Record added in v0.5.0

func (a *AuditLog) Record(tx *DB, ctx context.Context, ev AuditEvent) error

Record inserts ev using tx so the audit row lives or dies with the surrounding transaction.

type AuditMixin added in v0.5.0

type AuditMixin[T any] struct {
	Target *Col[T]
	Cols   AuditCols[T]
}

AuditMixin registers nullable "createdBy" / "updatedBy" foreign keys to target (typically a users.id PK) and exposes typed handles. The values are populated by the caller; the mixin does not infer the current user.

func (*AuditMixin[T]) Apply added in v0.5.0

func (m *AuditMixin[T]) Apply(t *Table)

Apply implements Mixin.

type Backfill added in v0.5.0

type Backfill struct {
	// contains filtered or unexported fields
}

Online chunked backfill — the safe way to modify large tables without long locks. drops drives the chunk loop, persists per-chunk state so a crash resumes from the last committed checkpoint, and throttles between chunks to bound load.

bf := sqlite.NewBackfill(db, "userRegionFill").
    ChunkSize(10_000).
    Throttle(50 * time.Millisecond).
    Fetch(func(ctx context.Context, lastID int64, limit int) ([]int64, int64, error) {
        // SELECT id FROM users WHERE id > ? AND region IS NULL ORDER BY id LIMIT ?
        // return ids, max(ids), nil
    }).
    Process(func(ctx context.Context, tx *sqlite.DB, ids []int64) error {
        // UPDATE users SET region = ? WHERE id IN (...)
        return nil
    })
err := bf.Run(ctx)

Each chunk is processed inside a transaction; failures roll back per chunk so state always advances atomically. Timestamps in the state table are stored as Unix seconds (INTEGER) to sidestep SQLite's datetime-format ambiguity.

func NewBackfill added in v0.5.0

func NewBackfill(db *DB, name string) *Backfill

NewBackfill returns a Backfill bound to db. name is the unique state key used for resuming across restarts.

func (*Backfill) ChunkSize added in v0.5.0

func (b *Backfill) ChunkSize(n int) *Backfill

ChunkSize sets the per-chunk row cap (default 1000).

func (*Backfill) Fetch added in v0.5.0

func (b *Backfill) Fetch(fn func(ctx context.Context, lastID int64, limit int) ([]int64, int64, error)) *Backfill

Fetch installs the chunk-fetching callback: given the last processed ID and a limit, return the next batch of IDs, the new last-ID (typically max(ids)), and any error. An empty slice signals completion.

func (*Backfill) OnProgress added in v0.5.0

func (b *Backfill) OnProgress(fn func(processed, lastID int64)) *Backfill

OnProgress wires a callback fired after each successful chunk commit.

func (*Backfill) Process added in v0.5.0

func (b *Backfill) Process(fn func(ctx context.Context, tx *DB, ids []int64) error) *Backfill

Process installs the per-chunk worker, run inside a transaction.

func (*Backfill) Reset added in v0.5.0

func (b *Backfill) Reset(ctx context.Context) error

Reset clears persisted state for the job.

func (*Backfill) Run added in v0.5.0

func (b *Backfill) Run(ctx context.Context) error

Run drives the backfill loop until Fetch returns an empty batch, ctx is cancelled, or a chunk errors. State is persisted between chunks so a crash resumes from the last successful commit.

func (*Backfill) StateTable added in v0.5.0

func (b *Backfill) StateTable(name string) *Backfill

StateTable overrides the state table name (default "backfillJobs").

func (*Backfill) Status added in v0.5.0

func (b *Backfill) Status(ctx context.Context) (BackfillStatus, error)

Status loads the current persisted state, or a zero status (LastID=0) when the job has never run.

func (*Backfill) Throttle added in v0.5.0

func (b *Backfill) Throttle(d time.Duration) *Backfill

Throttle sleeps between chunks to bound the rate (default 50ms; 0 runs as fast as possible).

type BackfillStatus added in v0.5.0

type BackfillStatus struct {
	Name        string
	LastID      int64
	Processed   int64
	CompletedAt *time.Time
	UpdatedAt   time.Time
	LastError   string
}

BackfillStatus describes the persisted state of a backfill job.

type CTE added in v0.5.0

type CTE struct {
	// contains filtered or unexported fields
}

CTE describes one common table expression.

func CTEDef added in v0.5.0

func CTEDef(name string, query drops.Expression, columns ...string) *CTE

CTEDef returns a CTE definition with an optional column alias list.

func (*CTE) Col added in v0.5.0

func (c *CTE) Col(col string) drops.Expression

Col returns a column reference inside the CTE: "<cte>"."<col>".

func (*CTE) Name added in v0.5.0

func (c *CTE) Name() string

Name returns the CTE's alias.

func (*CTE) Ref added in v0.5.0

func (c *CTE) Ref() drops.Expression

Ref returns an expression that references the CTE as a relation — useful inside a raw FROM/JOIN fragment on a subsequent SELECT.

type CaseExpr added in v0.5.0

type CaseExpr struct {
	// contains filtered or unexported fields
}

CaseExpr is the in-progress CASE expression.

func Case added in v0.5.0

func Case() *CaseExpr

Case begins a searched CASE expression. Chain When / Else / End:

sqlite.Case().
    When(UserAge.Lt(18), "minor").
    When(UserAge.Lt(65), "adult").
    Else("senior").
    End()

func CaseOn added in v0.5.0

func CaseOn(value any) *CaseExpr

CaseOn begins a simple CASE expression on a value:

sqlite.CaseOn(UserStatus).
    When("active", 1).
    When("pending", 2).
    Else(0).
    End()

func (*CaseExpr) Else added in v0.5.0

func (c *CaseExpr) Else(value any) *CaseExpr

Else sets the ELSE value.

func (*CaseExpr) End added in v0.5.0

func (c *CaseExpr) End() drops.Expression

End finalises the CASE expression.

func (*CaseExpr) When added in v0.5.0

func (c *CaseExpr) When(cond, value any) *CaseExpr

When adds a WHEN <cond> THEN <value> branch.

type CheckSnapshot

type CheckSnapshot struct {
	Name  string `json:"name"`
	Value string `json:"value"`
}

CheckSnapshot is one entry in TableSnapshot.CheckConstraints. Value is the SQL expression inside CHECK (...).

type Col

type Col[T any] struct{ *Column }

Col is the typed column handle whose Go value type is T.

func Add

func Add[T any](t *Table, c *Col[T]) *Col[T]

Add registers c on t and returns the same typed handle, so callers keep the *Col[T] for use in queries:

id := sqlite.Add(users, sqlite.Integer("id").PrimaryKey())

func BigInt

func BigInt(name string) *Col[int64]

func BigSerial

func BigSerial(name string) *Col[int64]

func Blob

func Blob(name string) *Col[[]byte]

Blob / Bytea are BLOB columns (Bytea is the PostgreSQL-parity alias).

func Boolean

func Boolean(name string) *Col[bool]

Boolean is stored as INTEGER 0/1; declared as BOOLEAN for readability.

func Bytea

func Bytea(name string) *Col[[]byte]

func Char

func Char(name string, _ int) *Col[string]

Char maps to TEXT (see Varchar).

func Custom

func Custom[T any](name, typeSQL string) *Col[T]

Custom declares a column with a caller-supplied declared type.

func Date

func Date(name string) *Col[time.Time]

Date / Time / Timestamp map to SQLite's date-time storage (TEXT affinity via the DATE/TIME/DATETIME declared types).

func DoublePrecision

func DoublePrecision(name string) *Col[float64]

func EnumCol added in v0.5.0

func EnumCol(t *Table, col string, values ...string) *Col[string]

EnumCol is the free-function form of Enum.AddTo for one-off columns:

priority := sqlite.EnumCol(tasks, "priority", "low", "med", "high")

func Integer

func Integer(name string) *Col[int32]

func JSON

func JSON(name string) *Col[json.RawMessage]

JSON / JSONB are stored as TEXT (SQLite's JSON1 functions operate on TEXT). JSONB is an alias for parity with PostgreSQL.

func JSONB

func JSONB(name string) *Col[json.RawMessage]

func Numeric

func Numeric(name string, _, _ int) *Col[string]

Numeric maps to NUMERIC affinity. Precision/scale are accepted for parity but SQLite does not enforce them.

func Real

func Real(name string) *Col[float32]

Real / DoublePrecision map to REAL affinity.

func Serial

func Serial(name string) *Col[int32]

Serial / BigSerial map to INTEGER. Auto-increment in SQLite is a property of an INTEGER PRIMARY KEY (add .PrimaryKey(), and optionally .AutoIncrement() for the strict monotonic ROWID), not a distinct column type as in PostgreSQL.

func SmallInt

func SmallInt(name string) *Col[int16]

Integer family — all map to INTEGER affinity.

func Text

func Text(name string) *Col[string]

Text is a TEXT column.

func Time

func Time(name string) *Col[time.Time]

func Timestamp

func Timestamp(name string, _ bool) *Col[time.Time]

Timestamp maps to DATETIME. withTimeZone is accepted for parity; SQLite has no zoned type — store UTC and convert in application code.

func UUID

func UUID(name string) *Col[string]

UUID is stored as TEXT.

func Varchar

func Varchar(name string, _ int) *Col[string]

Varchar maps to TEXT — SQLite does not enforce length, but the length is accepted for schema parity with PostgreSQL.

func (*Col[T]) AsPII added in v0.5.0

func (c *Col[T]) AsPII() *Col[T]

AsPII flags the column as carrying PII, so formatters see "<redacted>".

func (*Col[T]) AutoIncrement

func (c *Col[T]) AutoIncrement() *Col[T]

AutoIncrement marks an INTEGER PRIMARY KEY as AUTOINCREMENT. SQLite only permits AUTOINCREMENT on an INTEGER PRIMARY KEY.

func (*Col[T]) Default

func (c *Col[T]) Default(sqlExpr string) *Col[T]

Default sets a raw SQL default expression (e.g. "0", "CURRENT_TIMESTAMP").

func (*Col[T]) Eq

func (c *Col[T]) Eq(v T) drops.Expression

Comparison operators — the rendered SQL is dialect-agnostic (the placeholder and quoting come from the Builder's dialect).

func (*Col[T]) EqCol

func (c *Col[T]) EqCol(other ColRef) drops.Expression

EqCol compares two columns.

func (*Col[T]) Gt

func (c *Col[T]) Gt(v T) drops.Expression

func (*Col[T]) Gte

func (c *Col[T]) Gte(v T) drops.Expression

func (*Col[T]) In

func (c *Col[T]) In(values ...T) drops.Expression

In renders (col IN (?, ?, ...)). Empty renders "(0)" (never matches).

func (*Col[T]) IsNotNull

func (c *Col[T]) IsNotNull() drops.Expression

func (*Col[T]) IsNull

func (c *Col[T]) IsNull() drops.Expression

IsNull / IsNotNull.

func (*Col[T]) Lt

func (c *Col[T]) Lt(v T) drops.Expression

func (*Col[T]) Lte

func (c *Col[T]) Lte(v T) drops.Expression

func (*Col[T]) Ne

func (c *Col[T]) Ne(v T) drops.Expression

func (*Col[T]) NotNull

func (c *Col[T]) NotNull() *Col[T]

NotNull marks the column NOT NULL.

func (*Col[T]) PrimaryKey

func (c *Col[T]) PrimaryKey() *Col[T]

PrimaryKey marks the column as the (single-column) PRIMARY KEY.

func (*Col[T]) References

func (c *Col[T]) References(target *Col[T], opts ...func(*FK)) *Col[T]

References declares a single-column foreign key to target.

func (*Col[T]) Unique

func (c *Col[T]) Unique() *Col[T]

Unique marks the column UNIQUE.

func (*Col[T]) Val

func (c *Col[T]) Val(v T) ColumnValue

Val binds a typed value for INSERT/UPDATE.

type ColRef

type ColRef interface {
	drops.Expression
	// contains filtered or unexported methods
}

ColRef is implemented by *Column and *Col[T]: a type-erased column reference for APIs that don't depend on the Go value type.

type Column

type Column struct {
	// contains filtered or unexported fields
}

Column is the type-erased column AST node. Most user code holds a *Col[T]; the untyped Column lets table column lists be heterogeneous.

func (*Column) DefaultSQL

func (c *Column) DefaultSQL() string

func (*Column) ForeignKey

func (c *Column) ForeignKey() *FK

func (*Column) HasDefault

func (c *Column) HasDefault() bool

func (*Column) IsAutoIncrement

func (c *Column) IsAutoIncrement() bool

func (*Column) IsNotNull

func (c *Column) IsNotNull() bool

func (*Column) IsPII added in v0.5.0

func (c *Column) IsPII() bool

IsPII reports whether the column is marked PII.

func (*Column) IsPrimaryKey

func (c *Column) IsPrimaryKey() bool

func (*Column) IsUnique

func (c *Column) IsUnique() bool

func (*Column) Name

func (c *Column) Name() string

func (*Column) Table

func (c *Column) Table() *Table

func (*Column) Type

func (c *Column) Type() ColumnType

func (*Column) WriteSQL

func (c *Column) WriteSQL(b *drops.Builder)

WriteSQL writes a table-qualified column reference. The dialect on the Builder controls the quote character.

type ColumnSnapshot

type ColumnSnapshot struct {
	Name          string  `json:"name"`
	Type          string  `json:"type"`
	PrimaryKey    bool    `json:"primaryKey"`
	NotNull       bool    `json:"notNull"`
	Unique        bool    `json:"unique"`
	AutoIncrement bool    `json:"autoincrement"`
	Default       *string `json:"default,omitempty"`
}

ColumnSnapshot is one entry in TableSnapshot.Columns. Type is the SQLite affinity keyword. Unique / PrimaryKey record single-column constraints that render inline in CREATE TABLE; AutoIncrement is only meaningful on an INTEGER PRIMARY KEY.

type ColumnType

type ColumnType interface{ TypeSQL() string }

ColumnType describes the SQL type of a column as it appears in CREATE TABLE — e.g. "INTEGER", "TEXT", "REAL", "BLOB", "NUMERIC".

type ColumnValue

type ColumnValue interface {
	// contains filtered or unexported methods
}

ColumnValue is a column bound to a value for INSERT/UPDATE.

type CompositeFK

type CompositeFK struct {
	Name          string
	Columns       []*Column
	Target        *Table
	TargetColumns []*Column
	OnDelete      string
	OnUpdate      string
}

CompositeFK is a multi-column foreign key: FOREIGN KEY (Columns...) REFERENCES Target (TargetColumns...).

type CompositePKSnapshot

type CompositePKSnapshot struct {
	Name    string   `json:"name"`
	Columns []string `json:"columns"`
}

CompositePKSnapshot is one entry in TableSnapshot.CompositePrimaryKeys.

type Cursor added in v0.5.0

type Cursor string

Cursor is an opaque page marker — obtain one from EncodeCursor and pass it to SelectBuilder.AfterCursor / BeforeCursor.

func EncodeCursor added in v0.5.0

func EncodeCursor(spec CursorSpec, values ...any) (Cursor, error)

EncodeCursor builds a cursor from values matching the spec, one per key in declaration order. Returns an error when values is the wrong length or contains an unsupported type.

func (Cursor) Decode added in v0.5.0

func (c Cursor) Decode() ([]any, error)

Decode returns the values held inside the cursor, in spec order. Unknown / corrupt cursors return an error so callers can treat the request as "first page" (or reject the input outright).

type CursorSpec added in v0.5.0

type CursorSpec struct {
	Keys []OrderKey
}

CursorSpec is the ordered list of keys that defines the cursor shape — used by OrderByCursor and AfterCursor so the same shape drives both the ORDER BY and the keyset WHERE.

func NewCursorSpec added in v0.5.0

func NewCursorSpec(keys ...OrderKey) CursorSpec

NewCursorSpec returns a CursorSpec carrying the supplied keys in declaration order. The last key should be a primary key (or otherwise unique column) so equal leading values still resolve to a deterministic page boundary.

type CustomGuard added in v0.5.0

type CustomGuard func(ctx context.Context) (drops.Expression, error)

CustomGuard wraps a function as a Guard.

func (CustomGuard) Predicate added in v0.5.0

func (g CustomGuard) Predicate(ctx context.Context) (drops.Expression, error)

Predicate implements Guard.

type DB

type DB struct {
	// contains filtered or unexported fields
}

DB is the entry point for issuing SQLite queries through a drops.Driver. Any driver — database/sql with mattn/go-sqlite3 or modernc.org/sqlite, or a custom connection — can back it.

It offers the same Hook / Ping / Close / InTx / Select / Insert / Update / Delete contract as drops/pg's DB, so switching a codebase from PostgreSQL to SQLite is largely a matter of swapping the constructor. Every builder it returns renders SQLite syntax ("?" placeholders) via the shared drops.Dialect.

Safe for concurrent use by multiple goroutines provided the underlying Driver is; builders are not — create one per query.

func New

func New(drv drops.Driver) *DB

New wraps a drops.Driver as a SQLite DB.

func (*DB) Begin

func (db *DB) Begin(ctx context.Context) (*DB, drops.Tx, error)

Begin opens a transaction and returns a DB bound to it plus the raw Tx. Prefer InTx for automatic commit/rollback.

func (*DB) Close

func (db *DB) Close() error

Close releases the underlying driver if it implements io.Closer.

func (*DB) Delete

func (db *DB) Delete(t *Table) *DeleteBuilder

Delete begins a DELETE FROM <t>.

func (*DB) Driver

func (db *DB) Driver() drops.Driver

Driver returns the underlying driver.

func (*DB) Exec

func (db *DB) Exec(ctx context.Context, sql string, args ...any) (drops.Result, error)

Exec runs a raw SQL statement. Placeholders are SQLite "?" markers.

func (*DB) ExecExpr

func (db *DB) ExecExpr(ctx context.Context, e drops.Expression) (drops.Result, error)

ExecExpr renders e with the SQLite dialect and runs it. Convenience for DDL helpers like CreateTable.

func (*DB) Find

func (db *DB) Find(t *Table) *FindBuilder

Find begins a relational query against t. The result type passed to All/One determines which columns are scanned (via the same struct-field mapping rules as Select.All).

func (*DB) Hook

func (db *DB) Hook() drops.Hook

Hook returns the currently attached hook, or nil.

func (*DB) InTx

func (db *DB) InTx(ctx context.Context, fn func(*DB) error) error

InTx runs fn inside a transaction, committing on nil and rolling back otherwise (including on panic, which is re-raised). Rollback uses a detached, short-timeout context so a cancelled caller-ctx can't block cleanup.

When a RetryPolicy is installed via WithRetry, the whole callback is re-run inside a fresh transaction on a retryable error (SQLITE_BUSY / SQLITE_LOCKED by default), up to MaxAttempts times, sleeping Backoff between attempts. Callbacks must be idempotent across retries.

func (*DB) Insert

func (db *DB) Insert(t *Table) *InsertBuilder

Insert begins an INSERT INTO <t>.

func (*DB) Ping

func (db *DB) Ping(ctx context.Context) error

Ping verifies the connection with SELECT 1.

func (*DB) Query

func (db *DB) Query(ctx context.Context, sql string, args ...any) (drops.Rows, error)

Query runs a raw SQL query.

func (*DB) RetryPolicyValue added in v0.5.0

func (db *DB) RetryPolicyValue() RetryPolicy

RetryPolicyValue returns the active retry policy, or the zero value.

func (*DB) Select

func (db *DB) Select(cols ...drops.Expression) *SelectBuilder

Select begins a SELECT. With no columns the projection is "*".

func (*DB) Tracer added in v0.5.0

func (db *DB) Tracer() Tracer

Tracer returns the configured tracer, or nil.

func (*DB) Update

func (db *DB) Update(t *Table) *UpdateBuilder

Update begins an UPDATE <t>.

func (*DB) WithHook

func (db *DB) WithHook(hook drops.Hook) *DB

WithHook returns a shallow copy with hook installed; nil removes it.

func (*DB) WithRetry added in v0.5.0

func (db *DB) WithRetry(policy RetryPolicy) *DB

WithRetry returns a shallow copy of db with policy installed. The zero RetryPolicy clears any previously-installed policy.

func (*DB) WithTracer added in v0.5.0

func (db *DB) WithTracer(t Tracer) *DB

WithTracer attaches t, returning a shallow copy of db (mirrors WithHook). Pass nil to clear.

type DeleteBuilder

type DeleteBuilder struct {
	// contains filtered or unexported fields
}

DeleteBuilder builds a DELETE statement. Create one via DB.Delete.

func (*DeleteBuilder) DB added in v0.5.0

func (d *DeleteBuilder) DB() *DB

DB returns the executing DB — used by DeleteHooks that build a replacement statement (an UPDATE for soft-delete).

func (*DeleteBuilder) Exec

func (d *DeleteBuilder) Exec(ctx context.Context) (drops.Result, error)

Exec runs the DELETE.

func (*DeleteBuilder) IsUnscoped added in v0.5.0

func (d *DeleteBuilder) IsUnscoped() bool

IsUnscoped reports whether the caller opted out of default scopes.

func (*DeleteBuilder) Table added in v0.5.0

func (d *DeleteBuilder) Table() *Table

Table returns the target table.

func (*DeleteBuilder) ToSQL

func (d *DeleteBuilder) ToSQL() (sql string, args []any)

ToSQL renders the statement with SQLite placeholders.

func (*DeleteBuilder) Unscoped added in v0.5.0

func (d *DeleteBuilder) Unscoped() *DeleteBuilder

Unscoped opts out of both DeleteHooks and DefaultFilters for this statement. On a soft-deleted table it forces a real, hard DELETE.

func (*DeleteBuilder) Where

func (d *DeleteBuilder) Where(preds ...drops.Expression) *DeleteBuilder

Where AND-s the given predicates onto the statement. A DELETE with no WHERE removes every row — that is intentional but rarely desired.

func (*DeleteBuilder) Wheres added in v0.5.0

func (d *DeleteBuilder) Wheres() []drops.Expression

Wheres returns a copy of the predicate slice, so a DeleteHook can read the original WHERE clauses when synthesising replacement SQL.

func (*DeleteBuilder) WriteSQL

func (d *DeleteBuilder) WriteSQL(b *drops.Builder)

WriteSQL implements drops.Expression. If the table has DeleteHooks and the caller has not opted out via Unscoped, a hook may replace the statement entirely — used by SoftDelete to flip DELETE into UPDATE.

type DeleteHook added in v0.5.0

type DeleteHook interface {
	BeforeDelete(d *DeleteBuilder) drops.Expression
}

DeleteHook is invoked by DeleteBuilder.WriteSQL. A non-nil returned expression replaces the rendered DELETE entirely — used by SoftDelete to translate DELETE into UPDATE. Returning nil lets the DELETE render normally. Hooks are tried in registration order; the first non-nil expression wins.

type DeleteHookFunc added in v0.5.0

type DeleteHookFunc func(*DeleteBuilder) drops.Expression

DeleteHookFunc adapts a function to the DeleteHook interface.

func (DeleteHookFunc) BeforeDelete added in v0.5.0

func (f DeleteHookFunc) BeforeDelete(d *DeleteBuilder) drops.Expression

BeforeDelete implements DeleteHook.

type DriftReport added in v0.5.0

type DriftReport struct {
	// PendingMigrations is the statement list drops would emit to
	// bring live UP TO repo — empty means production has every
	// schema change in the repo applied.
	PendingMigrations []string

	// UnauthorizedChanges is the statement list drops would emit to
	// bring repo UP TO live — empty means production matches the
	// repo with no unrecorded manual edits.
	UnauthorizedChanges []string

	// InSync is true when both PendingMigrations and
	// UnauthorizedChanges are empty.
	InSync bool
}

DriftReport summarises the gap between two snapshots — typically the repo's canonical schema and a live introspection of production.

func DetectDrift added in v0.5.0

func DetectDrift(repo, live *Snapshot) DriftReport

DetectDrift computes the two-way diff between the repo's canonical snapshot and a live introspection. Both arguments must be non-nil — use EmptySnapshot when one side is genuinely empty (e.g. fresh database).

func (DriftReport) HasPendingMigrations added in v0.5.0

func (r DriftReport) HasPendingMigrations() bool

HasPendingMigrations reports whether production is behind the repo.

func (DriftReport) HasUnauthorizedChanges added in v0.5.0

func (r DriftReport) HasUnauthorizedChanges() bool

HasUnauthorizedChanges reports whether production has changes the repo doesn't know about — typically the headline CI alert.

type DrizzleEntry added in v0.5.0

type DrizzleEntry struct {
	Tag         string
	SQL         string
	Hash        string
	Breakpoints bool
	When        int64
}

DrizzleEntry is a parsed, hash-computed migration ready to apply.

type DrizzleHook added in v0.5.0

type DrizzleHook func(ctx context.Context, tx *DB, entry DrizzleEntry) error

DrizzleHook runs around a drizzle migration file, inside the same transaction as the file's SQL, so a data migration it performs commits atomically with the schema change (or rolls back with it on error). Because drizzle files are pure SQL, this is the only seam for Go-based data migrations — backfills, cross-table copies — that must run between the generated schema statements.

Register hooks with DrizzleMigrator.BeforeEach / AfterEach: "before" runs before the file's statements, "after" runs after them. Use entry.Tag to scope a data migration to a specific file. A hook that returns an error aborts that migration; the whole transaction rolls back and Up returns the error.

type DrizzleMigrator added in v0.5.0

type DrizzleMigrator struct {
	// contains filtered or unexported fields
}

DrizzleMigrator runs migrations from a drizzle-kit-formatted directory.

func NewDrizzleMigrator added in v0.5.0

func NewDrizzleMigrator(db *DB, fsys fs.FS, dir string) *DrizzleMigrator

NewDrizzleMigrator wraps db with a migrator that reads from dir within fsys. dir is typically "drizzle" when using `//go:embed drizzle/*` from a project root that has a `drizzle/` directory; pass "." when fsys is already rooted at the migrations directory.

func (*DrizzleMigrator) AfterEach added in v0.5.0

func (d *DrizzleMigrator) AfterEach(h DrizzleHook) *DrizzleMigrator

AfterEach registers a hook that runs immediately after each migration file's statements, within that migration's transaction. Hooks fire in registration order; the first to error aborts the migration. This is the usual home for a data migration that depends on the file's schema change having just landed. See DrizzleHook.

func (*DrizzleMigrator) BeforeEach added in v0.5.0

func (d *DrizzleMigrator) BeforeEach(h DrizzleHook) *DrizzleMigrator

BeforeEach registers a hook that runs immediately before each migration file's statements, within that migration's transaction. Hooks fire in registration order; the first to error aborts the migration. See DrizzleHook.

func (*DrizzleMigrator) LoadEntries added in v0.5.0

func (d *DrizzleMigrator) LoadEntries() ([]DrizzleEntry, error)

LoadEntries reads and hashes every migration referenced by the journal. Useful for tooling — Up calls it internally.

func (*DrizzleMigrator) Status added in v0.5.0

func (d *DrizzleMigrator) Status(ctx context.Context) ([]DrizzleStatus, error)

Status reports every entry in the journal and whether it has been applied (matched by hash, the same way drizzle-orm matches).

func (*DrizzleMigrator) Up added in v0.5.0

func (d *DrizzleMigrator) Up(ctx context.Context) error

Up applies every pending migration in journal order. Each migration runs in its own transaction; failure of any statement rolls back that migration only.

func (*DrizzleMigrator) WithTable added in v0.5.0

func (d *DrizzleMigrator) WithTable(table string) *DrizzleMigrator

WithTable overrides the migration history table name. Match drizzle.config.ts's `migrationsTable` to stay interoperable.

type DrizzleStatus added in v0.5.0

type DrizzleStatus struct {
	Tag     string
	Hash    string
	Applied bool
	When    int64 // journal timestamp (unix milliseconds)
}

DrizzleStatus is one row of the Status report.

type EmitOptions added in v0.5.0

type EmitOptions struct {
	AggregateType string
	AggregateID   string
	Headers       map[string]string
}

EmitOptions extends Emit with aggregate metadata and tracing headers.

type Entity

type Entity[T any] struct {
	// contains filtered or unexported fields
}

Entity is the typed CRUD layer over a Table, mirroring drops/pg's Entity[T]. It precomputes the column↔struct-field mapping for T and offers Get / Create / Update / Delete plus a fluent Query. T must be a struct with a field bound to each column (by `drop:"col"` tag, or by field name / camelCase), and the table must have a single-column primary key.

func NewAutoEntity added in v0.5.0

func NewAutoEntity[T any](name string) *Entity[T]

NewAutoEntity bundles AutoTable + NewEntity into one call.

func NewEntity

func NewEntity[T any](t *Table) *Entity[T]

NewEntity builds the entity, panicking on misconfiguration (schemas are declared at startup, so bad config should fail loudly there).

func WithAudit added in v0.5.0

func WithAudit[T any](e *Entity[T], log *AuditLog) *Entity[T]

WithAudit attaches log to the entity so subsequent Create / Update / Delete record audit events in the same transaction.

func (*Entity[T]) AuthorizeWith added in v0.5.0

func (e *Entity[T]) AuthorizeWith(g Guard) *Entity[T]

AuthorizeWith installs g on the entity. Pass nil to clear.

func (*Entity[T]) Create

func (e *Entity[T]) Create(db *DB, ctx context.Context, r *T) error

Create inserts r. Stamps the tenant (when scoped), writes an audit row in the same transaction (when audited), and populates the cache.

func (*Entity[T]) CreateMany added in v0.5.0

func (e *Entity[T]) CreateMany(db *DB, ctx context.Context, rows []T) (drops.Result, error)

CreateMany inserts every row in a single multi-row INSERT. When the entity is tenant-scoped the ctx tenant is stamped onto each row first. Autogenerated PKs are not read back (batch path).

func (*Entity[T]) Delete

func (e *Entity[T]) Delete(db *DB, ctx context.Context, id any) (drops.Result, error)

Delete removes the row whose primary key equals id. Applies the tenant scope and authorization guard, records an audit row in the same transaction (when audited), and invalidates the cache entry.

func (*Entity[T]) Get

func (e *Entity[T]) Get(db *DB, ctx context.Context, id any) (T, error)

Get fetches the row whose primary key equals id, returning ErrNoRows if absent. Applies the tenant scope and the authorization guard when configured, and reads through the cache when one is attached and no scope/guard narrows the query.

func (*Entity[T]) HasCache added in v0.5.0

func (e *Entity[T]) HasCache() bool

HasCache reports whether a cache is wired up.

func (*Entity[T]) PK

func (e *Entity[T]) PK() *Column

PK returns the primary-key column.

func (*Entity[T]) Page added in v0.5.0

func (e *Entity[T]) Page(db *DB) *PageBuilder[T]

Page returns a cursor-paginated builder for this entity. Default limit is 50; override with Limit.

func (*Entity[T]) Patch added in v0.5.0

func (e *Entity[T]) Patch(db *DB, ctx context.Context, id any, ops ...PatchOp) (drops.Result, error)

Patch applies ops to the row identified by id.

func (*Entity[T]) Query

func (e *Entity[T]) Query(db *DB) *EntityQuery[T]

Query begins a fluent, entity-typed query. When the entity is tenant-scoped the caller must pass a tenant-carrying ctx to All/One (via WithTenant); the tenant predicate is applied at execution time.

func (*Entity[T]) Restore added in v0.5.0

func (e *Entity[T]) Restore(db *DB, ctx context.Context, id any, sd SoftDeleteCols) (drops.Result, error)

Restore clears deletedAt (SET deletedAt = NULL) for the row whose primary key equals id, un-hiding it. Runs Unscoped because the row is, by definition, currently filtered out.

func (*Entity[T]) ScopeByTenant added in v0.5.0

func (e *Entity[T]) ScopeByTenant(col ColRef) *Entity[T]

ScopeByTenant marks col as the entity's tenant axis. Every subsequent Get / Query / Update / Delete AND-s "<col> = <ctx-tenant>" into the predicate; Create stamps the tenant onto the row.

Panics if col has no matching struct field — fail loudly at startup.

func (*Entity[T]) SoftDeleteByID added in v0.5.0

func (e *Entity[T]) SoftDeleteByID(db *DB, ctx context.Context, id any, sd SoftDeleteCols) (drops.Result, error)

SoftDeleteByID marks the row whose primary key equals id as soft-deleted by setting deletedAt to CURRENT_TIMESTAMP. It runs Unscoped so it also works idempotently on an already-hidden row.

func (*Entity[T]) Table

func (e *Entity[T]) Table() *Table

Table returns the entity's table.

func (*Entity[T]) Update

func (e *Entity[T]) Update(db *DB, ctx context.Context, r *T) error

Update writes every non-PK column of r, matched by primary key. Applies the tenant scope and authorization guard, records an audit row in the same transaction (when audited), and refreshes the cache.

func (*Entity[T]) WithCache added in v0.5.0

func (e *Entity[T]) WithCache(c cache.Cache, ttl time.Duration) *Entity[T]

WithCache attaches c to the entity. ttl=0 means "no expiry" — the entries live until evicted by the backend (memory LRU) or until the cache backend's own policy kicks in. A non-zero ttl is recommended for query-result caching.

type EntityCache added in v0.5.0

type EntityCache struct {
	// contains filtered or unexported fields
}

EntityCache wires a cache backend into an Entity so reads pass through the cache and writes invalidate the matching entries. Construct one via (*Entity[T]).WithCache; do not instantiate directly.

Cache key conventions:

drops:<table>:pk:<id>          — Get by primary key
drops:<table>:q:<sql-hash>     — query results (best-effort, TTL)

Primary-key entries are invalidated on Update / Save / Delete. Query entries rely on TTL alone — invalidation across an arbitrary WHERE/JOIN topology is intractable in the general case, and TTL is usually the right trade for read-heavy services.

A built-in single-flight group dedupes concurrent identical PK-by-cache-miss reads, so a thundering herd of "give me user 42" resolves to one DB query.

type EntityQuery

type EntityQuery[T any] struct {
	// contains filtered or unexported fields
}

EntityQuery is a typed wrapper over SelectBuilder that returns []T / T.

func (*EntityQuery[T]) All

func (q *EntityQuery[T]) All(ctx context.Context) ([]T, error)

All executes and returns every matching row.

func (*EntityQuery[T]) Limit

func (q *EntityQuery[T]) Limit(n int64) *EntityQuery[T]

Limit / Offset bound the result window.

func (*EntityQuery[T]) Offset

func (q *EntityQuery[T]) Offset(n int64) *EntityQuery[T]

func (*EntityQuery[T]) One

func (q *EntityQuery[T]) One(ctx context.Context) (T, error)

One executes and returns the first row, or ErrNoRows.

func (*EntityQuery[T]) OrderBy

func (q *EntityQuery[T]) OrderBy(exprs ...drops.Expression) *EntityQuery[T]

OrderBy sets ORDER BY expressions.

func (*EntityQuery[T]) Where

func (q *EntityQuery[T]) Where(preds ...drops.Expression) *EntityQuery[T]

Where AND-s predicates onto the query.

type Enum added in v0.5.0

type Enum struct {
	// contains filtered or unexported fields
}

Enum describes a fixed set of string labels. Declare it once and add a constrained column to any table with AddTo.

func NewEnum added in v0.5.0

func NewEnum(name string, values ...string) *Enum

NewEnum declares an enum with the given labels. name is used to build the CHECK constraint's name; it is not a SQL type (SQLite has none).

func (*Enum) AddTo added in v0.5.0

func (e *Enum) AddTo(t *Table, col string) *Col[string]

AddTo registers a TEXT column named col on t plus a table-level CHECK constraint restricting it to the enum's labels, and returns the typed column handle.

status := statusEnum.AddTo(orders, "status")
// orders."status" TEXT, CHECK ("status" IN ('open','paid','shipped'))

func (*Enum) CheckExpr added in v0.5.0

func (e *Enum) CheckExpr(col string) string

CheckExpr renders the CHECK body constraining col to the enum labels: "col" IN ('a', 'b', ...).

func (*Enum) Name added in v0.5.0

func (e *Enum) Name() string

Name returns the enum's declared name.

func (*Enum) Values added in v0.5.0

func (e *Enum) Values() []string

Values returns the labels in declaration order.

type Event added in v0.5.0

type Event struct {
	Offset        int64
	AggregateType string
	AggregateID   string
	Version       int64
	Type          string
	Payload       json.RawMessage
	Headers       map[string]string
	CreatedAt     time.Time
}

Event is one entry in the store.

type EventInput added in v0.5.0

type EventInput struct {
	Type    string
	Payload any
	Headers map[string]string
}

EventInput is the per-event payload passed to Append.

type EventStore added in v0.5.0

type EventStore struct {
	// contains filtered or unexported fields
}

EventStore is the append-only event log bound to a single table.

func NewEventStore added in v0.5.0

func NewEventStore(db *DB, table string) *EventStore

NewEventStore returns a store bound to db.

func (*EventStore) Append added in v0.5.0

func (s *EventStore) Append(tx *DB, ctx context.Context, aggregateType, aggregateID string, expectedVersion int64, events ...EventInput) error

Append writes events to the (aggregateType, aggregateID) stream starting at expectedVersion+1, returning ErrConcurrencyConflict if the head has advanced. Use tx so the append commits atomically with the rest of the aggregate's work.

func (*EventStore) LatestVersion added in v0.5.0

func (s *EventStore) LatestVersion(ctx context.Context, aggregateType, aggregateID string) (int64, error)

LatestVersion returns the highest version recorded for the stream, or -1 when empty. Use before Append to compute expectedVersion.

func (*EventStore) Load added in v0.5.0

func (s *EventStore) Load(ctx context.Context, aggregateType, aggregateID string, fromVersion int64) ([]Event, error)

Load returns events for an aggregate in version order, starting after fromVersion (pass 0 for the beginning).

func (*EventStore) LoadSnapshot added in v0.5.0

func (s *EventStore) LoadSnapshot(ctx context.Context, table, aggregateType, aggregateID string) (AggregateSnapshot, bool, error)

LoadSnapshot fetches the latest snapshot for an aggregate (ok=false when none exists).

func (*EventStore) SaveSnapshot added in v0.5.0

func (s *EventStore) SaveSnapshot(ctx context.Context, table string, snap AggregateSnapshot) error

SaveSnapshot upserts the supplied snapshot — newer versions replace older ones in place.

func (*EventStore) Stream added in v0.5.0

func (s *EventStore) Stream(ctx context.Context, fromOffset int64, limit int) ([]Event, error)

Stream returns events from the global append log after fromOffset, up to limit rows — used by projection workers.

type ExplainPlan added in v0.5.0

type ExplainPlan struct {
	Steps []PlanStep
}

ExplainPlan is the parsed result of EXPLAIN QUERY PLAN.

func Explain added in v0.5.0

func Explain(db *DB, ctx context.Context, sql string, args ...any) (*ExplainPlan, error)

Explain runs EXPLAIN QUERY PLAN for sql and returns the parsed plan. The supplied sql is not modified — drops prepends the EXPLAIN prefix.

func (*ExplainPlan) Fingerprint added in v0.5.0

func (p *ExplainPlan) Fingerprint() string

Fingerprint is a stable hash over the plan's step details — the plan's shape without any run-specific values. Persist it next to a query and diff future captures to catch plan regressions.

func (*ExplainPlan) SeqScans added in v0.5.0

func (p *ExplainPlan) SeqScans() []string

SeqScans returns the tables the planner reads with a full table scan — a bare "SCAN <table>" with no "USING INDEX". These are the usual suspects behind a slow query on a large table.

func (*ExplainPlan) String added in v0.5.0

func (p *ExplainPlan) String() string

String renders the plan as an indented tree, matching how the SQLite CLI prints it.

func (*ExplainPlan) UsedIndexes added in v0.5.0

func (p *ExplainPlan) UsedIndexes() []string

UsedIndexes returns the index names the planner chose, parsed from "USING [COVERING] INDEX <name>" fragments.

type FK

type FK struct {
	Target   *Column
	OnDelete string
	OnUpdate string
}

FK describes a single-column foreign-key reference.

type Factory added in v0.5.0

type Factory[T any] struct {
	// contains filtered or unexported fields
}

Factory builds and inserts test rows for an Entity. The template callback returns a fresh value each call; the supplied seq is a monotonically increasing counter the template can interpolate into fields that need to stay unique across a batch.

var PlayerFactory = sqlite.NewFactory(PlayerEntity, func(seq int) Player {
    return Player{
        Name:      fmt.Sprintf("player-%d", seq),
        Level:     1,
        Region:    "eu",
        CreatedAt: time.Now(),
    }
})

// In a test
p, _ := PlayerFactory.Create(ctx, db)             // one row, inserted
ps, _ := PlayerFactory.CreateN(ctx, db, 100)     // batch insert
admin := PlayerFactory.With(func(p *Player) { p.Role = "admin" })
a, _ := admin.Create(ctx, db)

Factories are safe to share across goroutines — the sequence counter is atomic. Call Reset between subtests if you need stable identifiers.

func NewFactory added in v0.5.0

func NewFactory[T any](e *Entity[T], template func(seq int) T) *Factory[T]

NewFactory binds template to entity. The template fires once per Build / Create call; the seq it receives is the post-increment counter so the first invocation sees seq=1.

func (*Factory[T]) Build added in v0.5.0

func (f *Factory[T]) Build() T

Build returns a fresh value without touching the database.

func (*Factory[T]) BuildN added in v0.5.0

func (f *Factory[T]) BuildN(n int) []T

BuildN returns n freshly templated values without touching the database. Each value receives a distinct seq.

func (*Factory[T]) Create added in v0.5.0

func (f *Factory[T]) Create(ctx context.Context, db *DB) (T, error)

Create builds one value and inserts it. The returned T reflects any server-side defaults / autogenerated PK fields the entity scans back.

func (*Factory[T]) CreateN added in v0.5.0

func (f *Factory[T]) CreateN(ctx context.Context, db *DB, n int) ([]T, error)

CreateN builds n values and inserts them in a single statement via Entity.CreateMany — vastly cheaper than N Create calls when seeding wide test fixtures. Returns the templated values; note that autogenerated PKs are not currently read back from the batch path (matches Entity.CreateMany's semantics).

func (*Factory[T]) Entity added in v0.5.0

func (f *Factory[T]) Entity() *Entity[T]

Entity returns the underlying Entity[T] handle. Tests that need to call entity-level helpers (Find, Delete, ...) skip the indirection through the factory.

func (*Factory[T]) Reset added in v0.5.0

func (f *Factory[T]) Reset()

Reset rewinds the sequence counter to zero. Typically called in test setup between subtests so identifiers stay stable from one run to the next.

func (*Factory[T]) Sequence added in v0.5.0

func (f *Factory[T]) Sequence() int

Sequence returns the current sequence value without advancing. Useful for assertions on factory state in test helpers.

func (*Factory[T]) With added in v0.5.0

func (f *Factory[T]) With(mutate func(*T)) *Factory[T]

With returns a child factory whose Build / Create calls run the parent template, then apply mutate to the result. Use it to spin off variant factories — admins, banned players, expired sessions — without redeclaring the whole template.

Child factories share the parent's sequence counter so identifiers remain unique across both. Call WithSequence to start a new one.

type FindBuilder

type FindBuilder struct {
	// contains filtered or unexported fields
}

FindBuilder composes a root SELECT and (optionally) eager-loads relations declared via NewRelations. It mirrors a subset of SelectBuilder's API — Where, OrderBy, Limit, Offset — and adds With for relations.

Each requested relation costs exactly one extra batched query (an IN (...) over the collected parent keys), so eager loading never degrades into N+1.

func (*FindBuilder) All

func (f *FindBuilder) All(ctx context.Context, dest any) error

All runs the find and populates dest, which must be *[]Struct or *[]*Struct. Each requested relation is loaded with a single follow-up query and stitched onto the matching dropRel-tagged field of every parent.

func (*FindBuilder) Limit

func (f *FindBuilder) Limit(n int64) *FindBuilder

Limit sets the LIMIT on the root query.

func (*FindBuilder) Offset

func (f *FindBuilder) Offset(n int64) *FindBuilder

Offset sets the OFFSET on the root query.

func (*FindBuilder) One

func (f *FindBuilder) One(ctx context.Context, dest any) error

One runs the find and populates dest, a pointer to a struct. Returns ErrNoRows when no row matches.

func (*FindBuilder) OrderBy

func (f *FindBuilder) OrderBy(exprs ...drops.Expression) *FindBuilder

OrderBy appends ORDER BY expressions to the root query.

func (*FindBuilder) Where

func (f *FindBuilder) Where(preds ...drops.Expression) *FindBuilder

Where appends predicates joined by AND to the root query.

func (*FindBuilder) With

func (f *FindBuilder) With(names ...string) *FindBuilder

With marks one or more relations to eager-load. Names must match relations declared on the table via NewRelations. Only single-level (non-nested) relations are supported.

type ForeignKeySnapshot

type ForeignKeySnapshot struct {
	Name        string   `json:"name"`
	TableFrom   string   `json:"tableFrom"`
	ColumnsFrom []string `json:"columnsFrom"`
	TableTo     string   `json:"tableTo"`
	ColumnsTo   []string `json:"columnsTo"`
	OnDelete    string   `json:"onDelete"`
	OnUpdate    string   `json:"onUpdate"`
}

ForeignKeySnapshot is one entry in TableSnapshot.ForeignKeys. A single-column FK (len(ColumnsFrom)==1) renders inline on its column; a composite FK renders as a table-level CONSTRAINT. OnDelete/OnUpdate hold the raw referential action ("CASCADE", "SET NULL", …) or "" for the SQLite default.

type GenerateOptions added in v0.5.0

type GenerateOptions struct {
	// Schema is the current desired schema. Required.
	Schema *Schema

	// Dir is the migration directory — both the root for FS reads (if FS
	// is nil) and the destination for Write (if Write is nil). Required.
	Dir string

	// Name is the suffix appended to the migration tag — e.g. "init"
	// produces "0000_init". If empty, a random two-word name is generated.
	Name string

	// FS is an optional override for reads. When set, the journal and
	// previous snapshot are read from FS (paths relative to Dir become
	// paths relative to the FS root). When nil, os.DirFS is used.
	FS fs.FS

	// Write is an optional override for file writes. When set, it is
	// called for each output file (relative path within Dir + bytes).
	// When nil, files are written to disk under Dir with os.WriteFile.
	Write func(relPath string, data []byte) error

	// Now overrides the timestamp written into journal entries (unix
	// milliseconds). When nil, time.Now().UnixMilli() is used.
	Now func() int64

	// NameFn overrides the random-name generator used when Name is empty.
	NameFn func() string

	// WithDown enables auto-generated rollback SQL. When true, the
	// generator emits a paired <tag>.down.sql file alongside the
	// up SQL containing DiffDown(prev, cur). The down direction is
	// best-effort and applies cleanly only when the up direction's
	// inverse is itself well-defined (column ADD ↔ DROP, type ↔
	// type swap, etc.); DROP COLUMN can never be reversed
	// losslessly because the data is gone — review generated down
	// scripts before relying on them.
	WithDown bool
}

GenerateOptions configures a single migration-generation run.

The default behaviour reads from and writes to a single Dir on disk. All FS-touching fields can be overridden for tests or for embedding the generator in tooling that wants to capture output in memory.

type GenerateResult added in v0.5.0

type GenerateResult struct {
	Tag      string // e.g. "0003_warm_iron_man"; empty when NoOp
	Idx      int    // sequence index for the new migration
	SQL      string // statement-breakpoint-joined migration SQL (up)
	DownSQL  string // rollback SQL; empty unless WithDown was set
	NoOp     bool   // true when prev and cur snapshots are equivalent
	Snapshot []byte // bytes written to meta/<idx>_snapshot.json
	Journal  []byte // bytes written to meta/_journal.json
}

GenerateResult describes what a Run produced.

func GenerateMigration added in v0.5.0

func GenerateMigration(opts GenerateOptions) (*GenerateResult, error)

GenerateMigration computes the schema diff and writes a new drizzle-kit migration set: <tag>.sql, meta/<idx>_snapshot.json and an updated meta/_journal.json.

It is a no-op when there are no differences between the current Go schema and the latest snapshot; no files are written in that case.

type Guard added in v0.5.0

type Guard interface {
	Predicate(ctx context.Context) (drops.Expression, error)
}

Guard materialises an authorization predicate. Implementations are stateless — every query rebuilds the expression.

func AllOf added in v0.5.0

func AllOf(guards ...Guard) Guard

AllOf authorises only when every guard does (AND composition).

func AnyOf added in v0.5.0

func AnyOf(guards ...Guard) Guard

AnyOf authorises when any guard does (OR composition).

type IdempotencyStore added in v0.5.0

type IdempotencyStore struct {
	// contains filtered or unexported fields
}

IdempotencyStore stamps a (response, completed) record per key so retries of the same logical operation observe the original result instead of executing again — the canonical "idempotency key" pattern for mutating API endpoints.

store := sqlite.NewIdempotencyStore(db, "idempotency_keys", 24*time.Hour)
raw, err := store.Run(ctx, requestKey, func(tx *sqlite.DB) ([]byte, error) {
    if err := PaymentEntity.Create(tx, ctx, &p); err != nil {
        return nil, err
    }
    return json.Marshal(map[string]any{"paymentId": p.ID})
})

The first call with a given key runs the closure and stores its response; later calls with the same key skip the closure and return the cached bytes. A failed closure rolls back the claim so the next attempt re-executes.

SQLite has no SELECT ... FOR UPDATE, but its write transactions serialise: the INSERT below takes the database write lock, so concurrent Run calls with the same key queue at the transaction boundary and the late arrivals observe the committed response.

func NewIdempotencyStore added in v0.5.0

func NewIdempotencyStore(db *DB, table string, ttl time.Duration) *IdempotencyStore

NewIdempotencyStore returns a store bound to db. table is the keys table (create one via NewIdempotencyTable); ttl is how long records survive before Cleanup may reclaim them.

func (*IdempotencyStore) Cleanup added in v0.5.0

func (s *IdempotencyStore) Cleanup(ctx context.Context) (int64, error)

Cleanup removes records whose expiresAt has passed. The cutoff is bound as a parameter (not CURRENT_TIMESTAMP) so both sides of the comparison share the driver's time encoding.

func (*IdempotencyStore) Run added in v0.5.0

func (s *IdempotencyStore) Run(ctx context.Context, key string, fn func(tx *DB) ([]byte, error)) ([]byte, error)

Run executes fn under key. The first invocation runs fn and stores its response; later invocations with the same key return the stored bytes without re-running. A non-nil error rolls the claim back.

func (*IdempotencyStore) SweepEvery added in v0.5.0

func (s *IdempotencyStore) SweepEvery(ctx context.Context, interval time.Duration, onError func(error))

SweepEvery launches a goroutine calling Cleanup every interval until ctx is cancelled. Errors go to onError when supplied.

type InsertBuilder

type InsertBuilder struct {
	// contains filtered or unexported fields
}

InsertBuilder builds an INSERT statement. Create one via DB.Insert.

func (*InsertBuilder) Exec

func (i *InsertBuilder) Exec(ctx context.Context) (drops.Result, error)

Exec runs the INSERT.

func (*InsertBuilder) OrIgnore

func (i *InsertBuilder) OrIgnore() *InsertBuilder

OrIgnore emits INSERT OR IGNORE (SQLite's conflict-skip form).

func (*InsertBuilder) OrReplace

func (i *InsertBuilder) OrReplace() *InsertBuilder

OrReplace emits INSERT OR REPLACE (upsert-by-replace).

func (*InsertBuilder) Returning

func (i *InsertBuilder) Returning(cols ...ColRef) *InsertBuilder

Returning adds a RETURNING clause (SQLite >= 3.35).

func (*InsertBuilder) ToSQL

func (i *InsertBuilder) ToSQL() (sql string, args []any)

ToSQL renders the statement with SQLite placeholders.

func (*InsertBuilder) Values

func (i *InsertBuilder) Values(vals ...ColumnValue) *InsertBuilder

Values appends one row of column bindings. Every row must bind the same set of columns, in the same order.

func (*InsertBuilder) WriteSQL

func (i *InsertBuilder) WriteSQL(b *drops.Builder)

WriteSQL implements drops.Expression.

type InsertHook added in v0.5.0

type InsertHook interface {
	BeforeInsert(ctx *InsertHookCtx)
}

InsertHook is invoked once per INSERT statement, before rendering.

type InsertHookCtx added in v0.5.0

type InsertHookCtx struct {
	// contains filtered or unexported fields
}

InsertHookCtx is the controlled handle a hook uses to inspect the statement and append hook-supplied values, applied uniformly to every row in the INSERT.

func (*InsertHookCtx) Has added in v0.5.0

func (c *InsertHookCtx) Has(col *Column) bool

Has reports whether col is already bound on the INSERT.

func (*InsertHookCtx) Set added in v0.5.0

func (c *InsertHookCtx) Set(v ColumnValue)

Set binds a typed ColumnValue (e.g. (*Col[T]).Val(v)).

func (*InsertHookCtx) SetExpr added in v0.5.0

func (c *InsertHookCtx) SetExpr(col *Column, expr drops.Expression)

SetExpr binds expr to col across every row, unless col is already bound. Use for DB-evaluated defaults (drops.Raw("CURRENT_TIMESTAMP")).

type InsertHookFunc added in v0.5.0

type InsertHookFunc func(*InsertHookCtx)

InsertHookFunc adapts a plain function to the InsertHook interface.

func (InsertHookFunc) BeforeInsert added in v0.5.0

func (f InsertHookFunc) BeforeInsert(ctx *InsertHookCtx)

BeforeInsert implements InsertHook.

type JSONPath added in v0.5.0

type JSONPath[T any] struct {
	// contains filtered or unexported fields
}

JSONPath is a typed accessor inside a JSON (TEXT) column. The type parameter T fixes the Go type of the leaf value at declaration time, keeping comparisons type-safe:

type Settings struct {
    Theme string `json:"theme"`
    Beta  bool   `json:"beta"`
}

var (
    Users    = sqlite.NewTable("users")
    UserID   = sqlite.Add(Users, sqlite.BigInt("id").PrimaryKey())
    UserMeta = sqlite.Add(Users, sqlite.JSON("meta"))
)

beta := sqlite.JSONField[bool](UserMeta, "settings", "beta")
db.Select(UserID).From(Users).Where(beta.Eq(true))
// SELECT "users"."id" FROM "users"
// WHERE (json_extract("users"."meta", '$.settings.beta') = ?)

SQLite's json_extract already returns a value with the right storage class (INTEGER / REAL / TEXT) for the JSON leaf, so — unlike the PostgreSQL accessor — no explicit cast is emitted.

func JSONField added in v0.5.0

func JSONField[T any](col ColRef, path ...string) *JSONPath[T]

JSONField builds a typed JSONPath. col is the JSON column, path the keys to walk. An empty path targets the document root ('$').

func (*JSONPath[T]) Column added in v0.5.0

func (j *JSONPath[T]) Column() *Column

Column returns the source JSON column.

func (*JSONPath[T]) Eq added in v0.5.0

func (j *JSONPath[T]) Eq(v T) drops.Expression

func (*JSONPath[T]) Gt added in v0.5.0

func (j *JSONPath[T]) Gt(v T) drops.Expression

func (*JSONPath[T]) Gte added in v0.5.0

func (j *JSONPath[T]) Gte(v T) drops.Expression

func (*JSONPath[T]) In added in v0.5.0

func (j *JSONPath[T]) In(values ...T) drops.Expression

In tests whether the path's value is one of values.

func (*JSONPath[T]) IsNotNull added in v0.5.0

func (j *JSONPath[T]) IsNotNull() drops.Expression

IsNotNull renders "(path) IS NOT NULL".

func (*JSONPath[T]) IsNull added in v0.5.0

func (j *JSONPath[T]) IsNull() drops.Expression

IsNull renders "(path) IS NULL" — true when the key is absent or the JSON value is null.

func (*JSONPath[T]) Like added in v0.5.0

func (j *JSONPath[T]) Like(pattern string) drops.Expression

Like applies the SQL LIKE operator (meaningful when T is a string).

func (*JSONPath[T]) Lt added in v0.5.0

func (j *JSONPath[T]) Lt(v T) drops.Expression

func (*JSONPath[T]) Lte added in v0.5.0

func (j *JSONPath[T]) Lte(v T) drops.Expression

func (*JSONPath[T]) Ne added in v0.5.0

func (j *JSONPath[T]) Ne(v T) drops.Expression

func (*JSONPath[T]) Path added in v0.5.0

func (j *JSONPath[T]) Path() []string

Path returns a copy of the path segments.

func (*JSONPath[T]) WriteSQL added in v0.5.0

func (j *JSONPath[T]) WriteSQL(b *drops.Builder)

WriteSQL makes JSONPath usable as a SELECT projection or generic Expression. Implements drops.Expression.

type LoggerFunc added in v0.5.0

type LoggerFunc = drops.LoggerFunc

LoggerFunc / LoggerOptions / LoggerHook are aliases for the dialect-neutral versions in the root drops package, kept here so a codebase can install a logging hook symmetrically across dialects (sqlite.LoggerHook, pg.LoggerHook, clickhouse.LoggerHook).

New code may prefer drops.LoggerHook + drops.LoggerOptions directly — the same hook works against sqlite.DB, pg.DB, clickhouse.DB and qdrant.Client without modification.

type LoggerOptions added in v0.5.0

type LoggerOptions = drops.LoggerOptions

LoggerFunc / LoggerOptions / LoggerHook are aliases for the dialect-neutral versions in the root drops package, kept here so a codebase can install a logging hook symmetrically across dialects (sqlite.LoggerHook, pg.LoggerHook, clickhouse.LoggerHook).

New code may prefer drops.LoggerHook + drops.LoggerOptions directly — the same hook works against sqlite.DB, pg.DB, clickhouse.DB and qdrant.Client without modification.

type MembershipGuard added in v0.5.0

type MembershipGuard struct {
	Junction         *Table
	JunctionSubject  *Column
	JunctionResource *Column
	ResourceOwner    *Column
}

MembershipGuard authorises when the subject is a member of the resource's containing group, expressed via a junction table.

func (MembershipGuard) Predicate added in v0.5.0

func (g MembershipGuard) Predicate(ctx context.Context) (drops.Expression, error)

Predicate implements Guard.

type Migration

type Migration struct {
	Version string // sortable string — zero-padded numeric is recommended ("0001")
	Name    string // human-readable label, used only for status output
	Up      func(ctx context.Context, db *DB) error
	Down    func(ctx context.Context, db *DB) error
}

Migration is one unit of schema change. Up and Down may be nil; a nil Down means the migration is irreversible (Down() will refuse to roll it back).

type MigrationDirection

type MigrationDirection int

MigrationDirection tells a MigrationHook whether the migrator is applying a migration (DirectionUp) or rolling one back (DirectionDown).

const (
	// DirectionUp is passed to hooks firing during Up.
	DirectionUp MigrationDirection = iota
	// DirectionDown is passed to hooks firing during Down.
	DirectionDown
)

func (MigrationDirection) String

func (d MigrationDirection) String() string

String renders the direction as "up" or "down".

type MigrationHook

type MigrationHook func(ctx context.Context, tx *DB, mig Migration, dir MigrationDirection) error

MigrationHook runs around a migration's Up/Down body, inside the same transaction, so any data it reads or writes commits atomically with the schema change (or rolls back with it on error). This is the seam for data migrations that must run between schema migrations — backfilling a new column, copying rows into a split-out table, rewriting a value before an old column is dropped.

Register hooks with Migrator.BeforeEach / Migrator.AfterEach. A "before" hook runs just before the migration body; an "after" hook runs just after it — in both directions. Use mig.Version / mig.Name to scope a data migration to a specific step, and dir to run it only on the way up (or only on the way down):

m.AfterEach(func(ctx context.Context, tx *sqlite.DB, mig sqlite.Migration, dir sqlite.MigrationDirection) error {
	if dir == sqlite.DirectionUp && mig.Version == "0003" {
		_, err := tx.Exec(ctx, `UPDATE users SET full_name = first_name || ' ' || last_name`)
		return err
	}
	return nil
})

A hook that returns an error aborts the migration: the whole transaction (schema change plus every hook) rolls back and Up/Down return the error.

type Migrator

type Migrator struct {
	// contains filtered or unexported fields
}

Migrator runs database migrations and tracks their history in a table.

func NewMigrator

func NewMigrator(db *DB) *Migrator

NewMigrator returns a migrator bound to db. Add migrations with Add / AddSQL / AddFS, then call Up.

func (*Migrator) Add

func (m *Migrator) Add(mig Migration) *Migrator

Add registers a single migration.

func (*Migrator) AddFS

func (m *Migrator) AddFS(fsys fs.FS, dir string) error

AddFS scans dir within fsys for migration files and registers them.

Filename format: <version>_<name>.up.sql and (optionally) <version>_<name>.down.sql — for example, "0001_create_users.up.sql". Versions are compared lexicographically; zero-pad numeric versions.

func (*Migrator) AddSQL

func (m *Migrator) AddSQL(version, name, upSQL, downSQL string) *Migrator

AddSQL registers a migration whose Up and Down are raw SQL. downSQL may be empty.

func (*Migrator) AfterEach

func (m *Migrator) AfterEach(h MigrationHook) *Migrator

AfterEach registers a hook that runs immediately after every migration body, within the migration's transaction. Hooks fire in registration order; the first one to error aborts the migration. This is the usual home for a data migration that depends on the schema change having just landed. See MigrationHook.

func (*Migrator) BeforeEach

func (m *Migrator) BeforeEach(h MigrationHook) *Migrator

BeforeEach registers a hook that runs immediately before every migration body, within the migration's transaction. Hooks fire in registration order; the first one to error aborts the migration. See MigrationHook.

func (*Migrator) Down

func (m *Migrator) Down(ctx context.Context) error

Down rolls back the most recently applied migration. Returns ErrNoMigrationsApplied if there are none.

func (*Migrator) Status

func (m *Migrator) Status(ctx context.Context) ([]Status, error)

Status reports every registered migration and whether it has been applied.

func (*Migrator) Up

func (m *Migrator) Up(ctx context.Context) error

Up applies every registered migration that hasn't been applied yet, in version order. Each migration runs in its own transaction.

func (*Migrator) WithTable

func (m *Migrator) WithTable(name string) *Migrator

WithTable overrides the migrations history table (default DefaultMigrationsTable).

type Mixin added in v0.5.0

type Mixin interface {
	Apply(*Table)
}

Mixin is the richer companion of the plain template functions. While a template function only contributes columns, a Mixin can also register lifecycle hooks and default filters in a single Apply call.

sqlite.ApplyMixins(Users,
    sqlite.UUIDPrimaryKeyMixin{},
    &sqlite.TimestampsMixin{},  // adds timestamps + bumps updatedAt on UPDATE
    &sqlite.SoftDeleteMixin{},  // adds deletedAt, default-scopes queries,
                                // and rewrites DELETE into UPDATE
)

type MixinFunc added in v0.5.0

type MixinFunc func(*Table)

MixinFunc adapts a plain function to the Mixin interface.

func (MixinFunc) Apply added in v0.5.0

func (f MixinFunc) Apply(t *Table)

Apply implements Mixin.

type Money added in v0.5.0

type Money struct {
	// contains filtered or unexported fields
}

Money is a precision-safe monetary amount represented as a signed 64-bit integer in minor units (cents for USD, eurocents for EUR, etc.). The default exponent is 2 decimal places — override at declaration time when the currency uses something else (JPY=0, BTC=8).

type Payment struct {
    ID     int64    `drop:"id,primaryKey,autoIncrement"`
    Amount sqlite.Money `drop:"amount"`
}

p := Payment{Amount: sqlite.MoneyFromString("12.34")}
p.Amount = p.Amount.Add(sqlite.MoneyFromCents(50))
p.Amount = p.Amount.MulRate(0.10) // apply 10% (banker's rounding)

Floats in monetary code are bugs waiting to ship — Money never converts to float for storage or comparison; only MulRate uses a floating-point factor and rounds half-to-even at the end. For chains of percentage operations, use Decimal in a future commit.

Wire / storage: bigint (cents). JSON: string in canonical "<sign>?<integer>.<fraction>" form so JavaScript clients don't lose precision on values > 2^53.

Equality, ordering and zero-checks compare cents directly, so two Money values with the same cents but different exponents are NOT equal — wrap mixed-exponent stores carefully.

func MoneyFromCents added in v0.5.0

func MoneyFromCents(cents int64) Money

MoneyFromCents returns a Money expressed as a count of minor units at the default 2-place exponent.

func MoneyFromString added in v0.5.0

func MoneyFromString(s string) (Money, error)

MoneyFromString parses "12.34", "-1.5", "10" into Money at the default 2-place exponent. Truncates extra fractional digits; callers needing different rounding should pre-round.

func MoneyFromUnits added in v0.5.0

func MoneyFromUnits(units, cents int64) Money

MoneyFromUnits returns a Money for the supplied whole-number amount (units) plus a minor-unit remainder (cents). 12.34 -> MoneyFromUnits(12, 34).

func MoneyWithExponent added in v0.5.0

func MoneyWithExponent(units int64, exponent uint8) Money

MoneyWithExponent returns a Money with explicit precision. Use for currencies whose minor unit isn't 1/100 (JPY uses 0, BTC often uses 8).

func (Money) Add added in v0.5.0

func (m Money) Add(other Money) Money

Add returns m + other. Panics on exponent mismatch — the caller should normalise first.

func (Money) Cents added in v0.5.0

func (m Money) Cents() int64

Cents returns the underlying minor-unit count.

func (Money) Compare added in v0.5.0

func (m Money) Compare(other Money) int

Compare returns -1, 0, +1 if m is less than, equal to, greater than other.

func (Money) Exponent added in v0.5.0

func (m Money) Exponent() uint8

Exponent returns the number of decimal places.

func (Money) IsNegative added in v0.5.0

func (m Money) IsNegative() bool

IsNegative reports whether the amount is < 0.

func (Money) IsZero added in v0.5.0

func (m Money) IsZero() bool

IsZero reports whether the amount is zero.

func (Money) MarshalJSON added in v0.5.0

func (m Money) MarshalJSON() ([]byte, error)

MarshalJSON renders as a string to preserve precision on JS clients (numbers > 2^53 lose precision in JSON.parse).

func (Money) MulInt added in v0.5.0

func (m Money) MulInt(n int64) Money

MulInt scales m by n (e.g. quantity × unit price).

func (Money) MulRate added in v0.5.0

func (m Money) MulRate(rate float64) Money

MulRate scales m by a floating-point rate (e.g. tax 0.07). The result is rounded half-to-even (banker's rounding) to keep rounding bias out of long sums. Use for tax / fee / interest calculations where the rate is genuinely a fraction.

Float precision means MulRate is appropriate for rates with at most ~12 significant digits; for sub-cent precision over many operations, use a dedicated decimal library and round once at the end.

func (Money) Neg added in v0.5.0

func (m Money) Neg() Money

Neg returns -m.

func (*Money) Scan added in v0.5.0

func (m *Money) Scan(src any) error

Scan implements sql.Scanner.

func (Money) String added in v0.5.0

func (m Money) String() string

String renders m as "<integer>.<fraction>".

func (Money) Sub added in v0.5.0

func (m Money) Sub(other Money) Money

Sub returns m - other.

func (*Money) UnmarshalJSON added in v0.5.0

func (m *Money) UnmarshalJSON(b []byte) error

UnmarshalJSON accepts string ("12.34"), number (123) or null.

func (Money) Value added in v0.5.0

func (m Money) Value() (driver.Value, error)

Value implements driver.Valuer — stores as int64 (minor units).

type N1Pattern added in v0.5.0

type N1Pattern struct {
	SQL   string
	Count int
}

N1Pattern is a single SQL skeleton and the number of times it ran.

type N1Report added in v0.5.0

type N1Report struct {
	Threshold int
	Total     int
	Patterns  []N1Pattern
}

N1Report summarises the queries observed during a tracked context's lifetime. Patterns lists every SQL skeleton that fired at least Threshold times.

func (N1Report) IsClean added in v0.5.0

func (r N1Report) IsClean() bool

IsClean reports whether no pattern crossed the threshold.

type NullsOrdering added in v0.5.0

type NullsOrdering string

NullsOrdering is the NULLS FIRST / NULLS LAST modifier on an ORDER BY clause.

const (
	// NullsDefault inherits SQLite's per-direction default (NULLS
	// FIRST for ASC, NULLS LAST for DESC).
	NullsDefault NullsOrdering = ""
	// NullsFirst pushes NULL values to the start of the order.
	NullsFirst NullsOrdering = "FIRST"
	// NullsLast pushes NULL values to the end of the order.
	NullsLast NullsOrdering = "LAST"
)

type OrderKey added in v0.5.0

type OrderKey struct {
	// Col is the column referenced by both the ORDER BY clause
	// and the keyset WHERE comparison.
	Col ColRef

	// Desc selects descending order. Defaults to ascending.
	Desc bool

	// Nulls picks the NULLS FIRST / NULLS LAST clause. Leaving
	// it empty inherits SQLite's default ("NULLS FIRST" for ASC,
	// "NULLS LAST" for DESC). Cursor columns are typically
	// non-null (timestamps, IDs) so the default is fine — but
	// when you cursor on nullable columns set this explicitly
	// so the WHERE clause and ORDER BY agree.
	Nulls NullsOrdering
}

OrderKey is one (column, direction) pair making up a cursor shape. Combine multiple OrderKey values in a CursorSpec to form a stable ordering — the typical pattern is (sort_column DESC, primary_key DESC) so rows with identical sort values still produce a unique page boundary.

type OrderingColumn added in v0.5.0

type OrderingColumn struct {
	// contains filtered or unexported fields
}

OrderingColumn pairs a *Column with its sort direction. Build one with Asc / Desc.

func Asc added in v0.5.0

func Asc(c ColRef) OrderingColumn

Asc returns an OrderingColumn for c sorted ascending.

func Desc added in v0.5.0

func Desc(c ColRef) OrderingColumn

Desc returns an OrderingColumn for c sorted descending.

type Outbox added in v0.5.0

type Outbox struct {
	// contains filtered or unexported fields
}

Outbox is the transactional outbox pattern for SQLite. The event is written into a co-resident outbox table inside the same transaction as the business write; a background worker then drains the table and hands rows to a publisher.

ob := sqlite.NewOutbox(db, "outbox")
err := db.InTx(ctx, func(tx *sqlite.DB) error {
    if err := UserEntity.Create(tx, ctx, &u); err != nil { return err }
    return ob.Emit(tx, ctx, "user.created", u)
})

worker := sqlite.NewOutboxWorker(ob).
    WithInterval(time.Second).
    OnEvent(func(ctx context.Context, e sqlite.OutboxEvent) error {
        return publisher.Publish(ctx, e.Kind, e.Payload)
    })
go worker.Run(ctx)

Differences from drops/pg: SQLite has no LISTEN/NOTIFY, SKIP LOCKED or advisory locks, so this is a poll-based, single-worker outbox (run one worker per outbox table). Timestamps are stored as INTEGER Unix seconds to keep comparisons portable. Delivery is at-least-once — make the publisher idempotent on the event id.

func NewOutbox added in v0.5.0

func NewOutbox(db *DB, table string) *Outbox

NewOutbox returns an Outbox bound to db.

func (*Outbox) Cleanup added in v0.5.0

func (o *Outbox) Cleanup(ctx context.Context, retainAfter time.Duration) (int64, error)

Cleanup deletes rows published more than retainAfter ago. Terminally failed rows are kept as the poison-message audit log.

func (*Outbox) Drain added in v0.5.0

func (o *Outbox) Drain(ctx context.Context, limit int) ([]OutboxEvent, error)

Drain fetches up to limit unpublished, non-failed, currently-available events in id order. Mark rows via MarkPublished / MarkFailed after handling. Intended for a single worker per table.

func (*Outbox) Emit added in v0.5.0

func (o *Outbox) Emit(tx *DB, ctx context.Context, kind string, payload any) error

Emit inserts an event using tx (the *DB from an InTx callback).

func (*Outbox) EmitWith added in v0.5.0

func (o *Outbox) EmitWith(tx *DB, ctx context.Context, kind string, payload any, opts EmitOptions) error

EmitWith is the extended variant carrying aggregate metadata and tracing headers.

func (*Outbox) MarkFailed added in v0.5.0

func (o *Outbox) MarkFailed(ctx context.Context, id int64, attempts int, nextRetryAt time.Time, lastErr string) error

MarkFailed records a handler failure. A zero nextRetryAt parks the row as terminally failed (failedAt set); otherwise it becomes available again at nextRetryAt with attempts bumped.

func (*Outbox) MarkPublished added in v0.5.0

func (o *Outbox) MarkPublished(ctx context.Context, ids ...int64) error

MarkPublished stamps publishedAt on the supplied event ids.

type OutboxBatchHandler added in v0.5.0

type OutboxBatchHandler func(ctx context.Context, events []OutboxEvent) error

OutboxBatchHandler receives the whole drain batch in one call. nil marks every event published; an error fails every event in the batch.

type OutboxEvent added in v0.5.0

type OutboxEvent struct {
	ID            int64
	Kind          string
	AggregateType string
	AggregateID   string
	Payload       json.RawMessage
	Headers       map[string]string
	Attempts      int
	LastError     string
	CreatedAt     time.Time
}

OutboxEvent is one drained row.

type OutboxHandler added in v0.5.0

type OutboxHandler func(ctx context.Context, e OutboxEvent) error

OutboxHandler is the per-event callback invoked when configured via OnEvent. nil marks the row published; an error schedules a retry (or terminal failure once MaxAttempts is reached).

type OutboxWorker added in v0.5.0

type OutboxWorker struct {
	// contains filtered or unexported fields
}

OutboxWorker polls the outbox table and forwards rows to a handler. Run a single worker per outbox table (SQLite has no SKIP LOCKED).

func NewOutboxWorker added in v0.5.0

func NewOutboxWorker(ob *Outbox) *OutboxWorker

NewOutboxWorker returns a worker with sensible defaults: 1s interval, 50-row batch, exponential-jitter backoff (1s base, 5min cap), unlimited retries.

func (*OutboxWorker) OnBatch added in v0.5.0

OnBatch attaches the batch handler (mutually exclusive with OnEvent).

func (*OutboxWorker) OnError added in v0.5.0

func (w *OutboxWorker) OnError(fn func(error)) *OutboxWorker

OnError attaches an error sink for drain / mark failures. Handler errors are reported through MarkFailed and do not surface here.

func (*OutboxWorker) OnEvent added in v0.5.0

func (w *OutboxWorker) OnEvent(fn OutboxHandler) *OutboxWorker

OnEvent attaches the per-event handler (mutually exclusive with OnBatch).

func (*OutboxWorker) Run added in v0.5.0

func (w *OutboxWorker) Run(ctx context.Context) error

Run drains the outbox until ctx is cancelled. Blocks the caller; typically invoked as go worker.Run(ctx).

func (*OutboxWorker) Tick added in v0.5.0

func (w *OutboxWorker) Tick(ctx context.Context) error

Tick performs one drain-and-dispatch pass. Exposed so callers can drive the outbox from their own scheduler instead of Run's ticker.

func (*OutboxWorker) WithBackoff added in v0.5.0

func (w *OutboxWorker) WithBackoff(fn func(attempt int) time.Duration) *OutboxWorker

WithBackoff overrides the per-attempt retry delay.

func (*OutboxWorker) WithBatch added in v0.5.0

func (w *OutboxWorker) WithBatch(n int) *OutboxWorker

WithBatch overrides the per-tick batch size.

func (*OutboxWorker) WithInterval added in v0.5.0

func (w *OutboxWorker) WithInterval(d time.Duration) *OutboxWorker

WithInterval overrides the polling cadence.

func (*OutboxWorker) WithMaxAttempts added in v0.5.0

func (w *OutboxWorker) WithMaxAttempts(n int) *OutboxWorker

WithMaxAttempts caps the retry count. After n attempts the row is parked as terminally failed. Zero means unlimited.

type OwnerGuard added in v0.5.0

type OwnerGuard struct {
	Owner *Column
}

OwnerGuard authorises when the subject matches an ownership column.

func (OwnerGuard) Predicate added in v0.5.0

func (g OwnerGuard) Predicate(ctx context.Context) (drops.Expression, error)

Predicate implements Guard.

type PIIParam added in v0.5.0

type PIIParam struct{ Value any }

PIIParam is a drops.Expression that binds a value already wrapped in the redaction marker — used by entity bindings for PII columns.

func (PIIParam) WriteSQL added in v0.5.0

func (p PIIParam) WriteSQL(b *drops.Builder)

WriteSQL implements drops.Expression.

type Page added in v0.5.0

type Page[T any] struct {
	Items      []T
	NextCursor string
	HasMore    bool
}

Page is the typed result of a cursor-based pagination. NextCursor is empty when no further rows exist; HasMore short-circuits the presence check.

type PageBuilder added in v0.5.0

type PageBuilder[T any] struct {
	// contains filtered or unexported fields
}

PageBuilder composes a cursor-paginated query, keeping the cursor encoding/decoding internal — callers never construct or inspect cursors directly. Cursors are opaque, URL-safe base64 strings whose payload is a gob-encoded slice of the ordering columns' values, stable as long as the OrderBy spec doesn't change between calls.

func (*PageBuilder[T]) After added in v0.5.0

func (p *PageBuilder[T]) After(cursor string) *PageBuilder[T]

After resumes iteration after the supplied cursor. Pass "" for the first page.

func (*PageBuilder[T]) All added in v0.5.0

func (p *PageBuilder[T]) All(ctx context.Context) (*Page[T], error)

All runs the query and returns the page. Honours the entity's tenant scope when one is configured.

func (*PageBuilder[T]) Limit added in v0.5.0

func (p *PageBuilder[T]) Limit(n int) *PageBuilder[T]

Limit caps the page size (default 50).

func (*PageBuilder[T]) OrderBy added in v0.5.0

func (p *PageBuilder[T]) OrderBy(cols ...OrderingColumn) *PageBuilder[T]

OrderBy fixes the cursor's stability axis. At least one column is required; the last should be unique (typically the PK) so every row has a distinct cursor.

func (*PageBuilder[T]) Where added in v0.5.0

func (p *PageBuilder[T]) Where(preds ...drops.Expression) *PageBuilder[T]

Where appends predicates joined by AND, composing with the cursor guard so filters narrow the page set.

type PatchOp added in v0.5.0

type PatchOp = ColumnValue

PatchOp describes one SET assignment in a Patch. It is exactly the sqlite ColumnValue shape, so patch ops slot directly into UpdateBuilder.Set. Construct one with Inc / Dec / Set / SetIfGreater / SetIfLess / SetIfChanged.

func Dec added in v0.5.0

func Dec[T number](col *Col[T], delta T) PatchOp

Dec is shorthand for Inc(col, -delta).

func Inc added in v0.5.0

func Inc[T number](col *Col[T], delta T) PatchOp

Inc emits "col = col + delta".

func Set added in v0.5.0

func Set[T any](col *Col[T], v T) PatchOp

Set is a typed plain assignment, usable in the Patch op list.

func SetIfChanged added in v0.5.0

func SetIfChanged[T any](col *Col[T], v T) PatchOp

SetIfChanged emits a CASE that assigns ? only when it differs from the current value, using SQLite's NULL-safe IS NOT comparison so the branch is well-defined even when the column is NULL.

func SetIfGreater added in v0.5.0

func SetIfGreater[T any](col *Col[T], v T) PatchOp

SetIfGreater emits "col = max(col, ?)" — raises but never lowers the value (high-watermark). SQLite's scalar max() is the GREATEST analog.

func SetIfLess added in v0.5.0

func SetIfLess[T any](col *Col[T], v T) PatchOp

SetIfLess emits "col = min(col, ?)" — lowers but never raises the value (low-watermark).

type PlanStep added in v0.5.0

type PlanStep struct {
	ID     int64
	Parent int64
	Detail string
}

PlanStep is one row of EXPLAIN QUERY PLAN output.

type PushOptions added in v0.5.0

type PushOptions struct {
	// DryRun returns the statements that would be applied without
	// executing them — useful for previewing changes in CI.
	DryRun bool
}

PushOptions tunes how Push applies schema changes.

type PushResult added in v0.5.0

type PushResult struct {
	// Statements is the ordered SQL diff between the live database and
	// the supplied Go schema.
	Statements []string
	// Applied is true when the statements were executed (false for
	// DryRun or an empty diff).
	Applied bool
}

PushResult is the outcome of a Push call.

func Push added in v0.5.0

func Push(ctx context.Context, db *DB, schema *Schema, opts ...PushOptions) (*PushResult, error)

Push introspects the live database, diffs it against the supplied Go schema, and applies the changes — the drops equivalent of drizzle-kit `push`.

  • Reads the current state via Introspect.
  • Builds the target snapshot from schema.
  • Diffs the two.
  • If DryRun, returns the statements unexecuted.
  • Otherwise applies them in a single transaction; any failure rolls the whole push back.

Push is convenient for development but skips migration history; prefer the Migrator for production, so changes are reviewable and reproducible.

type Relation

type Relation struct {
	Name   string
	Kind   RelationKind
	Target *Table

	// Local / Foreign are the join columns for HasMany / HasOne /
	// BelongsTo: Local lives on the owning table, Foreign on Target.
	Local   *Column
	Foreign *Column

	// ManyToMany junction wiring: Through is the junction table;
	// ThroughLocal / ThroughForeign are its FK columns pointing at the
	// owning table and the target respectively; LocalKey / TargetKey
	// are the referenced keys on the owning and target tables.
	Through        *Table
	ThroughLocal   *Column
	ThroughForeign *Column
	LocalKey       *Column
	TargetKey      *Column
}

Relation describes one association edge.

type RelationKind

type RelationKind int

RelationKind enumerates the supported association shapes.

const (
	// HasMany: parent has many children; the child holds the FK.
	HasMany RelationKind = iota
	// HasOne: parent has one child; the child holds the FK.
	HasOne
	// BelongsTo: this table holds the FK pointing at the parent.
	BelongsTo
	// ManyToMany: association through a junction table.
	ManyToMany
)

type RelationsBuilder

type RelationsBuilder struct {
	// contains filtered or unexported fields
}

RelationsBuilder declares relations on a table fluently.

func NewRelations

func NewRelations(t *Table) *RelationsBuilder

NewRelations begins declaring relations on t.

func (*RelationsBuilder) BelongsTo

func (b *RelationsBuilder) BelongsTo(name string, target *Table, local, foreign ColRef) *RelationsBuilder

BelongsTo declares that t holds the FK (local) pointing at Target's key (foreign).

func (*RelationsBuilder) HasMany

func (b *RelationsBuilder) HasMany(name string, target *Table, local, foreign ColRef) *RelationsBuilder

HasMany declares that t has many Target rows, joined local == foreign (foreign is the FK column on Target).

func (*RelationsBuilder) HasOne

func (b *RelationsBuilder) HasOne(name string, target *Table, local, foreign ColRef) *RelationsBuilder

HasOne declares a one-to-one where Target holds the FK.

func (*RelationsBuilder) ManyToMany

func (b *RelationsBuilder) ManyToMany(name string, target, through *Table, throughLocal, throughForeign, localKey, targetKey ColRef) *RelationsBuilder

ManyToMany declares an association through a junction table. throughLocal / throughForeign are the junction FK columns pointing at the owning table and target; localKey / targetKey are the referenced keys on the owning and target tables.

type RetryPolicy added in v0.5.0

type RetryPolicy struct {
	// MaxAttempts caps the total callback runs. Values < 1 mean 1.
	MaxAttempts int

	// Errors are the retryable sentinels; an error is retried when it
	// matches one (via errors.Is, or — for ErrBusy / ErrLocked — the
	// driver error's message). Nil means "retry on every error", which
	// is almost always wrong; set this explicitly.
	Errors []error

	// Backoff returns the wait between attempt and attempt+1 (1-based).
	// nil disables sleeping.
	Backoff func(attempt int) time.Duration
}

RetryPolicy declares how InTx reacts to transient failures — under SQLite, principally SQLITE_BUSY / SQLITE_LOCKED when another connection holds a write lock. Without a policy InTx runs the callback exactly once and propagates every error.

db := sqlite.New(drv).WithRetry(sqlite.DefaultRetryPolicy())

InTx then retries the callback up to MaxAttempts times, sleeping Backoff(attempt) between tries (attempts are 1-based, so Backoff(1) is the wait before the second try). Context cancellation short-circuits both the sleep and the loop. The retry is at the transaction level — the whole callback re-runs in a fresh transaction — so callbacks must be idempotent across retries.

func DefaultRetryPolicy added in v0.5.0

func DefaultRetryPolicy() RetryPolicy

DefaultRetryPolicy returns the conventional safe default: up to 3 attempts, retry on SQLITE_BUSY / SQLITE_LOCKED, 10ms base exponential backoff capped at 1s.

type SafetyOptions added in v0.5.0

type SafetyOptions struct {
	// Ignore lists rule IDs to skip (known-safe migrations).
	Ignore []string
}

SafetyOptions tunes the analyser — currently rule suppression.

type SafetySeverity added in v0.5.0

type SafetySeverity int

SafetySeverity ranks a warning's urgency.

const (
	// SeverityInfo is a heads-up — usually fine.
	SeverityInfo SafetySeverity = iota
	// SeverityWarn flags a likely behaviour change or a rewrite.
	SeverityWarn
	// SeverityError flags a statement that destroys data or that SQLite
	// will reject at execution time.
	SeverityError
)

func (SafetySeverity) String added in v0.5.0

func (s SafetySeverity) String() string

String renders the level as "info" / "warn" / "error".

type SafetyWarning added in v0.5.0

type SafetyWarning struct {
	Severity   SafetySeverity
	Rule       string
	Statement  string
	Message    string
	Suggestion string
}

SafetyWarning is one finding from the migration analyser.

func AnalyzeMigration added in v0.5.0

func AnalyzeMigration(sql string, opts ...SafetyOptions) []SafetyWarning

AnalyzeMigration splits a migration script on the drizzle-kit "--> statement-breakpoint" boundary and analyses each statement.

func AnalyzeStatements added in v0.5.0

func AnalyzeStatements(stmts []string, opts ...SafetyOptions) []SafetyWarning

AnalyzeStatements runs the safety rules against each statement in order, preserving statement order in the output.

type Saga added in v0.5.0

type Saga struct {
	// contains filtered or unexported fields
}

Saga collects a named sequence of steps to run.

func NewSaga added in v0.5.0

func NewSaga(name string) *Saga

NewSaga returns a saga with the supplied name.

func (*Saga) Run added in v0.5.0

func (s *Saga) Run(db *DB, ctx context.Context, state *SagaState) error

Run executes the saga against db. Each step runs in its own transaction; on failure, compensations of completed steps run in reverse order.

func (*Saga) Step added in v0.5.0

func (s *Saga) Step(name string, forward, compensate SagaStepFn) *Saga

Step appends a step. compensate may be nil (skipped on rollback).

type SagaCompensationFailure added in v0.5.0

type SagaCompensationFailure struct {
	StepName string
	StepIdx  int
	Err      error
}

SagaCompensationFailure is one compensation that itself errored.

type SagaError added in v0.5.0

type SagaError struct {
	SagaName      string
	FailedStep    string
	FailedStepIdx int
	Cause         error
	CompFailures  []SagaCompensationFailure
}

SagaError is returned by Saga.Run when a step fails.

func (*SagaError) Error added in v0.5.0

func (e *SagaError) Error() string

Error implements error.

func (*SagaError) Unwrap added in v0.5.0

func (e *SagaError) Unwrap() error

Unwrap exposes the failing-step cause to errors.Is / errors.As.

type SagaState added in v0.5.0

type SagaState struct {
	// contains filtered or unexported fields
}

SagaState is the typed bag flowing between steps.

func (*SagaState) Get added in v0.5.0

func (s *SagaState) Get(key string) (any, bool)

Get returns the value under key and ok=true when present.

func (*SagaState) Set added in v0.5.0

func (s *SagaState) Set(key string, v any)

Set stores v under key.

type SagaStepFn added in v0.5.0

type SagaStepFn func(ctx context.Context, tx *DB, state *SagaState) error

SagaStepFn is the signature for forward and compensating actions.

type Schema

type Schema struct {
	// contains filtered or unexported fields
}

Schema is a registered set of tables — the input to snapshotting and diffing. SQLite has no schemas (namespaces), so a Schema is just a thin wrapper around a slice of *Table.

func NewSchema

func NewSchema(tables ...*Table) *Schema

NewSchema returns a Schema containing the supplied tables.

func (*Schema) Add

func (s *Schema) Add(t *Table) *Schema

Add appends a table and returns the schema for chaining.

func (*Schema) Tables

func (s *Schema) Tables() []*Table

Tables returns the registered tables in declaration order.

type Seeder added in v0.5.0

type Seeder struct {
	// contains filtered or unexported fields
}

Seeder accumulates seed data declarations and applies them in order. Use it to populate dev / test databases and fixture scenarios without hand-wiring transactions:

seeder := sqlite.NewSeeder(db)
sqlite.SeedAdd(seeder, UserEntity, alice, bob)
sqlite.SeedAdd(seeder, PostEntity, post1, post2)
if err := seeder.Apply(ctx); err != nil { ... }

Apply runs every op inside a single transaction by default, so a failure rolls back every prior insert. Pass WithoutTransaction() to opt out — useful when the seeded data spans tables that the surrounding migration tool can't tx-wrap together.

func NewSeeder added in v0.5.0

func NewSeeder(db *DB) *Seeder

NewSeeder returns a Seeder bound to db.

func SeedAdd added in v0.5.0

func SeedAdd[T any](s *Seeder, ent *Entity[T], rows ...T) *Seeder

SeedAdd registers rows for entity ent. It is a free function because Go does not allow generic methods. Returns s so calls chain.

func SeedAddCreate added in v0.5.0

func SeedAddCreate[T any](s *Seeder, ent *Entity[T], rows ...*T) *Seeder

SeedAddCreate registers rows for ent and uses Create per row, which populates RETURNING fields back into the pointed-to values. Use this when later seeds depend on generated PKs from earlier seeds.

func SeedDo added in v0.5.0

func SeedDo(s *Seeder, fn func(db *DB, ctx context.Context) error) *Seeder

SeedDo registers an arbitrary function. Use it for cross-entity fixups or seed steps that don't map cleanly to Create / CreateMany.

func (*Seeder) Apply added in v0.5.0

func (s *Seeder) Apply(ctx context.Context) error

Apply runs every registered op in declaration order. By default wraps the run in a transaction; on first error the transaction rolls back and the failure is returned.

func (*Seeder) WithoutTransaction added in v0.5.0

func (s *Seeder) WithoutTransaction() *Seeder

WithoutTransaction disables the transactional wrapper Apply uses by default. Returns the seeder for chaining.

type SelectBuilder

type SelectBuilder struct {
	// contains filtered or unexported fields
}

SelectBuilder builds a SELECT statement. Create one via DB.Select.

func (*SelectBuilder) AfterCursor added in v0.5.0

func (s *SelectBuilder) AfterCursor(spec CursorSpec, c Cursor) *SelectBuilder

AfterCursor appends the keyset WHERE clause for forward paging past cursor c. Decoding errors fall through to the next builder method — execution-time scan will surface the wrapped error so callers don't have to guard every chained call.

func (*SelectBuilder) All

func (s *SelectBuilder) All(ctx context.Context, dest any) error

All executes the query and scans every row into dest (pointer to slice of struct or *struct).

func (*SelectBuilder) BeforeCursor added in v0.5.0

func (s *SelectBuilder) BeforeCursor(spec CursorSpec, c Cursor) *SelectBuilder

BeforeCursor is the reverse — appends WHERE constraints that page backward past c. Combine with a reversed ORDER BY (or reverse the returned slice on the caller side) to present the page in the expected order.

func (*SelectBuilder) Distinct

func (s *SelectBuilder) Distinct() *SelectBuilder

Distinct adds the DISTINCT keyword.

func (*SelectBuilder) From

func (s *SelectBuilder) From(t *Table) *SelectBuilder

From sets the table to select from.

func (*SelectBuilder) Join

Join adds an INNER JOIN ... ON.

func (*SelectBuilder) LeftJoin

func (s *SelectBuilder) LeftJoin(t *Table, on drops.Expression) *SelectBuilder

LeftJoin adds a LEFT JOIN ... ON.

func (*SelectBuilder) Limit

func (s *SelectBuilder) Limit(n int64) *SelectBuilder

Limit sets a LIMIT.

func (*SelectBuilder) Offset

func (s *SelectBuilder) Offset(n int64) *SelectBuilder

Offset sets an OFFSET.

func (*SelectBuilder) One

func (s *SelectBuilder) One(ctx context.Context, dest any) error

One executes the query and scans the first row into dest (pointer to struct), returning ErrNoRows when empty.

func (*SelectBuilder) OrderBy

func (s *SelectBuilder) OrderBy(exprs ...drops.Expression) *SelectBuilder

OrderBy sets the ORDER BY expressions (use (*Column).WriteSQL refs, or raw drops.Raw("col DESC")).

func (*SelectBuilder) OrderByCursor added in v0.5.0

func (s *SelectBuilder) OrderByCursor(spec CursorSpec) *SelectBuilder

OrderByCursor sets ORDER BY according to spec. The same spec must be passed to AfterCursor / BeforeCursor on subsequent pages so the keyset WHERE matches.

func (*SelectBuilder) ToSQL

func (s *SelectBuilder) ToSQL() (sql string, args []any)

ToSQL renders the statement with SQLite placeholders.

func (*SelectBuilder) Unscoped added in v0.5.0

func (s *SelectBuilder) Unscoped() *SelectBuilder

Unscoped opts out of the FROM table's DefaultFilter predicates for this SELECT (e.g. to read soft-deleted rows).

func (*SelectBuilder) Where

func (s *SelectBuilder) Where(preds ...drops.Expression) *SelectBuilder

Where AND-s the given predicates onto the statement.

func (*SelectBuilder) With added in v0.5.0

func (s *SelectBuilder) With(ctes ...*CTE) *SelectBuilder

With prepends a WITH clause to the SELECT. Multiple calls accumulate.

func (*SelectBuilder) WithRecursive added in v0.5.0

func (s *SelectBuilder) WithRecursive(ctes ...*CTE) *SelectBuilder

WithRecursive marks the WITH clause as RECURSIVE. The mode is sticky — one WITH prefix per statement.

func (*SelectBuilder) WriteSQL

func (s *SelectBuilder) WriteSQL(b *drops.Builder)

WriteSQL implements drops.Expression.

type Snapshot

type Snapshot struct {
	ID      string                    `json:"id"`
	PrevID  string                    `json:"prevId"`
	Version string                    `json:"version"`
	Dialect string                    `json:"dialect"`
	Tables  map[string]*TableSnapshot `json:"tables"`
}

Snapshot captures the shape of a SQLite schema — tables, columns and every table-level constraint — so two revisions can be diffed into migration SQL. Unlike the drops/pg snapshot (which mirrors drizzle-kit's PostgreSQL v7 format), this is SQLite-flavoured: type strings are affinity keywords (INTEGER/TEXT/REAL/BLOB/NUMERIC), columns carry an autoincrement flag, and single-column foreign keys and uniques live inline on the column while composite ones are table-level constraints.

func BuildSnapshot

func BuildSnapshot(schema *Schema) *Snapshot

BuildSnapshot constructs a snapshot from a Go schema definition. The resulting ID is a fresh UUID v4; PrevID defaults to zeroUUID and the caller should overwrite it from the previous snapshot, if any.

func EmptySnapshot

func EmptySnapshot() *Snapshot

EmptySnapshot returns a fresh snapshot with no tables. Useful as the "previous" snapshot when diffing the first migration.

func Introspect

func Introspect(ctx context.Context, db *DB) (*Snapshot, error)

Introspect queries a live SQLite database and returns a Snapshot describing its current state — tables, columns (declared type, NOT NULL, default, primary key), single- and multi-column foreign keys with referential actions, composite primary keys, and UNIQUE constraints/indexes.

It mirrors pg.Introspect but reads SQLite's catalog: sqlite_master for the table list and the PRAGMA table_info / foreign_key_list / index_list / index_info functions for per-table detail. The migration-history table (_drops_sqlite_migrations) and SQLite's internal sqlite_* tables are skipped.

func UnmarshalSnapshot

func UnmarshalSnapshot(data []byte) (*Snapshot, error)

UnmarshalSnapshot parses a snapshot from JSON, restoring zero-valued maps for any nil collections so subsequent diffs don't have to check for nil.

func (*Snapshot) Marshal

func (s *Snapshot) Marshal() ([]byte, error)

Marshal returns the snapshot as 2-space-indented JSON.

type SoftDeleteCols added in v0.5.0

type SoftDeleteCols struct {
	// DeletedAt is the nullable timestamp column: NULL means live, a
	// timestamp means soft-deleted.
	DeletedAt *Col[time.Time]
}

SoftDeleteCols holds the column(s) a SoftDelete registers.

func SoftDelete added in v0.5.0

func SoftDelete(t *Table) SoftDeleteCols

SoftDelete registers a nullable "deletedAt" column on t plus a DefaultFilter (deletedAt IS NULL), mirroring drops/pg's SoftDeleteMixin. After this, every Select / Update / Delete against t automatically excludes soft-deleted rows unless the builder calls Unscoped.

Declare it during schema setup, before NewEntity, so the entity's column mapping includes deletedAt:

posts := sqlite.NewTable("posts")
sqlite.Add(posts, sqlite.BigInt("id").PrimaryKey())
sd := sqlite.SoftDelete(posts)
postEntity := sqlite.NewEntity[Post](posts)
...
postEntity.SoftDeleteByID(db, ctx, id, sd) // hide the row
postEntity.Restore(db, ctx, id, sd)        // bring it back

type SoftDeleteMixin added in v0.5.0

type SoftDeleteMixin struct {
	Cols SoftDeleteCols
}

SoftDeleteMixin registers a "deletedAt" column, a DefaultFilter (deletedAt IS NULL), and a DeleteHook that rewrites DELETE as UPDATE deletedAt = CURRENT_TIMESTAMP — the row stays but is hidden by default. Use Unscoped() on any builder to bypass the guard.

func (*SoftDeleteMixin) Apply added in v0.5.0

func (m *SoftDeleteMixin) Apply(t *Table)

Apply implements Mixin.

type Span added in v0.5.0

type Span interface {
	SetAttribute(key string, value any)
	RecordError(err error)
	End()
}

Span is the per-query handle returned by Tracer.Start.

type Status

type Status struct {
	Version   string
	Name      string
	Applied   bool
	AppliedAt time.Time // zero if not applied (or unparseable)
}

Status is a single row produced by Migrator.Status.

type TB added in v0.5.0

type TB interface {
	Helper()
	Errorf(format string, args ...any)
	Fatalf(format string, args ...any)
	Logf(format string, args ...any)
	Cleanup(fn func())
}

TB is the subset of *testing.T the helpers below need. Mirroring the testing.TB interface lets the helpers be called from anything that satisfies it (including testing.T and testing.B) without pulling testing into the import graph of production code.

type Table

type Table struct {
	// contains filtered or unexported fields
}

Table represents a SQLite table. SQLite has no schemas (only attached databases), so a table is just a name plus its columns and table-level constraints.

Unlike PostgreSQL, SQLite cannot add most constraints with ALTER TABLE, so composite primary keys, UNIQUE constraints and foreign keys are rendered inside CREATE TABLE (see ddl.go).

func ApplyMixins added in v0.5.0

func ApplyMixins(t *Table, mixins ...Mixin) *Table

ApplyMixins runs each mixin against t in order and returns t. Later mixins observe the columns / hooks installed by earlier ones.

func AutoTable added in v0.5.0

func AutoTable[T any](name string) *Table

AutoTable derives a Table from the `drop` struct tags on T. Tag syntax:

drop:"<col_name>[,opt[,opt...]]"

where each opt is one of: primaryKey, autoIncrement, notNull, unique, pii, default=<sql>. Use `drop:"-"` (or an untagged field) to skip.

Go type → SQLite affinity:

bool                  → BOOLEAN (INTEGER 0/1)
int16/int32/int/int64 → INTEGER
float32/float64       → REAL
string                → TEXT
[]byte                → BLOB
time.Time             → DATETIME
json.RawMessage       → TEXT
sqlite.Money          → INTEGER
*T                    → same as T (nullable unless notNull is set)

SQLite has no serial family — autoIncrement simply keeps INTEGER; pair it with primaryKey to get an auto-assigning rowid alias.

func NewAuditTable added in v0.5.0

func NewAuditTable(name string) *Table

NewAuditTable declares the canonical audit table.

func NewBackfillStateTable added in v0.5.0

func NewBackfillStateTable(name string) *Table

NewBackfillStateTable declares the canonical state table. Timestamps are INTEGER Unix seconds.

func NewEventStoreTable added in v0.5.0

func NewEventStoreTable(name string) *Table

NewEventStoreTable declares the canonical event-store layout. The concurrency constraint rides on the unique index, which also serves in-order stream replay (SQLite has no separate Index type).

func NewIdempotencyTable added in v0.5.0

func NewIdempotencyTable(name string) *Table

NewIdempotencyTable declares the canonical idempotency table.

func NewOutboxTable added in v0.5.0

func NewOutboxTable(name string) *Table

NewOutboxTable declares the canonical outbox table. Timestamps are INTEGER Unix seconds (0 / NULL where unset).

func NewSnapshotTable added in v0.5.0

func NewSnapshotTable(name string) *Table

NewSnapshotTable declares the snapshot store.

func NewTable

func NewTable(name string) *Table

NewTable creates a table. The name is validated; a bad identifier panics at declaration time.

func (*Table) AddCheck

func (t *Table) AddCheck(name, expr string) *Table

AddCheck declares a named CHECK constraint whose value is the raw SQL expression (e.g. "age >= 0").

func (*Table) AddUnique

func (t *Table) AddUnique(name string, cols ...ColRef) *Table

AddUnique declares a named multi-column UNIQUE constraint.

func (*Table) Alias

func (t *Table) Alias() string

func (*Table) As

func (t *Table) As(alias string) *Table

As returns an aliased view of the table for use in joins.

func (*Table) Checks

func (t *Table) Checks() map[string]string

Checks returns the table's CHECK constraints keyed by name.

func (*Table) Col

func (t *Table) Col(name string) *Column

func (*Table) Columns

func (t *Table) Columns() []*Column

func (*Table) CompositeForeignKeys

func (t *Table) CompositeForeignKeys() []*CompositeFK

CompositeForeignKeys returns the table's multi-column foreign keys.

func (*Table) CompositePrimaryKey

func (t *Table) CompositePrimaryKey() []*Column

CompositePrimaryKey returns the composite PK columns, or nil.

func (*Table) CompositeUniques

func (t *Table) CompositeUniques() map[string][]*Column

CompositeUniques returns the table's multi-column unique constraints.

func (*Table) DefaultFilter added in v0.5.0

func (t *Table) DefaultFilter(e drops.Expression) *Table

DefaultFilter appends a predicate applied automatically to every Select / Update / Delete against the table, unless the builder opts out with Unscoped(). Used to implement default scopes (soft-delete hiding, tenant guards).

func (*Table) DefaultFilters added in v0.5.0

func (t *Table) DefaultFilters() []drops.Expression

DefaultFilters returns the table's default-scope predicates.

func (*Table) ForeignKey

func (t *Table) ForeignKey(col, target *Column, opts ...func(*FK)) *Table

ForeignKey declares a single-column foreign key from col to target.

func (*Table) ForeignKeyN

func (t *Table) ForeignKeyN(cols []ColRef, target *Table, targetCols []ColRef, opts ...func(*FK)) *Table

ForeignKeyN declares a composite (N-column) foreign key from cols to targetCols on target, paired positionally. len(cols) must equal len(targetCols) and both be non-empty.

func (*Table) Name

func (t *Table) Name() string

func (*Table) OnDelete added in v0.5.0

func (t *Table) OnDelete(h DeleteHook) *Table

OnDelete registers a DELETE hook, run before every DELETE renders. A hook may replace the DELETE with another statement (soft delete).

func (*Table) OnInsert added in v0.5.0

func (t *Table) OnInsert(h InsertHook) *Table

OnInsert registers an INSERT hook, run before every INSERT renders.

func (*Table) OnUpdate added in v0.5.0

func (t *Table) OnUpdate(h UpdateHook) *Table

OnUpdate registers an UPDATE hook, run before every UPDATE renders.

func (*Table) PrimaryKey

func (t *Table) PrimaryKey(cols ...ColRef) *Table

PrimaryKey declares a composite (multi-column) primary key. For a single-column key use (*Col[T]).PrimaryKey() instead.

func (*Table) Relation

func (t *Table) Relation(name string) *Relation

Relation returns the named relation declared on t, or nil.

func (*Table) WriteSQL

func (t *Table) WriteSQL(b *drops.Builder)

WriteSQL implements drops.Expression (renders the FROM form).

type TableSnapshot

type TableSnapshot struct {
	Name                 string                          `json:"name"`
	Columns              map[string]*ColumnSnapshot      `json:"columns"`
	ForeignKeys          map[string]*ForeignKeySnapshot  `json:"foreignKeys"`
	CompositePrimaryKeys map[string]*CompositePKSnapshot `json:"compositePrimaryKeys"`
	UniqueConstraints    map[string]*UniqueSnapshot      `json:"uniqueConstraints"`
	CheckConstraints     map[string]*CheckSnapshot       `json:"checkConstraints"`
}

TableSnapshot is one entry in Snapshot.Tables.

type TimestampsCols added in v0.5.0

type TimestampsCols struct {
	CreatedAt *Col[time.Time]
	UpdatedAt *Col[time.Time]
}

TimestampsCols holds the typed handles created by Timestamps.

func Timestamps added in v0.5.0

func Timestamps(t *Table) TimestampsCols

Timestamps appends NOT NULL "createdAt" / "updatedAt" DATETIME columns defaulting to CURRENT_TIMESTAMP to t.

type TimestampsMixin added in v0.5.0

type TimestampsMixin struct {
	Cols TimestampsCols
}

TimestampsMixin registers "createdAt" / "updatedAt" columns and an UpdateHook that bumps updatedAt to CURRENT_TIMESTAMP on every UPDATE the caller hasn't already touched. INSERT relies on the column DEFAULT.

func (*TimestampsMixin) Apply added in v0.5.0

func (m *TimestampsMixin) Apply(t *Table)

Apply implements Mixin.

type Tracer added in v0.5.0

type Tracer interface {
	Start(ctx context.Context, name string) (context.Context, Span)
}

Tracer is the minimal contract drops needs to emit a span around every Exec / Query. It mirrors a subset of OpenTelemetry's trace.Tracer without importing it, so drops carries no external tracing dependency. Wire it up with db.WithTracer, passing an adapter that bridges to your real tracer. Implementations must be safe for concurrent use.

type UUIDPrimaryKeyCols added in v0.5.0

type UUIDPrimaryKeyCols struct {
	ID *Col[string]
}

UUIDPrimaryKeyCols holds the typed handle created by UUIDPrimaryKey.

func UUIDPrimaryKey added in v0.5.0

func UUIDPrimaryKey(t *Table) UUIDPrimaryKeyCols

UUIDPrimaryKey appends a TEXT "id" PRIMARY KEY column that defaults to a random RFC-4122 v4 UUID. SQLite has no gen_random_uuid(), so the default is the canonical randomblob() expression (parenthesised, as SQLite requires for expression defaults).

type UUIDPrimaryKeyMixin added in v0.5.0

type UUIDPrimaryKeyMixin struct {
	Cols UUIDPrimaryKeyCols
}

UUIDPrimaryKeyMixin registers a TEXT "id" PRIMARY KEY column defaulting to a random v4 UUID.

func (*UUIDPrimaryKeyMixin) Apply added in v0.5.0

func (m *UUIDPrimaryKeyMixin) Apply(t *Table)

Apply implements Mixin.

type UniqueSnapshot

type UniqueSnapshot struct {
	Name    string   `json:"name"`
	Columns []string `json:"columns"`
}

UniqueSnapshot is one entry in TableSnapshot.UniqueConstraints (multi-column / named UNIQUE declared via Table.AddUnique).

type UpdateBuilder

type UpdateBuilder struct {
	// contains filtered or unexported fields
}

UpdateBuilder builds an UPDATE statement. Create one via DB.Update.

func (*UpdateBuilder) Exec

func (u *UpdateBuilder) Exec(ctx context.Context) (drops.Result, error)

Exec runs the UPDATE.

func (*UpdateBuilder) Set

func (u *UpdateBuilder) Set(vals ...ColumnValue) *UpdateBuilder

Set adds a column assignment.

func (*UpdateBuilder) SetExpr added in v0.5.0

func (u *UpdateBuilder) SetExpr(col *Column, expr drops.Expression) *UpdateBuilder

SetExpr assigns a raw SQL expression to col (e.g. CURRENT_TIMESTAMP, NULL, or "count + 1") rather than a bound value.

func (*UpdateBuilder) ToSQL

func (u *UpdateBuilder) ToSQL() (sql string, args []any)

ToSQL renders the statement with SQLite placeholders.

func (*UpdateBuilder) Unscoped added in v0.5.0

func (u *UpdateBuilder) Unscoped() *UpdateBuilder

Unscoped opts out of the table's DefaultFilter predicates for this UPDATE (e.g. an admin job bypassing a soft-delete or tenant guard).

func (*UpdateBuilder) Where

func (u *UpdateBuilder) Where(preds ...drops.Expression) *UpdateBuilder

Where AND-s the given predicates onto the statement.

func (*UpdateBuilder) WriteSQL

func (u *UpdateBuilder) WriteSQL(b *drops.Builder)

WriteSQL implements drops.Expression.

type UpdateHook added in v0.5.0

type UpdateHook interface {
	BeforeUpdate(ctx *UpdateHookCtx)
}

UpdateHook is invoked once per UPDATE statement, before rendering.

type UpdateHookCtx added in v0.5.0

type UpdateHookCtx struct {
	// contains filtered or unexported fields
}

UpdateHookCtx is the controlled handle a hook uses to add SET assignments without clobbering user-supplied values.

func (*UpdateHookCtx) Has added in v0.5.0

func (c *UpdateHookCtx) Has(col *Column) bool

Has reports whether col is already bound on the UPDATE.

func (*UpdateHookCtx) Set added in v0.5.0

func (c *UpdateHookCtx) Set(v ColumnValue)

Set appends v to the UPDATE's SET list, unless its column is already bound.

func (*UpdateHookCtx) SetExpr added in v0.5.0

func (c *UpdateHookCtx) SetExpr(col *Column, expr drops.Expression)

SetExpr is the raw-expression variant of Set.

type UpdateHookFunc added in v0.5.0

type UpdateHookFunc func(*UpdateHookCtx)

UpdateHookFunc adapts a plain function to the UpdateHook interface.

func (UpdateHookFunc) BeforeUpdate added in v0.5.0

func (f UpdateHookFunc) BeforeUpdate(ctx *UpdateHookCtx)

BeforeUpdate implements UpdateHook.

type Window added in v0.5.0

type Window struct {
	// contains filtered or unexported fields
}

Window describes the contents of an OVER (...) clause.

func WindowSpec added in v0.5.0

func WindowSpec() *Window

WindowSpec begins building a window specification.

func (*Window) Frame added in v0.5.0

func (w *Window) Frame(spec string) *Window

Frame sets the raw frame specification — e.g. "ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING".

func (*Window) OrderBy added in v0.5.0

func (w *Window) OrderBy(exprs ...drops.Expression) *Window

OrderBy adds ORDER BY entries to the window.

func (*Window) PartitionBy added in v0.5.0

func (w *Window) PartitionBy(exprs ...drops.Expression) *Window

PartitionBy adds PARTITION BY columns.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL