Documentation
¶
Index ¶
- Variables
- func FileReferenceViolation(err error) (column string, ok bool)
- func ForeignKeyViolation(err error) (msg string, ok bool)
- func IsBadInput(err error) bool
- func IsMissingTenant(err error) bool
- func IsUnavailable(err error) bool
- func NewPool(ctx context.Context, connStr string) (*pgxpool.Pool, error)
- func UndefinedColumnField(err error) (field string, ok bool)
- func UniqueViolationField(err error) (field string, ok bool)
- type TenantDB
- func (tdb *TenantDB) ExecRowsTenant(ctx context.Context, schemaName, query string, args ...any) ([]map[string]any, error)
- func (tdb *TenantDB) ExecRowsTenantEmit(ctx context.Context, schemaName, query string, ...) ([]map[string]any, error)
- func (tdb *TenantDB) ExecTenant(ctx context.Context, schemaName, query string, args ...any) (int64, error)
- func (tdb *TenantDB) ExecTenantEmit(ctx context.Context, schemaName, query string, ...) (int64, error)
- func (tdb *TenantDB) IncludeListJSON(ctx context.Context, pgSchema, query string, args ...any) (data []byte, n int64, err error)
- func (tdb *TenantDB) IncludeOneJSON(ctx context.Context, pgSchema, query string, args ...any) (data []byte, found bool, err error)
- func (tdb *TenantDB) QueryDirect(ctx context.Context, pgSchema, tableName, query string, args ...any) (pgx.Rows, error)
- func (tdb *TenantDB) QueryScalarDirect(ctx context.Context, pgSchema, tableName, query string, args ...any) (int64, error)
- func (tdb *TenantDB) QueryScalarTenant(ctx context.Context, schemaName, query string, args ...any) (int64, error)
- func (tdb *TenantDB) QueryTenant(ctx context.Context, schemaName, query string, args ...any) (pgx.Rows, error)
- func (tdb *TenantDB) WithTenantTx(ctx context.Context, schemaName string, ...) error
Constants ¶
This section is empty.
Variables ¶
ErrUnavailable is returned when the database cannot SERVE the query (as opposed to rejecting it): the queryTimeout elapsed, the circuit breaker is open, the connection failed, or PostgreSQL reported that it cannot take the work (connection_exception, insufficient_resources, shutting down). Handlers map it to 503 + Retry-After — "retry later", not "your request is broken". See isUnavailableCause for the exact classification and what is deliberately excluded.
Functions ¶
func FileReferenceViolation ¶
FileReferenceViolation reports whether err is a foreign_key_violation on a `file` field (FILES-LINK-S1): a write whose file_id references no row of the tenant's files table. It returns the violating column (the file field's name, parsed from the error Detail) so handlers can answer the write with the SAME field-addressed 422 shape the declarative validator uses — a bad file reference is field-level input validation to the client, even though the guard is the real FK. A delete blocked by a file FK (the other direction) is NOT this case — that stays the generic 409 (ForeignKeyViolation).
func ForeignKeyViolation ¶
ForeignKeyViolation reports whether err is a Postgres foreign_key_violation (SQLSTATE 23503) and, if so, returns a safe, human-readable message naming the related resource (parsed from the error Detail, never raw SQL). Handlers map this to 409 Conflict — a delete blocked by a RESTRICT FK, or a write referencing a row that does not exist — instead of leaking a masked 500. ok is false for any other error. The resource names exposed are the tenant's OWN schema resources, not engine internals.
func IsBadInput ¶
IsBadInput reports whether err is a client-supplied value that Postgres could not parse or store for a column type. These are caller errors, so handlers answer 400 instead of leaking a 500. See badInputCodes for the exact set and why it is wider than the 22P02 it started as.
func IsMissingTenant ¶
IsMissingTenant reports whether err is a "schema/relation does not exist" Postgres error (SQLSTATE 42P01 undefined_table or 3F000 invalid_schema_name). In this schema-per-tenant design that means the tenant is not provisioned, so handlers answer 400 "invalid tenant" rather than leaking a 500 SQL error.
func IsUnavailable ¶
IsUnavailable reports whether err signals an unreachable database. It matches both the classified sentinel (errors wrapped by this package's classify) AND a RAW unclassified cause (a bare pgconn/net error a CUSTOM handler got from its own SQL on the tenant tx) — CONSUMER-PATH-S1 (ENG-10): custom handlers never pass through classify, so with the sentinel-only check a stopped PostgreSQL surfaced as 500 "internal error" on every custom route while the generated routes answered the honest 503. Error-path only — never on the hot path.
func UndefinedColumnField ¶
UndefinedColumnField reports whether err is a Postgres undefined_column (SQLSTATE 42703) and, if so, returns the column name parsed from the message. On the write path this means the client sent a field that neither the schema nor the live table knows: handlers map it to 422 unknown_field instead of a masked 500. The DB stays the source of truth for the writable column set (the schema can grow columns at runtime — see the no-whitelist NOTE in codegen.BuildRouter), so this classification happens on the error, never as a pre-write whitelist.
func UniqueViolationField ¶
UniqueViolationField reports whether err is a Postgres unique_violation (SQLSTATE 23505) and, if so, returns the offending column name parsed from the error Detail (falling back to the constraint name). Handlers map this to 409 without leaking raw SQL. ok is false for any other error.
Types ¶
type TenantDB ¶
type TenantDB struct {
// contains filtered or unexported fields
}
TenantDB wraps a pgxpool and enforces per-tenant schema isolation via SET LOCAL search_path.
func NewTenantDB ¶
func (*TenantDB) ExecRowsTenant ¶
func (tdb *TenantDB) ExecRowsTenant(ctx context.Context, schemaName, query string, args ...any) ([]map[string]any, error)
ExecRowsTenant runs a write query with RETURNING * within a tenant schema, reads all returned rows into memory, and commits the transaction. UUID columns ([16]byte) are converted to hyphenated UUID strings.
func (*TenantDB) ExecRowsTenantEmit ¶
func (tdb *TenantDB) ExecRowsTenantEmit(ctx context.Context, schemaName, query string, emit func(ctx context.Context, tx pgx.Tx, rows []map[string]any) error, args ...any) ([]map[string]any, error)
ExecRowsTenantEmit is ExecRowsTenant plus a same-transaction emit step (CRUD-EMIT-V1): after the write's RETURNING rows are read and BEFORE commit, it calls emit(ctx, tx, rows) on the SAME pgx.Tx. If emit returns an error the transaction rolls back, so the business write and its emitted event are atomic — either both commit or neither does. emit may be nil (then this is exactly ExecRowsTenant). db stays decoupled from pkg/outbox: the caller supplies a closure that enqueues using the handed-in tx.
func (*TenantDB) ExecTenant ¶
func (tdb *TenantDB) ExecTenant(ctx context.Context, schemaName, query string, args ...any) (int64, error)
ExecTenant executes an INSERT/UPDATE/DELETE within a transaction scoped to schemaName. Returns the number of rows affected. The transaction is committed on success.
func (*TenantDB) ExecTenantEmit ¶
func (tdb *TenantDB) ExecTenantEmit(ctx context.Context, schemaName, query string, emit func(ctx context.Context, tx pgx.Tx, affected int64) error, args ...any) (int64, error)
ExecTenantEmit is ExecTenant plus a same-transaction emit step (CRUD-EMIT-V1). After the write executes and BEFORE commit it calls emit(ctx, tx, affected) on the SAME tx — the affected-row count lets the caller skip emission when nothing changed (e.g. a DELETE that matched no row). An emit error rolls the write back. emit may be nil (then this is exactly ExecTenant).
func (*TenantDB) IncludeListJSON ¶
func (tdb *TenantDB) IncludeListJSON(ctx context.Context, pgSchema, query string, args ...any) (data []byte, n int64, err error)
IncludeListJSON runs a RELATIONS-V1 list-with-embeds query (built by query.BuildListInclude) inside the tenant search_path and scans its single row of (data text, n bigint): data is the JSON array of nested rows (the engine writes it straight to the client, no Go round-trip), n the row count for has_next. The query references multiple tables unqualified — the tenant search_path (set by QueryTenant) resolves them, so this path supports embeds across the tenant's tables without per-table qualification.
func (*TenantDB) IncludeOneJSON ¶
func (tdb *TenantDB) IncludeOneJSON(ctx context.Context, pgSchema, query string, args ...any) (data []byte, found bool, err error)
IncludeOneJSON runs a RELATIONS-V1 get-by-id-with-embeds query (built by query.BuildGetInclude) and scans its single JSON object column. found is false when no row matched (→ 404), so an embed never resurrects a row the base WHERE (including the row-level RBAC condition) excluded.
func (*TenantDB) QueryDirect ¶
func (tdb *TenantDB) QueryDirect(ctx context.Context, pgSchema, tableName, query string, args ...any) (pgx.Rows, error)
QueryDirect runs a SELECT using a schema-qualified table name — no transaction, no SET LOCAL. One roundtrip instead of four. Use for read-only list/get handlers. tableName must be the unqualified resource name (e.g. "tasks"); pgSchema the tenant schema (e.g. "tenant_acme"). Both are validated before use.
func (*TenantDB) QueryScalarDirect ¶
func (tdb *TenantDB) QueryScalarDirect(ctx context.Context, pgSchema, tableName, query string, args ...any) (int64, error)
QueryScalarDirect runs a single-int64 query (e.g. COUNT(*)) against the schema-qualified table, mirroring QueryDirect (no transaction, no SET LOCAL) — used by the REST list's opt-in ?count=true total, which shares the list's QueryDirect path.
func (*TenantDB) QueryScalarTenant ¶
func (tdb *TenantDB) QueryScalarTenant(ctx context.Context, schemaName, query string, args ...any) (int64, error)
QueryScalarTenant runs a query that returns a single int64 value (e.g. COUNT(*)) within the tenant schema. Returns 0 if no rows are returned.
func (*TenantDB) QueryTenant ¶
func (tdb *TenantDB) QueryTenant(ctx context.Context, schemaName, query string, args ...any) (pgx.Rows, error)
QueryTenant executes a SELECT within a transaction scoped to schemaName. The transaction is rolled back automatically when the returned rows are closed.
func (*TenantDB) WithTenantTx ¶
func (tdb *TenantDB) WithTenantTx(ctx context.Context, schemaName string, fn func(ctx context.Context, tx pgx.Tx) error) error
WithTenantTx opens ONE transaction scoped to schemaName (search_path set once via SET LOCAL, exactly like the single-statement helpers), runs fn against that tx, and COMMITS iff fn returns nil — otherwise it ROLLS BACK. It is the atomic multi-resource primitive (G4): a caller runs several writes through the one tx and they are all-or-nothing. fn must use ONLY the handed-in tx (never the pool) so every statement shares the tenant search_path and the atomic boundary; a panic or error in fn leaves nothing partially committed (the deferred Rollback runs). The tenant cannot be crossed — the search_path is fixed for the transaction's life.