codegen

package
v0.8.1 Latest Latest
Warning

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

Go to latest
Published: Sep 14, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Index

Constants

View Source
const DefaultModuleName = "default"

DefaultModuleName is the name of the single implicit module that a config without a modules block describes.

Variables

This section is empty.

Functions

func DiffSchemas added in v0.3.0

func DiffSchemas(old, newSchema Schema, dialect string) (upSQL, downSQL string, err error)

DiffSchemas compares an old (previous snapshot) schema against a new (desired) schema and produces incremental up/down migration SQL containing only the changes. It returns ("", "") when the schemas are equivalent.

Coverage (up order; down is the sensible inverse):

  • new enums (Postgres only): CREATE TYPE / down DROP TYPE
  • new tables: CREATE TABLE + indexes / down DROP TABLE
  • dropped tables: DROP TABLE / down recreate
  • per-table column add/drop, type changes, NOT NULL changes
  • per-table index add/drop
  • table and column renames declared with a renamed_from marker
  • SQLite table rebuilds for the changes SQLite cannot ALTER in place

A table or a column declared with a renamed_from marker emits RENAME instead of a drop and an add; an ambiguous marker is rejected with an error rather than guessed. SQLite cannot ALTER a column's type, nullability, default, or CHECK in place, so those changes are emitted as a full table rebuild (create a scratch table, copy the rows, drop the original, rename, recreate the indexes) rather than as a warning comment or a silent skip.

func EmitDBFile

func EmitDBFile(models []ModelInfo, dbPkgName string) string

EmitDBFile generates the aggregated DB struct file that wires up all repositories. dbPkgName is the Go package name for the output file (e.g. "db").

func EmitMigrationsEmbed added in v0.7.0

func EmitMigrationsEmbed(pkgName, module string) string

EmitMigrationsEmbed returns the source of the file that embeds one module's migrations. The host passes the FS of each module to Engine.ApplyMigrationsFS, which merges the sets in version order.

pkgName is the Go package of the migration directory, and module is the name the file reports in its comment.

func EmitModelFile

func EmitModelFile(m ModelInfo) string

EmitModelFile generates the complete per-model _drel.go file content.

func EmitModelFileChecked

func EmitModelFileChecked(m ModelInfo) (string, error)

EmitModelFileChecked generates the per-model file, returning an error if the model uses unsupported features. Multi-column value objects are fully supported (see emitMultiValHelpers / expanded scan/diff/DDL).

func Generate

func Generate(configPath string) error

Generate runs the full code generation pipeline atomically: it loads config, scans packages, validates the model set, emits and gofmts every output file in memory, and only then writes anything to disk (write-to-temp + rename). A failure at any step before the write phase leaves the working tree untouched.

Stale generated *_drel.go files that would obstruct the package scan (because they reference deleted model types) are removed before scanning, but their contents are saved so they can be restored if any later step fails. After a successful write, truly stale files that no longer back any scanned model are permanently removed.

func GenerateCreateTable

func GenerateCreateTable(m ModelInfo, fks map[string]string, dialect string) string

GenerateCreateTable emits a CREATE TABLE statement for a single model. fks maps column names to referenced table names for foreign key constraints. dialect controls SQL type mappings and syntax ("postgres" or "sqlite").

func GenerateDropSchema

func GenerateDropSchema(models []ModelInfo) string

GenerateDropSchema emits DROP TABLE statements in reverse order to respect foreign key dependencies.

func GenerateModule added in v0.7.0

func GenerateModule(configPath, module string) error

GenerateModule generates the code of one module of the config. An empty module name covers every module.

The model files and the aggregated DB struct always cover every module, because the DB struct reaches all of the slices and a model file is the same whichever module asked for it. The module name selects which migration embeds are written.

func GenerateSchema

func GenerateSchema(models []ModelInfo, dialect string) string

GenerateSchema emits the full schema DDL for a slice of models, including enum type definitions, foreign key constraints, and many-to-many pivot tables. dialect controls SQL type mappings and syntax ("postgres" or "sqlite").

func GenerateWatch added in v0.5.0

func GenerateWatch(ctx context.Context, configPath string, pollInterval time.Duration) error

GenerateWatch runs Generate once, then re-runs it whenever a .go source file under the scanned package directories changes. It blocks until ctx is cancelled, at which point it returns nil. Generated *_drel.go files and the DB output file are ignored when detecting changes so regeneration never self-triggers. pollInterval controls the mtime poll cadence; pass 0 for the default (500ms). Watch uses only the standard library (no fsnotify) to honour the zero-runtime-dependency rule.

func GoTypeToSQL

func GoTypeToSQL(goType string, dialect string) string

GoTypeToSQL maps a Go type string to its corresponding SQL type for the given dialect. Pointer types are unwrapped to their base type. Unknown types default to "text"/"TEXT". Supported dialects: "postgres" (default), "sqlite".

func ResolveModuleRoot added in v0.5.0

func ResolveModuleRoot(startDir string) string

ResolveModuleRoot finds the Go module root (the nearest ancestor directory containing go.mod) by walking up from startDir. It falls back to startDir when no go.mod is found, preserving the prior behaviour for configs that already sit at the module root.

func SaveSnapshot added in v0.3.0

func SaveSnapshot(path string, s Schema) error

SaveSnapshot writes the schema to path as pretty-printed JSON, creating the parent directory if needed. The file is written with 0644 permissions.

func ValidateModels added in v0.5.0

func ValidateModels(models []ModelInfo) error

ValidateModels checks the aggregated model set for problems that would otherwise produce non-compiling or invalid generated code, and that the existing emit pipeline silently tolerated:

  • two models that generate the same DB-struct field name (pluralize(Name)), which yields duplicate struct fields that do not compile;
  • a relation field whose target model is not in the scanned set (typo or a package missing from drel.yaml), which was silently dropped;
  • a model with no db-mapped columns, which emits an empty INSERT column list and an invalid `INSERT INTO t () VALUES ()` at runtime.

It is called from Generate before any file is written so a failure leaves the working tree untouched.

func WriteMigrationsEmbed added in v0.7.0

func WriteMigrationsEmbed(dir, module string) (bool, error)

WriteMigrationsEmbed writes the embed file into a module's migration directory. It writes nothing when the directory holds no .sql file, because a //go:embed pattern that matches nothing does not compile.

Types

type Column added in v0.3.0

type Column struct {
	Name    string `json:"name"`
	Type    string `json:"type"`
	NotNull bool   `json:"notNull"`
	Default string `json:"default,omitempty"`
	Check   string `json:"check,omitempty"`
	Ref     string `json:"ref,omitempty"`
	PK      bool   `json:"pk,omitempty"`

	// RenamedFrom is the column's previous name. Diff-time only; see Table.
	RenamedFrom string `json:"-"`
}

Column describes a single column. Type is the dialect-resolved SQL type (already including any quoted enum type name). Ref, when non-empty, is the referenced table name for a foreign key on this column (always referencing that table's "id").

type Config

type Config struct {
	Packages []string       `yaml:"packages"`
	Modules  []ModuleConfig `yaml:"modules"`
	Output   OutputConfig   `yaml:"output"`
	Dialect  string         `yaml:"dialect"`
	// Seed is an optional path to a Go main package that seeds the database.
	// `drel seed` runs it with `go run`, passing through DATABASE_URL.
	Seed string `yaml:"seed"`

	// NoSlices records that the file declares an empty packages or modules
	// list. An application with no feature slice yet is a valid state: codegen
	// writes the aggregated DB file and generates no model. It differs from a
	// file that declares neither key, which is a mistake.
	NoSlices bool `yaml:"-"`
}

func LoadConfig

func LoadConfig(path string) (*Config, error)

func (*Config) AllPackages added in v0.7.0

func (c *Config) AllPackages() []string

AllPackages returns every package of every module. The aggregated DB struct needs all of them, even when generation targets one module.

func (*Config) Module added in v0.7.0

func (c *Config) Module(name string) (ModuleConfig, error)

Module returns one module by name. The error lists the names that exist.

func (*Config) ModuleList added in v0.7.0

func (c *Config) ModuleList() []ModuleConfig

ModuleList returns the modules of the config. A config that lists packages instead of modules describes one module named "default", so every caller works with one shape.

type EnumDef added in v0.3.0

type EnumDef struct {
	Name     string   `json:"name"`
	Values   []string `json:"values"`
	IsInt    bool     `json:"isInt,omitempty"`
	BaseType string   `json:"baseType,omitempty"`
}

EnumDef describes an enum type. Name is the lower-cased local Go type name. String enums are emitted as a Postgres CREATE TYPE; integer enums never get a CREATE TYPE (they map to an integer column + CHECK) and are excluded here.

type FieldInfo

type FieldInfo struct {
	Name       string
	GoType     string
	ColumnName string
	// RenamedFrom, when set, is this column's previous name. The migration
	// differ turns it into an ALTER TABLE ... RENAME COLUMN rather than a drop
	// and an add.
	RenamedFrom    string
	IsExported     bool
	RelTag         string
	Relation       *RelationFieldInfo
	IsVO           bool     // implements sql.Scanner + driver.Valuer (single-column VO)
	VOBaseType     string   // single-column VO: underlying basic Go type (e.g. "string", "int64"); empty if not derivable
	HasEqual       bool     // single-column VO defines an Equal(T) bool method usable for diffing
	IsComparable   bool     // single-column VO's Go type is comparable with == / != (types.Comparable)
	HasIsZero      bool     // single-column VO defines IsZero() bool, enabling the zero<->NULL bridge
	IsMultiColVO   bool     // implements drel.MultiColumnMapper (multi-column VO)
	MultiColPrefix string   // db tag used as column prefix for multi-column VOs
	MultiColNames  []string // expanded sub-column names from the db tag (multi-column VOs)
	MultiColTypes  []string // resolved SQL types per sub-column (default "text"; multi-column VOs)
	LocalGoType    string   // type name without package qualifier, for same-package generated code
	TypePkgPath    string   // import path of the field type's package (empty for primitives/same-package)
	// TypeRefPkgs lists the import paths an unnamed composite type refers to,
	// e.g. []time.Time needs "time". LocalGoType then holds placeholders that
	// the emitter replaces with the file's import aliases.
	TypeRefPkgs      []string
	IsPointer        bool // whether the field is a pointer type
	IsEnum           bool
	EnumValues       []string
	EnumIsInt        bool   // enum's underlying basic kind is integer (not string)
	EnumBaseType     string // Go base type of the enum, e.g. "string", "int", "int64"
	IsNamedPrimitive bool   // named type over a comparable basic kind with no enum consts (e.g. type Priority int)
	Unique           bool   // db tag option: unique index on this column
	Indexed          bool   // db tag option: index on this column
	IndexName        string // explicit index name (db:"...,index=name"); fields sharing a name form a composite index
	CheckExpr        string // db tag option: column CHECK constraint expression (db:"...,check=expr")
	Default          string // db tag option: column DEFAULT value (db:"...,default=expr")
	TypeOverride     string // db tag option: explicit SQL type (db:"...,type=jsonb"); overrides inference
	IsJSON           bool   // map/slice/struct mapped as JSON/jsonb (default for non-primitive, non-VO, non-enum container types)
	IsArray          bool   // slice field (JSON array by default; native T[] when TypeOverride is set on Postgres)
}

type Index added in v0.3.0

type Index struct {
	Name    string   `json:"name"`
	Columns []string `json:"columns"`
	Unique  bool     `json:"unique,omitempty"`
}

Index describes a single (possibly composite, possibly unique) index.

type KeyColumn added in v0.8.0

type KeyColumn struct {
	FieldName  string // exported Go field on the key struct; empty for a scalar key
	ColumnName string // database column name
	GoType     string // local Go type name, e.g. "int" or "UUID"
	// PkgPath is the import path of the field's type. It is empty for a
	// builtin type and for a type declared in the model's own package.
	PkgPath string
	// UnderlyingGoType is the normalization kind of the column: "int" for any
	// signed integer, "string", or "uuid.UUID". It selects the conversion
	// rule and names the type the conversion helper returns, which is not
	// always GoType: a named type or a sized integer needs a conversion back.
	UnderlyingGoType string
}

KeyColumn is one column of a model's primary key.

type ModelInfo

type ModelInfo struct {
	Name       string
	PkgPath    string
	PkgName    string
	PKType     string // display type for generated code (e.g., "int", "uuid.UUID")
	PKTypeFull string // fully qualified (e.g., "github.com/google/uuid.UUID")
	PKTypePkg  string // import path for external PK types (empty for primitives)
	TableName  string
	// RenamedFrom, when set, is this table's previous name. The migration
	// differ turns it into an ALTER TABLE ... RENAME TO.
	RenamedFrom   string
	Fields        []FieldInfo
	HasSoftDelete bool
	HasVersioned  bool
	HasAudit      bool
	Dir           string // filesystem directory of the package
	// Module is the feature slice this model belongs to. It is empty for a
	// config that lists packages instead of modules.
	Module string
	// Key lists the primary key columns in key order. A scalar key type has
	// exactly one entry whose FieldName is empty.
	Key []KeyColumn
	// KeyIsStruct reports whether the key type argument is a struct.
	KeyIsStruct bool
	// EnumOwners names the enum types whose Values()/IsValid() helpers this
	// model's generated file declares. Two models of one package that share an
	// enum type must not declare the helpers two times, so assignEnumOwners
	// gives each enum type to exactly one model of the package. A nil map means
	// the model owns every enum it uses, which is the case when a caller emits
	// one model on its own.
	EnumOwners map[string]bool
}

func ScanPackages

func ScanPackages(patterns []string, dir ...string) ([]ModelInfo, error)

func (ModelInfo) IsCompositeKey added in v0.8.0

func (m ModelInfo) IsCompositeKey() bool

IsCompositeKey reports whether the model's primary key spans more than one column.

func (ModelInfo) PKColumns added in v0.8.0

func (m ModelInfo) PKColumns() []string

PKColumns returns the primary key column names in key order.

type ModuleConfig added in v0.7.0

type ModuleConfig struct {
	Name       string   `yaml:"name"`
	Packages   []string `yaml:"packages"`
	Migrations string   `yaml:"migrations"`
}

ModuleConfig describes one feature slice. A slice owns its models and its migrations, so a person can delete its directory and remove the feature.

type OutputConfig

type OutputConfig struct {
	DB         string `yaml:"db"`
	Migrations string `yaml:"migrations"`
}

type RelationFieldInfo

type RelationFieldInfo struct {
	Type        string // "has_many", "has_one", "belongs_to", "many_to_many"
	FK          string // foreign key column
	JoinTable   string // for many_to_many
	RefColumn   string // target FK column in pivot table (many-to-many)
	TargetModel string // target model name for foreign key resolution
}

RelationFieldInfo holds parsed relationship metadata from a `rel:"..."` struct tag.

type Schema added in v0.3.0

type Schema struct {
	Tables []Table   `json:"tables"`
	Enums  []EnumDef `json:"enums,omitempty"`
}

Schema is a JSON-serializable, dialect-resolved logical representation of the full database schema. It is the canonical input to the structured migration diff engine and the persisted migration snapshot.

func BuildSchema added in v0.3.0

func BuildSchema(models []ModelInfo, dialect string) Schema

BuildSchema produces the full logical schema for the given models and dialect. It includes all tables (models + many-to-many pivots), all columns (user columns + trait columns), all indexes (explicit/unique/composite from db tag options), and enum type definitions (Postgres only).

Ordering is deterministic: tables preserve model declaration order (pivots appended after, in discovery order), columns preserve PK-first then field order then trait order, and indexes within a table are sorted by name.

func LoadSnapshot added in v0.3.0

func LoadSnapshot(path string) (Schema, bool, error)

LoadSnapshot reads a schema snapshot from path. The returned bool is false when the file does not exist (which is not treated as an error).

type Table added in v0.3.0

type Table struct {
	Name       string   `json:"name"`
	Columns    []Column `json:"columns"`
	Indexes    []Index  `json:"indexes,omitempty"`
	PrimaryKey []string `json:"primaryKey,omitempty"`

	// RenamedFrom is the table's previous name, declared by a renamed_from
	// marker on the model. It is a diff-time input only, never persisted: a
	// marker in the snapshot would make every future diff re-emit the rename.
	RenamedFrom string `json:"-"`
}

Table describes a single relation (table or pivot) and its columns/indexes. PrimaryKey, when non-empty, declares a table-level composite primary key: used by many-to-many pivot tables, and by a model with a multi-field struct key. A model with a single key column (scalar or single-field struct key) leaves PrimaryKey empty and instead declares the key inline on that column, via its PK field and a type string embedding "PRIMARY KEY".

Jump to

Keyboard shortcuts

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