Documentation
¶
Overview ¶
Package postgres is the PostgreSQL store: it writes finalized records and their initial event, reads records back, runs the contract's text search, and records injections, all as the application role inside a transaction that first declared its bounds.
It runs against a schema the .NET migrator applied (ExperienceSchemaMigrator.MigrateAsync, then ApplyApplicationRolePrivilegesAsync with row-level security on, or the contract's two privileges files) and never creates or alters a schema object. The bounds, the search and the injection record are the contract's SQL files, embedded and run as written: only their @name placeholders are spelled the way the driver spells them ($1, $2, ...).
Index ¶
- Constants
- Variables
- func ContractSQL(name string) (string, error)
- type Beginner
- type Statement
- type Store
- func (s *Store) Close()
- func (s *Store) Get(ctx context.Context, bounds ae.AuthorizationBounds, scope ae.Scope, id ae.UUID) (*ae.ExperienceRecord, error)
- func (s *Store) InsertFinalized(ctx context.Context, bounds ae.AuthorizationBounds, ...) error
- func (s *Store) InsertRecord(ctx context.Context, bounds ae.AuthorizationBounds, ...) error
- func (s *Store) RecordInjection(ctx context.Context, bounds ae.AuthorizationBounds, scope ae.Scope, ...) error
- func (s *Store) Search(ctx context.Context, bounds ae.AuthorizationBounds, ...) ([]ae.SearchRow, error)
Constants ¶
const ( DeclareBoundsSQL = "declare-bounds.sql" SearchSQL = "search-experience-text.sql" RecordInjectionSQL = "record-injection.sql" )
The contract's SQL files, as this package embeds them.
Variables ¶
var ErrSealed = errors.New("the record is sealed (payload_version 2) and a contract 1.x port cannot read it")
ErrSealed is Get's answer for a record that exists in the scope but is sealed (payload_version 2, crypto-shredding), which a contract 1.x port cannot read. A record that does not exist is (nil, nil).
Functions ¶
func ContractSQL ¶
ContractSQL is one of the contract's SQL files, as this package embeds it (DeclareBoundsSQL, SearchSQL, RecordInjectionSQL). A test keeps the embedded copies identical to contract/sql/.
Types ¶
type Beginner ¶
Beginner starts the transactions the store runs in. *pgxpool.Pool, *pgx.Conn and pgx.Tx (each transaction is then a savepoint inside it) all satisfy it. Every connection it hands out must be the application role's.
type Statement ¶
Statement is a SQL text with its @name placeholders numbered for the driver, and the parameter each number stands for.
func ToDriverPlaceholders ¶
ToDriverPlaceholders numbers the contract's @name placeholders as $1, $2, ... (one number per distinct name). Only code is rewritten: a -- or /* */ comment, a single-quoted literal (E” escapes included), a double-quoted identifier and a dollar-quoted string are kept as written, so a parameter named only inside one of them is never asked for. An @ followed by a letter or _ in code is always a placeholder, so PostgreSQL's prefix @ operator must be written with a space after it.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store keeps records in PostgreSQL through pgx. Each operation runs one transaction, begun on the connection source the host gave it. A Store is safe for concurrent use exactly when its source is: a *pgxpool.Pool is, a single *pgx.Conn or pgx.Tx is not (use it from one goroutine at a time).
func NewStore ¶
NewStore is a store that runs its transactions on db, which the host controls (and closes).
func Open ¶
Open is a store on a new connection pool for a connection string (as the application role). Close it with Close.
func (*Store) Close ¶
func (s *Store) Close()
Close closes the pool Open made. It does nothing for a store on a source the host gave NewStore.
func (*Store) Get ¶
func (s *Store) Get(ctx context.Context, bounds ae.AuthorizationBounds, scope ae.Scope, id ae.UUID) (*ae.ExperienceRecord, error)
Get reads one record in exactly scope (every scope field equal, an unset one unset), within bounds. It returns (nil, nil) when there is none (or it was deleted), and ErrSealed when it is sealed (payload_version 2, which a contract 1.x port cannot read).
func (*Store) InsertFinalized ¶
func (s *Store) InsertFinalized(ctx context.Context, bounds ae.AuthorizationBounds, finalized *ae.FinalizedExperience, actor string, recordedAt *time.Time) error
InsertFinalized writes a finalized record in its final state at revision 1, and the initial lifecycle event that took it there, in one transaction. actor is the authorizing principal; recordedAt defaults to now. The event must be the record's own (its experience ID, its status and its revision); anything else is refused before writing.
func (*Store) InsertRecord ¶
func (s *Store) InsertRecord(ctx context.Context, bounds ae.AuthorizationBounds, record *ae.ExperienceRecord) error
InsertRecord writes one record's row exactly as given, with no lifecycle event. It is for seeding and tests: a record this package finalizes is written with InsertFinalized, or it has no history.
func (*Store) RecordInjection ¶
func (s *Store) RecordInjection(ctx context.Context, bounds ae.AuthorizationBounds, scope ae.Scope, experienceIDs []ae.UUID, injectedAt ae.Instant) error
RecordInjection records that experienceIDs were injected in scope at injectedAt: it runs sql/record-injection.sql (contract 1.4) once, in one transaction that declared bounds. Only a record live in exactly scope gets a row (one borrowed through a grant, or in another scope, is skipped by the statement); the instant is truncated to whole microseconds and clamped to the database clock. It refuses more than agentexperience.MaxRecordInjectionIDs IDs or a nil one, and a scope outside the bounds (agentexperience.ErrAuthorizationDenied); otherwise a failure is the driver's (insufficient_privilege, 42501, until the role holds the grants of sql/application-role-privileges-1.4.sql). The agent integration calls it in the background, fail-open.
func (*Store) Search ¶
func (s *Store) Search(ctx context.Context, bounds ae.AuthorizationBounds, parameters ae.SearchParameters) ([]ae.SearchRow, error)
Search runs sql/search-experience-text.sql and returns its rows in the order returned, each relevance widened from real. A sealed row (payload_version 2) is skipped. Rank them with agentexperience.Rank.