schemadef

package
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Jul 15, 2026 License: MIT Imports: 8 Imported by: 0

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

View Source
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

func SplitStatements(sqlText string) []string

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

type ForeignKey struct {
	Column    string
	RefTable  string
	RefColumn string
}

ForeignKey is a declared REFERENCES constraint.

type Index

type Index struct {
	Name    string
	Columns []string
	Unique  bool
}

Index is a non-PK index on a table.

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

func ApplyAndIntrospect(migDir string) ([]Table, error)

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.

Jump to

Keyboard shortcuts

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