Documentation
¶
Overview ¶
Package schemadef projects a project's APPLIED database schema — db/migrations/*.up.sql executed in order against a REAL ephemeral postgres — into a typed model that code generation consumes.
SQL is the schema language in forge: migrations are the single source of truth for what tables and columns exist. Everything else (entity structs, the ORM, CRUD wiring, frontend gating) is a projection of the schema this package reports.
Shadow strategy ¶
forge is postgres-pinned. Migrations are applied verbatim — byte for byte, no rewriting — to a real ephemeral postgres (pkg/pgtest: embedded-postgres by default, an already-running server when FORGE_TEST_POSTGRES_URL is set), then the resulting schema is read back through postgres's own catalog (information_schema / pg_catalog).
This is a hard improvement over the previous in-memory SQLite shadow, which approximated postgres by exploiting SQLite's permissive type affinity and required a normalization pass (DEFAULT (now()) wrapping, '::type' cast stripping, multi-ADD splitting) to coax idiomatic postgres DDL through SQLite's parser. That approximation broke the moment a project used a construct SQLite couldn't parse — most notably schema-qualified DDL (CREATE TABLE controlplane.foo), which froze cp-forge's ORM. Real postgres needs none of that: it IS the target, so the schema the generator sees is exactly the schema production runs.
Statements that fail to apply are still skipped when they cannot affect the table/column model (DML data movement, CREATE FUNCTION / TRIGGER / EXTENSION / VIEW, COMMENT, SET ...) — postgres rejects some of these in the bare ephemeral DB (e.g. an extension that isn't installed) and that must not abort introspection. A failing CREATE TABLE / ALTER TABLE / DROP TABLE / CREATE INDEX is a hard error: those define the schema being projected, so silently skipping one would generate an ORM that lies.
Index ¶
Constants ¶
const ( ColCreatedAt = "created_at" ColUpdatedAt = "updated_at" ColDeletedAt = "deleted_at" ColTenantID = "tenant_id" )
managed column names recognized by convention.
Variables ¶
This section is empty.
Functions ¶
func SplitStatements ¶
SplitStatements splits SQL text into individual statements, honoring single/double quotes, line and block comments, postgres dollar-quoted strings ($tag$ ... $tag$), and trigger bodies (BEGIN ... END;).
Types ¶
type CanonicalType ¶
type CanonicalType string
CanonicalType is the dialect-neutral type vocabulary the generators consume. The mapping from declared SQL types is documented on MapDeclaredType.
const ( TypeString CanonicalType = "string" TypeInt CanonicalType = "int64" TypeFloat CanonicalType = "float64" TypeBool CanonicalType = "bool" TypeTime CanonicalType = "time" TypeJSON CanonicalType = "json" TypeBytes CanonicalType = "bytes" )
The Type* constants enumerate every CanonicalType the generators recognise.
func MapDeclaredType ¶
func MapDeclaredType(decl string) (CanonicalType, bool)
MapDeclaredType maps a declared SQL column type to the canonical forge type. The second return is true for array types ("TEXT[]"), in which case the canonical type describes the ELEMENT.
Mapping table (case-insensitive, length/precision suffixes ignored):
TEXT, VARCHAR, CHAR, CITEXT, UUID → string BIGINT, INTEGER, INT, SMALLINT, *SERIAL → int64 DOUBLE PRECISION, REAL, FLOAT, NUMERIC, DECIMAL → float64 BOOLEAN, BOOL → bool TIMESTAMPTZ, TIMESTAMP[ WITH(OUT) TIME ZONE], DATE, DATETIME → time JSONB, JSON → json BYTEA, BLOB → bytes anything else (incl. an empty/unknown udt) → string
type Column ¶
type Column struct {
Name string
// DeclType is the declared SQL type verbatim from the migration
// (e.g. "TIMESTAMPTZ", "TEXT[]", "DOUBLE PRECISION").
DeclType string
// Type is the canonical forge type the declared type maps to.
Type CanonicalType
// IsArray is true for declared array types ("TEXT[]"); Type then
// holds the ELEMENT type.
IsArray bool
NotNull bool
// Default is the raw default expression, "" when none.
Default string
IsPK bool
// IsGenerated is true for GENERATED ALWAYS AS (...) STORED columns —
// the database computes them, so they must never be written on
// INSERT/UPDATE (postgres rejects writes to generated columns).
IsGenerated bool
}
Column is one introspected column.
type Conventions ¶
type Conventions struct {
SoftDelete bool
Timestamps bool
HasTenant bool
// TenantColumn is "tenant_id" when HasTenant.
TenantColumn string
// SearchColumns are the text columns (excluding the PK, tenant and
// managed columns) the generated search filter matches against.
SearchColumns []string
}
Conventions are the behavior-by-convention signals derived from real columns. No annotations: the columns ARE the declaration.
deleted_at (time) ⇒ soft delete (UPDATE-not-DELETE,
reads filter IS NULL, ListAll* unfiltered)
created_at + updated_at ⇒ managed timestamps
tenant_id (string, NOT NULL) ⇒ tenant-scoped rows
text columns ⇒ searchable by the generated list filter
func DetectConventions ¶
func DetectConventions(t Table) Conventions
DetectConventions reads the behavior conventions off a table's columns.
type ForeignKey ¶
ForeignKey is a declared REFERENCES constraint.
type Table ¶
type Table struct {
Name string
Columns []Column
// PKCols lists primary-key column names in key order.
PKCols []string
// Indexes lists non-PK indexes (unique and plain).
Indexes []Index
// ForeignKeys lists declared REFERENCES constraints.
ForeignKeys []ForeignKey
}
Table is one introspected table of the applied schema.
func ApplyAndIntrospect ¶
ApplyAndIntrospect applies every *.up.sql under migDir (in lexical order) to a fresh real-postgres shadow database and returns the resulting schema. A missing or empty migrations directory returns (nil, nil): no schema, no entities, nothing to project.
The shadow is a freshly-created database on the process-shared ephemeral postgres (pkg/pgtest). It is dropped before returning, so every call sees a clean schema. Booting the shared server the first time downloads/caches the postgres binary (embedded-postgres) unless FORGE_TEST_POSTGRES_URL points at a running server.