Documentation
¶
Overview ¶
Package migrations is fabriq's DDL authority: grove Go-code migrations for every fabriq-owned table. The registry never generates DDL; the registry-conformance integration test keeps the two in sync.
Run them with `fabriq migrate up|down|status` (which wraps grove's orchestrator under its advisory migration lock) — never at app startup. Expand/contract discipline is documented in docs/MIGRATIONS.md.
Index ¶
- Constants
- Variables
- func EnsureAppRole(ctx context.Context, dsn, role, password string) error
- func Group() *migrate.Group
- func HeadVersion() string
- func NewOrchestrator(drv any) (*migrate.Orchestrator, error)
- func NewOrchestratorFromGrove(gdb *grove.DB) (*migrate.Orchestrator, func() error, error)
- func OpenOrchestrator(ctx context.Context, dsn string) (*migrate.Orchestrator, func() error, error)
- func OpenOrchestratorWith(ctx context.Context, dsn string, gdb *grove.DB) (*migrate.Orchestrator, func() error, error)
- func ScopeAwareTenantPolicy(table string) []string
- func ValidateAppRole(role, password string) error
- type ExtensionReport
- type ExtensionStatus
Constants ¶
const GroupName = "fabriq"
GroupName identifies fabriq's migration group; host applications that embed fabriq alongside their own grove migration groups can depend on it (migrate.DependsOn(migrations.GroupName)).
Variables ¶
var DefaultRequiredExtensions = []string{"timescaledb", "vector", "postgis"}
DefaultRequiredExtensions are the extensions fabriq's migrations create.
var ErrInvalidAppRole = errors.New("fabriq: invalid app role")
ErrInvalidAppRole marks a role name or password that EnsureAppRole refuses before it touches the database. Callers map it to a usage error.
Functions ¶
func EnsureAppRole ¶ added in v1.9.0
EnsureAppRole creates the non-superuser application role, or rotates its password when it already exists, and grants it what `fabriq serve` needs on the public schema. Run it with a privileged DSN after migrations.
Row-level security does not bind superusers or BYPASSRLS roles, and a REPLICATION role can read around it too, so serving as the migration owner would turn the tenant fence off. For that reason an existing role is not just re-passworded: SUPERUSER, BYPASSRLS, CREATEDB, CREATEROLE and REPLICATION are all reset, so a role that was once over-privileged comes back safe. The role must not be the one the DSN connects as, or the command could lock it out. The role gets CREATE on the schema so serve can create dynamic tables as their owner, and FORCE ROW LEVEL SECURITY then binds that owner.
The role name and password never pass through Go string formatting into SQL: each statement is assembled by the server with format(%I, %L) and then executed. Running it again is safe and is how a password is rotated.
func Group ¶
Group returns fabriq's migration group with all migrations registered, in version order.
func HeadVersion ¶ added in v0.0.6
func HeadVersion() string
HeadVersion returns the newest migration version in fabriq's chain — the fleet roller records it per tenant database, and catalog-mode routing fails closed below it.
func NewOrchestrator ¶
func NewOrchestrator(drv any) (*migrate.Orchestrator, error)
NewOrchestrator builds a grove migration orchestrator for the given driver (a *pgdriver.PgDB). The orchestrator acquires grove's migration lock on a dedicated connection, so concurrent `fabriq migrate` runs are safe.
func NewOrchestratorFromGrove ¶ added in v0.0.3
NewOrchestratorFromGrove builds an orchestrator on an already-open grove.DB borrowed from a host DI container — the migration counterpart to the forge extension's grove fallback (mirrors how xraph/authsome shares the host grove). The grove MUST be pg-backed. The returned close func is a no-op: the host owns the connection lifecycle, so migrations must not tear it down.
func OpenOrchestrator ¶
OpenOrchestrator dials Postgres and returns a ready orchestrator plus a close function. It exists so the CLI (and worker bootstrap) can run migrations without importing grove drivers directly — driver imports stay fenced to adapters/, fabriqtest/ and this package.
func OpenOrchestratorWith ¶ added in v0.0.3
func OpenOrchestratorWith(ctx context.Context, dsn string, gdb *grove.DB) (*migrate.Orchestrator, func() error, error)
OpenOrchestratorWith returns a migration orchestrator, preferring an explicit DSN (dialing a fresh connection it owns and closes) and otherwise borrowing the given grove.DB. It errors when neither is available, so a misconfigured caller gets a clear message instead of an "empty dsn" dial failure.
func ScopeAwareTenantPolicy ¶
ScopeAwareTenantPolicy returns the SQL to (re)create the tenant_isolation policy with the soft secondary-scope predicate for a table that has a nullable scope_id column. Tenant stays the hard boundary; scope is soft: an unscoped read (app.scope_id=”) sees all rows in the tenant; a scoped read sees its scope plus shared (NULL-scope) rows. Consumers (e.g. kgkit) reuse this for their own entity tables that adopt scope_id.
func ValidateAppRole ¶ added in v1.9.0
ValidateAppRole checks the role name and password EnsureAppRole will use, without touching the database. The error never contains the password.
Types ¶
type ExtensionReport ¶ added in v1.9.0
type ExtensionReport struct {
OK bool `json:"ok"`
Extensions []ExtensionStatus `json:"extensions"`
Problems []string `json:"problems"` // e.g. "timescaledb: not in shared_preload_libraries"
}
ExtensionReport is the result of CheckExtensions.
func CheckExtensions ¶ added in v1.9.0
CheckExtensions reports whether each required extension can be created on the server behind dsn. An extension is OK when the server ships it (pg_available_extensions) and, for timescaledb, when it is loaded through shared_preload_libraries, because CREATE EXTENSION timescaledb succeeds without the preload but the database then misbehaves. It does not create anything. The error is for connection and query failures only; a missing extension is reported in the returned report.
type ExtensionStatus ¶ added in v1.9.0
type ExtensionStatus struct {
Name string `json:"name"`
Available bool `json:"available"`
Installed string `json:"installed"` // installed version, "" when not yet created
Preloaded bool `json:"preloaded"` // timescaledb: in shared_preload_libraries; others: true
OK bool `json:"ok"`
}
ExtensionStatus is one required extension's state on the target server.
Source Files
¶
- 0001_outbox.go
- 0002_projection_state.go
- 0003_site_asset_tag.go
- 0004_rls_policies.go
- 0005_timescale.go
- 0006_pgvector.go
- 0007_crdt_updates.go
- 0008_crdt_docs.go
- 0009_link.go
- 0010_live_subscriptions.go
- 0011_postgis.go
- 0012_scope.go
- 0013_crdt_scope.go
- 0014_blob.go
- 0015_blob_cas_created_at.go
- 0016_fs_node.go
- 0017_fs_node_mount.go
- 0018_fs_permission.go
- 0019_fs_share.go
- 0020_fs_bookmark.go
- 0021_blob_source.go
- 0022_digest_node.go
- 0023_digest_tokens.go
- 0024_digest_content_hash_idx.go
- 0025_embeddings_meta_gin.go
- 0026_crdt_segments.go
- 0027_api_key.go
- 0028_api_key_expires_at.go
- 0029_fs_node_drop_path.go
- 0030_namespace_rename.go
- 0031_insights.go
- 0032_insights_rollup_state.go
- 0033_dynamic_entity.go
- 0034_tenant.go
- 0035_api_key_last_used.go
- 0036_aggregate_retention.go
- approle.go
- module.go
- preflight.go
- runner.go