pg

package
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: Jun 13, 2026 License: MIT Imports: 35 Imported by: 0

Documentation

Overview

Package pg provides PostgreSQL schema declarations and a fluent query builder for use with drops.

Tables are declared with NewTable and a sequence of column constructors (Text, Integer, Serial, Boolean, Timestamp, UUID, JSONB, ...). Columns are themselves drops.Expressions and may be referenced anywhere a SQL fragment is expected.

Queries are composed via the methods on DB (Select, Insert, Update, Delete). Each builder is immutable in spirit — methods return the builder to support chaining — and ends with an executor (All, One, Exec, Returning + Scan).

Templates (Timestamps, SoftDelete, Audit, UUIDPrimaryKey) are reusable column groups that can be applied to any table — see template.go for the function-style pattern, mixin.go for the richer Mixin interface that also contributes indexes / lifecycle hooks / default filters. Libraries and applications expose their own templates by following either recipe.

Lifecycle hooks (OnInsert / OnUpdate / OnDelete on Table) let templates extend INSERT / UPDATE / DELETE statements automatically. User-supplied values always win, so hooks are safe to register on shared tables. Default scopes (Table.DefaultFilter) apply a predicate to every SELECT / UPDATE / DELETE against the table unless the caller opts out with the builder's Unscoped() method — the mechanism behind soft-delete-aware queries.

Entity[T] (see entity.go) binds a Go struct to a Table and exposes type-safe CRUD shortcuts: Get / Create / Update / Save / Delete / Query. It composes with lifecycle hooks and default scopes, so a SoftDelete mixin makes UserEntity.Delete() automatically rewrite into an UPDATE that flips deletedAt.

Entity.Validate registers per-row validators that run before Create / Update / Save. A column marked with OptimisticLock turns Update into a guarded "AND version = current, SET version = version + 1" — mismatched versions return ErrStaleObject.

Entity.SetFastScan plugs in a zero-reflection per-row scanner — typically the Scan<T> helper emitted by cmd/dropsgen. When set, Get / EntityQuery.All / EntityQuery.One skip the reflection path entirely. Eager-loaded relations (via With/WithRel) still need reflection, so the slow path kicks in automatically for those.

AutoTable[T] / NewAutoEntity[T] derive a Table (and an Entity) from extended `drop:"col,primaryKey,autoIncrement,notNull,unique,default=...,version"` struct tags — the struct becomes the single source of truth, and a working ORM falls out of one declaration:

var UserEntity = pg.NewAutoEntity[User]("users")

Entity.WithCache plugs in a drops/cache backend so Get / EntityQuery.All / EntityQuery.One are read-through and Create / Update / Save / Delete are write-invalidate. A built-in single-flight group dedupes concurrent identical PK misses so a thundering herd resolves to one DB query. Query results are keyed by sha256(SQL+args) and rely on the cache's TTL for freshness.

Index

Examples

Constants

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

attribute keys used by drops spans — kept as named constants so downstream alerts / dashboards can match them reliably.

View Source
const DefaultMigrationsTable = "_drops_migrations"

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

View Source
const DrizzleSchema = "drizzle"

DrizzleSchema is the schema where drizzle stores migration history.

View Source
const DrizzleTable = "__drizzle_migrations"

DrizzleTable is the migration history table.

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

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

Variables

View Source
var (
	// ErrReturningRequired is returned by *Builder.All / *Builder.One on
	// INSERT/UPDATE/DELETE statements that were not paired with a
	// Returning(...) clause.
	ErrReturningRequired = errors.New("drops/pg: builder.All/One requires Returning(...)")

	// ErrNoRowsToInsert is returned by InsertBuilder.Exec when no row has
	// been added via Row / Rows.
	ErrNoRowsToInsert = errors.New("drops/pg: INSERT with no rows")

	// ErrNoUpdateAssignments is returned by UpdateBuilder.Exec when no
	// Set assignment has been added.
	ErrNoUpdateAssignments = errors.New("drops/pg: UPDATE with no Set assignments")

	// ErrSchemaRequired is returned by Push when schema is nil.
	ErrSchemaRequired = errors.New("drops/pg: Schema is required")

	// ErrInvalidIdentifier is returned when a SQL identifier (table,
	// column, schema) contains characters that would break out of
	// double-quoted identifier safety (NUL, embedded double quotes).
	ErrInvalidIdentifier = errors.New("drops/pg: invalid SQL identifier")
)

Package-level sentinel errors. Callers may check them with errors.Is to branch on a specific failure mode without inspecting the message.

The decorating wrappers used by builders use fmt.Errorf("...: %w", err) so the chain stays intact when extra context is added.

View Source
var (
	// ErrUniqueViolation — SQLSTATE 23505. INSERT / UPDATE collided
	// with a UNIQUE or PRIMARY KEY constraint.
	ErrUniqueViolation = errors.New("drops/pg: unique constraint violation")

	// ErrForeignKeyViolation — SQLSTATE 23503. The referenced row
	// is missing, or a referenced row is being deleted while still
	// referenced.
	ErrForeignKeyViolation = errors.New("drops/pg: foreign-key constraint violation")

	// ErrCheckViolation — SQLSTATE 23514. A CHECK constraint
	// returned false for the supplied value.
	ErrCheckViolation = errors.New("drops/pg: check constraint violation")

	// ErrNotNullViolation — SQLSTATE 23502. A NOT NULL column
	// received NULL.
	ErrNotNullViolation = errors.New("drops/pg: not-null constraint violation")

	// ErrUndefinedTable — SQLSTATE 42P01. Query referenced an
	// unknown table.
	ErrUndefinedTable = errors.New("drops/pg: undefined table")

	// ErrUndefinedColumn — SQLSTATE 42703. Query referenced an
	// unknown column.
	ErrUndefinedColumn = errors.New("drops/pg: undefined column")

	// ErrSerializationFailure — SQLSTATE 40001. A SERIALIZABLE
	// transaction must be retried.
	ErrSerializationFailure = errors.New("drops/pg: serialization failure")

	// ErrDeadlock — SQLSTATE 40P01. The transaction was aborted
	// because it was selected as a deadlock victim.
	ErrDeadlock = errors.New("drops/pg: deadlock detected")
)

SQLSTATE-classified errors. Driver-level failures from Exec / Query are wrapped in a PgError whose Sentinel field points at the matching value below, so callers can branch with errors.Is without depending on driver-specific types:

if errors.Is(err, pg.ErrUniqueViolation) {
    return "email already taken"
}

PgError exposes the SQLSTATE code and the constraint name when the driver reports them.

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

ErrAuditTableMissing is returned when an audit operation fails because the configured table does not exist. Surfaces a clearer error than the raw "relation does not exist".

View Source
var ErrBudgetExceeded = errors.New("drops/pg: query budget exceeded")

ErrBudgetExceeded is returned when the request would breach the entity's Budget. MaxDuration produces context.DeadlineExceeded directly, which wraps to ErrBudgetExceeded via errors.Is so a single check covers every budget mode.

View Source
var ErrCiphertextTooShort = errors.New("drops/pg: ciphertext too short")

ErrCiphertextTooShort is returned when Decrypt receives bytes smaller than a nonce — typically a sign of a corrupt or truncated row.

View Source
var ErrCircularDependency = errors.New("drops/pg: MatViewManager: circular dependency")

ErrCircularDependency is returned by topological helpers when a cycle is detected. Kept as a package value so handlers can detect it without string matching.

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

ErrConcurrencyConflict is returned by Append when the expectedVersion no longer matches the stream's head — another writer beat us to that version. Wrap your callsite in a retry loop that re-reads the stream before retrying.

View Source
var ErrCopyNotSupported = errors.New("drops/pg: driver does not implement Copier — fall back to CreateMany")

ErrCopyNotSupported is returned by CopyFrom when the underlying driver does not satisfy Copier. Callers should fall back to Entity.CreateMany / UpsertMany.

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

ErrEmptyKey is returned by Run when key is the empty string. An empty key would collapse every operation onto the same row, which is almost always a bug.

View Source
var ErrListenNotSupported = errors.New("drops/pg: driver does not implement Listener")

ErrListenNotSupported is returned by Listen when the driver does not satisfy Listener — fall back to polling or wire pgx / lib/pq's listener APIs into an adapter.

View Source
var ErrLockNotAcquired = errors.New("drops/pg: advisory lock not acquired")

ErrLockNotAcquired is returned by TryWithAdvisoryLock when another holder has the lock. Distinct from a SQL error so callers can branch cleanly with errors.Is.

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

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

View Source
var ErrNoKeyring = errors.New("drops/pg: no Keyring registered; call SetKeyring first")

ErrNoKeyring signals that a Secret[T] was used without SetKeyring having been called.

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

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

View Source
var ErrNoReplicas = errors.New("drops/pg: no replicas configured")

ErrNoReplicas is returned by NewReplicated when called with a nil primary. Kept as a package-level value so handlers and config loaders can branch cleanly.

View Source
var ErrNoRows = errors.New("drops/pg: no rows in result set")

ErrNoRows is returned by One when the query produced no rows.

View Source
var ErrPKNotSet = errors.New("drops/pg: primary key field is the zero value")

ErrPKNotSet is returned by Update / Save when r's primary-key field is the zero value but the operation requires it to be set.

View Source
var ErrShardKeyMissing = errors.New("drops/pg: shard key missing; call pg.WithShardKey first")

ErrShardKeyMissing is returned when a sharded driver receives a request without a shard key on ctx. Surfacing this rather than picking a default prevents silent cross-shard data leaks.

View Source
var ErrStaleObject = errors.New("drops/pg: stale object — optimistic-lock version mismatch")

ErrStaleObject is returned by Update on an entity whose table declares an OptimisticLock column when no row matches both the PK and the supplied version — another transaction has bumped the version, or the row was deleted between read and write.

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

ErrSubjectMissing is returned when a guarded entity is invoked on a ctx without WithSubject.

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

ErrTenantMismatch is returned by Create when r carries a tenant value that disagrees with the ctx tenant. Catches the "background job stamped the wrong tenant" class of bug.

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

ErrTenantMissing is returned when an entity is scoped by tenant but ctx lacks one. Surfacing this error rather than silently running a cross-tenant query is the whole point of the feature.

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

ErrUnauthorized signals that an operation was rejected by the guard pipeline. Compared to a "no rows" outcome, this means the guard explicitly denied the request (e.g. a custom guard returned the error directly).

Functions

func Abs

func Abs(e any) drops.Expression

func Acos

func Acos(e any) drops.Expression

func ActorFrom added in v0.2.0

func ActorFrom(ctx context.Context) string

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

func AdvisoryLockKey added in v0.2.0

func AdvisoryLockKey(key string) int64

AdvisoryLockKey returns the int64 key drops would use for the supplied string. Exposed so callers can pass the hashed value directly when they want full control of the lock identifier (e.g. to match a key generated elsewhere).

func Age

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

Age renders age(<a>, <b>) — the interval between two timestamps. Pass only one timestamp to compute age(now(), ts).

func All

func All(value, array any) drops.Expression

All renders <value> = ALL(<array>).

func AllSub

func AllSub(value, sub any) drops.Expression

AllSub renders <value> = ALL(<subquery>).

func AlterEnumAddValue

func AlterEnumAddValue(enumName, value string, before, after string) drops.Expression

AlterEnumAddValue appends a new label to an existing enum (PG 9.1+). before/after are optional anchors; if both are empty the value is appended at the end.

func AlterEnumRenameValue

func AlterEnumRenameValue(enumName, oldValue, newValue string) drops.Expression

AlterEnumRenameValue renames an existing enum label (PG 10+).

func And

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

And joins the predicates with AND. With no arguments it renders TRUE.

func Any

func Any(value, array any) drops.Expression

Any renders <value> = ANY(<array>).

func AnySub

func AnySub(value, sub any) drops.Expression

AnySub renders <value> = ANY(<subquery>). Use a subquery expression (typically SelectBuilder) as the right-hand side.

func ArrayAgg

func ArrayAgg(e any) drops.Expression

func ArrayAppend

func ArrayAppend(arr, v any) drops.Expression

func ArrayConcat

func ArrayConcat(a, b any) drops.Expression

ArrayConcat renders <a> || <b>.

func ArrayContainedIn

func ArrayContainedIn(a, b any) drops.Expression

ArrayContainedIn renders <a> <@ <b>.

func ArrayContains

func ArrayContains(a, b any) drops.Expression

ArrayContains renders <a> @> <b> (array containment).

func ArrayLength

func ArrayLength(arr, dim any) drops.Expression

func ArrayLit

func ArrayLit(values ...any) drops.Expression

ArrayLit renders an ARRAY[...] literal. Values may be expressions or Go values (bound as params).

func ArrayLower

func ArrayLower(arr, dim any) drops.Expression

func ArrayOverlaps

func ArrayOverlaps(a, b any) drops.Expression

ArrayOverlaps renders <a> && <b>.

func ArrayPosition

func ArrayPosition(arr, v any) drops.Expression

func ArrayPositions

func ArrayPositions(arr, v any) drops.Expression

func ArrayPrepend

func ArrayPrepend(v, arr any) drops.Expression

func ArrayRemove

func ArrayRemove(arr, v any) drops.Expression

func ArrayReplace

func ArrayReplace(arr, oldV, newV any) drops.Expression

func ArrayToString

func ArrayToString(arr, sep any) drops.Expression

func ArrayUpper

func ArrayUpper(arr, dim any) drops.Expression

func As

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

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

func Asin

func Asin(e any) drops.Expression

func AtTimeZone

func AtTimeZone(ts, zone any) drops.Expression

AtTimeZone renders <ts> AT TIME ZONE <zone>.

func Atan

func Atan(e any) drops.Expression

func Avg

func AvgDistinct

func AvgDistinct(e drops.Expression) drops.Expression

func Between

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

Between renders "left BETWEEN low AND high".

func BoolAnd

func BoolAnd(e any) drops.Expression

BoolAnd / BoolOr aggregates.

func BoolOr

func BoolOr(e any) drops.Expression

func Cardinality

func Cardinality(arr any) drops.Expression

func Cast

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

Cast renders <e>::<type> — the PostgreSQL shorthand for explicit type conversion. Equivalent to CAST(<e> AS <type>).

func CastAs

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

CastAs renders CAST(<e> AS <type>) — the standard-SQL form.

func Ceil

func Ceil(e any) drops.Expression

func CharLength

func CharLength(e any) drops.Expression

CharLength renders char_length(<e>).

func Coalesce

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

Coalesce renders coalesce(<args...>). Arguments may be Expressions or Go values (bound as parameters).

func ColumnExists

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

ColumnExists reports whether a column exists on a table. schema may be empty to mean "public".

func CommentOnColumn

func CommentOnColumn(c ColRef, text string) drops.Expression

CommentOnColumn returns COMMENT ON COLUMN <t>.<c> IS 'text'.

func CommentOnTable

func CommentOnTable(t *Table, text string) drops.Expression

CommentOnTable returns COMMENT ON TABLE <t> IS 'text'. text must not contain unsanitised single quotes.

func Concat

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

Concat renders concat(args...).

func ConcatOp

func ConcatOp(left, right any) drops.Expression

ConcatOp renders the SQL || concatenation operator: (a || b).

func ConcatWS

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

ConcatWS renders concat_ws(sep, args...).

func ConstraintExists

func ConstraintExists(ctx context.Context, db *DB, schema, table, constraint string) (bool, error)

ConstraintExists reports whether a named constraint exists on a table. schema may be empty to mean "public".

func CopyFrom added in v0.2.0

func CopyFrom[T any](db *DB, ctx context.Context, ent *Entity[T], rs []T) (int64, error)

CopyFrom bulk-loads rs into the entity's table via the driver's COPY path. Returns the number of rows accepted. Validators run per row before any bytes hit the wire — bad input aborts the whole batch before the server is touched.

Bypasses the cache (COPY-loaded rows never populate it), the audit log (audit per row would defeat the bandwidth advantage), and the per-row INSERT hooks. Use Entity.CreateMany / UpsertMany when those guarantees matter; use CopyFrom when raw throughput is the point.

func Cos

func Cos(e any) drops.Expression

func CosineDistance

func CosineDistance(left, right any) drops.Expression

CosineDistance renders <col> <=> <vec>.

func Count

Count renders count(<e>).

func CountAll

func CountAll() drops.Expression

CountAll renders count(*).

func CountDistinct

func CountDistinct(e drops.Expression) drops.Expression

CountDistinct renders count(DISTINCT <e>).

func CreateEnum

func CreateEnum(e *PgEnum) drops.Expression

CreateEnum returns a CREATE TYPE ... AS ENUM (...) statement.

func CreateExtension

func CreateExtension(name string) drops.Expression

CreateExtension returns CREATE EXTENSION "name". Common values: "pgcrypto", "uuid-ossp", "postgis", "pg_trgm".

func CreateExtensionIfNotExists

func CreateExtensionIfNotExists(name string) drops.Expression

CreateExtensionIfNotExists is the IF NOT EXISTS variant.

func CreateFunction

func CreateFunction(name string, opts FunctionOptions) drops.Expression

CreateFunction returns a CREATE FUNCTION statement. The body is wrapped in $func$ delimiters so it may contain arbitrary single quotes.

func CreateIndex

func CreateIndex(idx *Index) drops.Expression

CreateIndex returns the CREATE INDEX statement for idx.

func CreateIndexIfNotExists

func CreateIndexIfNotExists(idx *Index) drops.Expression

CreateIndexIfNotExists is the IF NOT EXISTS variant.

func CreateIndexOnline added in v0.2.0

func CreateIndexOnline(idx *Index) drops.Expression

CreateIndexOnline marks idx as CONCURRENTLY and returns the CREATE INDEX expression. Convenience for online migrations:

stmt := pg.CreateIndexOnline(myIdx)
_, err := db.ExecExpr(ctx, stmt)

PostgreSQL forbids CONCURRENTLY inside a transaction, so the caller must invoke it outside InTx — the analyzer will not complain about CONCURRENTLY statements, but a transaction containing one will fail at the database.

func CreateMaterializedView

func CreateMaterializedView(name string, query drops.Expression, withData bool) drops.Expression

CreateMaterializedView returns CREATE MATERIALIZED VIEW. withData=false emits WITH NO DATA so the view is empty until first refreshed.

func CreateOrReplaceView

func CreateOrReplaceView(name string, query drops.Expression) drops.Expression

CreateOrReplaceView returns CREATE OR REPLACE VIEW ...

func CreateSchema

func CreateSchema(name string) drops.Expression

CreateSchema returns CREATE SCHEMA "name".

func CreateSchemaIfNotExists

func CreateSchemaIfNotExists(name string) drops.Expression

CreateSchemaIfNotExists is the IF NOT EXISTS variant.

func CreateSequence

func CreateSequence(name string, opts ...SequenceOptions) drops.Expression

CreateSequence returns a CREATE SEQUENCE statement.

func CreateSequenceIfNotExists

func CreateSequenceIfNotExists(name string, opts ...SequenceOptions) drops.Expression

CreateSequenceIfNotExists is the IF NOT EXISTS variant.

func CreateTable

func CreateTable(t *Table) drops.Expression

CreateTable returns a CREATE TABLE statement for t.

The generated SQL covers column types, NOT NULL, DEFAULT, UNIQUE, PRIMARY KEY (single-column only), and inline FOREIGN KEY references. More elaborate DDL — composite keys, indexes, partitioning — is out of scope; emit it via raw SQL.

func CreateTableIfNotExists

func CreateTableIfNotExists(t *Table) drops.Expression

CreateTableIfNotExists is the IF NOT EXISTS variant.

func CreateTrigger

func CreateTrigger(name string, opts TriggerOptions) drops.Expression

CreateTrigger returns a CREATE TRIGGER statement.

func CreateView

func CreateView(name string, query drops.Expression) drops.Expression

CreateView returns CREATE VIEW "name" AS <select>.

func CumeDist

func CumeDist() drops.Expression

func CurrVal

func CurrVal(name string) drops.Expression

CurrVal returns currval('"name"').

func CurrentDate

func CurrentDate() drops.Expression

CurrentDate renders current_date.

func CurrentTime

func CurrentTime() drops.Expression

CurrentTime renders current_time.

func CurrentTimestamp

func CurrentTimestamp() drops.Expression

CurrentTimestamp renders current_timestamp.

func DatePart

func DatePart(field string, ts any) drops.Expression

DatePart renders date_part('field', <ts>) — the function form of EXTRACT.

func DateTrunc

func DateTrunc(field string, ts any) drops.Expression

DateTrunc renders date_trunc('field', <ts>) — e.g. DateTrunc("day", col).

func Day

func Day(n int) drops.Expression

Day / Hour / Minute / Second build INTERVAL literals from numeric n.

func Decode

func Decode(text, format any) drops.Expression

Decode renders decode(<text>, <format>).

func DenseRank

func DenseRank() drops.Expression

func Diff

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

Diff returns the ordered list of SQL statements that, applied in order, evolve a database from prev's schema to cur's. Output is deterministic for a given (prev, cur, opts) — keys are walked in sorted order — so re-running against the same input produces byte-identical SQL.

Operation order:

  1. DROP TABLE for tables removed entirely
  2. CREATE TABLE for new tables (column defs + inline UNIQUE only)
  3. ALTER TABLE for column-level changes on surviving tables (drop, add, type, NOT NULL, DEFAULT)
  4. UNIQUE constraint adds/drops on surviving tables
  5. FOREIGN KEY adds/drops on every table — emitted after CREATE TABLEs so cross-table references resolve.

func DiffDown added in v0.2.0

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

DiffDown returns the SQL that reverses the migration from cur back to prev — applying these statements after the corresponding Diff(prev, cur) would restore the original schema. Provided as a distinct entry point so generated migration sets can carry the rollback alongside the forward direction without the caller having to swap arguments.

up := pg.Diff(prev, cur, opts)
down := pg.DiffDown(prev, cur, opts) // = Diff(cur, prev, opts)

func DistanceFrom added in v0.2.0

func DistanceFrom(col ColRef, p Point) drops.Expression

DistanceFrom renders ST_Distance(col, point) — distance in metres when col is a geography column. Suitable for SELECT projections and ORDER BY:

q.OrderBy(pg.DistanceFrom(DriversTable.Col("position"), userLoc))

For nearest-N queries that should use the spatial KNN index, prefer NearestFrom (which emits the <-> operator).

func Div

func Div(left, right any) drops.Expression

func DropEnum

func DropEnum(name string) drops.Expression

DropEnum returns DROP TYPE "name".

func DropEnumIfExists

func DropEnumIfExists(name string) drops.Expression

DropEnumIfExists is the IF EXISTS variant.

func DropExtension

func DropExtension(name string) drops.Expression

DropExtension returns DROP EXTENSION "name".

func DropExtensionIfExists

func DropExtensionIfExists(name string) drops.Expression

DropExtensionIfExists is the IF EXISTS variant.

func DropFunction

func DropFunction(name, args string) drops.Expression

DropFunction returns DROP FUNCTION "name"(args). args is the raw argument-types signature, e.g. "integer, integer".

func DropFunctionIfExists

func DropFunctionIfExists(name, args string) drops.Expression

DropFunctionIfExists is the IF EXISTS variant.

func DropIndex

func DropIndex(name string) drops.Expression

DropIndex returns DROP INDEX "name".

func DropIndexConcurrently

func DropIndexConcurrently(name string) drops.Expression

DropIndexConcurrently emits DROP INDEX CONCURRENTLY.

func DropIndexIfExists

func DropIndexIfExists(name string) drops.Expression

DropIndexIfExists is the IF EXISTS variant.

func DropMaterializedView

func DropMaterializedView(name string) drops.Expression

DropMaterializedView returns DROP MATERIALIZED VIEW "name".

func DropSchema

func DropSchema(name string) drops.Expression

DropSchema returns DROP SCHEMA "name".

func DropSchemaIfExists

func DropSchemaIfExists(name string, cascade bool) drops.Expression

DropSchemaIfExists is the IF EXISTS variant. Cascade=true appends CASCADE so dependent objects are dropped too.

func DropSequence

func DropSequence(name string) drops.Expression

DropSequence returns DROP SEQUENCE "name".

func DropSequenceIfExists

func DropSequenceIfExists(name string) drops.Expression

DropSequenceIfExists is the IF EXISTS variant.

func DropTable

func DropTable(t *Table) drops.Expression

DropTable returns a DROP TABLE statement.

func DropTableIfExists

func DropTableIfExists(t *Table) drops.Expression

DropTableIfExists is the IF EXISTS variant of DropTable.

func DropTrigger

func DropTrigger(name string, table *Table) drops.Expression

DropTrigger returns DROP TRIGGER "name" ON <table>.

func DropTriggerIfExists

func DropTriggerIfExists(name string, table *Table) drops.Expression

DropTriggerIfExists is the IF EXISTS variant.

func DropView

func DropView(name string) drops.Expression

DropView returns DROP VIEW "name".

func DropViewIfExists

func DropViewIfExists(name string) drops.Expression

DropViewIfExists is the IF EXISTS variant.

func Encode

func Encode(bytea, format any) drops.Expression

Encode renders encode(<bytea>, <format>) — format is 'base64', 'hex', or 'escape'.

func Eq

func Eq(left, right any) drops.Expression

func Every

func Every(e any) drops.Expression

Every is the standard-SQL alias for bool_and.

func Excluded

func Excluded(c ColRef) drops.Expression

Excluded refers to a column in the EXCLUDED pseudo-table inside an ON CONFLICT update — i.e. the value that would have been inserted. Accepts either *Column or *Col[T] via the ColRef interface.

func Exists

func Exists(q drops.Expression) drops.Expression

Exists renders EXISTS (<subquery>).

func Exp

func Exp(e any) drops.Expression

func ExponentialJitter added in v0.2.0

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

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

attempt 1: base + jitter
attempt 2: 2*base + jitter
attempt 3: 4*base + jitter
...
clipped at max + jitter

func Extract

func Extract(field string, ts any) drops.Expression

Extract renders extract(<field> FROM <ts>).

func Filter

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

Filter wraps an aggregate with a FILTER (WHERE ...) clause:

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

func FirstValue

func FirstValue(expr any) drops.Expression

func Floor

func Floor(e any) drops.Expression

func ForEachShard added in v0.2.0

func ForEachShard(s *Sharded, ctx context.Context, fn func(shardIdx int, drv drops.Driver) error) []error

ForEachShard runs fn against every shard concurrently — the "scatter" half of scatter/gather. Use for cross-shard reads / admin tasks; results are aggregated by the caller. Errors are collected and returned per-shard.

The supplied ctx is passed to fn unchanged; if fn needs the shard-bound *DB it should call pg.New(drv) inside.

func Format

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

Format renders format(<fmt>, args...).

func FormatLSN added in v0.2.0

func FormatLSN(lsn uint64) string

FormatLSN renders an LSN back into PG's "X/Y" text shape — the inverse of ParseLSN.

func Func

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

Func renders an arbitrary function call <name>(<args...>). Use it as an escape hatch when a built-in helper isn't provided.

func Greatest

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

Greatest renders greatest(args...).

func Gt

func Gt(left, right any) drops.Expression

func Gte

func Gte(left, right any) drops.Expression

func HammingDistance

func HammingDistance(left, right any) drops.Expression

HammingDistance renders <col> <~> <vec> — bit-vector Hamming distance (BitVec columns only).

func HasDangerousMigration added in v0.2.0

func HasDangerousMigration(stmts []string) bool

HasDangerousMigration reports whether stmts contains at least one RiskDanger finding. Suitable as a CI gate.

func HashShardKey added in v0.2.0

func HashShardKey(n int) func(key any) int

HashShardKey is a convenience picker for string / []byte keys. Uses FNV-1a (the same hash as advisory locks) and modulo N.

sharded := pg.NewSharded(shards, pg.HashShardKey(len(shards)))

func Hour

func Hour(n int) drops.Expression

func ILike

func ILike(left, pattern any) drops.Expression

func In

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).

func Initcap

func Initcap(e any) drops.Expression

Initcap renders initcap(<e>).

func InnerProduct

func InnerProduct(left, right any) drops.Expression

InnerProduct renders <col> <#> <vec> — pgvector's negative inner product. Smaller is better; for raw inner product, negate the expression or use the cosine helper if your vectors are normalised.

func InstallChangeFeed added in v0.2.0

func InstallChangeFeed(t *Table, opts ...ChangeFeedOptions) ([]string, error)

InstallChangeFeed returns the DDL statements that wire a NOTIFY trigger onto t. The trigger fires AFTER INSERT/UPDATE/DELETE and emits a JSON payload of {"op": ..., "id": ...} on the channel.

Single-column primary keys only — composite PKs would need a different payload shape and are not yet supported.

func IntervalLit

func IntervalLit(literal string) drops.Expression

IntervalLit renders an INTERVAL literal — e.g. IntervalLit("1 day"), IntervalLit("2 hours 30 minutes"). The value is wrapped in INTERVAL '...' with single quotes doubled.

Named with "Lit" to avoid a collision with the Interval(name) column type constructor in types.go.

func IsNotNull

func IsNotNull(e any) drops.Expression

func IsNull

func IsNull(e any) drops.Expression

func IsPII added in v0.2.0

func IsPII(v any) bool

IsPII returns true when v carries the redaction marker.

func IsSagaError added in v0.2.0

func IsSagaError(err error) bool

IsSagaError reports whether err is a *SagaError.

func JSONAgg

func JSONAgg(e any) drops.Expression

JSONAgg / JSONBAgg are aggregates.

func JSONArrayLength

func JSONArrayLength(e any) drops.Expression

func JSONBAgg

func JSONBAgg(e any) drops.Expression

func JSONBArrayLength

func JSONBArrayLength(e any) drops.Expression

func JSONBBuildArray

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

func JSONBBuildObject

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

JSONBBuildObject / JSONBBuildArray are the jsonb variants.

func JSONBConcat

func JSONBConcat(a, b any) drops.Expression

JSONBConcat renders <a> || <b>.

func JSONBContainedIn

func JSONBContainedIn(a, b any) drops.Expression

JSONBContainedIn renders <a> <@ <b>.

func JSONBContains

func JSONBContains(a, b any) drops.Expression

JSONBContains renders <a> @> <b> (does a contain b? jsonb only).

func JSONBDelete

func JSONBDelete(e, key any) drops.Expression

JSONBDelete renders <e> - <key>.

func JSONBHasAllKeys

func JSONBHasAllKeys(e, keys any) drops.Expression

JSONBHasAllKeys renders <e> ?& <keys>.

func JSONBHasAnyKey

func JSONBHasAnyKey(e, keys any) drops.Expression

JSONBHasAnyKey renders <e> ?| <keys> (keys is text[]).

func JSONBHasKey

func JSONBHasKey(e, key any) drops.Expression

JSONBHasKey renders <e> ? <key>.

func JSONBInsert

func JSONBInsert(target, path, newVal any, insertAfter ...bool) drops.Expression

JSONBInsert renders jsonb_insert(<target>, <path>, <newVal>, [<insertAfter>]).

func JSONBObjectAgg

func JSONBObjectAgg(k, v any) drops.Expression

func JSONBPretty

func JSONBPretty(e any) drops.Expression

JSONBPretty renders jsonb_pretty(<e>).

func JSONBSet

func JSONBSet(target, path, value any, createMissing ...bool) drops.Expression

JSONBSet renders jsonb_set(<target>, <path>, <value>, [<createMissing>]).

func JSONBStripNulls

func JSONBStripNulls(e any) drops.Expression

JSONBStripNulls renders jsonb_strip_nulls(<e>).

func JSONBTypeof

func JSONBTypeof(e any) drops.Expression

func JSONBuildArray

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

JSONBuildArray renders json_build_array(args...).

func JSONBuildObject

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

JSONBuildObject renders json_build_object(args...). Pairs are key/value: JSONBuildObject("name", UserName, "age", UserAge).

func JSONContains added in v0.2.0

func JSONContains(col ColRef, value any) drops.Expression

JSONContains renders "col @> $1" — the jsonb containment operator. Accepts anything that drivers can serialise as jsonb (json.RawMessage, []byte, or a marshaled struct value).

func JSONGet

func JSONGet(e, key any) drops.Expression

JSONGet renders <e> -> <key> (JSON object/array element, returns json). key may be a string (object key) or int (array index).

func JSONGetPath added in v0.2.0

func JSONGetPath(e, path any) drops.Expression

JSONGetPath renders <e> #> <path> (path is text[] — pass a Go []string). Renamed from JSONPath to free that name for the typed accessor in jsonpath.go.

func JSONGetPathText added in v0.2.0

func JSONGetPathText(e, path any) drops.Expression

JSONGetPathText renders <e> #>> <path>.

func JSONGetText

func JSONGetText(e, key any) drops.Expression

JSONGetText renders <e> ->> <key> (as text).

func JSONHasAllKeys added in v0.2.0

func JSONHasAllKeys(col ColRef, keys []string) drops.Expression

JSONHasAllKeys renders "col ?& $1" — true only when every key in keys is present at the top level.

func JSONHasAnyKey added in v0.2.0

func JSONHasAnyKey(col ColRef, keys []string) drops.Expression

JSONHasAnyKey renders "col ?| $1" — true when any of keys is present at the jsonb top level.

func JSONHasKey added in v0.2.0

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

JSONHasKey renders "col ? $1" — the jsonb key-existence operator.

func JSONObjectAgg

func JSONObjectAgg(k, v any) drops.Expression

JSONObjectAgg / JSONBObjectAgg.

func JSONTypeof

func JSONTypeof(e any) drops.Expression

func JaccardDistance

func JaccardDistance(left, right any) drops.Expression

JaccardDistance renders <col> <%> <vec> — bit-vector Jaccard distance.

func L1Distance

func L1Distance(left, right any) drops.Expression

L1Distance renders <col> <+> <vec> — Manhattan distance.

func L2Distance

func L2Distance(left, right any) drops.Expression

L2Distance renders <col> <-> <vec> — Euclidean distance. The right-hand side is typically a Go []float32 (bound as a param) or another vector column.

func LTrim

func LTrim(e any) drops.Expression

LTrim renders ltrim(<e>).

func Lag

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

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

func LastValue

func LastValue(expr any) drops.Expression

func Lead

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

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

func Least

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

Least renders least(args...).

func Length

func Length(e any) drops.Expression

Length renders length(<e>).

func Like

func Like(left, pattern any) drops.Expression

func Listen added in v0.2.0

func Listen[T any](db *DB, ctx context.Context, channel string) (<-chan T, error)

Listen subscribes to channel and returns a typed channel that receives decoded T values. Payloads that fail JSON decoding are silently dropped — a misaligned producer is a deployment bug, not a per-message error.

The returned channel closes when ctx is done or the driver's underlying subscription terminates. Spawn one goroutine per channel; combine with select to multiplex many channels.

func Ln

func Ln(e any) drops.Expression

func LocalTime

func LocalTime() drops.Expression

LocalTime / LocalTimestamp (without time zone).

func LocalTimestamp

func LocalTimestamp() drops.Expression

func Log

func Log(e any) drops.Expression

func LoggerHook

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

LoggerHook re-exports drops.LoggerHook. See its documentation for behaviour and tuning options.

func Lower

Lower / Upper case-folding helpers.

func Lt

func Lt(left, right any) drops.Expression

func Lte

func Lte(left, right any) drops.Expression

func MakeDate

func MakeDate(y, m, d any) drops.Expression

MakeDate renders make_date(<y>, <m>, <d>).

func MakeTime

func MakeTime(h, m, s any) drops.Expression

MakeTime renders make_time(<h>, <m>, <s>).

func MakeTimestamp

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

MakeTimestamp renders make_timestamp(y, mo, d, h, mi, s).

func MakeTimestampTZ

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

MakeTimestampTZ is the TZ-aware variant.

func Max

func Md5

func Md5(e any) drops.Expression

Md5 renders md5(<e>).

func MermaidDiagram added in v0.2.0

func MermaidDiagram(s *Schema) string

MermaidDiagram renders the schema as a Mermaid `erDiagram` block. Pipe it through any Mermaid-aware renderer (web preview tools, Markdown viewers, IDE previews) for an up-to-date ER diagram driven directly by the table declarations.

fmt.Println(pg.MermaidDiagram(pg.NewSchema(Users, Posts)))
// erDiagram
//   USERS {
//     bigserial id PK
//     text name
//   }
//   POSTS {
//     bigserial id PK
//     bigint userId FK
//   }
//   USERS ||--o{ POSTS : posts

Cardinality marks come from the registered Relation metadata:

HasMany     →  ||--o{
HasOne      →  ||--o|
BelongsTo   →  rendered from the parent side as the inverse
ManyToMany  →  }o--o{   (with the junction omitted for clarity)
MorphTo     →  rendered as one edge per registered morph entry

func Min

func Minus

func Minus(left, right any) drops.Expression

func Minute

func Minute(n int) drops.Expression

func Mod

func Mod(a, b any) drops.Expression

func Month

func Month(n int) drops.Expression

func Mul

func Mul(left, right any) drops.Expression

func N1Hook added in v0.2.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

func Ne(left, right any) drops.Expression

func NearestFrom added in v0.2.0

func NearestFrom(col ColRef, p Point) drops.Expression

NearestFrom emits the `<->` operator (KNN distance) which PostgreSQL can plan against a GiST or SP-GiST index on col. Use as an ORDER BY term; the result is ordered by closest-first.

func NextVal

func NextVal(name string) drops.Expression

NextVal returns the SQL expression nextval('"name"'::regclass).

func Not

Not negates a predicate.

func NotExists

func NotExists(q drops.Expression) drops.Expression

NotExists renders NOT EXISTS (<subquery>).

func NotIn

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

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

func Notify added in v0.2.0

func Notify(db *DB, ctx context.Context, channel string, payload any) error

Notify publishes payload on channel. The payload is JSON-encoded before being handed to pg_notify, so any encodable value works — string, struct, json.RawMessage. Empty channel names error at the call site rather than at the database.

PostgreSQL caps NOTIFY payloads at 8000 bytes; anything larger either truncates or errors depending on driver. For larger payloads emit a row id and have the consumer fetch the row.

func Now

func Now() drops.Expression

Now renders now().

func NthValue

func NthValue(expr, n any) drops.Expression

func Ntile

func Ntile(n any) drops.Expression

func OnDelete

func OnDelete(action string) func(*FK)

OnDelete configures the referential action for ON DELETE.

func OnUpdate

func OnUpdate(action string) func(*FK)

OnUpdate configures the referential action for ON UPDATE.

func Or

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

Or joins the predicates with OR. With no arguments it renders FALSE.

func Over

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

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

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

func PII added in v0.2.0

func PII(value any) any

PII wraps value so it travels through drops as a redaction marker. Use it in raw queries when you don't have an Entity column to tag:

db.Exec(ctx, "UPDATE users SET password = $1 WHERE id = $2",
    pg.PII(hashedPw), id)

func ParseLSN added in v0.2.0

func ParseLSN(s string) (uint64, error)

ParseLSN converts PG's "X/Y" hex-pair LSN text format into the underlying byte position. Returns an error on malformed input. Exposed so tests and tooling can build LSN values without going through the driver.

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

func PercentRank() drops.Expression

func Plus

func Plus(left, right any) drops.Expression

Plus, Minus, Mul, Div: arithmetic operators as parenthesised binary expressions. Useful for column-arithmetic in SELECT lists and updates.

func Position

func Position(substring, str any) drops.Expression

Position renders position(<substring> IN <string>).

func Power

func Power(a, b any) drops.Expression

func RTrim

func RTrim(e any) drops.Expression

RTrim renders rtrim(<e>).

func Random

func Random() drops.Expression

Random renders random().

func Rank

func Rank() drops.Expression

func RefreshMaterializedView

func RefreshMaterializedView(name string, concurrently bool) drops.Expression

RefreshMaterializedView returns REFRESH MATERIALIZED VIEW. concurrently emits the CONCURRENTLY keyword (PG 9.4+) which requires a UNIQUE index.

func RegexpMatch

func RegexpMatch(e, pattern any) drops.Expression

RegexpMatch renders regexp_match(<e>, <pattern>).

func RegexpReplace

func RegexpReplace(e, pattern, replacement any, flags ...string) drops.Expression

RegexpReplace renders regexp_replace(<e>, <pattern>, <replacement>, [<flags>]).

func Replace

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

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

func Round

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

Round renders round(<e>) or round(<e>, <digits>) when digits is non-nil.

func RowNumber

func RowNumber() drops.Expression

func RunJSON added in v0.2.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. fn returns a typed result; Run handles the serialisation. Subsequent calls unmarshal the cached bytes back into T.

func SagaStateGet added in v0.2.0

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

SagaStateGet is the type-safe getter. Returns the zero value of T plus ok=false when the key is missing OR the stored value is the wrong type.

func ScanAll added in v0.2.0

func ScanAll(rows drops.Rows, dest any) error

ScanAll consumes every row from rows into dest (pointer to slice of struct or *struct). Exported for the same reason as ScanOne.

func ScanOne added in v0.2.0

func ScanOne(rows drops.Rows, dest any) error

ScanOne consumes the first row from rows into dest (pointer to struct), returning ErrNoRows when the cursor is empty. Exported for code generators (cmd/dropsgen sql) that want the reflection scanner without going through a builder.

func SchemaExists

func SchemaExists(ctx context.Context, db *DB, schema string) (bool, error)

SchemaExists reports whether the named PostgreSQL schema exists.

func Second

func Second(n int) drops.Expression

func SetKeyring added in v0.2.0

func SetKeyring(k Keyring)

SetKeyring registers the active keyring used by Secret[T] for (de)serialisation. Call once at startup, before issuing the first query. Passing nil clears the keyring — subsequent Secret[T].Value / Scan calls then return ErrNoKeyring.

func SetVal

func SetVal(name string, value any) drops.Expression

SetVal returns setval('"name"', value).

func ShardKeyFrom added in v0.2.0

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

ShardKeyFrom returns the shard key stored on ctx, if any.

func Sign

func Sign(e any) drops.Expression

func Sin

func Sin(e any) drops.Expression

Trigonometric helpers — pass radians.

func Sqrt

func Sqrt(e any) drops.Expression

func StrPos

func StrPos(str, substring any) drops.Expression

StrPos renders strpos(<string>, <substring>).

func StringAgg

func StringAgg(e, sep any) drops.Expression

StringAgg renders string_agg(<e>, <sep>).

func StringToArray

func StringToArray(str, sep any) drops.Expression

func SubjectFrom added in v0.2.0

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

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

func Subquery

func Subquery(q drops.Expression) drops.Expression

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

func Subscribe added in v0.2.0

func Subscribe[T any](db *DB, ctx context.Context, e *Entity[T], opts ...SubscribeOptions) (<-chan TypedChange[T], error)

Subscribe returns a typed channel of change events for the entity's table. The channel closes when ctx is done or the driver's underlying LISTEN terminates.

drops dispatches via the Listener interface (see listen.go). Drivers that don't expose listen return ErrListenNotSupported.

func Substring

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

Substring renders substring(<e> FROM <from> FOR <count>). count may be nil to omit the FOR clause.

func Sum

Sum / Avg / Min / Max aggregates.

func SumDistinct

func SumDistinct(e drops.Expression) drops.Expression

SumDistinct / AvgDistinct apply DISTINCT to the aggregate.

func SupportsCopy added in v0.2.0

func SupportsCopy(db *DB) bool

SupportsCopy reports whether the driver behind db exposes the Copier interface. Useful for code paths that want to choose between CopyFrom and CreateMany at runtime.

func SupportsListen added in v0.2.0

func SupportsListen(db *DB) bool

SupportsListen reports whether db's driver implements Listener. Useful for code paths that want to choose between Listen-based push and polling.

func SupportsPoolStats added in v0.2.0

func SupportsPoolStats(db *DB) bool

SupportsPoolStats reports whether the underlying driver implements PoolStatsProvider. Useful for code paths that want to register metrics only when introspection is available.

func TableExists

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

TableExists reports whether a table exists in the given schema. schema may be empty to mean "public".

func Tan

func Tan(e any) drops.Expression

func TenantFrom added in v0.2.0

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

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

func TestTx added in v0.2.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.

pg.TestTx(t, db, ctx, func(tx *pg.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 ToChar

func ToChar(value, pattern any) drops.Expression

ToChar renders to_char(<value>, <pattern>).

func ToDate

func ToDate(text, pattern any) drops.Expression

ToDate / ToTimestamp / ToNumber — text conversion helpers.

func ToJSON

func ToJSON(e any) drops.Expression

func ToJSONB

func ToJSONB(e any) drops.Expression

func ToNumber

func ToNumber(text, pattern any) drops.Expression

func ToTimestamp

func ToTimestamp(text, pattern any) drops.Expression

func Trim

func Trim(e any) drops.Expression

Trim renders trim(<e>).

func TryWithAdvisoryLock added in v0.2.0

func TryWithAdvisoryLock(db *DB, ctx context.Context, key string, fn func(*DB) error) error

TryWithAdvisoryLock is the non-blocking variant: returns ErrLockNotAcquired when someone else holds the lock. Useful for "elected leader per tick" patterns where missing the election is better than waiting.

func UninstallChangeFeed added in v0.2.0

func UninstallChangeFeed(t *Table) []string

UninstallChangeFeed returns the DDL to remove a previously- installed feed on t.

func Unnest

func Unnest(arr any) drops.Expression

func Upper

func Week

func Week(n int) drops.Expression

func WithActor added in v0.2.0

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

WithActor annotates ctx with an actor identifier. Pass anything that fmt prints sensibly (string id, int64 user id, struct implementing Stringer).

func WithAdvisoryLock added in v0.2.0

func WithAdvisoryLock(db *DB, ctx context.Context, key string, fn func(*DB) error) error

WithAdvisoryLock opens a transaction, takes the lock keyed by key (FNV-hashed to int64), runs fn against the tx, and releases the lock when the tx ends. Blocks until the lock is free or ctx is cancelled.

func WithN1Detector added in v0.2.0

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

WithN1Detector returns a derived context that records every SQL statement issued through pg.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 WithReadYourWrites added in v0.2.0

func WithReadYourWrites(ctx context.Context, d time.Duration) context.Context

WithReadYourWrites annotates ctx with a sticky read-your-writes window. The window arms automatically on the first Exec / Replicated.Exec through this context and persists for d after every subsequent write. Reads done within the window go to the primary so callers always observe their own writes.

Pass d=0 to clear the window from a derived context.

func WithShardKey added in v0.2.0

func WithShardKey(ctx context.Context, key any) context.Context

WithShardKey annotates ctx with the value the picker uses to choose a shard. Typically the user id, customer id, or geographic region.

func WithSubject added in v0.2.0

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

WithSubject annotates ctx with the acting subject — typically the user id of the caller. Distinct from WithActor (which records "who did this" for audit): an admin impersonating a user has the user as subject (so authz checks apply to the user) but the admin as actor (so the audit trail captures the real actor).

func WithTenant added in v0.2.0

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

WithTenant returns a context that carries tenant. Pass anything drivers can bind — a string id, int64 user id, UUID, or struct implementing driver.Valuer.

func Within added in v0.2.0

func Within(col ColRef, box Box) drops.Expression

Within renders ST_Within(col, ST_MakeEnvelope(...)::geography), the canonical "is point inside box" predicate. The box is implicit-cast to geography so the result respects the SRID declared on col.

func WithinRadius added in v0.2.0

func WithinRadius(col ColRef, p Point, metres float64) drops.Expression

WithinRadius renders ST_DWithin(col, point, metres) — true when col is within metres of point. Uses the spherical / spheroidal distance for geography columns, so the result matches the canonical "X km radius" semantics.

func Year

func Year(n int) drops.Expression

Types

type AggregateRef added in v0.2.0

type AggregateRef struct {
	Type string
	ID   string
}

AggregateRef identifies an aggregate that has pending outbox work.

type AggregateSnapshot added in v0.2.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 specific version so reads avoid replaying every event since the beginning of time. Optional — small aggregates can replay fast enough without.

type AuditCols added in v0.2.0

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

AuditCols holds the typed handles created by Audit. The type parameter mirrors the referenced PK column.

func Audit added in v0.2.0

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

Audit appends nullable "createdBy" and "updatedBy" columns to t and declares foreign keys against target — typically a users.id PK. The referencing columns' SQL type is derived from target; serial-family types are mapped to their underlying integer type (bigserial→bigint, serial→integer, smallserial→smallint).

type AuditEvent added in v0.2.0

type AuditEvent struct {
	Entity  string          // table name
	Op      string          // "create" | "update" | "delete"
	PK      json.RawMessage // pk value, JSON-encoded
	Payload json.RawMessage // row snapshot (Create / Update only)
	Actor   string          // from ctx via WithActor, "" if absent
}

AuditEvent is the row written per mutation.

type AuditLog added in v0.2.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 — required for any compliance regime where audit trails must be authoritative.

audit := pg.NewAuditLog(db, "audit_events")
pg.WithAudit(UserEntity, audit)
pg.WithAudit(PostEntity, audit)

The actor is read from ctx via pg.WithActor:

ctx = pg.WithActor(ctx, currentUserID)
UserEntity.Update(db, ctx, &u)
// inserts an audit row: (entity=users, op=update,
//                       pk=42, payload={...},
//                       actor=currentUserID, createdAt=now())

func NewAuditLog added in v0.2.0

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

NewAuditLog binds the log to db and a destination table. Pair with NewAuditTable() to provision matching DDL.

func (*AuditLog) Record added in v0.2.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.2.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 to the rest of the application. Audit information is populated by the caller (e.g. via context-aware middleware); the mixin itself does not infer the current user.

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

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

Apply implements Mixin.

type Backfill added in v0.2.0

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

Backfill orchestrates the chunk loop.

func NewBackfill added in v0.2.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 process restarts; choose something descriptive ("playerRegionFill-2026Q2") so the entry in the state table is self-documenting.

func (*Backfill) ChunkSize added in v0.2.0

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

ChunkSize sets the per-chunk row cap. Default 1000. Larger chunks finish faster but hold locks longer and inflate WAL replication volume; smaller chunks are gentler but extend wall-clock time.

func (*Backfill) Fetch added in v0.2.0

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

Fetch installs the chunk-fetching callback. It receives the last processed ID and a limit; returns the next batch of IDs, the new last-ID (typically max(ids)), and any error. Return an empty slice to signal completion.

func (*Backfill) OnProgress added in v0.2.0

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

OnProgress wires a progress callback fired after each successful chunk commit. The second argument is the latest processed ID so callers can render an ETA against the table's max(id).

func (*Backfill) PauseIfLag added in v0.2.0

func (b *Backfill) PauseIfLag(repl *Replicated, thresholdBytes uint64) *Backfill

PauseIfLag installs a gate that delays the next chunk while the configured replica set's lag exceeds threshold bytes. Pairs with Replicated.WithLSNTracking — the gate reuses the same primary / replica handles.

func (*Backfill) Process added in v0.2.0

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

Process installs the per-chunk worker. The callback runs inside a transaction so partial-chunk failures roll back cleanly.

func (*Backfill) Reset added in v0.2.0

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

Reset clears persisted state for the job. Run once before re-executing a completed backfill from scratch.

func (*Backfill) Run added in v0.2.0

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

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

func (*Backfill) StateTable added in v0.2.0

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

StateTable overrides the state table name. Default "backfillJobs".

func (*Backfill) Status added in v0.2.0

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

Status loads the current persisted state. Returns a zero status with LastID=0 when the job has never run.

func (*Backfill) Throttle added in v0.2.0

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

Throttle sleeps between chunks to bound the rate. Default 50ms. Set to zero to run as fast as the database can keep up — usually only appropriate during scheduled maintenance windows.

type BackfillStatus added in v0.2.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 Box added in v0.2.0

type Box struct {
	SW Point
	NE Point
}

Box is an axis-aligned bounding box defined by its south-west / north-east corners.

type Budget added in v0.2.0

type Budget struct {
	MaxArgs     int
	MaxRows     int
	MaxDuration time.Duration
}

Budget caps the cost of each Entity operation so a single typo or runaway query can't take down the database. Configure one via (*Entity[T]).WithBudget. Zero-valued fields disable that individual limit.

UserEntity.WithBudget(pg.Budget{
    MaxArgs:     1000,            // catch huge IN clauses early
    MaxRows:     10_000,          // bound result-set size
    MaxDuration: 250 * time.Millisecond,
})

MaxArgs is checked after SQL rendering; MaxRows is enforced by auto-injecting a LIMIT clause on EntityQuery.All when the user hasn't already supplied a tighter one; MaxDuration wraps the caller's context with a deadline.

Get / One / Update / Delete operate on a single row and so bypass MaxRows (they always cap at one). Stream and Page are explicitly paginated and ignore MaxRows. Bulk writes (CreateMany / UpsertMany) are honoured by MaxArgs and MaxDuration but not MaxRows (which is a SELECT concept).

type CTE

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

CTE describes one common table expression.

func CTEDef

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

CTEDef returns a CTE definition with optional column aliasing.

func (*CTE) Col

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

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

func (*CTE) Name

func (c *CTE) Name() string

Name returns the CTE's alias.

func (*CTE) Ref

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

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

type CaseExpr

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

CaseExpr is the in-progress CASE expression.

func Case

func Case() *CaseExpr

Case begins a CASE expression. Chain When / Else / End to finish.

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

func CaseOn

func CaseOn(value any) *CaseExpr

CaseOn begins a simple CASE expression on a value:

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

func (*CaseExpr) Else

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

Else sets the ELSE value.

func (*CaseExpr) End

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

End finalises the CASE expression.

func (*CaseExpr) When

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

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

type ChangeEvent added in v0.2.0

type ChangeEvent struct {
	// Op is the SQL operation — INSERT, UPDATE, DELETE.
	Op ChangeOp `json:"op"`

	// ID is the primary-key value of the affected row, coerced
	// to text by the trigger so heterogeneous PKs (int / uuid /
	// text) flow through the same channel.
	ID string `json:"id"`
}

ChangeEvent is one row change captured by the trigger, before any typed hydration. Subscribe[T] wraps it in TypedChange[T] with the row fetched.

func (*ChangeEvent) MarshalJSON added in v0.2.0

func (e *ChangeEvent) MarshalJSON() ([]byte, error)

MarshalJSON renders the event as `{"op":...,"id":...}`.

func (*ChangeEvent) UnmarshalJSON added in v0.2.0

func (e *ChangeEvent) UnmarshalJSON(data []byte) error

UnmarshalJSON parses a payload of `{"op":...,"id":...}`.

type ChangeFeedOptions added in v0.2.0

type ChangeFeedOptions struct {
	// Channel is the pg_notify channel name. Defaults to
	// "drops_<tablename>" — derived from the table so multiple
	// feeds coexist in the same database.
	Channel string
}

ChangeFeedOptions tunes the trigger DDL produced by InstallChangeFeed.

type ChangeOp added in v0.2.0

type ChangeOp string

ChangeOp is the kind of mutation captured by the trigger.

const (
	// OpInsert fires after a successful INSERT.
	OpInsert ChangeOp = "INSERT"
	// OpUpdate fires after a successful UPDATE.
	OpUpdate ChangeOp = "UPDATE"
	// OpDelete fires after a successful DELETE.
	OpDelete ChangeOp = "DELETE"
)

type CheckSnapshot added in v0.2.0

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

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

type Col

type Col[T any] struct {
	*Column
}

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

It embeds *Column so it implements drops.Expression and exposes Asc/Desc/As; its own methods (NotNull, PrimaryKey, Eq, In, Val, ...) preserve the type parameter so the chain stays typed end-to-end.

func Add

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

Add registers c with t and returns it. It is the primary way to attach columns to a table — type inference keeps the *Col[T] handle typed:

var Users    = pg.NewTable("users")
var (
    UserID   = pg.Add(Users, pg.BigSerial("id").PrimaryKey())   // *Col[int64]
    UserName = pg.Add(Users, pg.Text("name").NotNull())          // *Col[string]
    UserAge  = pg.Add(Users, pg.Integer("age"))                  // *Col[int32]
)

Go does not allow generic methods, so Add lives as a free function.

Example

ExampleAdd shows the typical schema-declaration pattern: NewTable followed by package-level pg.Add calls, each binding a typed column to the table. Type inference keeps the column handles typed (*pg.Col[int64], *pg.Col[string], …) so subsequent comparisons and value bindings are compile-time checked.

package main

import (
	"fmt"

	"github.com/bernardoforcillo/drops/pg"
)

func main() {
	products := pg.NewTable("products")
	id := pg.Add(products, pg.BigSerial("id").PrimaryKey())
	name := pg.Add(products, pg.Text("name").NotNull())
	priceCents := pg.Add(products, pg.Integer("price_cents").NotNull())

	fmt.Printf("%s.%s, %s.%s, %s.%s\n",
		products.Name(), id.Name(),
		products.Name(), name.Name(),
		products.Name(), priceCents.Name(),
	)
}
Output:
products.id, products.name, products.price_cents

func BigInt

func BigInt(name string) *Col[int64]

func BigSerial

func BigSerial(name string) *Col[int64]

func BitVec

func BitVec(name string, dim int) *Col[string]

BitVec returns a bit(N) column for binary-vector similarity search.

func Boolean

func Boolean(name string) *Col[bool]

func Bytea

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

func Char

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

func Custom

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

Custom creates a column with an arbitrary type literal. Specify the Go value type as the type parameter — e.g. pg.Custom[string]("status", "user_status_enum").

func Date

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

func DoublePrecision

func DoublePrecision(name string) *Col[float64]

func HalfVec

func HalfVec(name string, dim int) *Col[[]float32]

HalfVec returns a halfvec(N) column (half-precision float). The Go value type stays []float32; the driver converts on the wire.

func Integer

func Integer(name string) *Col[int32]

func Interval

func Interval(name string) *Col[string]

func JSON

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

func JSONB

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

func Numeric

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

Numeric returns NUMERIC(precision, scale) — represented as string so arbitrary-precision values aren't truncated. precision=0 means unconstrained NUMERIC.

func Real

func Real(name string) *Col[float32]

func Serial

func Serial(name string) *Col[int32]

func SmallInt

func SmallInt(name string) *Col[int16]

func SparseVec

func SparseVec(name string, dim int) *Col[string]

SparseVec returns a sparsevec(N) column. Encoding is driver-specific; represented as string for portability.

func Text

func Text(name string) *Col[string]

func Time

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

func Timestamp

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

Timestamp creates a TIMESTAMP column (TIMESTAMPTZ if withTimeZone).

func UUID

func UUID(name string) *Col[string]

func Varchar

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

func Vector

func Vector(name string, dim int) *Col[[]float32]

Vector returns a vector(N) column. The Go value type is []float32 — the same shape pgvector exposes through pgx and lib/pq's text codec.

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

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

AsPII flags the column as carrying PII. Entity binders wrap values for this column so any logger / tracer formatting them sees "<redacted>" instead of the real value. The wire path downstream of db.Exec / db.Query is unchanged — the driver receives the unwrapped value.

func (*Col[T]) Between

func (c *Col[T]) Between(lo, hi T) drops.Expression

func (*Col[T]) Cosine

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

Cosine is the method form of CosineDistance.

func (*Col[T]) Default

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

Default sets a raw SQL default expression — e.g. "now()", "0", "'pending'". PostgreSQL DEFAULT clauses cannot be parameterised.

func (*Col[T]) Eq

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

ExampleEq shows the type-safe comparison shorthand on a typed column.

package main

import (
	"fmt"

	"github.com/bernardoforcillo/drops"
	"github.com/bernardoforcillo/drops/pg"
)

// Schema fixtures used by the examples. Declaring them at package level
// mirrors how a real application stores its schema and lets every
// example reuse the same definitions.
var (
	exUsers = pg.NewTable("users")

	exUserName = pg.Add(exUsers, pg.Text("name").NotNull())
)

func main() {
	sql, args := drops.String(exUserName.Eq("Alice"))
	fmt.Println(sql)
	fmt.Println(args)
}
Output:
("users"."name" = $1)
[Alice]

func (*Col[T]) EqCol

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

func (*Col[T]) Excluded

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

Excluded returns an EXCLUDED.<col> reference for use inside an ON CONFLICT DO UPDATE clause.

func (*Col[T]) Expr

func (c *Col[T]) Expr(e drops.Expression) ColumnValue

Expr binds an arbitrary expression to the column.

func (*Col[T]) Gt

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

func (*Col[T]) GtCol

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

func (*Col[T]) Gte

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

func (*Col[T]) GteCol

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

func (*Col[T]) ILike

func (c *Col[T]) ILike(pattern string) drops.Expression

func (*Col[T]) IP

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

IP is the method form of InnerProduct.

func (*Col[T]) In

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

func (*Col[T]) IsNotNull

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

func (*Col[T]) IsNull

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

func (*Col[T]) L1

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

L1 is the method form of L1Distance.

func (*Col[T]) L2

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

L2 is the method form of L2Distance.

func (*Col[T]) Like

func (c *Col[T]) Like(pattern string) drops.Expression

func (*Col[T]) Lt

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

func (*Col[T]) LtCol

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

func (*Col[T]) Lte

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

func (*Col[T]) LteCol

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

func (*Col[T]) Ne

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

func (*Col[T]) NeCol

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

func (*Col[T]) NotIn

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

func (*Col[T]) NotNull

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

func (*Col[T]) OptimisticLock added in v0.2.0

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

OptimisticLock marks the column as the version column for optimistic locking. When an Entity bound to the table issues an UPDATE, it automatically guards with "AND version = current" and bumps the column ("SET version = version + 1"). If no row matches the version, ErrStaleObject is returned. Apply this to a single integer column per table.

func (*Col[T]) PrimaryKey

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

func (*Col[T]) References

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

References declares a foreign-key constraint to a target column. The target's value type must match — type inference catches mismatches at declaration time.

func (*Col[T]) SetDefault

func (c *Col[T]) SetDefault() ColumnValue

SetDefault binds the SQL DEFAULT keyword.

func (*Col[T]) Unique

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

func (*Col[T]) Val

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

Val binds a typed value as the column's payload in an INSERT row or UPDATE assignment.

type ColRef

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

ColRef is implemented by *Column and *Col[T]. It is the type-erased column reference used by APIs that don't depend on the column's Go value type — JOIN ON wiring, ON CONFLICT targets, EXCLUDED references.

type Column

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

Column is the type-erased AST node for a column reference. It is registered with a Table and can be written into a drops.Builder.

Most user code holds a *Col[T] instead, which embeds *Column and adds type-safe builder and operator methods. The untyped Column exists so table column lists can be heterogeneous and so generic methods (Go does not allow them on non-generic types) need not be added to Table.

func (*Column) As

func (c *Column) As(alias string) drops.Expression

As returns an aliased column expression: "<col>" AS "<alias>".

func (*Column) Asc

func (c *Column) Asc() drops.Expression

Asc / Desc produce ORDER BY direction expressions.

func (*Column) DefaultSQL

func (c *Column) DefaultSQL() string

DefaultSQL returns the raw SQL DEFAULT expression.

func (*Column) Desc

func (c *Column) Desc() drops.Expression

func (*Column) ForeignKey

func (c *Column) ForeignKey() *FK

ForeignKey returns the foreign-key reference, or nil if none.

func (*Column) HasDefault

func (c *Column) HasDefault() bool

HasDefault reports whether a DEFAULT clause was declared.

func (*Column) IsNotNull

func (c *Column) IsNotNull() bool

IsNotNull reports whether the column was declared NOT NULL.

func (*Column) IsOptimisticVersion added in v0.2.0

func (c *Column) IsOptimisticVersion() bool

IsOptimisticVersion reports whether the column is the version column used for optimistic locking. Marked via (*Col[T]).OptimisticLock().

func (*Column) IsPII added in v0.2.0

func (c *Column) IsPII() bool

IsPII reports whether the column is marked PII.

func (*Column) IsPrimaryKey

func (c *Column) IsPrimaryKey() bool

IsPrimaryKey reports whether the column was declared PRIMARY KEY.

func (*Column) IsUnique

func (c *Column) IsUnique() bool

IsUnique reports whether the column was declared UNIQUE.

func (*Column) Name

func (c *Column) Name() string

Name returns the unqualified column name.

func (*Column) Table

func (c *Column) Table() *Table

Table returns the table this column was registered with, or nil before registration.

func (*Column) Type

func (c *Column) Type() ColumnType

Type returns the column's SQL type.

func (*Column) WriteSQL

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

WriteSQL writes a qualified reference to the column.

type ColumnSnapshot

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

ColumnSnapshot is one entry in TableSnapshot.Columns.

type ColumnType

type ColumnType interface {
	// TypeSQL returns the PostgreSQL type expression as it appears in
	// CREATE TABLE — e.g. "text", "integer", "varchar(255)", "uuid".
	TypeSQL() string
}

ColumnType describes the SQL type of a column.

type ColumnValue

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

ColumnValue pairs a target column with the value or expression to write for it in an INSERT row or UPDATE SET assignment. Construct one via (*Col[T]).Val, (*Col[T]).Expr, or (*Col[T]).SetDefault.

type CompositePKSnapshot added in v0.2.0

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

CompositePKSnapshot is one entry in TableSnapshot.CompositePrimaryKeys.

type ConflictUpdate

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

ConflictUpdate is the configuration handle returned by OnConflictUpdate.

func (*ConflictUpdate) Done

func (cu *ConflictUpdate) Done() *InsertBuilder

Done returns the InsertBuilder for further chaining (e.g. Returning).

func (*ConflictUpdate) Set

func (cu *ConflictUpdate) Set(values ...ColumnValue) *ConflictUpdate

Set adds an assignment to the conflict update.

func (*ConflictUpdate) Where

func (cu *ConflictUpdate) Where(preds ...drops.Expression) *ConflictUpdate

Where adds predicates that gate the conflict update.

type Copier added in v0.2.0

type Copier interface {
	// Copy ingests rows into table. cols carries the destination
	// columns in order; each entry in rows must align with cols.
	// Returns the number of rows accepted by the server.
	Copy(ctx context.Context, table string, cols []string, rows [][]any) (int64, error)
}

Copier is the contract drops uses to dispatch a bulk COPY. Any driver that satisfies it (typically by adding a Copy method to the existing drops.Driver implementation) gets the fast path for free.

type Cursor added in v0.2.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.2.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.2.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.2.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.2.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.2.0

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

CustomGuard wraps a function so application code can compose arbitrary authorization rules without implementing the interface explicitly.

func (CustomGuard) Predicate added in v0.2.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 PostgreSQL queries through a drops.Driver. Any driver implementation — database/sql, pgx, or a custom connection — can back a DB.

A DB is safe for concurrent use by multiple goroutines provided the underlying Driver is. The builder types returned by Select/Insert/ Update/Delete/Find are NOT safe for concurrent use; create one per query.

An optional drops.Hook can be attached via WithHook to observe every driver operation — query logging, slow-query alerts, tracing, metrics. The hook is propagated into the transaction-bound DBs returned by Begin and InTx, and InTx emits "begin"/"commit"/"rollback" events for the transaction lifecycle. For full lifecycle observability prefer InTx; with an explicit Begin you must call Commit/Rollback yourself, and those bypass the hook unless you wrap them.

func New

func New(drv drops.Driver) *DB

New wraps a drops.Driver as a 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 handle. Most callers should prefer InTx for automatic commit/ rollback and full hook coverage.

func (*DB) Close

func (db *DB) Close() error

Close shuts down the underlying driver if it implements io.Closer. For Drivers that don't (most pool wrappers do), it is a no-op and returns nil.

Typical usage is `defer db.Close()` next to the connection-open call:

sqlDB, _ := sql.Open("pgx", dsn)
db := pg.New(stdlib.New(sqlDB))
defer db.Close()

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. Useful for adapters or for dropping down to raw SQL.

func (*DB) Exec

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

Exec runs a raw SQL statement with positional ($1, $2, ...) placeholders.

func (*DB) ExecExpr

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

ExecExpr renders e to SQL and runs it as a statement. 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 what 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. The transaction is committed if fn returns nil and rolled back otherwise (including on panic, after which the panic is re-raised). Hook events are emitted for begin, commit and rollback alongside the ordinary exec/query events fn produces.

Rollback uses a detached context with a short timeout so a cancelled or expired caller-ctx doesn't prevent cleanup. When a RetryPolicy is installed via WithRetry, transient failures (those the policy marks retryable) cause the transaction to be re-opened and fn re-run, up to MaxAttempts times.

func (*DB) Insert

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

Insert begins an INSERT INTO <t>.

Example

ExampleDB_Insert demonstrates a typed INSERT with RETURNING.

package main

import (
	"fmt"

	"github.com/bernardoforcillo/drops/pg"
)

// Schema fixtures used by the examples. Declaring them at package level
// mirrors how a real application stores its schema and lets every
// example reuse the same definitions.
var (
	exUsers    = pg.NewTable("users")
	exUserID   = pg.Add(exUsers, pg.BigSerial("id").PrimaryKey())
	exUserName = pg.Add(exUsers, pg.Text("name").NotNull())
	exUserAge  = pg.Add(exUsers, pg.Integer("age"))
)

func main() {
	db := pg.New(nil)
	sql, args := db.Insert(exUsers).
		Row(exUserName.Val("Alice"), exUserAge.Val(30)).
		Returning(exUserID).
		ToSQL()
	fmt.Println(sql)
	fmt.Println(args)
}
Output:
INSERT INTO "users" ("name", "age") VALUES ($1, $2) RETURNING "users"."id"
[Alice 30]

func (*DB) Ping

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

Ping verifies that the database accepts a no-op query.

func (*DB) PoolStats added in v0.2.0

func (db *DB) PoolStats() (PoolStats, bool)

PoolStats returns a snapshot when the underlying driver implements PoolStatsProvider, plus ok=true. Drivers without pool introspection return the zero value + ok=false.

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.2.0

func (db *DB) RetryPolicyValue() RetryPolicy

RetryPolicyValue returns the active retry policy, or the zero RetryPolicy when none is configured.

func (*DB) Select

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

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

Example

ExampleDB_Select shows a typical filtered + ordered SELECT and how the typed column helpers produce parameter-safe SQL.

package main

import (
	"fmt"

	"github.com/bernardoforcillo/drops/pg"
)

// Schema fixtures used by the examples. Declaring them at package level
// mirrors how a real application stores its schema and lets every
// example reuse the same definitions.
var (
	exUsers    = pg.NewTable("users")
	exUserID   = pg.Add(exUsers, pg.BigSerial("id").PrimaryKey())
	exUserName = pg.Add(exUsers, pg.Text("name").NotNull())
	exUserAge  = pg.Add(exUsers, pg.Integer("age"))
)

func main() {
	db := pg.New(nil) // no driver — we only render SQL
	sql, args := db.Select(exUserID, exUserName).
		From(exUsers).
		Where(exUserAge.Gte(18)).
		OrderBy(exUserName.Asc()).
		Limit(10).
		ToSQL()
	fmt.Println(sql)
	fmt.Println(args)
}
Output:
SELECT "users"."id", "users"."name" FROM "users" WHERE ("users"."age" >= $1) ORDER BY "users"."name" ASC LIMIT $2
[18 10]

func (*DB) StartPoolMetrics added in v0.2.0

func (db *DB) StartPoolMetrics(ctx context.Context, interval time.Duration, sink func(PoolStats)) func()

StartPoolMetrics launches a goroutine that polls PoolStats every interval and pushes each snapshot to sink. Returns a cancel function the caller must call to stop the goroutine — usually via defer.

Returns nil (and never starts the goroutine) when the driver does not implement PoolStatsProvider — callers can branch on db.PoolStats's ok return to know in advance.

func (*DB) Tracer added in v0.2.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 of db with hook installed. Passing nil removes the hook. Compose multiple hooks via drops.ChainHooks.

Example

ExampleDB_WithHook shows attaching a tiny observability hook for query logging. Production code usually pairs LoggerHook with a structured logger and a SlowQuery threshold.

package main

import (
	"context"
	"fmt"

	"github.com/bernardoforcillo/drops"
	"github.com/bernardoforcillo/drops/pg"
)

func main() {
	hook := func(_ context.Context, e drops.QueryEvent) {
		fmt.Printf("%s in %v err=%v\n", e.Kind, e.Duration > 0, e.Err)
	}
	db := pg.New(&exampleNoopDriver{}).WithHook(hook)
	_, _ = db.Exec(context.Background(), "SELECT 1")
}

// exampleNoopDriver lets the examples render SQL through DB.Exec
// without depending on the test-only fakeDriver. It is purely for
// documentation; production code never embeds a no-op driver.
type exampleNoopDriver struct{}

func (exampleNoopDriver) Exec(context.Context, string, ...any) (drops.Result, error) {
	return exampleNoopResult{}, nil
}
func (exampleNoopDriver) Query(context.Context, string, ...any) (drops.Rows, error) {
	return nil, fmt.Errorf("not implemented")
}
func (exampleNoopDriver) Begin(context.Context) (drops.Tx, error) {
	return nil, fmt.Errorf("not implemented")
}

type exampleNoopResult struct{}

func (exampleNoopResult) RowsAffected() (int64, error) { return 0, nil }
Output:
exec in true err=<nil>

func (*DB) WithRetry added in v0.2.0

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

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

func (*DB) WithTracer added in v0.2.0

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

WithTracer attaches t. Returns a shallow copy of db so global instances stay unaffected — mirrors the WithHook pattern. Pass nil to clear.

type DeleteBuilder

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

DeleteBuilder composes a DELETE statement.

func (*DeleteBuilder) All

func (d *DeleteBuilder) All(ctx context.Context, dest any) error

All executes the DELETE and scans the RETURNING rows into dest.

func (*DeleteBuilder) DB added in v0.2.0

func (d *DeleteBuilder) DB() *DB

DB returns the executing DB. Hooks that need to build a replacement statement (an UPDATE for soft-delete, for instance) use it.

func (*DeleteBuilder) Exec

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

Exec runs the DELETE.

func (*DeleteBuilder) IsUnscoped added in v0.2.0

func (d *DeleteBuilder) IsUnscoped() bool

IsUnscoped reports whether the caller opted out of default scopes.

func (*DeleteBuilder) One

func (d *DeleteBuilder) One(ctx context.Context, dest any) error

One executes the DELETE and scans the first RETURNING row into dest.

func (*DeleteBuilder) Returning

func (d *DeleteBuilder) Returning(cols ...drops.Expression) *DeleteBuilder

Returning sets a RETURNING clause.

func (*DeleteBuilder) ReturningClauses added in v0.2.0

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

ReturningClauses returns a copy of the RETURNING projection list.

func (*DeleteBuilder) Table added in v0.2.0

func (d *DeleteBuilder) Table() *Table

Table returns the target table.

func (*DeleteBuilder) ToSQL

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

ToSQL renders the statement.

func (*DeleteBuilder) Unscoped added in v0.2.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 that bypasses the rewrite-to-UPDATE behaviour.

func (*DeleteBuilder) Using

func (d *DeleteBuilder) Using(tables ...*Table) *DeleteBuilder

Using adds tables to a PostgreSQL DELETE ... USING clause for joins.

func (*DeleteBuilder) Where

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

Where appends predicates joined by AND.

func (*DeleteBuilder) Wheres added in v0.2.0

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

Wheres returns a copy of the predicate slice — exposed so custom DeleteHooks (e.g. soft-delete rewrites) can read the original WHERE clauses when synthesising replacement SQL.

func (*DeleteBuilder) WriteSQL

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

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

type DeleteHook added in v0.2.0

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

DeleteHook is invoked by DeleteBuilder.WriteSQL. If it returns a non-nil expression, that expression replaces the rendered DELETE entirely — used by SoftDelete to translate DELETE into an 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.2.0

type DeleteHookFunc func(*DeleteBuilder) drops.Expression

DeleteHookFunc adapts a function to the DeleteHook interface.

func (DeleteHookFunc) BeforeDelete added in v0.2.0

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

BeforeDelete implements DeleteHook.

type DiffOptions

type DiffOptions struct {
	// Safe wraps every destructive or creative DDL in IF [NOT] EXISTS so
	// the migration can be re-run without errors. ALTER COLUMN does not
	// have an IF EXISTS form in PostgreSQL, so it is emitted unchanged.
	Safe bool
}

DiffOptions tunes how Diff renders statements.

type DriftReport added in v0.2.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.2.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.2.0

func (r DriftReport) HasPendingMigrations() bool

HasPendingMigrations reports whether production is behind the repo.

func (DriftReport) HasUnauthorizedChanges added in v0.2.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

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

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

type DrizzleMigrator

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

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

func NewDrizzleMigrator

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) LoadEntries

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

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

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) WithSchema

func (d *DrizzleMigrator) WithSchema(schema string) *DrizzleMigrator

WithSchema overrides the migration history schema. Match drizzle.config.ts's `migrationsSchema` to stay interoperable.

func (*DrizzleMigrator) WithTable

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

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.2.0

type EmitOptions struct {
	// AggregateType is the entity kind, e.g. "player", "match",
	// "wallet". Optional but recommended — pairs with AggregateID
	// to identify the ordering scope.
	AggregateType string

	// AggregateID is the per-aggregate ordering key, e.g. "42",
	// "match-abc". Events sharing the same AggregateID are
	// delivered in id order when the worker is configured with
	// WithOrdering(OrderingPerAggregate).
	AggregateID string

	// Headers carry tracing metadata (traceparent, correlationID,
	// userID, ...). Stored as jsonb on the row and surfaced to
	// the handler so context propagates from emitter to consumer.
	Headers map[string]string
}

EmitOptions extends Emit with aggregate metadata and tracing headers. Aggregate fields enable per-aggregate ordering in the worker (see WithOrdering(OrderingPerAggregate)).

type Entity added in v0.2.0

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

Entity binds a Go struct T to a Table and precomputes the metadata the CRUD shortcuts need: the column-to-field index path, the primary-key column, and the PK's field path inside T. It is the entry point for type-safe CRUD operations that scan into T directly, without the caller having to declare scan targets.

Declare an Entity once at package level alongside its table:

type User struct {
    ID    int64  `drop:"id"`
    Name  string `drop:"name"`
    Email string `drop:"email"`
}

var (
    Users      = pg.NewTable("users")
    UserID     = pg.Add(Users, pg.BigSerial("id").PrimaryKey())
    UserName   = pg.Add(Users, pg.Text("name").NotNull())
    UserEmail  = pg.Add(Users, pg.Text("email").NotNull().Unique())
    UserEntity = pg.NewEntity[User](Users)
)

All Entity methods take *DB as their first argument so the same entity can be reused across transactions and connection pools:

u, err := UserEntity.Get(db, ctx, 42)
err  = UserEntity.Create(db, ctx, &u)
err  = UserEntity.Update(db, ctx, &u)
err  = UserEntity.Save(db, ctx, &u) // INSERT or UPDATE depending on PK
res, err := UserEntity.Delete(db, ctx, 42)

Entity composes with Phase-1 features: lifecycle hooks (Timestamps, SoftDelete, …) registered on the table fire normally because every operation routes through the underlying Insert / Update / Delete builders.

func NewAutoEntity added in v0.2.0

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

NewAutoEntity bundles AutoTable + NewEntity into one call — typically the only line a small entity needs.

var UserEntity = pg.NewAutoEntity[User]("users")

func NewEntity added in v0.2.0

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

NewEntity validates that T has a field bound to every primary-key column on t and precomputes the column ↔ field mapping. It panics on misalignment because schemas are typically declared in package init blocks — bad config should fail at startup, not at the first query.

Field matching rules mirror the row scanner: `drop:"colname"` tag wins; otherwise the field name and its snake_case form are tried. Fields tagged `drop:"-"` are skipped.

func WithAudit added in v0.2.0

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

WithAudit attaches log to the entity so subsequent Create / Update / Delete calls record audit events in the same transaction. Free function because Go does not allow generic methods.

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

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

AuthorizeWith installs g on the entity. Subsequent Get / Query / Update / Delete AND the guard's predicate into the WHERE clause; ctx-less subject failures surface as ErrSubjectMissing. Pass nil to clear an existing guard.

func (*Entity[T]) Create added in v0.2.0

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

Create INSERTs r and refreshes it from the RETURNING row — useful for picking up generated PKs, server-side defaults, and hook-added values (e.g. createdAt = now()).

Columns whose Go field is the zero value are omitted from the INSERT when the column either has a declared DEFAULT or is the primary key — letting the DB generate the value. To override that behaviour for a specific field, set it to a non-zero value before calling Create.

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

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

CreateMany INSERTs every row in rs in a single statement. Compared to looping over Create, this batches the round-trip cost — large payloads stay one network hop. RETURNING is not used (refreshing N rows is rarely what callers want), so generated PKs and hook-supplied values do not flow back into rs; use Create when you need the post-INSERT row.

func (*Entity[T]) Delete added in v0.2.0

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

Delete removes the row whose primary key equals id. The table's DeleteHooks (e.g. SoftDelete) fire normally — so on a soft-deleted table this rewrites to UPDATE deletedAt = now() instead.

func (*Entity[T]) Get added in v0.2.0

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

Get fetches the row whose primary key equals id. Returns ErrNoRows if no row matches.

When a cache is attached via WithCache, Get serves hits from the cache and dedupes concurrent cache misses via single-flight so a thundering herd resolves to one DB query.

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

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

HasCache reports whether a cache is wired up.

func (*Entity[T]) HasFastScan added in v0.2.0

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

HasFastScan reports whether a zero-reflection scanner is wired up.

func (*Entity[T]) PK added in v0.2.0

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

PK returns the entity's primary-key column.

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

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

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

pg, err := UserEntity.Page(db).
    OrderBy(pg.Asc(UserID)).
    Limit(20).
    After(prevCursor).
    All(ctx)
if pg.HasMore {
    // request next page with pg.NextCursor
}

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

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

Patch issues an UPDATE that applies ops to the row whose PK equals id. Honours the entity's tenant scope, authorisation guard, and audit log (the audit row's payload is empty since the post-update state isn't fetched; callers needing post-row snapshots should use Update with a refreshed struct).

Returns the result so callers can detect "no row matched" without an additional SELECT.

func (*Entity[T]) Query added in v0.2.0

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

Query returns a typed query builder for ad-hoc Where / OrderBy / Limit / Offset / With chains that scan into T or []T.

When the entity is tenant-scoped the caller must use the ctx-aware All / One / Stream methods so the tenant predicate can be injected; using the builder methods without a ctx-bound tenant returns ErrTenantMissing.

func (*Entity[T]) Save added in v0.2.0

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

Save inserts r if its primary-key field is the zero value, or updates it otherwise. Compared to a single ON CONFLICT statement, this incurs an extra branch in Go but keeps the generated SQL straightforward; switch to a hand-written upsert when the race-window between the read and the write matters.

func (*Entity[T]) ScopeByTenant added in v0.2.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 reads the tenant from ctx (via WithTenant) and AND-s "<col> = $tenant" into the predicate. Create stamps the tenant onto r automatically.

Panics if col has no matching struct field — fail loudly at startup rather than at the first query.

func (*Entity[T]) SetFastScan added in v0.2.0

func (e *Entity[T]) SetFastScan(scan func(Scanner, *T) error) *Entity[T]

SetFastScan registers a zero-reflection per-row scanner — the generated Scan<T> helper from cmd/dropsgen is the canonical implementation. When set, Get / Query.One / Query.All consume rows directly through scan instead of routing through the reflection scanner. Eager-loaded relations still fall back to the reflection path because they rely on field-map introspection of the loaded slice.

func (*Entity[T]) Table added in v0.2.0

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

Table returns the table the entity is bound to.

func (*Entity[T]) Update added in v0.2.0

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

Update writes r's current field values to the row whose PK equals r's PK and refreshes r from the RETURNING row. ErrPKNotSet is returned if r's PK is the zero value.

All non-PK columns mapped to fields on T are included in the SET list — the typical "blind UPDATE" semantics. Change-tracking is out of scope for now; callers needing finer control use db.Update directly.

func (*Entity[T]) UpsertMany added in v0.2.0

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

UpsertMany INSERTs rs and, on PK conflict, updates every non-PK column with the new row's value (ON CONFLICT (pk) DO UPDATE SET col = EXCLUDED.col). Returns the underlying Result so callers can inspect rows-affected; row values are not refreshed.

Useful for idempotent ingestion: the same set of rows can be replayed safely without producing duplicates.

func (*Entity[T]) Validate added in v0.2.0

func (e *Entity[T]) Validate(v Validator[T]) *Entity[T]

Validate registers a validator that runs before Create / Update / Save. Validators are invoked in registration order; the first to return a non-nil error aborts the operation. Returns the entity so the call can be chained next to NewEntity.

func (*Entity[T]) WithBudget added in v0.2.0

func (e *Entity[T]) WithBudget(b Budget) *Entity[T]

WithBudget attaches the supplied limits to the entity. Returns the entity for chaining at declaration time.

func (*Entity[T]) WithCache added in v0.2.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.2.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 added in v0.2.0

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

EntityQuery is the typed counterpart of FindBuilder — same shape, but its executors return ([]T, error) and (T, error) directly.

func (*EntityQuery[T]) All added in v0.2.0

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

All executes the query and returns the matching rows as a typed slice. Uses the fast-scan path (zero reflection) when available and no eager-loaded relations are queued. When the entity has a cache attached and the query has no eager-loaded relations, the result is cached under sha256(SQL+args) with the cache's TTL.

func (*EntityQuery[T]) Limit added in v0.2.0

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

Limit sets the LIMIT.

func (*EntityQuery[T]) Offset added in v0.2.0

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

Offset sets the OFFSET.

func (*EntityQuery[T]) One added in v0.2.0

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

One executes the query and returns the first matching row. Returns ErrNoRows if the query produces no rows. Honours the entity cache the same way All does.

func (*EntityQuery[T]) OrderBy added in v0.2.0

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

OrderBy appends ORDER BY expressions.

func (*EntityQuery[T]) Stream added in v0.2.0

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

Stream iterates the matching rows one at a time, invoking fn for each. Memory stays bounded — Stream never buffers the full result set — which matters for batch jobs and exports. Returning an error from fn aborts the iteration and propagates the error to the caller. Eager-loaded relations are not supported in Stream (relation loaders need the populated parent slice).

func (*EntityQuery[T]) Unscoped added in v0.2.0

func (q *EntityQuery[T]) Unscoped() *EntityQuery[T]

Unscoped opts out of the table's DefaultFilter predicates.

func (*EntityQuery[T]) Where added in v0.2.0

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

Where appends predicates joined by AND.

func (*EntityQuery[T]) With added in v0.2.0

func (q *EntityQuery[T]) With(names ...string) *EntityQuery[T]

With eager-loads the named relations (see FindBuilder.With).

func (*EntityQuery[T]) WithRel added in v0.2.0

func (q *EntityQuery[T]) WithRel(name string, fn func(*RelConfig)) *EntityQuery[T]

WithRel eager-loads a relation with per-edge configuration.

type EnumSnapshot added in v0.2.0

type EnumSnapshot struct {
	Name   string   `json:"name"`
	Schema string   `json:"schema"`
	Values []string `json:"values"`
}

EnumSnapshot is one entry in Snapshot.Enums.

type EnvelopeCipher added in v0.2.0

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

EnvelopeCipher performs AES-256-GCM with per-call data encryption keys wrapped by the configured KMS. Ciphertext format on the wire:

[wrappedDEK length: uint32 BE] [wrappedDEK] [nonce: 12 bytes] [ciphertext + GCM tag]

The header is fixed-length-prefixed so the decryptor can split without scanning. The format is versioned via the magic byte at position 0 so future revisions stay distinguishable.

func NewEnvelopeCipher added in v0.2.0

func NewEnvelopeCipher(kms KMS) *EnvelopeCipher

NewEnvelopeCipher wraps kms in the cipher contract. Panics on a nil KMS — encryption with a nil KMS is never a useful programmer intent.

func (*EnvelopeCipher) Decrypt added in v0.2.0

func (c *EnvelopeCipher) Decrypt(ctx context.Context, blob []byte) ([]byte, error)

Decrypt undoes Encrypt — unwraps the DEK via KMS, then opens the ciphertext with AES-GCM. Returns an error when the input has the wrong magic byte, truncated header, or fails the AEAD check.

func (*EnvelopeCipher) Encrypt added in v0.2.0

func (c *EnvelopeCipher) Encrypt(ctx context.Context, plaintext []byte) ([]byte, error)

Encrypt wraps plaintext into a self-contained ciphertext. The returned bytes are safe to store in a bytea column.

type Event added in v0.2.0

type Event struct {
	// Offset is the store-wide append offset — monotonic across
	// all aggregates. Use it as the high-watermark when streaming
	// for projections.
	Offset int64

	// AggregateType / AggregateID identify the stream.
	AggregateType string
	AggregateID   string

	// Version is the per-stream offset starting at 0. Append's
	// expectedVersion compares against this.
	Version int64

	// Type is the application-level event name (e.g.
	// "matchStarted", "playerJoined").
	Type string

	// Payload is the JSON-encoded event body — caller-defined
	// shape, decoded on read by the consumer.
	Payload json.RawMessage

	// Headers carry tracing / correlation metadata.
	Headers map[string]string

	// CreatedAt is the wall-clock time of the append.
	CreatedAt time.Time
}

Event is one entry in the store.

type EventInput added in v0.2.0

type EventInput struct {
	// Type is required — names the event.
	Type string

	// Payload is encoded with encoding/json. Pre-encoded
	// json.RawMessage / []byte / string pass through untouched.
	Payload any

	// Headers attach tracing / correlation metadata. Stored as
	// jsonb on the row.
	Headers map[string]string
}

EventInput is the per-event payload passed to Append.

type EventStore added in v0.2.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.2.0

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

NewEventStore returns a store bound to db. The table name is the SQL identifier; use NewEventStoreTable to declare matching DDL.

func (*EventStore) Append added in v0.2.0

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

Append writes events to the stream identified by (aggregateType, aggregateID) starting at expectedVersion+1. Returns ErrConcurrencyConflict if the stream's head has advanced past expectedVersion — typically resolved by reloading and retrying.

Use tx so the append commits with the rest of the aggregate's transactional work (typically a write to a projection table or to the outbox).

func (*EventStore) LatestVersion added in v0.2.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 the stream is empty. Use this before Append to compute the expectedVersion for a fresh write.

func (*EventStore) Load added in v0.2.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 to read from the beginning.

func (*EventStore) LoadSnapshot added in v0.2.0

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

LoadSnapshot fetches the latest snapshot for an aggregate. Returns ok=false when no snapshot exists; callers should fall back to replaying from version 0.

func (*EventStore) SaveSnapshot added in v0.2.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.2.0

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

Stream returns events from the global append log starting after fromOffset, up to limit rows. Used by projection workers to fan events out into derived tables; the offset is the high-watermark the projection persists between runs.

type ExplainOptions added in v0.2.0

type ExplainOptions struct {
	// Analyze flips EXPLAIN (ANALYZE) — actually runs the query
	// and reports real timings / row counts. Skip for INSERT /
	// UPDATE / DELETE in production unless you wrap the call in
	// a rolled-back transaction.
	Analyze bool

	// Buffers adds I/O accounting. Requires Analyze.
	Buffers bool

	// Verbose includes per-node target lists. Useful for
	// fingerprinting projection changes but bloats the JSON.
	Verbose bool
}

ExplainOptions tunes the EXPLAIN PG runs.

type ExplainPlan added in v0.2.0

type ExplainPlan struct {
	// JSON is the raw EXPLAIN (FORMAT JSON) payload returned by
	// PostgreSQL. Persist this verbatim for debugging — Root and
	// the helpers below are derived from it.
	JSON json.RawMessage

	// Root is the head of the parsed plan tree.
	Root *PlanNode

	// TotalCost mirrors Root.TotalCost — the planner's estimated
	// upper-bound cost in arbitrary units.
	TotalCost float64

	// PlanRows mirrors Root.PlanRows — the planner's row-count
	// estimate.
	PlanRows int64

	// ActualMs is the actual execution time when Analyze was set.
	// Zero otherwise.
	ActualMs float64
}

ExplainPlan is the parsed result of an EXPLAIN — the raw JSON, the root node of the plan tree, and a few derived shortcuts.

func Explain added in v0.2.0

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

Explain returns the parsed EXPLAIN plan for sql with default options — planner-only, no execution.

func ExplainWith added in v0.2.0

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

ExplainWith runs EXPLAIN with the supplied options and returns the parsed plan. The supplied sql is not modified — drops prepends "EXPLAIN (...) " with the chosen flags.

func (*ExplainPlan) Fingerprint added in v0.2.0

func (p *ExplainPlan) Fingerprint() string

Fingerprint returns a stable hash of the plan's structural shape — node types, relations, indexes, join types — without the cost / row estimates that fluctuate between runs. Two fingerprints comparing equal indicate the planner picked the same shape; a mismatch flags a regression candidate.

func (*ExplainPlan) JoinTypes added in v0.2.0

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

JoinTypes returns the join methods used across the plan in pre-order — "Hash Join", "Merge Join", "Nested Loop". Useful when a regression manifests as a nested-loop replacing what used to be a hash join.

func (*ExplainPlan) SeqScans added in v0.2.0

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

SeqScans returns the relations scanned with a Seq Scan node — often the headline finding (a missing or unused index).

func (*ExplainPlan) UsedIndexes added in v0.2.0

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

UsedIndexes returns the index names referenced by Index Scan / Bitmap Index Scan / Index Only Scan nodes — useful for asserting that the planner is honouring an expected index.

type FK

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

FK describes a foreign-key reference.

type Factory added in v0.2.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 = pg.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.2.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.2.0

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

Build returns a fresh value without touching the database.

func (*Factory[T]) BuildN added in v0.2.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.2.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.2.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.2.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.2.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.2.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.2.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 SELECT and (optionally) eager-loads relations declared via NewRelations. It mirrors a subset of SelectBuilder's API — Where, OrderBy, Limit, Offset — and adds With/WithRel for relations.

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 right field of every parent.

func (*FindBuilder) HasEagerLoads added in v0.2.0

func (f *FindBuilder) HasEagerLoads() bool

HasEagerLoads reports whether any relations have been queued for eager loading via With / WithRel. Used by Entity[T] to decide whether the fast-scan path is safe — relation loaders need the reflection-populated parent slice.

func (*FindBuilder) Limit

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

Limit sets the LIMIT.

func (*FindBuilder) Offset

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

Offset sets the OFFSET.

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 if no row matches.

func (*FindBuilder) OrderBy

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

OrderBy appends ORDER BY expressions.

func (*FindBuilder) Select added in v0.2.0

func (f *FindBuilder) Select() *SelectBuilder

Select exposes the underlying SELECT builder so callers (typically Entity[T]) can stream results through their own scanner.

func (*FindBuilder) Unscoped added in v0.2.0

func (f *FindBuilder) Unscoped() *FindBuilder

Unscoped opts out of the table's DefaultFilter predicates for the root SELECT. Eager-loaded relations inherit their own table's scopes independently.

func (*FindBuilder) Where

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

Where appends predicates joined by AND.

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.

Nested relations are expressed with dot paths: With("posts.comments") loads each parent's posts, then every comment of those posts. Paths that share a prefix are merged, so With("posts.comments", "posts.tags") fetches posts once and fans out into comments and tags. Each relation edge costs one extra query regardless of how many parents it spans.

Use WithRel when a relation needs filtering or ordering.

func (*FindBuilder) WithRel

func (f *FindBuilder) WithRel(name string, fn func(*RelConfig)) *FindBuilder

WithRel eager-loads a single relation with per-relation constraints. The callback receives a RelConfig to filter (Where), order (OrderBy), and declare deeper relations (With/WithRel):

db.Find(Users).WithRel("posts", func(p *pg.RelConfig) {
    p.Where(Posts.Published.Eq(true)).
        OrderBy(Posts.CreatedAt.Desc()).
        With("comments")
}).All(ctx, &users)

name may itself be a dot path; the constraints attach to its leaf. A relation configured by WithRel and also named in With merges into the same edge, so it is still fetched once.

type ForeignKeySnapshot

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

ForeignKeySnapshot is one entry in TableSnapshot.ForeignKeys.

type FunctionOptions

type FunctionOptions struct {
	Args       string // raw, e.g. "a integer, b integer"
	Returns    string // raw, e.g. "integer" or "trigger"
	Language   string // default "plpgsql"
	Body       string // function body (without surrounding $$ delimiters)
	Volatility string // "IMMUTABLE", "STABLE", "VOLATILE" (default omitted)
	OrReplace  bool
}

FunctionOptions configures CreateFunction.

type GenerateOptions

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

	// Safe wraps every destructive or creative DDL in IF [NOT] EXISTS
	// so the migration can be re-run idempotently. See DiffOptions.Safe.
	Safe bool

	// 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

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

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.2.0

type Guard interface {
	// Predicate returns the SQL expression to AND into the
	// guarded query. ctx carries the subject (set via
	// WithSubject); the guard is free to inspect any other ctx
	// values (e.g. role, tenant) to compose richer rules.
	Predicate(ctx context.Context) (drops.Expression, error)
}

Guard is the interface drops calls to materialise an authorization predicate. Implementations are stateless — every query rebuilds the expression so changes to the subject (e.g. admin impersonating a user) take effect immediately.

func AllOf added in v0.2.0

func AllOf(guards ...Guard) Guard

AllOf returns a Guard that authorises only when every guard does (AND composition). Empty conjunction errors at Predicate time.

func AnyOf added in v0.2.0

func AnyOf(guards ...Guard) Guard

AnyOf returns a Guard that authorises when any of guards does (OR composition). Empty disjunction errors at Predicate time.

type IdempotencyStore added in v0.2.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 pattern is the canonical "idempotency key" used by payment APIs; every POST endpoint that mutates money or emits events needs one.

store := pg.NewIdempotencyStore(db, "idempotency_keys", 24*time.Hour)

// In the request handler:
raw, err := store.Run(ctx, requestKey, func(tx *pg.DB) ([]byte, error) {
    // mutate state inside the tx
    _, err := PaymentEntity.Create(tx, ctx, &p)
    if err != nil { return nil, err }
    return json.Marshal(map[string]any{"paymentId": p.ID})
})

On the FIRST call with a given key the closure runs and its response is stored; subsequent calls with the same key skip the closure entirely and return the cached bytes. A failed closure rolls back the row, so the next attempt with the same key re-executes — exactly what callers expect.

The store leverages SELECT ... FOR UPDATE to serialise concurrent calls with the same key: late arrivals wait for the in-flight callback to commit, then observe its response. The wait window is bounded by the caller's context.

func NewIdempotencyStore added in v0.2.0

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

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

func (*IdempotencyStore) Cleanup added in v0.2.0

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

Cleanup removes records whose expiresAt has passed. Returns the number of rows reclaimed.

func (*IdempotencyStore) Run added in v0.2.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; later invocations with the same key return fn's previously-stored response without re-running it. A non-nil error from fn rolls back the claim so a subsequent call with the same key retries.

func (*IdempotencyStore) SweepEvery added in v0.2.0

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

SweepEvery launches a background goroutine that calls Cleanup every interval until ctx is cancelled. Errors are forwarded to onError when supplied.

type Index

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

Index describes a PostgreSQL index. It is built via NewIndex (or the fluent helpers Unique, Concurrently, Where, Include, Using, On) and rendered with CreateIndex / DropIndex.

func NewIndex

func NewIndex(name string, t *Table, cols ...drops.Expression) *Index

NewIndex declares an index on t spanning cols. Cols may be column references (*Col[T] / *Column) or arbitrary expressions — Lower(c), pg.Func("upper", c), etc. — for functional indexes.

func (*Index) Concurrently

func (i *Index) Concurrently() *Index

Concurrently emits the CONCURRENTLY keyword (PG creates the index without taking a long-lived ACCESS EXCLUSIVE lock; cannot run inside a transaction).

func (*Index) Include

func (i *Index) Include(cols ...*Column) *Index

Include adds columns to the INCLUDE clause (covering indexes).

func (*Index) Name

func (i *Index) Name() string

Name returns the unqualified index name.

func (*Index) OpClass

func (i *Index) OpClass(class VectorOpClass) *Index

OpClass attaches a per-column operator class hint to the most recent column in the index. Use it alongside Using("hnsw") or Using("ivfflat") on a vector index:

idx := pg.NewIndex("items_embedding_idx", Items, Embedding).
    Using("hnsw").
    OpClass(pg.VectorCosineOps).
    With("m = 16, ef_construction = 64")

(Index.With and OpClass are pgvector additions to the Index type; see index.go for the underlying fields.)

func (*Index) Table

func (i *Index) Table() *Table

Table returns the table the index is on.

func (*Index) Unique

func (i *Index) Unique() *Index

Unique marks the index as UNIQUE.

func (*Index) Using

func (i *Index) Using(method string) *Index

Using sets the access method (btree, hash, gin, gist, brin, spgist).

func (*Index) Where

func (i *Index) Where(pred drops.Expression) *Index

Where adds a partial-index predicate.

func (*Index) With

func (i *Index) With(spec string) *Index

With attaches a `WITH (key = value, ...)` clause — the conventional place for pgvector index parameters (m, ef_construction, lists).

type IndexSnapshot added in v0.2.0

type IndexSnapshot struct {
	Name             string         `json:"name"`
	Columns          []string       `json:"columns"`
	IsUnique         bool           `json:"isUnique"`
	Where            string         `json:"where"`
	With             map[string]any `json:"with"`
	Method           string         `json:"method"`
	Concurrently     bool           `json:"concurrently"`
	NullsNotDistinct bool           `json:"nullsNotDistinct"`
}

IndexSnapshot is one entry in TableSnapshot.Indexes. JSON keys follow drizzle-kit's v7 PostgreSQL schema.

type InsertBuilder

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

InsertBuilder composes an INSERT statement.

Rows are supplied via Row (one row at a time) or Rows (a batch). The column order across rows is fixed by the first Row call — subsequent rows must target the same set of columns; columns not bound on a row receive DEFAULT.

func (*InsertBuilder) All

func (i *InsertBuilder) All(ctx context.Context, dest any) error

All executes the INSERT and scans the RETURNING rows into dest.

func (*InsertBuilder) Exec

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

Exec runs the INSERT.

func (*InsertBuilder) OnConflictDoNothing

func (i *InsertBuilder) OnConflictDoNothing(target ...ColRef) *InsertBuilder

OnConflictDoNothing adds ON CONFLICT [(target...)] DO NOTHING.

func (*InsertBuilder) OnConflictUpdate

func (i *InsertBuilder) OnConflictUpdate(target ...ColRef) *ConflictUpdate

OnConflictUpdate begins an ON CONFLICT (target...) DO UPDATE SET ... clause. Pair with Set / Where to populate the update.

func (*InsertBuilder) One

func (i *InsertBuilder) One(ctx context.Context, dest any) error

One executes the INSERT and scans the first RETURNING row into dest.

func (*InsertBuilder) Returning

func (i *InsertBuilder) Returning(cols ...drops.Expression) *InsertBuilder

Returning sets a RETURNING clause.

func (*InsertBuilder) Row

func (i *InsertBuilder) Row(values ...ColumnValue) *InsertBuilder

Row appends a single row. The first Row determines the column list.

func (*InsertBuilder) Rows

func (i *InsertBuilder) Rows(rows ...[]ColumnValue) *InsertBuilder

Rows appends multiple rows in a single call. Equivalent to calling Row once per slice element.

func (*InsertBuilder) ToSQL

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

ToSQL renders the statement.

func (*InsertBuilder) WriteSQL

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

WriteSQL renders the INSERT statement.

type InsertHook added in v0.2.0

type InsertHook interface {
	BeforeInsert(ctx *InsertHookCtx)
}

InsertHook is invoked once per INSERT statement, before rendering. It receives an InsertHookCtx that exposes which columns the caller already bound and lets the hook append further bindings that apply to every row.

type InsertHookCtx added in v0.2.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. Hook-added expressions apply uniformly to every row in the INSERT.

func (*InsertHookCtx) Has added in v0.2.0

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

Has reports whether c is already bound on the INSERT — either by the user or by an earlier hook.

func (*InsertHookCtx) Set added in v0.2.0

func (c *InsertHookCtx) Set(v ColumnValue)

Set binds a typed ColumnValue, e.g. the result of (*Col[T]).Val(v). Equivalent to SetExpr with the binding's writer.

func (*InsertHookCtx) SetExpr added in v0.2.0

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

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

type InsertHookFunc added in v0.2.0

type InsertHookFunc func(*InsertHookCtx)

InsertHookFunc adapts a plain function to the InsertHook interface.

func (InsertHookFunc) BeforeInsert added in v0.2.0

func (f InsertHookFunc) BeforeInsert(ctx *InsertHookCtx)

BeforeInsert implements InsertHook.

type IntrospectOptions

type IntrospectOptions struct {
	// Schemas restricts the introspection to these schema names. Empty
	// means just "public".
	Schemas []string
}

IntrospectOptions tunes which schemas Introspect inspects.

type JSONPath

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

JSONPath is a typed accessor inside a jsonb column. The type parameter T fixes the Go type of the leaf value, which in turn drives the SQL cast emitted at the comparison site so the resulting predicate stays index-friendly and type-safe at declaration time:

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

var (
    Users    = pg.NewTable("users")
    UserID   = pg.Add(Users, pg.BigSerial("id").PrimaryKey())
    UserMeta = pg.Add(Users, pg.JSONB("meta"))
)

beta := pg.JSONField[bool](UserMeta, "settings", "beta")
db.Select(UserID).From(Users).Where(beta.Eq(true))
// SELECT "users"."id" FROM "users"
// WHERE (("users"."meta" -> 'settings' ->> 'beta')::boolean = $1)

JSONField stitches arbitrary-length path segments together. The final accessor uses `->>` (text) so the cast lands on a scalar; the intermediate segments use `->` so they stay jsonb until the last step.

Containment / existence operators live alongside as JSONContains / JSONHasKey — they don't need a typed leaf.

func JSONField added in v0.2.0

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

JSONField builds a typed JSONPath. col is the jsonb column, path the keys to walk. An empty path targets the column itself (useful with JSONContains).

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

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

Column returns the source jsonb column.

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

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

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

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

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

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

func (*JSONPath[T]) In added in v0.2.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.2.0

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

IsNotNull renders "(path) IS NOT NULL".

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

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

IsNull renders "(path) IS NULL". Useful to filter rows where the key is absent from the json structure entirely.

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

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

Like applies the SQL LIKE operator. Only meaningful when T is a string; the call compiles for any T but won't be useful elsewhere.

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

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

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

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

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

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

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

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

Path returns a copy of the path segments.

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

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

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

type KMS added in v0.2.0

type KMS interface {
	// Wrap encrypts dek under the master key. drops calls this
	// once per encrypted column write.
	Wrap(ctx context.Context, dek []byte) ([]byte, error)

	// Unwrap decrypts wrappedDEK. drops calls this once per
	// encrypted column read.
	Unwrap(ctx context.Context, wrappedDEK []byte) ([]byte, error)
}

KMS is the key-management contract drops uses for envelope encryption. It wraps and unwraps a data encryption key (DEK) without exposing the master key to drops.

Real implementations live in user code so drops doesn't carry a dependency on any specific KMS SDK. Common adapters are around 30 lines each — see the docs.

type Keyring added in v0.2.0

type Keyring interface {
	// Encrypt seals plaintext. The returned bytes must include any
	// nonce / authentication tag — drops treats them as opaque.
	Encrypt(plaintext []byte) ([]byte, error)
	// Decrypt undoes Encrypt. Implementations should return a
	// distinct error (typed or sentinel) on tampered / corrupted
	// ciphertext so callers can react.
	Decrypt(ciphertext []byte) ([]byte, error)
}

Keyring abstracts the encryption operation drops uses for column-level secrets. Production keyrings wrap a KMS (AWS KMS, GCP KMS, HashiCorp Vault, ...) and rotate keys behind the scenes; drops ships AESGCMKeyring as a zero-dep default for dev / smaller deployments.

func AESGCMKeyring added in v0.2.0

func AESGCMKeyring(key []byte) (Keyring, error)

AESGCMKeyring returns a Keyring backed by AES-GCM with the supplied key (16, 24 or 32 bytes for AES-128/192/256). Nonces are 96-bit random per Seal and prepended to the ciphertext.

For production: derive the key from a KMS-backed wrapping key; rotate by re-encrypting columns under a new ring and swapping it in via SetKeyring.

func ActiveKeyring added in v0.2.0

func ActiveKeyring() Keyring

ActiveKeyring returns the registered keyring or nil.

type Listener added in v0.2.0

type Listener interface {
	Listen(ctx context.Context, channel string) (<-chan Notification, error)
}

Listener is the driver contract drops uses to subscribe to a channel. Implementations should keep their own goroutine pumping notifications onto the returned channel and close it when ctx is done.

type LocalKMS added in v0.2.0

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

LocalKMS is an in-process KMS backed by a 32-byte master key. Useful for tests and local development — DO NOT use in production where the master key needs to come from an HSM / KMS service.

func NewLocalKMS added in v0.2.0

func NewLocalKMS(key []byte) (*LocalKMS, error)

NewLocalKMS wraps key (must be 32 bytes for AES-256-GCM) as a LocalKMS. The same key must be supplied on every process restart — losing it loses every encrypted row.

func (*LocalKMS) Unwrap added in v0.2.0

func (k *LocalKMS) Unwrap(_ context.Context, wrappedDEK []byte) ([]byte, error)

Unwrap is the inverse of Wrap.

func (*LocalKMS) Wrap added in v0.2.0

func (k *LocalKMS) Wrap(_ context.Context, dek []byte) ([]byte, error)

Wrap encrypts dek under the master key. The output prefixes the nonce so Unwrap can recover it without external metadata.

type LoggerFunc

type LoggerFunc = drops.LoggerFunc

LoggerFunc / LoggerOptions / LoggerHook are aliases for the dialect-neutral versions in the root drops package. They are kept here so existing pg-only call sites compile unchanged.

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

type LoggerOptions

type LoggerOptions = drops.LoggerOptions

LoggerFunc / LoggerOptions / LoggerHook are aliases for the dialect-neutral versions in the root drops package. They are kept here so existing pg-only call sites compile unchanged.

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

type MatView added in v0.2.0

type MatView struct {
	// Name is the (unquoted) view name.
	Name string

	// DependsOn lists the upstream relations (base tables and
	// other materialised views) feeding into this view. Used to
	// pick refresh order in RefreshDownstream.
	DependsOn []string

	// Mode controls REFRESH's lock semantics.
	Mode RefreshMode

	// Every, when non-zero, schedules a periodic refresh under
	// Start.
	Every time.Duration
}

MatView registers one materialised view with the manager.

type MatViewManager added in v0.2.0

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

MatViewManager tracks the registered views and coordinates refresh across dependents.

func NewMatViewManager added in v0.2.0

func NewMatViewManager(db *DB) *MatViewManager

NewMatViewManager returns an empty manager bound to db.

func (*MatViewManager) Add added in v0.2.0

Add registers v. Returns the manager for chaining. Duplicate names overwrite the prior entry — typically used when the schedule changes between deployments.

func (*MatViewManager) LastRefresh added in v0.2.0

func (m *MatViewManager) LastRefresh(name string) time.Time

LastRefresh reports the last successful refresh time for a view, or zero if it has never been refreshed by this manager.

func (*MatViewManager) Refresh added in v0.2.0

func (m *MatViewManager) Refresh(ctx context.Context, name string) error

Refresh issues REFRESH MATERIALIZED VIEW (with optional CONCURRENTLY) on the single view name. Returns an error when the view is not registered or the SQL fails.

func (*MatViewManager) RefreshAll added in v0.2.0

func (m *MatViewManager) RefreshAll(ctx context.Context) error

RefreshAll refreshes every registered view in topological order. Failures abort the run.

func (*MatViewManager) RefreshDownstream added in v0.2.0

func (m *MatViewManager) RefreshDownstream(ctx context.Context, upstream string) error

RefreshDownstream refreshes every view registered with the manager that transitively depends on upstream, in topological order. Used when an upstream base table has been written to and callers want to fan the refresh out to derived views.

func (*MatViewManager) Start added in v0.2.0

func (m *MatViewManager) Start(ctx context.Context) error

Start runs the periodic scheduler honouring each view's Every interval. Blocks until ctx is cancelled.

func (*MatViewManager) Views added in v0.2.0

func (m *MatViewManager) Views() []MatView

Views returns the registered views in name-sorted order.

func (*MatViewManager) WithPollInterval added in v0.2.0

func (m *MatViewManager) WithPollInterval(d time.Duration) *MatViewManager

WithPollInterval overrides how often Start wakes to check for due refreshes. The default (250ms) is fine for typical schedules measured in seconds; turn it down for sub-second cadences or up when refreshes are minute-scale and you want to spare the timer.

type MembershipGuard added in v0.2.0

type MembershipGuard struct {
	// Junction is the table that proves membership (e.g.
	// organization_members, project_collaborators).
	Junction *Table
	// JunctionSubject is the column of Junction holding the
	// subject identifier (the "who").
	JunctionSubject *Column
	// JunctionResource is the column of Junction pointing at the
	// resource's containing group (the "what").
	JunctionResource *Column
	// ResourceOwner is the column on the GUARDED table that
	// matches JunctionResource — e.g. invoices.organizationId
	// when invoices belong to an organization.
	ResourceOwner *Column
}

MembershipGuard authorises when the subject is a member of the resource's containing group, expressed as a junction table. Implements the M-N relationship pattern: "user can access invoice if user is in invoice's organization".

MembershipGuard{
    Junction:         OrgMembersTable,
    JunctionSubject:  OrgMembersTable.Col("userId"),
    JunctionResource: OrgMembersTable.Col("organizationId"),
    ResourceOwner:    InvoicesTable.Col("organizationId"),
}
// emits:  WHERE "invoices"."organizationId" IN (
//             SELECT "organizationId" FROM "org_members"
//             WHERE "userId" = $subject
//         )

func (MembershipGuard) Predicate added in v0.2.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 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) 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.2.0

type Mixin interface {
	Apply(*Table)
}

Mixin is the richer companion of the plain template functions in template.go. While a template function (Timestamps, SoftDelete, ...) only contributes columns, a Mixin can also register indexes, foreign keys, lifecycle hooks, and default filters in a single Apply call.

The two styles compose freely:

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

External libraries follow the same recipe: define a struct that holds the typed handles it will expose, implement Apply, and register whatever side effects the template needs.

type MixinFunc added in v0.2.0

type MixinFunc func(*Table)

MixinFunc adapts a plain function to the Mixin interface — useful when a template doesn't need its own state.

func (MixinFunc) Apply added in v0.2.0

func (f MixinFunc) Apply(t *Table)

Apply implements Mixin.

type Money added in v0.2.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 pg.Money `drop:"amount"`
}

p := Payment{Amount: pg.MoneyFromString("12.34")}
p.Amount = p.Amount.Add(pg.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.2.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.2.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.2.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.2.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.2.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.2.0

func (m Money) Cents() int64

Cents returns the underlying minor-unit count.

func (Money) Compare added in v0.2.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.2.0

func (m Money) Exponent() uint8

Exponent returns the number of decimal places.

func (Money) IsNegative added in v0.2.0

func (m Money) IsNegative() bool

IsNegative reports whether the amount is < 0.

func (Money) IsZero added in v0.2.0

func (m Money) IsZero() bool

IsZero reports whether the amount is zero.

func (Money) MarshalJSON added in v0.2.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.2.0

func (m Money) MulInt(n int64) Money

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

func (Money) MulRate added in v0.2.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.2.0

func (m Money) Neg() Money

Neg returns -m.

func (*Money) Scan added in v0.2.0

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

Scan implements sql.Scanner.

func (Money) String added in v0.2.0

func (m Money) String() string

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

func (Money) Sub added in v0.2.0

func (m Money) Sub(other Money) Money

Sub returns m - other.

func (*Money) UnmarshalJSON added in v0.2.0

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

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

func (Money) Value added in v0.2.0

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

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

type MorphMap added in v0.2.0

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

MorphMap binds a polymorphic discriminator string to the target table and Go struct it identifies. Build it once and share it between the MorphTo declarations that need it.

func NewMorphMap added in v0.2.0

func NewMorphMap() *MorphMap

NewMorphMap returns an empty MorphMap.

func RegisterMorph added in v0.2.0

func RegisterMorph[T any](m *MorphMap, value string, t *Table) *MorphMap

RegisterMorph binds value to a target table and the Go struct type T used to scan that table's rows. It is a free function rather than a method because Go does not allow generic methods.

type N1Pattern added in v0.2.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.2.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.2.0

func (r N1Report) IsClean() bool

IsClean reports whether no pattern crossed the threshold.

type Notification added in v0.2.0

type Notification struct {
	Channel string
	Payload string
	PID     int
}

Notification is one delivery from PG's NOTIFY mechanism.

type NullsOrdering added in v0.2.0

type NullsOrdering string

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

const (
	// NullsDefault inherits PostgreSQL's per-direction default.
	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.2.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 PG's default ("NULLS LAST" for ASC,
	// "NULLS FIRST" 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.2.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.2.0

func Asc(c ColRef) OrderingColumn

Asc returns an OrderingColumn for c sorted ascending. Accepts either *Column or *Col[T] via ColRef.

func Desc added in v0.2.0

func Desc(c ColRef) OrderingColumn

Desc returns an OrderingColumn for c sorted descending.

type Outbox added in v0.2.0

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

Outbox is the transactional outbox pattern wired to drops/pg. It solves the "publish only if the DB write committed" problem by writing the event 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.

// Schema setup (once)
OutboxTable := pg.NewOutboxTable("outbox")
schema := pg.NewSchema(Users, OutboxTable)
_ = pg.Push(ctx, db, schema)

// Emit inside the business transaction
ob := pg.NewOutbox(db, "outbox").WithNotifyChannel("outbox_event")
err := db.InTx(ctx, func(tx *pg.DB) error {
    if err := UserEntity.Create(tx, ctx, &u); err != nil { return err }
    return ob.EmitWith(tx, ctx, "user.created", u, pg.EmitOptions{
        AggregateType: "user",
        AggregateID:   fmt.Sprintf("%d", u.ID),
        Headers:       map[string]string{"traceparent": tp},
    })
})

// Drain in a worker goroutine
worker := pg.NewOutboxWorker(ob).
    WithInterval(1 * time.Second).
    WithMaxAttempts(10).
    WithBackoff(pg.ExponentialJitter(time.Second, 5*time.Minute)).
    WithOrdering(pg.OrderingPerAggregate).
    OnEvent(func(ctx context.Context, e pg.OutboxEvent) error {
        return publisher.Publish(ctx, e.Kind, e.Payload)
    })
go worker.Run(ctx)

The pattern is at-least-once: a crash between publish and the follow-up UPDATE marking the row published will replay it. Make the publisher idempotent on Kind+Payload — or use the per-event id as the dedup key downstream.

func NewOutbox added in v0.2.0

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

NewOutbox returns an Outbox bound to db. Table is the SQL identifier of the outbox table; use NewOutboxTable to declare matching DDL.

func (*Outbox) Cleanup added in v0.2.0

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

Cleanup deletes rows that were published more than retainAfter ago. Run periodically (typically once per minute) to keep the outbox table small; without it the table grows unbounded and the drain index, although partial, still has to skip stale rows during VACUUM.

Failed (terminal) rows are intentionally kept — they're the audit log of poison messages. Drop them manually after triage.

func (*Outbox) Drain added in v0.2.0

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

Drain fetches up to limit unpublished, non-failed, available events for processing. Uses SKIP LOCKED so multiple workers can drain in parallel without stepping on each other. Caller must mark rows published via MarkPublished when the handler succeeds, or MarkFailed when it errors.

func (*Outbox) DrainAggregate added in v0.2.0

func (o *Outbox) DrainAggregate(ctx context.Context, aggregateType, aggregateID string, limit int, fn func(tx *DB, events []OutboxEvent) error) error

DrainAggregate fetches events for a single aggregate in id order, under a transaction-scoped advisory lock keyed on the aggregate ID. Parallel workers calling DrainAggregate for the same aggregate skip silently (the lock is non-blocking), so per-aggregate order is preserved without serialising the whole worker pool.

The callback executes within the lock-holding transaction; the lock auto-releases when the transaction ends. Returning an error rolls the transaction back — typically used so partial progress (MarkPublished calls) inside the callback is undone on failure.

func (*Outbox) Emit added in v0.2.0

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

Emit inserts an event using tx (typically the *DB passed into the InTx callback). Encodes payload as JSON; pass an already-encoded json.RawMessage to keep control of the serialisation.

Equivalent to EmitWith with zero EmitOptions.

func (*Outbox) EmitWith added in v0.2.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 — see EmitOptions.

func (*Outbox) MarkFailed added in v0.2.0

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

MarkFailed records a handler failure on the supplied event. When nextRetryAt is the zero value the row is parked as terminally failed (failedAt set, won't be drained again). Otherwise the row becomes available for retry at nextRetryAt with attempts bumped and the error message stored in lastError.

func (*Outbox) MarkPublished added in v0.2.0

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

MarkPublished updates the publishedAt timestamp for the supplied event ids. Safe to call repeatedly; subsequent drains skip published rows.

func (*Outbox) NotifyChannel added in v0.2.0

func (o *Outbox) NotifyChannel() string

NotifyChannel returns the configured pg_notify channel, or "" if none was set.

func (*Outbox) PendingAggregates added in v0.2.0

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

PendingAggregates returns up to limit distinct aggregate keys that have unpublished work available right now. Used by the per- aggregate worker mode to discover which advisory locks to try.

func (*Outbox) WithNotifyChannel added in v0.2.0

func (o *Outbox) WithNotifyChannel(channel string) *Outbox

WithNotifyChannel makes Emit also issue pg_notify(channel, id) so a worker that LISTENs on the same channel wakes immediately instead of waiting for the polling tick. Falls back gracefully when the driver does not implement Listener — Emit succeeds either way; only the wakeup latency degrades.

type OutboxBatchHandler added in v0.2.0

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

OutboxBatchHandler is the batched alternative — receives the entire drain batch in one call. Useful for message brokers with expensive per-call overhead (Kafka producer, etc.).

Returning nil marks every event in the batch published; returning an error fails every event in the batch (each row's attempts is bumped). Use OnEvent if you need per-event success granularity.

type OutboxEvent added in v0.2.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.2.0

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

OutboxHandler is the per-event callback the worker invokes when configured via OnEvent. Returning nil marks the row published; returning an error schedules a retry (or terminal failure when MaxAttempts has been reached).

type OutboxOrdering added in v0.2.0

type OutboxOrdering int

OutboxOrdering controls how the worker schedules events.

const (
	// OrderingNone delivers events as soon as drain returns them.
	// Highest throughput; events for different aggregates can
	// interleave, and parallel workers may deliver out of order
	// within the same aggregate when timestamps tie.
	OrderingNone OutboxOrdering = iota

	// OrderingPerAggregate preserves emission order within each
	// (AggregateType, AggregateID) by routing each aggregate
	// through a single worker at a time via advisory locks.
	// Events that lack an AggregateID fall back to OrderingNone.
	OrderingPerAggregate
)

type OutboxWorker added in v0.2.0

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

OutboxWorker polls the outbox table and forwards rows to a handler. Single goroutine per worker; spin up several with distinct names to parallelise drain — SKIP LOCKED keeps them from collisions.

func NewOutboxWorker added in v0.2.0

func NewOutboxWorker(ob *Outbox) *OutboxWorker

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

func (*OutboxWorker) OnBatch added in v0.2.0

OnBatch attaches the batch handler. Mutually exclusive with OnEvent — whichever was set last wins.

func (*OutboxWorker) OnError added in v0.2.0

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

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

func (*OutboxWorker) OnEvent added in v0.2.0

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

OnEvent attaches the per-event handler. Mutually exclusive with OnBatch — whichever was set last wins.

func (*OutboxWorker) Run added in v0.2.0

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

Run drains the outbox until ctx is cancelled. Blocks the calling goroutine; typically invoked via go worker.Run(ctx). Returns the reason for termination — typically ctx.Err().

When the underlying Outbox was configured with WithNotifyChannel and the driver implements Listener, Run wakes immediately on each NOTIFY in addition to the periodic tick — sub-second delivery without hammering the database.

func (*OutboxWorker) WithBackoff added in v0.2.0

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

WithBackoff overrides the per-attempt retry delay. The function receives the new attempt count (1-based) and returns how long to wait before the next try. Defaults to ExponentialJitter(1s, 5min).

func (*OutboxWorker) WithBatch added in v0.2.0

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

WithBatch overrides the per-tick batch size.

func (*OutboxWorker) WithInterval added in v0.2.0

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

WithInterval overrides the polling cadence. Returns the worker for chaining.

func (*OutboxWorker) WithMaxAttempts added in v0.2.0

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

WithMaxAttempts caps the retry count. After n attempts the row is marked terminally failed (failedAt set) and never drained again — surface it via metrics / alerting and drop manually after triage. Zero means unlimited retries.

func (*OutboxWorker) WithOrdering added in v0.2.0

func (w *OutboxWorker) WithOrdering(m OutboxOrdering) *OutboxWorker

WithOrdering selects the scheduling strategy. See OutboxOrdering.

type OwnerGuard added in v0.2.0

type OwnerGuard struct {
	Owner *Column
}

OwnerGuard authorises when the subject matches an ownership column on the resource — the simplest possible rule.

OwnerGuard{Owner: PostsTable.Col("authorId")}
// emits:  WHERE "posts"."authorId" = $subject

func (OwnerGuard) Predicate added in v0.2.0

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

Predicate implements Guard.

type PIIParam added in v0.2.0

type PIIParam struct{ Value any }

PIIParam is a drops.Expression that AddArgs a value already wrapped in the redaction marker. Used internally by entity bindings when the column is flagged PII.

func (PIIParam) WriteSQL added in v0.2.0

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

WriteSQL implements drops.Expression.

type Page added in v0.2.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.2.0

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

PageBuilder composes a cursor-paginated query. It exists to keep 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.2.0

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

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

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

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

All runs the query and returns the page.

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

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

Limit caps the page size. Defaults to 50.

func (*PageBuilder[T]) OrderBy added in v0.2.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 column should be unique (typically the PK) so every row has a distinct cursor.

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

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

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

type PatchOp added in v0.2.0

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

PatchOp describes one SET assignment in a Patch. Construct one with Inc / Dec / SetVal / SetIfGreater / SetIfLess / SetIfChanged.

func Dec added in v0.2.0

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

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

func Inc added in v0.2.0

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

Inc emits "col = col + delta". For unsigned counters use a non-negative delta; for decrements pass a negative one or use Dec.

func Set added in v0.2.0

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

Set is a typed plain assignment — equivalent to (*Col[T]).Val but usable in the Patch op list alongside the SQL-side ops.

func SetIfChanged added in v0.2.0

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

SetIfChanged emits "col = $1" only when $1 differs from the current value — implemented as a CASE WHEN so the row is touched (and triggers fire) even if no change happens. Useful when the surrounding entity has hook-driven side effects you don't want to elide silently.

func SetIfGreater added in v0.2.0

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

SetIfGreater emits "col = GREATEST(col, $1)" — only raises the value, never lowers it. Useful for high-watermark counters (max score, last-seen timestamp).

func SetIfLess added in v0.2.0

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

SetIfLess emits "col = LEAST(col, $1)" — only lowers the value, never raises it. Useful for low-watermark counters.

type PgEnum

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

PgEnum describes a PostgreSQL enum type. Use it to declare the type once and then reference it from one or more columns via EnumCol.

func NewEnum

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

NewEnum declares a PostgreSQL enum type with the given values.

func (*PgEnum) Col

func (e *PgEnum) Col(name string) *Col[string]

EnumCol returns a column of this enum type. The Go value type is string — drizzle-orm uses the same mapping. Use Custom[Foo] if you have a typed string wrapper you want preserved instead.

func (*PgEnum) Name

func (e *PgEnum) Name() string

Name returns the enum's type name.

func (*PgEnum) Values

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

Values returns the labels in declaration order.

type PgError added in v0.2.0

type PgError struct {
	// Code is the five-character SQLSTATE class returned by the
	// driver, e.g. "23505".
	Code string

	// Constraint is the offending constraint name when the driver
	// reports it. Empty for failures unrelated to a constraint.
	Constraint string

	// Sentinel is the package-level Err* value classifying the
	// error. nil when the SQLSTATE was recognised but is not
	// mapped, or when no SQLSTATE was reported at all.
	Sentinel error

	// Err is the original driver error. Use errors.Unwrap to reach
	// it directly.
	Err error
}

PgError wraps a driver-level error with the matching typed Sentinel, the SQLSTATE class, and (when available) the constraint name. Use errors.Is(err, pg.ErrXxx) to branch on the failure mode, or type-assert through errors.As to read Code / Constraint.

func (*PgError) Error added in v0.2.0

func (e *PgError) Error() string

Error implements error.

func (*PgError) Is added in v0.2.0

func (e *PgError) Is(target error) bool

Is matches the package sentinel so errors.Is(err, pg.ErrXxx) returns true for the appropriate failure mode.

func (*PgError) Unwrap added in v0.2.0

func (e *PgError) Unwrap() error

Unwrap returns the original driver error so errors.As can walk the chain.

type PgSequence added in v0.2.0

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

PgSequence describes a top-level PostgreSQL sequence. Wrap SequenceOptions (already used by CreateSequence) so the configuration matches whatever DDL the runtime emits at apply time.

func NewSequence added in v0.2.0

func NewSequence(name string, opts ...SequenceOptions) *PgSequence

NewSequence declares a sequence by name with optional configuration. opts is variadic so the common "default everything" case is a single-arg call.

func (*PgSequence) Name added in v0.2.0

func (s *PgSequence) Name() string

Name returns the sequence's identifier.

func (*PgSequence) Options added in v0.2.0

func (s *PgSequence) Options() SequenceOptions

Options returns the configuration the sequence was declared with.

type PgView added in v0.2.0

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

PgView describes a (regular or materialized) view. Definition is the body that follows AS — typically a SELECT.

func MaterializedView added in v0.2.0

func MaterializedView(name string) *PgView

MaterializedView is the materialised counterpart of View — the same fluent builder with the materialised flag pre-set so the chain reads naturally.

func NewMaterializedView added in v0.2.0

func NewMaterializedView(name, definition string) *PgView

NewMaterializedView declares a materialized view.

func NewView added in v0.2.0

func NewView(name, definition string) *PgView

NewView declares a regular VIEW.

func View added in v0.2.0

func View(name string) *PgView

View opens a fluent builder for a regular view. Chain As (typed SelectBuilder) or AsSQL (raw SELECT text), optionally Materialized, to finalise:

pg.View("activeUsers").As(
    db.Select(Users.ID, Users.Name).
        From(Users).
        Where(Users.Active.Eq(true)))

pg.View("playerStats").
    Materialized().
    As(query)

NewView / NewMaterializedView remain available for the definition-as-string fast path.

func (*PgView) As added in v0.2.0

func (v *PgView) As(sel *SelectBuilder) *PgView

As wires the view body from a SelectBuilder. Parameter bindings ($1, $2, ...) are inlined as SQL literals because view definitions are static SQL — CREATE VIEW doesn't accept bound parameters. Unsupported parameter types panic at declaration time so the schema fails loudly at startup instead of producing broken DDL.

func (*PgView) AsSQL added in v0.2.0

func (v *PgView) AsSQL(definition string) *PgView

AsSQL sets the view body from raw SQL — escape hatch for SELECT shapes the builder doesn't cover (recursive CTEs, dialect-specific functions, hand-tuned plans). Caller is responsible for any literal escaping.

func (*PgView) Definition added in v0.2.0

func (v *PgView) Definition() string

Definition returns the view's SELECT body.

func (*PgView) IsMaterialized added in v0.2.0

func (v *PgView) IsMaterialized() bool

IsMaterialized reports whether this is a materialized view.

func (*PgView) Materialized added in v0.2.0

func (v *PgView) Materialized() *PgView

Materialized flips the view kind to materialised. Returns the same *PgView for chaining.

func (*PgView) Name added in v0.2.0

func (v *PgView) Name() string

Name returns the view's identifier.

type PlanDiff added in v0.2.0

type PlanDiff struct {
	// BeforeFingerprint / AfterFingerprint are the structural
	// hashes used to detect change. Identical values mean the
	// planner produced equivalent shapes.
	BeforeFingerprint string
	AfterFingerprint  string

	// Same is shorthand for BeforeFingerprint == AfterFingerprint.
	Same bool

	// SeqScansAdded / SeqScansRemoved name the relations that
	// gained / lost a Seq Scan between the two plans. A regression
	// commonly shows up as a relation moving from index to seq.
	SeqScansAdded   []string
	SeqScansRemoved []string

	// IndexesAdded / IndexesRemoved name the indexes the planner
	// started / stopped using.
	IndexesAdded   []string
	IndexesRemoved []string

	// CostDelta is the difference in TotalCost (after - before).
	// Positive means the new plan is more expensive.
	CostDelta float64

	// RowsDelta is the difference in estimated rows
	// (after - before).
	RowsDelta int64
}

PlanDiff describes the structural change between two plans for the same query. Same is true when fingerprints match — every other field is then zero / empty.

func DiffPlans added in v0.2.0

func DiffPlans(before, after *ExplainPlan) PlanDiff

DiffPlans compares two captured plans of the same query. Returns a structured diff highlighting plan shape changes that typically matter for tail latency.

type PlanNode added in v0.2.0

type PlanNode struct {
	// Type is the PostgreSQL node label — "Seq Scan", "Index
	// Scan", "Hash Join", "Sort", "Aggregate", ...
	Type string

	// Relation is the table name when Type touches a relation.
	Relation string

	// Index is the index name when Type is an index scan / search.
	Index string

	// JoinType is the join kind when Type is a join node.
	JoinType string

	// StartupCost / TotalCost are the planner's cost estimates.
	StartupCost float64
	TotalCost   float64

	// PlanRows is the planner's row-count estimate; ActualRows
	// the EXPLAIN ANALYZE measured value (0 without Analyze).
	PlanRows   int64
	ActualRows int64

	// ActualMs is the total time spent in this node when Analyze
	// was set. Zero otherwise.
	ActualMs float64

	// Children are the sub-plans feeding into this node, in
	// declaration order.
	Children []*PlanNode
}

PlanNode is one node in the plan tree.

type Point added in v0.2.0

type Point struct {
	Lat float64
	Lon float64
}

Point is a (latitude, longitude) pair in WGS84 (SRID 4326).

func (*Point) Scan added in v0.2.0

func (p *Point) Scan(src any) error

Scan implements sql.Scanner. Accepts:

  • WKT text: "POINT(lon lat)" or "SRID=4326;POINT(lon lat)"
  • EWKB hex: the default form PostGIS returns from a geography column.

Drivers that pre-parse the geometry into a custom type are not supported; force text output via ST_AsText if needed.

func (Point) String added in v0.2.0

func (p Point) String() string

String renders the canonical SRID-tagged WKT used by Value.

func (Point) Value added in v0.2.0

func (p Point) Value() (driver.Value, error)

Value implements driver.Valuer — emits SRID-tagged WKT so the PostgreSQL geography type accepts it directly.

type Policy added in v0.2.0

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

Policy is a row-level security policy attached to a table. The shape mirrors PostgreSQL's CREATE POLICY syntax exactly so emitting DDL stays mechanical.

func NewPolicy added in v0.2.0

func NewPolicy(name string) *Policy

NewPolicy declares a row-level security policy by name. Pair with Table.AddPolicy.

func (*Policy) As added in v0.2.0

func (p *Policy) As() string

As returns the policy mode ("PERMISSIVE" or "RESTRICTIVE").

func (*Policy) Command added in v0.2.0

func (p *Policy) Command() string

Command returns the SQL command the policy applies to.

func (*Policy) For added in v0.2.0

func (p *Policy) For(cmd string) *Policy

For sets the command the policy applies to ("ALL", "SELECT", "INSERT", "UPDATE", "DELETE"). Defaults to "ALL".

func (*Policy) Name added in v0.2.0

func (p *Policy) Name() string

Name returns the policy identifier.

func (*Policy) Restrictive added in v0.2.0

func (p *Policy) Restrictive() *Policy

Restrictive flips the policy to RESTRICTIVE (default is PERMISSIVE).

func (*Policy) Roles added in v0.2.0

func (p *Policy) Roles() []string

Roles returns the roles the policy is scoped to (or nil for PUBLIC).

func (*Policy) To added in v0.2.0

func (p *Policy) To(roles ...string) *Policy

To restricts the policy to the named roles. Without any role the policy applies to PUBLIC.

func (*Policy) Using added in v0.2.0

func (p *Policy) Using(expr string) *Policy

Using sets the USING expression — the predicate that decides which rows are visible / mutable.

func (*Policy) UsingExpr added in v0.2.0

func (p *Policy) UsingExpr() string

UsingExpr returns the USING expression text.

func (*Policy) WithCheck added in v0.2.0

func (p *Policy) WithCheck(expr string) *Policy

WithCheck sets the WITH CHECK expression — the predicate that rows must satisfy after an INSERT / UPDATE. Defaults to the USING expression when omitted.

func (*Policy) WithCheckExpr added in v0.2.0

func (p *Policy) WithCheckExpr() string

WithCheckExpr returns the WITH CHECK expression text.

type PolicySnapshot added in v0.2.0

type PolicySnapshot struct {
	Name      string   `json:"name"`
	As        string   `json:"as"`
	For       string   `json:"for"`
	To        []string `json:"to"`
	Using     string   `json:"using"`
	WithCheck string   `json:"withCheck"`
}

PolicySnapshot is one entry in TableSnapshot.Policies.

type PoolStats added in v0.2.0

type PoolStats struct {
	// MaxOpenConnections is the configured pool ceiling.
	MaxOpenConnections int
	// OpenConnections is the current count of live conns.
	OpenConnections int
	// InUse is the count currently held by a caller.
	InUse int
	// Idle is the count parked in the pool.
	Idle int
	// WaitCount is the cumulative count of waits for a free
	// conn since pool creation.
	WaitCount int64
	// WaitDuration is the cumulative time waited.
	WaitDuration time.Duration
	// MaxIdleClosed is the cumulative count of conns closed
	// because Idle exceeded MaxIdleConns.
	MaxIdleClosed int64
	// MaxIdleTimeClosed is the cumulative count closed because
	// they sat idle longer than MaxIdleTime.
	MaxIdleTimeClosed int64
	// MaxLifetimeClosed is the cumulative count closed because
	// they lived longer than ConnMaxLifetime.
	MaxLifetimeClosed int64
}

PoolStats is a snapshot of the underlying pool's health counters. Mirrors database/sql.DBStats with descriptive names so prometheus / OpenTelemetry adapters are direct.

type PoolStatsProvider added in v0.2.0

type PoolStatsProvider interface {
	Stats() PoolStats
}

PoolStatsProvider is the contract a driver implements when it can surface pool stats. database/sql.DB satisfies it natively (Stats() returns sql.DBStats which is the same shape).

type PushOptions

type PushOptions struct {
	// Schema restricts introspection to one PostgreSQL schema. Empty
	// defaults to "public".
	Schema string

	// Safe wraps every destructive or creative DDL in IF [NOT] EXISTS
	// so the apply is idempotent and safe to retry.
	Safe bool

	// 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

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 when the diff was empty).
	Applied bool
}

PushResult is the outcome of a Push call.

func Push

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 — drops's equivalent of drizzle-kit's `push` command.

Behaviour:

  • Reads the current state of the configured schema via Introspect.
  • Builds a target snapshot from `schema`.
  • Diffs the two using DiffOptions{Safe: opts.Safe}.
  • If DryRun, returns the statements unexecuted.
  • Otherwise applies them inside a single transaction; any failure rolls back the whole push.

Push is convenient for development but skips migration history. For production use, prefer GenerateMigration + DrizzleMigrator so changes are reviewable and reproducible.

type RefreshMode added in v0.2.0

type RefreshMode int

RefreshMode controls whether REFRESH locks the view or runs CONCURRENTLY (no lock, but the view must have a UNIQUE index).

const (
	// RefreshLocking issues plain REFRESH MATERIALIZED VIEW —
	// fastest but takes ACCESS EXCLUSIVE on the view for the
	// duration. Acceptable on small views during quiet hours.
	RefreshLocking RefreshMode = iota
	// RefreshConcurrently issues REFRESH MATERIALIZED VIEW
	// CONCURRENTLY — readers stay unblocked but the view must
	// have a UNIQUE index defined.
	RefreshConcurrently
)

type RelConfig

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

RelConfig configures a single eager-loaded relation: filter the related rows with Where, order them with OrderBy, and declare deeper relations with With/WithRel. It is handed to the callback passed to WithRel.

Where predicates and OrderBy expressions reference the related table's columns and are applied to that relation's batched query, so they cost nothing extra — one query per edge, just narrower or sorted.

func (*RelConfig) Limit added in v0.2.0

func (c *RelConfig) Limit(n int) *RelConfig

Limit caps the number of related rows attached PER PARENT. Implemented via a ROW_NUMBER() window over the join key:

SELECT * FROM (
  SELECT *, ROW_NUMBER() OVER (PARTITION BY <fk> ORDER BY <orderBy>) AS _rn
  FROM <child> WHERE <fk> IN (...)
) AS _ranked
WHERE _rn > <offset> AND _rn <= <offset> + <limit>

so each parent's slice gets the first N children (after the configured ORDER BY), not the first N rows globally. Mirrors drizzle's with: { posts: { limit: 5 } } shape.

Limit only applies to HasMany / MorphMany / ManyToMany — single-row relations (HasOne / BelongsTo) already cap at 1. Limit ≤ 0 disables the cap.

func (*RelConfig) Offset added in v0.2.0

func (c *RelConfig) Offset(n int) *RelConfig

Offset skips the first N matching rows per parent. Requires Limit to be set (offset alone produces every row past N which rarely matches caller intent — when you really need that, combine Offset with a very large Limit).

func (*RelConfig) OrderBy

func (c *RelConfig) OrderBy(exprs ...drops.Expression) *RelConfig

OrderBy appends ORDER BY expressions to this relation's batched query. Each parent's loaded slice ends up in this order.

func (*RelConfig) Where

func (c *RelConfig) Where(preds ...drops.Expression) *RelConfig

Where AND-s predicates into this relation's batched query, filtering the related rows (e.g. only published posts).

func (*RelConfig) With

func (c *RelConfig) With(names ...string) *RelConfig

With declares deeper relations to eager-load beneath this one, using the same dot-path syntax as FindBuilder.With.

func (*RelConfig) WithRel

func (c *RelConfig) WithRel(name string, fn func(*RelConfig)) *RelConfig

WithRel declares a deeper, individually configured relation beneath this one.

type Relation

type Relation struct {
	Name      string
	Kind      RelationKind
	From      *Table
	To        *Table
	ParentKey *Column
	ChildKey  *Column

	// Junction-table fields, populated only for ManyToManyKind.
	Through    *Table
	ThroughFK1 *Column
	ThroughFK2 *Column

	// Polymorphic fields, populated for MorphToKind / MorphManyKind.
	// MorphTypeCol is the discriminator column.
	// MorphMap maps the discriminator value to a target table + Go
	// struct type (MorphToKind only).
	// MorphType is the discriminator value identifying this side of
	// the polymorphic edge (MorphManyKind only).
	MorphTypeCol *Column
	MorphMap     *MorphMap
	MorphType    string
}

Relation describes a foreign-key relationship between two tables.

HasMany / HasOne: From is the parent, To is the child.
  ParentKey is the column on From (typically the PK).
  ChildKey  is the FK column on To pointing back at ParentKey.

BelongsTo: From is the child, To is the parent.
  ChildKey  is the FK column on From.
  ParentKey is the column on To pointed at by ChildKey.

ManyToMany: From and To are joined through a junction Table.
  ParentKey  is the column on From (typically the PK).
  ChildKey   is the column on To (typically the PK).
  Through    is the junction table.
  ThroughFK1 is the FK on Through pointing back to ParentKey.
  ThroughFK2 is the FK on Through pointing to ChildKey.

type RelationKind

type RelationKind int

RelationKind identifies the cardinality of a Relation.

const (
	// HasMany: the parent owns zero or more children. The relation is
	// loaded as a slice on the parent struct.
	HasManyKind RelationKind = iota
	// HasOne: the parent owns at most one child. The relation is loaded
	// as a struct or pointer-to-struct on the parent.
	HasOneKind
	// BelongsTo: this row references one parent. The relation is loaded
	// as a struct or pointer-to-struct.
	BelongsToKind
	// ManyToMany: parent and target are linked through a junction table.
	// The relation is loaded as a slice on the parent struct.
	ManyToManyKind
	// MorphTo: a polymorphic belongs-to — each row references a parent
	// in one of several possible tables, identified by a type
	// discriminator column. The relation field is typed `any` and
	// holds a pointer to the loaded parent struct after eager loading.
	MorphToKind
	// MorphMany: the inverse of MorphTo — fetches every child whose
	// morph_type equals a fixed value and morph_id equals the parent's
	// PK. The relation is loaded as a slice on the parent struct.
	MorphManyKind
)

type Relations

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

Relations is the registration handle for one table. Use NewRelations followed by HasMany/HasOne/BelongsTo to declare relations:

pg.NewRelations(Users).
    HasMany("posts", Posts, UserID, PostUserID).
    HasOne("profile", Profiles, UserID, ProfileUserID)

pg.NewRelations(Posts).
    BelongsTo("author", Users, PostUserID, UserID)

func NewRelations

func NewRelations(t *Table) *Relations

NewRelations begins relation declarations for t. The returned handle stores its declarations on t directly.

func (*Relations) BelongsTo

func (r *Relations) BelongsTo(name string, parent *Table, childFK, parentKey ColRef) *Relations

BelongsTo declares a many-to-one relation.

childFK:   FK column on the current table.
parentKey: column on `parent` pointed at by childFK.

func (*Relations) HasMany

func (r *Relations) HasMany(name string, child *Table, parentKey, childFK ColRef) *Relations

HasMany declares a one-to-many relation.

parentKey: column on the current table (usually the PK).
childFK:   FK column on `child` pointing at parentKey.

func (*Relations) HasOne

func (r *Relations) HasOne(name string, child *Table, parentKey, childFK ColRef) *Relations

HasOne declares a one-to-one relation. The cardinality is enforced by data conventions, not by SQL — it loads at most one row per parent.

func (*Relations) ManyToMany

func (r *Relations) ManyToMany(
	name string,
	target *Table,
	through *Table,
	throughLocal, throughRemote ColRef,
	localKey, targetKey ColRef,
) *Relations

ManyToMany declares a many-to-many relation through a junction table.

target:        the related table
through:       the junction table
throughLocal:  FK on through pointing back at the current table
throughRemote: FK on through pointing at the target table
localKey:      key on the current table (typically the PK)
targetKey:     key on the target table (typically the PK)

Loading runs two queries: one to fetch the junction rows for the parent IDs, one to fetch the target rows for the unique remote IDs. The relation field on the parent struct must be a slice.

func (*Relations) MorphMany added in v0.2.0

func (r *Relations) MorphMany(
	name string,
	child *Table,
	childTypeCol, childIDCol ColRef,
	parentKey ColRef,
	typeValue string,
) *Relations

MorphMany declares the inverse of MorphTo: every row in `child` whose typeCol equals typeValue and whose idCol equals the parent's PK becomes a member of the parent's named relation.

pg.NewRelations(Users).
    MorphMany("comments", Comments,
        CommentCommentableType, CommentCommentableID,
        UserID, "users")

typeValue is the literal string the child's typeCol carries when it points at this side of the morph — typically the parent's table name in the MorphMap registered on the MorphTo side.

func (*Relations) MorphTo added in v0.2.0

func (r *Relations) MorphTo(name string, typeCol, idCol ColRef, morphs *MorphMap) *Relations

MorphTo declares a polymorphic belongs-to relation. The current table has two columns — typeCol holding a discriminator string and idCol holding the target's primary-key value. Each entry registered on morphs binds a discriminator value to a target table and the Go struct used to scan its rows.

morphs := pg.NewMorphMap()
pg.RegisterMorph[User](morphs, "users", Users)
pg.RegisterMorph[Post](morphs, "posts", Posts)

pg.NewRelations(Comments).
    MorphTo("commentable", CommentCommentableType, CommentCommentableID, morphs)

The relation field on the child struct must be declared `any` (or any interface type) — the loader stores a *Parent pointer into it and the caller dispatches with a type switch.

type Replicated added in v0.2.0

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

Replicated wraps a primary driver and any number of read-only replicas. Exec / Begin always go to the primary; Query is routed round-robin to a replica unless the caller is inside a read-your-writes window (see WithReadYourWrites), in which case reads stick to the primary so a follow-up SELECT after a write observes the new state.

It implements drops.Driver, so you wire it into a regular *pg.DB:

primary  := stdlib.New(primarySQL)
r1       := stdlib.New(replica1SQL)
r2       := stdlib.New(replica2SQL)
repl     := pg.NewReplicated(primary, r1, r2)
db       := pg.New(repl)

// Sticky read-your-writes: any Exec on ctx starts a 2s window;
// follow-up Queries stay on the primary for that window so
// the read observes the prior write.
ctx = pg.WithReadYourWrites(ctx, 2*time.Second)
_ = UserEntity.Update(db, ctx, &u)
got, _ := UserEntity.Get(db, ctx, u.ID)  // hits primary

Without replicas, Replicated is identical to wrapping primary directly — Query falls back to the primary.

func NewReplicated added in v0.2.0

func NewReplicated(primary drops.Driver, replicas ...drops.Driver) *Replicated

NewReplicated returns a Replicated driver. Pass zero replicas for a primary-only setup that still respects WithReadYourWrites.

func (*Replicated) Begin added in v0.2.0

func (r *Replicated) Begin(ctx context.Context) (drops.Tx, error)

Begin opens a transaction on the primary. Transactional reads share the same connection as the writes, so consistency is guaranteed without needing the read-your-writes window.

func (*Replicated) Close added in v0.2.0

func (r *Replicated) Close() error

Close closes every underlying driver that exposes Close, returning the first error encountered.

func (*Replicated) Exec added in v0.2.0

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

Exec routes through the primary. The call also re-arms any read-your-writes window attached to ctx so subsequent reads on the same context stay sticky — and, when LSN tracking is enabled, captures pg_current_wal_lsn() so reads can route to a caught-up replica instead of unconditionally pinning to primary.

func (*Replicated) LSNTracking added in v0.2.0

func (r *Replicated) LSNTracking() bool

LSNTracking reports whether LSN routing is active.

func (*Replicated) Primary added in v0.2.0

func (r *Replicated) Primary() drops.Driver

Primary returns the primary driver, useful for code paths that must opt out of replica routing.

func (*Replicated) Query added in v0.2.0

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

Query routes to a replica unless ctx is inside an active read-your-writes window. With LSN tracking enabled, reads inside a window route to whichever replica has replayed past the captured write LSN — only falling back to primary when none have caught up. Without LSN tracking, the time-based stickiness pins reads to primary for the configured duration.

func (*Replicated) Replicas added in v0.2.0

func (r *Replicated) Replicas() []drops.Driver

Replicas returns the configured replicas in declaration order.

func (*Replicated) WithLSNTracking added in v0.2.0

func (r *Replicated) WithLSNTracking(ttl time.Duration) *Replicated

WithLSNTracking enables LSN-based replica routing on r. TTL is the max age of cached per-replica LSN samples; a query asks a replica for a fresh value when its cache entry is older than TTL.

Without LSN tracking, the read-your-writes window falls back to the time-based stickiness inherited from Replicated.

type RetryPolicy added in v0.2.0

type RetryPolicy struct {
	// MaxAttempts caps the total runs of the callback. Values < 1
	// are treated as 1 (no retries).
	MaxAttempts int

	// Errors are sentinel values; a returned error is retried when
	// errors.Is(err, e) is true for any e in the slice. 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). Use ExponentialJitter or supply your own. nil
	// disables sleeping between attempts.
	Backoff func(attempt int) time.Duration
}

RetryPolicy declares how InTx (and InTxRetry) should react to transient failures — typically SerializationFailure and Deadlock under SERIALIZABLE isolation. Without a policy InTx behaves as before: every error propagates immediately.

Configure once at DB setup:

db := pg.New(drv).WithRetry(pg.RetryPolicy{
    MaxAttempts: 3,
    Errors:      []error{pg.ErrSerializationFailure, pg.ErrDeadlock},
    Backoff:     pg.ExponentialJitter(10*time.Millisecond, 1*time.Second),
})

InTx then retries the supplied callback up to MaxAttempts times. Between attempts it sleeps for Backoff(attempt) — attempts are 1-based, so Backoff(1) returns the wait BEFORE the second try. Context cancellation short-circuits both the sleep and the loop.

The retry is at the transaction level — the entire callback is re-run inside a fresh transaction each time. Callbacks must be idempotent across retries. Read what you wrote before the rollback? Re-do it. Side effects (emails, HTTP, …)? Push them to the outbox so the rollback also rolls them back.

func DefaultRetryPolicy added in v0.2.0

func DefaultRetryPolicy() RetryPolicy

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

type Risk added in v0.2.0

type Risk struct {
	Statement  string
	Level      RiskLevel
	Reason     string
	Suggestion string
}

Risk is one finding from AnalyzeMigrationRisks.

func AnalyzeMigrationRisks added in v0.2.0

func AnalyzeMigrationRisks(stmts []string) []Risk

AnalyzeMigrationRisks returns a Risk for every statement in stmts that needs a second look. Statements considered safe produce no entry. The matcher is a pragmatic regex-based scan — false positives are preferred to false negatives.

type RiskLevel added in v0.2.0

type RiskLevel string

RiskLevel categorises how unsafe a statement is on a table that might be large or under read/write load.

const (
	// RiskInfo is for changes that are safe but worth flagging
	// (irreversible, requires app deploys, etc.).
	RiskInfo RiskLevel = "info"
	// RiskWarn covers changes that lock briefly or rewrite a
	// small amount of data. Often safe but worth scheduling.
	RiskWarn RiskLevel = "warn"
	// RiskDanger reserves catastrophic-on-large-tables changes:
	// table rewrites, blocking index builds, dropping tables.
	RiskDanger RiskLevel = "danger"
)

type SafetyOptions added in v0.2.0

type SafetyOptions struct {
	// Ignore lists rule IDs to skip. Useful when a particular
	// migration is known-safe (e.g. small table, scheduled
	// downtime).
	Ignore []string
}

SafetyOptions tunes the analyser — currently used for rule suppression. Add to it as the rule set grows.

type SafetySeverity added in v0.2.0

type SafetySeverity int

SafetySeverity ranks a warning's urgency.

const (
	// SeverityInfo is a heads-up — usually fine, occasionally
	// worth a second look.
	SeverityInfo SafetySeverity = iota
	// SeverityWarn flags a statement that will likely cause
	// downtime or visible behaviour change on a non-trivial
	// table. Reviewable.
	SeverityWarn
	// SeverityError flags a statement that almost certainly
	// breaks production at any reasonable table size — full
	// table rewrites, exclusive locks held indefinitely,
	// unrecoverable data loss.
	SeverityError
)

func (SafetySeverity) String added in v0.2.0

func (s SafetySeverity) String() string

String renders the level as "info" / "warn" / "error".

type SafetyWarning added in v0.2.0

type SafetyWarning struct {
	// Severity ranks the urgency — info / warn / error.
	Severity SafetySeverity

	// Rule is a stable identifier for the check (e.g.
	// "add-not-null-column"). Use it to suppress a known-
	// acceptable warning via SafetyOptions.Ignore.
	Rule string

	// Statement is the offending SQL fragment, trimmed of
	// surrounding whitespace and statement breakpoints.
	Statement string

	// Message describes the problem in plain language.
	Message string

	// Suggestion is a short hint on how to fix the issue —
	// typically a safer migration shape.
	Suggestion string
}

SafetyWarning is one finding from the migration analyser.

func AnalyzeMigration added in v0.2.0

func AnalyzeMigration(sql string, opts ...SafetyOptions) []SafetyWarning

AnalyzeMigration splits a migration script on the drizzle-kit "--> statement-breakpoint" boundary and runs the per-statement analyser on each piece. Pass the SQL field of a GenerateResult.

func AnalyzeStatements added in v0.2.0

func AnalyzeStatements(stmts []string, opts ...SafetyOptions) []SafetyWarning

AnalyzeStatements runs the safety rules against each statement in order. The output preserves statement order so callers can align warnings with their migration text.

type Saga added in v0.2.0

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

Saga collects a named sequence of steps to run.

func NewSaga added in v0.2.0

func NewSaga(name string) *Saga

NewSaga returns a saga with the supplied name. Use Step to add steps in execution order.

func (*Saga) Run added in v0.2.0

func (s *Saga) Run(db *DB, ctx context.Context, state *SagaState) error

Run executes the saga against db. state may be nil (a fresh state is allocated). Each step runs in its own transaction; on failure, compensations of completed steps run in reverse order. Returns nil on full success, *SagaError on partial failure, or the bare error from a step when nothing prior had completed.

func (*Saga) Step added in v0.2.0

func (s *Saga) Step(name string, forward, compensate SagaStepFn) *Saga

Step appends a step. compensate may be nil — drops will skip the step on rollback in that case (suitable for read-only or inherently-idempotent side effects like sending a not-already-sent email).

type SagaCompensationFailure added in v0.2.0

type SagaCompensationFailure struct {
	StepName string
	StepIdx  int
	Err      error
}

SagaCompensationFailure is one compensation that itself errored.

type SagaError added in v0.2.0

type SagaError struct {
	SagaName      string
	FailedStep    string
	FailedStepIdx int
	Cause         error
	CompFailures  []SagaCompensationFailure
}

SagaError is returned by Saga.Run when a step fails. It exposes the failing step's index / name, the original error, and any errors that surfaced during compensation. Use errors.As to reach it.

func (*SagaError) Error added in v0.2.0

func (e *SagaError) Error() string

Error implements error.

func (*SagaError) Unwrap added in v0.2.0

func (e *SagaError) Unwrap() error

Unwrap exposes the failing-step cause to errors.Is / errors.As.

type SagaState added in v0.2.0

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

SagaState is the typed bag flowing between steps. Concurrent access is safe because steps run sequentially, but the mutex keeps things tidy if a step spawns helpers that touch state.

func (*SagaState) Get added in v0.2.0

func (s *SagaState) Get(key string) (any, bool)

Get returns the value stored under key and ok=true when present.

func (*SagaState) Set added in v0.2.0

func (s *SagaState) Set(key string, v any)

Set stores v under key.

type SagaStepFn added in v0.2.0

type SagaStepFn func(ctx context.Context, tx *DB, state *SagaState) error

SagaStepFn is the signature for forward and compensating actions. The tx is a fresh transaction per step. State is shared across the saga; mutations made before an error are visible to compensations.

type Scanner added in v0.2.0

type Scanner interface {
	Scan(dest ...any) error
}

Scanner mirrors the subset of drops.Rows the fast scan helpers need: one Scan call per row. Generated code is written against this narrower interface so it does not depend on drops internals.

type Schema

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

Schema is a registered set of tables — the input to snapshotting, diffing and migration generation. It is just a thin wrapper around a slice of *Table plus optional top-level objects (enums, sequences, views); the table declarations themselves are unaffected.

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.

func (*Schema) AddEnum added in v0.2.0

func (s *Schema) AddEnum(e *PgEnum) *Schema

AddEnum registers a top-level enum type with the schema so the snapshot / diff generator emits the matching CREATE TYPE.

func (*Schema) AddSequence added in v0.2.0

func (s *Schema) AddSequence(seq *PgSequence) *Schema

AddSequence registers a top-level sequence with the schema so the snapshot / diff generator emits the matching CREATE SEQUENCE.

func (*Schema) AddView added in v0.2.0

func (s *Schema) AddView(v *PgView) *Schema

AddView registers a view with the schema so the snapshot / diff generator emits the matching CREATE VIEW.

func (*Schema) Enums added in v0.2.0

func (s *Schema) Enums() []*PgEnum

Enums returns the registered enum types in declaration order.

func (*Schema) Sequences added in v0.2.0

func (s *Schema) Sequences() []*PgSequence

Sequences returns the registered sequences.

func (*Schema) Tables

func (s *Schema) Tables() []*Table

Tables returns the registered tables.

func (*Schema) Views added in v0.2.0

func (s *Schema) Views() []*PgView

Views returns the registered views.

type Secret added in v0.2.0

type Secret[T any] struct {
	// Plain is the un-encrypted value. Reads / writes through this
	// field are normal Go field access; encryption / decryption
	// happens only when the secret crosses the driver boundary via
	// Value() / Scan().
	Plain T
}

Secret wraps a value of type T so it round-trips as encrypted bytea through the driver. Encrypt on Value(), decrypt on Scan(). The wire format is AES-GCM(gob-encoded T), keyed by the active keyring set via SetKeyring.

type User struct {
    ID    int64             `drop:"id,primaryKey,autoIncrement"`
    Email pg.Secret[string] `drop:"email"`
}

AutoTable recognises Secret[T] and emits a bytea column. Build the value via NewSecret(v) or by assigning to Secret[T]{Plain: v}.

func NewSecret added in v0.2.0

func NewSecret[T any](v T) Secret[T]

NewSecret is a tiny constructor that helps with type inference.

func (*Secret[T]) Scan added in v0.2.0

func (s *Secret[T]) Scan(src any) error

Scan implements sql.Scanner. Decrypts the bytea payload with the active keyring and decodes it back into s.Plain.

func (Secret[T]) Value added in v0.2.0

func (s Secret[T]) Value() (driver.Value, error)

Value implements driver.Valuer. Encrypts s.Plain with the active keyring and returns the resulting bytes for storage.

type Seeder added in v0.2.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 := pg.NewSeeder(db)
pg.SeedAdd(seeder, UserEntity, alice, bob)
pg.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.2.0

func NewSeeder(db *DB) *Seeder

NewSeeder returns a Seeder bound to db.

func SeedAdd added in v0.2.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.2.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.2.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.2.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.2.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 composes a SELECT statement.

func (*SelectBuilder) AfterCursor added in v0.2.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 SELECT and scans every row into dest, which must be a pointer to a slice of structs (or pointer-to-structs).

func (*SelectBuilder) AsSubquery

func (s *SelectBuilder) AsSubquery(alias string) drops.Expression

AsSubquery returns a parenthesised, aliased form of the SELECT for use as a subquery in another statement.

func (*SelectBuilder) BeforeCursor added in v0.2.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) Count

func (s *SelectBuilder) Count(ctx context.Context) (int64, error)

Count returns the number of rows the current SELECT would produce, computed as SELECT count(*) FROM (<original>) AS _drops_count. The original ORDER BY / LIMIT / OFFSET are kept inside the subquery so LIMIT-aware page counts work correctly.

For un-paginated counts on simple SELECTs, this is the natural and safe shape — PostgreSQL will optimise the inner query as needed.

func (*SelectBuilder) Distinct

func (s *SelectBuilder) Distinct() *SelectBuilder

Distinct toggles SELECT DISTINCT.

func (*SelectBuilder) DistinctOn

func (s *SelectBuilder) DistinctOn(exprs ...drops.Expression) *SelectBuilder

DistinctOn renders SELECT DISTINCT ON (exprs...). Mutually exclusive with Distinct().

func (*SelectBuilder) Except

func (s *SelectBuilder) Except(other *SelectBuilder) *SelectBuilder

Except appends EXCEPT <select>.

func (*SelectBuilder) ExceptAll

func (s *SelectBuilder) ExceptAll(other *SelectBuilder) *SelectBuilder

ExceptAll appends EXCEPT ALL <select>.

func (*SelectBuilder) ForUpdate

func (s *SelectBuilder) ForUpdate() *SelectBuilder

ForUpdate appends FOR UPDATE row locking.

func (*SelectBuilder) From

func (s *SelectBuilder) From(t *Table) *SelectBuilder

From sets the FROM clause. Required before execution.

func (*SelectBuilder) FromExpr

func (s *SelectBuilder) FromExpr(e drops.Expression) *SelectBuilder

FromExpr appends an arbitrary FROM source — a subquery, CTE reference, set-returning function, etc. Multiple FROMs are comma-joined (i.e. cross-joined).

func (*SelectBuilder) FullJoin

func (s *SelectBuilder) FullJoin(t *Table, on drops.Expression) *SelectBuilder

FullJoin appends a FULL OUTER JOIN.

func (*SelectBuilder) GroupBy

func (s *SelectBuilder) GroupBy(exprs ...drops.Expression) *SelectBuilder

GroupBy appends GROUP BY expressions.

func (*SelectBuilder) Having

func (s *SelectBuilder) Having(preds ...drops.Expression) *SelectBuilder

Having appends predicates to the HAVING clause (joined by AND).

func (*SelectBuilder) Intersect

func (s *SelectBuilder) Intersect(other *SelectBuilder) *SelectBuilder

Intersect appends INTERSECT <select>.

func (*SelectBuilder) IntersectAll

func (s *SelectBuilder) IntersectAll(other *SelectBuilder) *SelectBuilder

IntersectAll appends INTERSECT ALL <select>.

func (*SelectBuilder) Join

Join appends an INNER JOIN.

func (*SelectBuilder) LeftJoin

func (s *SelectBuilder) LeftJoin(t *Table, on drops.Expression) *SelectBuilder

LeftJoin appends a LEFT JOIN.

func (*SelectBuilder) Limit

func (s *SelectBuilder) Limit(n int64) *SelectBuilder

Limit sets the LIMIT.

func (*SelectBuilder) Offset

func (s *SelectBuilder) Offset(n int64) *SelectBuilder

Offset sets the OFFSET.

func (*SelectBuilder) One

func (s *SelectBuilder) One(ctx context.Context, dest any) error

One executes the SELECT and scans the first row into dest. Returns ErrNoRows if no row is produced.

func (*SelectBuilder) OrderBy

func (s *SelectBuilder) OrderBy(exprs ...drops.Expression) *SelectBuilder

OrderBy appends ORDER BY expressions. Use Column.Asc / Column.Desc for direction.

func (*SelectBuilder) OrderByCursor added in v0.2.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) RightJoin

func (s *SelectBuilder) RightJoin(t *Table, on drops.Expression) *SelectBuilder

RightJoin appends a RIGHT JOIN.

func (*SelectBuilder) Rows

func (s *SelectBuilder) Rows(ctx context.Context) (drops.Rows, error)

Rows executes the SELECT and returns the raw cursor for manual scanning.

func (*SelectBuilder) ToSQL

func (s *SelectBuilder) ToSQL() (string, []any)

ToSQL renders the statement to a SQL string and arg list.

func (*SelectBuilder) Union

func (s *SelectBuilder) Union(other *SelectBuilder) *SelectBuilder

Union appends UNION <select>. Multiple set operations are chainable.

func (*SelectBuilder) UnionAll

func (s *SelectBuilder) UnionAll(other *SelectBuilder) *SelectBuilder

UnionAll appends UNION ALL <select>.

func (*SelectBuilder) Unscoped added in v0.2.0

func (s *SelectBuilder) Unscoped() *SelectBuilder

Unscoped opts out of the FROM table's DefaultFilter predicates for this SELECT. Use to bypass a soft-delete or tenant guard.

func (*SelectBuilder) Where

func (s *SelectBuilder) Where(preds ...drops.Expression) *SelectBuilder

Where appends predicates joined by AND.

func (*SelectBuilder) With

func (s *SelectBuilder) With(ctes ...*CTE) *SelectBuilder

With prepends a WITH clause to the SELECT. Multiple calls accumulate.

func (*SelectBuilder) WithRecursive

func (s *SelectBuilder) WithRecursive(ctes ...*CTE) *SelectBuilder

WithRecursive marks the WITH clause as RECURSIVE. Only one mode is possible per statement; calling this is sticky.

func (*SelectBuilder) WriteSQL

func (s *SelectBuilder) WriteSQL(b *drops.Builder)

WriteSQL renders the SELECT into a Builder. Wrapped in parentheses so the same builder can be embedded as a subquery.

type SequenceOptions

type SequenceOptions struct {
	Start     *int64
	Increment *int64
	MinValue  *int64
	MaxValue  *int64
	Cache     *int64
	Cycle     bool
	OwnedBy   *Column // optional: link sequence ownership to a column
}

SequenceOptions configures CreateSequence.

type SequenceSnapshot added in v0.2.0

type SequenceSnapshot struct {
	Name      string `json:"name"`
	Schema    string `json:"schema"`
	Start     *int64 `json:"startWith,omitempty"`
	Increment *int64 `json:"incrementBy,omitempty"`
	MinValue  *int64 `json:"minValue,omitempty"`
	MaxValue  *int64 `json:"maxValue,omitempty"`
	Cache     *int64 `json:"cacheSize,omitempty"`
	Cycle     bool   `json:"cycle"`
}

SequenceSnapshot is one entry in Snapshot.Sequences.

type Sharded added in v0.2.0

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

Sharded routes every query to one of N underlying drivers based on a shard key the caller stamps on ctx. This is the opposite end of the Replicated wrapper: where Replicated splits reads across replicas of a single dataset, Sharded splits the entire dataset across N independent primaries. Use it to scale beyond what a single primary's write throughput allows — typical shard axes are user id (social-feed services), chat id (messaging), or geographic region (mobility / dispatch).

shards := []drops.Driver{db1, db2, db3, db4}
sharded := pg.NewSharded(shards, func(key any) int {
    return int(key.(int64) % int64(len(shards)))
})
db := pg.New(sharded)

ctx = pg.WithShardKey(ctx, userID)
got, err := UserEntity.Get(db, ctx, userID)
// the shard for userID handles the query

Missing shard key on ctx returns ErrShardKeyMissing — the bad code path fails closed (a "default to shard 0" policy would silently route writes to the wrong shard and leak data across customers). Cross-shard queries need explicit opt-in via ForEachShard.

Transactions stay on a single shard — a tx that spans shards would need 2PC and drops doesn't model it. Begin uses the shard for ctx's shard key.

func NewSharded added in v0.2.0

func NewSharded(shards []drops.Driver, pick func(key any) int) *Sharded

NewSharded wires the shards with a picker that maps a key to an index in [0, len(shards)). The picker is called for every query; keep it cheap. Drops does not enforce that the picker is deterministic, but non-determinism causes data corruption, so don't.

func (*Sharded) Begin added in v0.2.0

func (s *Sharded) Begin(ctx context.Context) (drops.Tx, error)

Begin opens a transaction on the ctx's shard. The tx stays on that shard for its lifetime — drops does not model cross- shard 2PC.

func (*Sharded) Close added in v0.2.0

func (s *Sharded) Close() error

Close closes every shard that exposes a Close method, returning the first error encountered.

func (*Sharded) Exec added in v0.2.0

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

Exec routes to the ctx's shard.

func (*Sharded) Query added in v0.2.0

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

Query routes to the ctx's shard.

func (*Sharded) Shards added in v0.2.0

func (s *Sharded) Shards() []drops.Driver

Shards returns a copy of the underlying drivers in declaration order. Used by ForEachShard / fan-out workflows.

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"`
	Enums     map[string]*EnumSnapshot     `json:"enums"`
	Schemas   map[string]any               `json:"schemas"`
	Sequences map[string]*SequenceSnapshot `json:"sequences"`
	Roles     map[string]any               `json:"roles"`
	Policies  map[string]any               `json:"policies"`
	Views     map[string]*ViewSnapshot     `json:"views"`
	Meta      SnapshotMeta                 `json:"_meta"`
}

Snapshot is the on-disk representation of a database schema as written to drizzle-kit's meta/<idx>_snapshot.json. The JSON keys match drizzle-kit's PostgreSQL snapshot v7 format so a Snapshot produced by drops is round-trippable through drizzle-kit, and vice versa.

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, opts ...IntrospectOptions) (*Snapshot, error)

Introspect queries the live database and returns a Snapshot describing its current state — tables, columns (with type normalisation matching drizzle-kit's conventions: bigserial / serial / smallserial detected from int + nextval default), primary keys, single-column unique constraints, and single-column foreign keys with referential actions.

The returned snapshot is in the same format as BuildSnapshot's output and can be diffed against a Go-schema snapshot via Diff. It deliberately leaves indexes, composite keys, enums, sequences and views empty — those features aren't yet representable in drops's schema layer.

func UnmarshalSnapshot

func UnmarshalSnapshot(data []byte) (*Snapshot, error)

UnmarshalSnapshot parses a snapshot from JSON, restoring zero-valued maps for any nil collections (so subsequent reads/diffs don't have to check for nil).

func (*Snapshot) Marshal

func (s *Snapshot) Marshal() ([]byte, error)

Marshal returns the snapshot as the canonical 2-space-indented JSON drizzle-kit produces.

type SnapshotMeta

type SnapshotMeta struct {
	Columns map[string]any `json:"columns"`
	Schemas map[string]any `json:"schemas"`
	Tables  map[string]any `json:"tables"`
}

SnapshotMeta carries rename-tracking annotations. drops never sets these; the field is present for drizzle-kit compatibility.

type SoftDeleteCols added in v0.2.0

type SoftDeleteCols struct {
	DeletedAt *Col[time.Time]
}

SoftDeleteCols holds the typed handle created by SoftDelete.

func SoftDelete added in v0.2.0

func SoftDelete(t *Table) SoftDeleteCols

SoftDelete appends a nullable "deletedAt" TIMESTAMPTZ column. A record is treated as live while deletedAt IS NULL.

type SoftDeleteMixin added in v0.2.0

type SoftDeleteMixin struct {
	Cols SoftDeleteCols
}

SoftDeleteMixin registers a "deletedAt" column, a DefaultFilter (deletedAt IS NULL), and a DeleteHook that rewrites DELETE statements as UPDATE deletedAt = now() — i.e. the row stays in the table but is hidden by default. Use Unscoped() on any builder to bypass the guard and operate on every row (including the already-deleted ones).

func (*SoftDeleteMixin) Apply added in v0.2.0

func (m *SoftDeleteMixin) Apply(t *Table)

Apply implements Mixin.

type Span added in v0.2.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
}

Status is a single row produced by Migrator.Status.

type SubscribeOptions added in v0.2.0

type SubscribeOptions struct {
	// Hydrate fetches the full row for insert/update events so
	// downstream consumers receive typed values instead of bare
	// IDs. Disabled by default — enable when the consumer needs
	// the row body and the per-event Get cost is acceptable.
	Hydrate bool

	// Channel overrides the pg_notify channel. Defaults to
	// "drops_<tablename>" matching InstallChangeFeed's default.
	Channel string

	// Buffer sizes the returned channel; events are dropped when
	// the channel is full so a slow consumer doesn't backlog
	// memory. Defaults to 64.
	Buffer int
}

SubscribeOptions controls subscription behaviour.

type TB added in v0.2.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 schema-qualified PostgreSQL table.

func ApplyMixins added in v0.2.0

func ApplyMixins(t *Table, mixins ...Mixin) *Table

ApplyMixins runs each mixin against t in order and returns t. Mixins applied later observe the columns / hooks installed by earlier mixins.

func AutoSchemaTable added in v0.2.0

func AutoSchemaTable[T any](schema, name string) *Table

AutoSchemaTable is the schema-qualified twin of AutoTable.

func AutoTable added in v0.2.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           — PRIMARY KEY
autoIncrement        — use the serial family (BigSerial / Serial /
                       SmallSerial) for the column's type
notNull              — NOT NULL
unique               — UNIQUE
default=<sql>        — raw DEFAULT clause (no parameterisation)
version              — mark as the optimistic-lock version column

Use `drop:"-"` to skip a field entirely. Untagged exported fields are also skipped.

Go type ↔ ColumnType mapping mirrors the manual constructors:

bool                  → boolean
int16                 → smallint   (smallserial if autoIncrement)
int32 / int           → integer    (serial      if autoIncrement)
int64                 → bigint     (bigserial   if autoIncrement)
float32               → real
float64               → double precision
string                → text
[]byte                → bytea
time.Time             → timestamptz
json.RawMessage       → jsonb
*T                    → same as T, column is nullable unless
                        `notNull` is set explicitly

Custom types — uuid, jsonb columns backed by app structs, etc. — fall back to drops.Custom; declare them by hand instead.

func NewAuditTable added in v0.2.0

func NewAuditTable(name string) *Table

NewAuditTable declares the canonical audit table:

id        bigserial PRIMARY KEY,
entity    text       NOT NULL,
op        text       NOT NULL,
pk        jsonb,
payload   jsonb,
actor     text,
createdAt timestamptz NOT NULL DEFAULT now()

func NewBackfillStateTable added in v0.2.0

func NewBackfillStateTable(name string) *Table

NewBackfillStateTable declares the canonical state table layout. Add to your schema once; every Backfill in the system shares the same table keyed by name.

func NewEventStoreTable added in v0.2.0

func NewEventStoreTable(name string) *Table

NewEventStoreTable declares the canonical event-store layout. Run the DDL once alongside the rest of your schema.

func NewIdempotencyTable added in v0.2.0

func NewIdempotencyTable(name string) *Table

NewIdempotencyTable declares the canonical idempotency table. Add it to your schema:

schema := pg.NewSchema(Users, pg.NewIdempotencyTable("idempotency_keys"))
pg.Push(ctx, db, schema)

func NewOutboxTable added in v0.2.0

func NewOutboxTable(name string) *Table

NewOutboxTable declares the canonical outbox table layout. Add it to your schema and let Push / Migrator manage the DDL. The table ships with a partial index that keeps the drain query O(log n) no matter how many published rows accumulate before cleanup.

func NewSchemaTable

func NewSchemaTable(schema, name string) *Table

NewSchemaTable creates a table in an explicit schema.

func NewSnapshotTable added in v0.2.0

func NewSnapshotTable(name string) *Table

NewSnapshotTable declares the snapshot store. Pair with NewEventStoreTable; snapshots live in a separate table so the event log stays append-only.

func NewTable

func NewTable(name string) *Table

NewTable creates a table in the default ("public") schema. The name is validated and the constructor panics on invalid identifiers — see ErrInvalidIdentifier — because schemas are typically declared in package init / var blocks where a bad name should fail loudly at startup rather than at the first query.

func (*Table) AddCheck added in v0.2.0

func (t *Table) AddCheck(name, expr string) *Table

AddCheck declares a CHECK constraint with the given name. expr is the raw SQL expression after CHECK (...), e.g. "age >= 0 AND age < 200".

func (*Table) AddIndex added in v0.2.0

func (t *Table) AddIndex(idx *Index) *Table

AddIndex registers an index to be created alongside the table. The index is not emitted by CreateTable; use CreateTableWithIndexes or emit pg.CreateIndex(idx) explicitly.

func (*Table) AddPolicy added in v0.2.0

func (t *Table) AddPolicy(p *Policy) *Table

AddPolicy attaches a row-level security policy to the table. Policies are inert until EnableRLS is also called.

func (*Table) AddUnique added in v0.2.0

func (t *Table) AddUnique(name string, cols ...ColRef) *Table

AddUnique declares a multi-column UNIQUE constraint named name spanning cols. Single-column uniques continue to live on the column via *Col[T].Unique().

func (*Table) Alias

func (t *Table) Alias() string

Alias returns the alias set via As, or "" if none.

func (*Table) As

func (t *Table) As(alias string) *Table

As returns a shallow copy of the table bound to alias.

func (*Table) Checks added in v0.2.0

func (t *Table) Checks() map[string]string

Checks returns the registered CHECK constraints.

func (*Table) Col

func (t *Table) Col(name string) *Column

Col looks up a registered column by name.

func (*Table) Columns

func (t *Table) Columns() []*Column

Columns returns all registered columns in declaration order.

func (*Table) CompositePrimaryKey added in v0.2.0

func (t *Table) CompositePrimaryKey() []*Column

CompositePrimaryKey returns the composite PK columns, or nil when the table uses a single-column PK (or none).

func (*Table) CompositeUniques added in v0.2.0

func (t *Table) CompositeUniques() map[string][]*Column

CompositeUniques returns the table's multi-column unique constraints.

func (*Table) DefaultFilter added in v0.2.0

func (t *Table) DefaultFilter(e drops.Expression) *Table

DefaultFilter appends a predicate applied to every Select / Update / Delete against the table, unless the builder is marked Unscoped(). Filters compose with AND.

func (*Table) EnableRLS added in v0.2.0

func (t *Table) EnableRLS() *Table

EnableRLS marks the table as having Row-Level Security enabled. The snapshot/diff generator emits the matching ALTER TABLE ... ENABLE ROW LEVEL SECURITY when the flag flips from false to true.

func (*Table) ForeignKey added in v0.2.1

func (t *Table) ForeignKey(col, target *Column, opts ...func(*FK)) *Table

ForeignKey attaches a foreign-key constraint from col to target on this table. Both must be non-nil registered columns. It is the untyped companion to (*Col[T]).References — use it to add FKs to AutoTable-derived columns. The FK is recorded on the column, so CreateTable and the schema diff/Push see it.

func (*Table) Indexes added in v0.2.0

func (t *Table) Indexes() []*Index

Indexes returns the indexes registered with AddIndex.

func (*Table) Name

func (t *Table) Name() string

Name returns the table's unqualified name.

func (*Table) OnDelete added in v0.2.0

func (t *Table) OnDelete(h DeleteHook) *Table

OnDelete registers a hook invoked by DeleteBuilder.WriteSQL. A hook may return a non-nil expression to replace the rendered DELETE entirely — used by SoftDelete to flip DELETE into UPDATE.

func (*Table) OnInsert added in v0.2.0

func (t *Table) OnInsert(h InsertHook) *Table

OnInsert registers a hook invoked by InsertBuilder.WriteSQL. The hook can fill column values the caller didn't explicitly bind; user values always win.

func (*Table) OnUpdate added in v0.2.0

func (t *Table) OnUpdate(h UpdateHook) *Table

OnUpdate registers a hook invoked by UpdateBuilder.WriteSQL.

func (*Table) Policies added in v0.2.0

func (t *Table) Policies() map[string]*Policy

Policies returns the registered policies keyed by name.

func (*Table) PrimaryKey added in v0.2.0

func (t *Table) PrimaryKey(cols ...ColRef) *Table

PrimaryKey declares a composite PRIMARY KEY spanning cols. Call only when the PK has more than one column; single-column PKs continue to be declared on the column via *Col[T].PrimaryKey().

func (*Table) RLSEnabled added in v0.2.0

func (t *Table) RLSEnabled() bool

RLSEnabled reports whether the table has RLS enabled.

func (*Table) Relation

func (t *Table) Relation(name string) *Relation

Relation looks up a registered relation by name. Returns nil if no such relation exists.

func (*Table) Schema

func (t *Table) Schema() string

Schema returns the table's schema (empty for the default schema).

func (*Table) WriteSQL

func (t *Table) WriteSQL(b *drops.Builder)

WriteSQL writes the FROM/JOIN form. Implements drops.Expression.

type TableSnapshot

type TableSnapshot struct {
	Name                 string                          `json:"name"`
	Schema               string                          `json:"schema"`
	Columns              map[string]*ColumnSnapshot      `json:"columns"`
	Indexes              map[string]*IndexSnapshot       `json:"indexes"`
	ForeignKeys          map[string]*ForeignKeySnapshot  `json:"foreignKeys"`
	CompositePrimaryKeys map[string]*CompositePKSnapshot `json:"compositePrimaryKeys"`
	UniqueConstraints    map[string]*UniqueSnapshot      `json:"uniqueConstraints"`
	Policies             map[string]*PolicySnapshot      `json:"policies"`
	CheckConstraints     map[string]*CheckSnapshot       `json:"checkConstraints"`
	IsRLSEnabled         bool                            `json:"isRLSEnabled"`
}

TableSnapshot is one entry in Snapshot.Tables.

type TimeSeriesTable added in v0.2.0

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

TimeSeriesTable declares a partition-by-range pattern with time-bucketed children and an automatic retention window.

func NewTimeSeriesTable added in v0.2.0

func NewTimeSeriesTable(parent, timeCol string) *TimeSeriesTable

NewTimeSeriesTable returns a fresh descriptor. parent is the SQL identifier of the partitioned parent table; timeCol is the column the parent is partitioned by.

func (*TimeSeriesTable) Bootstrap added in v0.2.0

func (t *TimeSeriesTable) Bootstrap(db *DB, ctx context.Context) error

Bootstrap creates the partition that covers now (rounded down to the bucket boundary) and BRIN-indexes it when configured. Idempotent — re-running it is safe.

func (*TimeSeriesTable) DropExpired added in v0.2.0

func (t *TimeSeriesTable) DropExpired(db *DB, ctx context.Context) (int, error)

DropExpired removes every child partition whose upper bound is older than now-retention. Disabled when retention is 0. Partition discovery uses pg_inherits + pg_class, so it skips the parent and any partitions belonging to a different schema.

func (*TimeSeriesTable) EnsureNext added in v0.2.0

func (t *TimeSeriesTable) EnsureNext(db *DB, ctx context.Context, count int) error

EnsureNext creates the next `count` partitions ahead of now, rounded to bucket boundaries. Scheduled invocation keeps the window of "ready" partitions ahead of traffic.

func (*TimeSeriesTable) Maintain added in v0.2.0

func (t *TimeSeriesTable) Maintain(db *DB, ctx context.Context) error

Maintain runs EnsureNext(2) followed by DropExpired — the typical "every hour" scheduled invocation.

func (*TimeSeriesTable) PartitionEvery added in v0.2.0

func (t *TimeSeriesTable) PartitionEvery(d time.Duration) *TimeSeriesTable

PartitionEvery sets the bucket size — the duration each child partition covers. Defaults to 24h.

func (*TimeSeriesTable) Retain added in v0.2.0

Retain sets the retention window — partitions whose upper bound is older than now-retention are dropped by DropExpired. Pass 0 to disable retention.

func (*TimeSeriesTable) WithBrinIndex added in v0.2.0

func (t *TimeSeriesTable) WithBrinIndex(col string) *TimeSeriesTable

WithBrinIndex names the column that EnsureNext / Bootstrap will BRIN-index on each newly created partition. BRIN is the canonical time-series index — tiny, fast to build, great for range scans on naturally-ordered columns.

type TimestampsCols added in v0.2.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.2.0

func Timestamps(t *Table) TimestampsCols

Timestamps appends NOT NULL "createdAt" and "updatedAt" TIMESTAMPTZ columns defaulting to now() to t.

type TimestampsMixin added in v0.2.0

type TimestampsMixin struct {
	Cols TimestampsCols
}

TimestampsMixin registers "createdAt" and "updatedAt" columns and an UpdateHook that bumps updatedAt to now() on every UPDATE the caller hasn't already touched. INSERT is left to the column's DEFAULT now() — no hook needed.

After Apply, the embedded Cols field exposes typed handles to the columns so the rest of the application can reference them.

func (*TimestampsMixin) Apply added in v0.2.0

func (m *TimestampsMixin) Apply(t *Table)

Apply implements Mixin.

type Tracer added in v0.2.0

type Tracer interface {
	Start(ctx context.Context, name string) (context.Context, Span)
}

Tracer is the minimal contract drops needs to emit a distributed trace span around every Exec / Query. It deliberately mirrors a subset of OpenTelemetry's trace.Tracer without importing it, so drops carries no external tracing dependency. Wire it up with db.WithTracer; pass an adapter that bridges to your real tracer:

type otelAdapter struct{ tracer trace.Tracer }

func (a otelAdapter) Start(ctx context.Context, name string) (context.Context, pg.Span) {
    ctx, span := a.tracer.Start(ctx, name)
    return ctx, otelSpan{span}
}

db := pg.New(drv).WithTracer(otelAdapter{tracer: otel.Tracer("myapp")})

The Span surface mirrors the methods drops needs: SetAttribute for query metadata, RecordError to mark failures, End to close the span. Implementations are expected to be safe for concurrent use — drops calls them from whichever goroutine happens to run the query.

type TriggerOptions

type TriggerOptions struct {
	Timing    string // "BEFORE" | "AFTER" | "INSTEAD OF"
	Events    string // "INSERT" | "UPDATE" | "DELETE" | combinations like "INSERT OR UPDATE"
	Table     *Table
	ForEach   string // "ROW" or "STATEMENT" (default "ROW")
	When      string // raw SQL condition (optional)
	Execute   string // raw, e.g. "my_func()"
	OrReplace bool
}

TriggerOptions configures CreateTrigger.

type TypedChange added in v0.2.0

type TypedChange[T any] struct {
	// Op is the underlying SQL operation.
	Op ChangeOp
	// ID is the primary-key value (text-coerced).
	ID string
	// Row is the fetched row body. Nil for OpDelete (the row is
	// already gone) and for any event when Hydrate is false.
	Row *T
	// At is the wall-clock time the event arrived in-process —
	// approximate by ~one network hop from the trigger fire.
	At time.Time
}

TypedChange[T] decorates a ChangeEvent with the optional fetched row body.

type UUIDPrimaryKeyCols added in v0.2.0

type UUIDPrimaryKeyCols struct {
	ID *Col[string]
}

UUIDPrimaryKeyCols holds the typed handle created by UUIDPrimaryKey.

func UUIDPrimaryKey added in v0.2.0

func UUIDPrimaryKey(t *Table) UUIDPrimaryKeyCols

UUIDPrimaryKey appends an "id" UUID PRIMARY KEY column defaulting to gen_random_uuid() — the function shipped by the pgcrypto extension and built into PostgreSQL 13+.

type UUIDPrimaryKeyMixin added in v0.2.0

type UUIDPrimaryKeyMixin struct {
	Cols UUIDPrimaryKeyCols
}

UUIDPrimaryKeyMixin registers an "id" UUID PRIMARY KEY column defaulting to gen_random_uuid().

func (*UUIDPrimaryKeyMixin) Apply added in v0.2.0

func (m *UUIDPrimaryKeyMixin) Apply(t *Table)

Apply implements Mixin.

type UniqueSnapshot

type UniqueSnapshot struct {
	Name             string   `json:"name"`
	NullsNotDistinct bool     `json:"nullsNotDistinct"`
	Columns          []string `json:"columns"`
}

UniqueSnapshot is one entry in TableSnapshot.UniqueConstraints.

type UpdateBuilder

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

UpdateBuilder composes an UPDATE statement.

func (*UpdateBuilder) All

func (u *UpdateBuilder) All(ctx context.Context, dest any) error

All executes the UPDATE and scans the RETURNING rows into dest.

func (*UpdateBuilder) Exec

func (u *UpdateBuilder) Exec(ctx context.Context) (drops.Result, error)

Exec runs the UPDATE.

func (*UpdateBuilder) From

func (u *UpdateBuilder) From(tables ...*Table) *UpdateBuilder

From adds tables to a PostgreSQL UPDATE ... FROM clause for joins.

func (*UpdateBuilder) One

func (u *UpdateBuilder) One(ctx context.Context, dest any) error

One executes the UPDATE and scans the first RETURNING row into dest.

func (*UpdateBuilder) Returning

func (u *UpdateBuilder) Returning(cols ...drops.Expression) *UpdateBuilder

Returning sets a RETURNING clause.

func (*UpdateBuilder) Set

func (u *UpdateBuilder) Set(values ...ColumnValue) *UpdateBuilder

Set adds one or more assignments. Use (*Col[T]).Val(v) to bind a typed value or (*Col[T]).Expr(e) to bind an expression.

func (*UpdateBuilder) ToSQL

func (u *UpdateBuilder) ToSQL() (string, []any)

ToSQL renders the statement.

func (*UpdateBuilder) Unscoped added in v0.2.0

func (u *UpdateBuilder) Unscoped() *UpdateBuilder

Unscoped opts out of the table's DefaultFilter predicates for this UPDATE. Use when an administrative job must bypass a soft-delete or tenant guard registered on the table.

func (*UpdateBuilder) Where

func (u *UpdateBuilder) Where(preds ...drops.Expression) *UpdateBuilder

Where appends predicates joined by AND.

func (*UpdateBuilder) WriteSQL

func (u *UpdateBuilder) WriteSQL(b *drops.Builder)

WriteSQL renders the UPDATE.

type UpdateHook added in v0.2.0

type UpdateHook interface {
	BeforeUpdate(ctx *UpdateHookCtx)
}

UpdateHook is invoked once per UPDATE statement, before rendering.

type UpdateHookCtx added in v0.2.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.2.0

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

Has reports whether col is already bound on the UPDATE.

func (*UpdateHookCtx) Set added in v0.2.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.2.0

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

SetExpr is the raw-expression variant of Set — useful for hooks that want to assign e.g. drops.Raw("now()") to a column.

type UpdateHookFunc added in v0.2.0

type UpdateHookFunc func(*UpdateHookCtx)

UpdateHookFunc adapts a plain function to the UpdateHook interface.

func (UpdateHookFunc) BeforeUpdate added in v0.2.0

func (f UpdateHookFunc) BeforeUpdate(ctx *UpdateHookCtx)

BeforeUpdate implements UpdateHook.

type Validator added in v0.2.0

type Validator[T any] func(*T) error

Validator is called before Create / Update / Save with a pointer to the candidate row. Returning a non-nil error aborts the operation before any SQL is issued. Validators compose: register as many as you need, the first to fail wins.

type VectorOpClass

type VectorOpClass string

VectorOpClass is one of the per-distance-metric operator classes pgvector exposes for indexing. Pass the relevant value to (*Index).OpClass() on a Vector column.

const (
	VectorL2Ops     VectorOpClass = "vector_l2_ops"
	VectorIPOps     VectorOpClass = "vector_ip_ops"
	VectorCosineOps VectorOpClass = "vector_cosine_ops"
	VectorL1Ops     VectorOpClass = "vector_l1_ops"

	HalfVecL2Ops     VectorOpClass = "halfvec_l2_ops"
	HalfVecIPOps     VectorOpClass = "halfvec_ip_ops"
	HalfVecCosineOps VectorOpClass = "halfvec_cosine_ops"

	BitHammingOps VectorOpClass = "bit_hamming_ops"
	BitJaccardOps VectorOpClass = "bit_jaccard_ops"
)

type ViewSnapshot added in v0.2.0

type ViewSnapshot struct {
	Name         string `json:"name"`
	Schema       string `json:"schema"`
	Definition   string `json:"definition"`
	Materialized bool   `json:"materialized"`
}

ViewSnapshot is one entry in Snapshot.Views.

type Window

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

Window describes the contents of an OVER (...) clause.

func WindowSpec

func WindowSpec() *Window

WindowSpec begins building a window specification.

func (*Window) Frame

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

func (w *Window) OrderBy(exprs ...drops.Expression) *Window

OrderBy adds ORDER BY entries to the window.

func (*Window) PartitionBy

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