Help us shape the future of Go! Take the 2026 Go Developer Survey

postgres

package
v0.0.0-...-373af4f Latest Latest
Warning

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

Go to latest
Published: Sep 23, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package postgres is the authz.Datastore on Postgres, via pgx. Reads run in a repeatable-read, read-only transaction so one resolution sees one snapshot; writes run in one transaction so a WriteRelationships lands whole or not at all, and every write records what it changed in the change log at a revision that follows commit order.

The datastore stores tuples and their changelog, and keeps its Index beside them when asked (WithIndex): the userset closure and the permission sets, maintained by a Hook inside the write transaction, or afterwards from the changelog (WithAsyncIndex). Tx hands the index the transaction behind a Reader so it can query its own tables at the same snapshot. Migrations create every table; see Migrate.

A write can also run inside a transaction the application owns, so a grant commits with the row it protects or not at all: TransactIn takes the application's pgx.Tx and does everything Transact does except begin and commit.

Index

Constants

View Source
const DefaultNestingBudget = 100_000

DefaultNestingBudget bounds how many closure rows one change may add.

View Source
const MigrationTable = "public.authz_migration"

MigrationTable records the applied versions. It sits outside the authz schema so that any runner can create it before the schema exists, and it is qualified because a role named authz puts the authz schema first in its search path once the first migration has created it; an unqualified name would then resolve to a different table than the one the first run made.

Variables

View Source
var (
	// ErrNestingTooLarge is returned when one change would add more closure
	// rows than the budget; a synchronous write is rolled back.
	ErrNestingTooLarge = errors.New("postgres: userset nesting exceeds the index budget")
	// ErrNotPostgres is returned when the index is asked to answer for a
	// Reader that did not come from this datastore.
	ErrNotPostgres = errors.New("postgres: the index needs this datastore's reader")
	// ErrNotMaterializable is returned by New for a permission whose rewrite
	// the permission sets cannot represent.
	ErrNotMaterializable = materialize.ErrNotMaterializable
)
View Source
var ErrSchemaVersion = errors.New("authz: database schema version does not match the library")

ErrSchemaVersion is Check's answer when the database is not at SchemaVersion: migrations are pending, or the code is older than the database.

View Source
var Migrations embed.FS

Migrations is every table the datastore and its index need, as numbered goose-annotated SQL: one file today, creating the relationships, the change log, the userset closure and the permission sets. Migrator applies it. A host that runs goose itself hands it to its own provider under MigrationTable; a host with another tool has the files.

View Source
var SchemaVersion = mustSchemaVersion()

SchemaVersion is the version this code expects the database at: the number of the last migration in Migrations. Check compares the database with it.

Functions

func Check

func Check(ctx context.Context, pool *pgxpool.Pool) error

Check fails with ErrSchemaVersion unless the database is at SchemaVersion. Run it at boot beside engine.Service.ValidateStored, so a deploy whose migrations did not run stops before its first query fails.

func Migrate

func Migrate(ctx context.Context, pool *pgxpool.Pool) error

Migrate brings the database to SchemaVersion: NewMigrator, Up, Close.

func Tx

func Tx(r authz.Reader) (pgx.Tx, bool)

Tx returns the transaction behind a Reader or Writer this package produced, so an index kept in the same database can query its tables at the same snapshot and maintain them in the same write.

Types

type Datastore

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

Datastore is the Postgres-backed store, with its Index when one was asked for.

func New

func New(pool *pgxpool.Pool, opts ...Option) (*Datastore, error)

New wraps a pool. The authz schema must exist (Migrate); the engine finds the index through Datastore.Index.

func (*Datastore) CreateAll

func (d *Datastore) CreateAll(ctx context.Context, rels []authz.Relationship) error

CreateAll implements authz.BulkCreator: one COPY into the relationships, one Change, the hooks once, one commit.

func (*Datastore) Index

func (d *Datastore) Index() authz.Index

Index returns the datastore's index, or nil when New was not asked for one. It is the engine's way in (authz.Indexed), and the way to Verify, Reindex, Catchup or Follow it.

func (*Datastore) Transact

func (d *Datastore) Transact(ctx context.Context, fn func(authz.Writer) error) error

Transact runs fn in a read-committed transaction of its own, records the change it produced, runs the hooks, and commits if everything returned nil.

func (*Datastore) TransactIn

func (d *Datastore) TransactIn(ctx context.Context, tx pgx.Tx, fn func(authz.Writer) error) error

TransactIn is Transact inside a transaction the caller owns: fn runs on a Writer over tx, then the change is recorded and the hooks run, all in tx, and the caller commits or rolls back. It is how a grant lands in the same transaction as the application row it protects:

tx, err := pool.Begin(ctx)
defer tx.Rollback(ctx)
// ... insert the row ...
err = ds.TransactIn(ctx, tx, func(w authz.Writer) error {
    return svc.WriteRelationshipsIn(ctx, w, updates)
})
// ... commit

Call it last, just before the commit: the change log's lock is taken here and held until tx ends, and every other write that changed something waits on it. AI: the change log and the hooks belong to the write, not to the commit, so they run here rather than in Transact; a Writer handed out without them would leave every index and mirror behind.

func (*Datastore) View

func (d *Datastore) View(ctx context.Context, fn func(authz.Reader) error) error

View runs fn in a repeatable-read, read-only transaction.

type Drift

type Drift = materialize.Drift

Drift is what Verify found; see materialize.Drift.

type Hook

type Hook func(ctx context.Context, tx pgx.Tx, change authz.Change) error

Hook runs inside a write transaction, after the relationships and the change log rows are written and before commit, with the Change the transaction produced. Returning an error rolls the whole write back.

type Index

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

Index is the datastore's index (authz.Index): the userset closure, after Zanzibar's Leopard, and the permission sets, after AuthZed's Materialize, in two tables beside the relationships. It is maintained in SQL inside every write transaction (WithIndex) or afterwards from the changelog (WithAsyncIndex), and answers each question with one query. Datastore.Index returns it.

func (*Index) Catchup

func (x *Index) Catchup(ctx context.Context) (int, error)

Catchup applies every change after the follower's cursor, each in its own transaction, and reports how many it applied. It is one pass; Follow loops. An asynchronous index (WithAsyncIndex) is kept in step by one of them.

func (*Index) Follow

func (x *Index) Follow(ctx context.Context, interval time.Duration, errs chan<- error)

Follow runs Catchup every interval until ctx ends. Errors are sent on errs when there is room and never stop the loop.

func (*Index) HasPermission

func (x *Index) HasPermission(ctx context.Context, r authz.Reader, resource authz.ObjectRef, permission string, subject authz.SubjectRef) (bool, error)

HasPermission implements authz.PermissionIndex. AI: it starts from the few sets that grant the permission on the one resource and tests the subject against each, never from every set the subject is in: a subject granted ninety thousand resources made the other order two hundred times slower.

func (*Index) Materialized

func (x *Index) Materialized(resourceType, permission string) bool

Materialized implements authz.PermissionIndex.

func (*Index) NestedRelationships

func (x *Index) NestedRelationships(ctx context.Context, r authz.Reader, q authz.RelationshipQuery) ([]authz.Relationship, error)

NestedRelationships implements authz.NestingIndex.

func (*Index) NestedResourceIDs

func (x *Index) NestedResourceIDs(ctx context.Context, r authz.Reader, subjectType string, subjectIDs []string, subjectRelation, resourceType, relation string) ([]string, error)

NestedResourceIDs implements authz.NestingIndex.

func (*Index) NestedResourceIDsAmong

func (x *Index) NestedResourceIDsAmong(ctx context.Context, r authz.Reader, subjectType string, subjectIDs []string, subjectRelation, resourceType, relation string, among []string) ([]string, error)

NestedResourceIDsAmong implements authz.NestingIndex.

func (*Index) Reindex

func (x *Index) Reindex(ctx context.Context) error

Reindex rebuilds every index table from the relationships and sets the follower's cursor to the latest revision. It is the repair tool; Verify says whether it is needed.

func (*Index) ResourcesWithPermission

func (x *Index) ResourcesWithPermission(ctx context.Context, r authz.Reader, resourceType, permission string, subject authz.SubjectRef) ([]string, error)

ResourcesWithPermission implements authz.PermissionIndex.

func (*Index) ResourcesWithPermissionAmong

func (x *Index) ResourcesWithPermissionAmong(ctx context.Context, r authz.Reader, resourceType, permission string, subject authz.SubjectRef, among []string) ([]string, error)

ResourcesWithPermissionAmong implements authz.PermissionIndex. AI: it is HasPermission over many resources at once, resource-first for the same reason: the granting sets of every resource in the batch, each tested for the subject, never the subject's every set.

func (*Index) SubjectsWithPermission

func (x *Index) SubjectsWithPermission(ctx context.Context, r authz.Reader, resource authz.ObjectRef, permission, subjectType, subjectRelation string) ([]string, error)

SubjectsWithPermission implements authz.PermissionIndex.

func (*Index) Verify

func (x *Index) Verify(ctx context.Context) (Drift, error)

Verify reads every relationship and every index row at one snapshot, derives what the index should hold with the materialize package, and reports how the maintained rows differ. Nothing is changed. AI: the derivation is a second implementation of the index, in Go, so a fault in the SQL maintenance cannot hide from it. It is the consistency check to run after a suspected fault, an index reconfiguration, or in a test after concurrent writes.

type Migrator

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

Migrator applies Migrations to one database with goose and records what ran in MigrationTable. Up and Down are what a deploy step calls; Version is what Check reads.

func NewMigrator

func NewMigrator(pool *pgxpool.Pool) (*Migrator, error)

NewMigrator builds a Migrator on the pool. Close it when done; the pool stays open.

func (*Migrator) Close

func (m *Migrator) Close() error

Close releases what the Migrator holds. The pool is untouched.

func (*Migrator) Down

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

Down rolls back the most recently applied migration.

func (*Migrator) DownTo

func (m *Migrator) DownTo(ctx context.Context, version int64) error

DownTo rolls back every migration above version; 0 removes everything.

func (*Migrator) Up

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

Up applies every pending migration in order, each in its own transaction.

func (*Migrator) Version

func (m *Migrator) Version(ctx context.Context) (int64, error)

Version is the highest applied migration, 0 before the first.

type Option

type Option func(*Datastore)

Option configures New.

func WithAsyncIndex

func WithAsyncIndex() Option

WithAsyncIndex maintains the index from the changelog after commit, through Index.Follow or Index.Catchup, instead of inside the write transaction. Reads then trail writes by the follower's lag, the trade AuthZed Materialize makes.

func WithHook

func WithHook(h Hook) Option

WithHook registers a Hook. Hooks run in registration order, after the index when there is one.

func WithIndex

func WithIndex(sch *schema.Schema, permissions ...string) Option

WithIndex keeps the index beside the tuples, maintained inside every write transaction so reads are never behind: the userset closure, and the permission sets of the named permissions, written type#permission, or of every permission the schema lets a set represent when none are named. A named permission the sets cannot represent fails New with ErrNotMaterializable.

func WithNestingBudget

func WithNestingBudget(n int64) Option

WithNestingBudget overrides DefaultNestingBudget for the index.

func WithNestingIndex

func WithNestingIndex() Option

WithNestingIndex keeps the userset closure alone, with no permission sets. It needs no schema.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL