Documentation
¶
Index ¶
- Constants
- func DiffSchemas(old, newSchema Schema, dialect string) (upSQL, downSQL string, err error)
- func EmitDBFile(models []ModelInfo, dbPkgName string) string
- func EmitMigrationsEmbed(pkgName, module string) string
- func EmitModelFile(m ModelInfo) string
- func EmitModelFileChecked(m ModelInfo) (string, error)
- func Generate(configPath string) error
- func GenerateCreateTable(m ModelInfo, fks map[string]string, dialect string) string
- func GenerateDropSchema(models []ModelInfo) string
- func GenerateModule(configPath, module string) error
- func GenerateSchema(models []ModelInfo, dialect string) string
- func GenerateWatch(ctx context.Context, configPath string, pollInterval time.Duration) error
- func GoTypeToSQL(goType string, dialect string) string
- func ResolveModuleRoot(startDir string) string
- func SaveSnapshot(path string, s Schema) error
- func ValidateModels(models []ModelInfo) error
- func WriteMigrationsEmbed(dir, module string) (bool, error)
- type Column
- type Config
- type EnumDef
- type FieldInfo
- type FieldKind
- type Index
- type KeyColumn
- type ModelInfo
- type ModuleConfig
- type OutputConfig
- type RelationFieldInfo
- type Schema
- type Table
Constants ¶
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
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 ¶
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
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 ¶
EmitModelFile generates the complete per-model _drel.go file content.
func EmitModelFileChecked ¶
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 ¶
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 ¶
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 ¶
GenerateDropSchema emits DROP TABLE statements in reverse order to respect foreign key dependencies.
func GenerateModule ¶ added in v0.7.0
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 ¶
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
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 ¶
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
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
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
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
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 (*Config) AllPackages ¶ added in v0.7.0
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 FieldKind ¶ added in v0.9.0
type FieldKind int
FieldKind is the closed set of column shapes codegen understands. Every db-mapped field gets exactly one kind, decided one time by classifyField.
The set is closed on purpose. Before it existed, an if/else ladder ended in "any struct, slice, map or array is JSON", so every type codegen did not recognise silently became a jsonb column -- time.Time among them. A type that matches no kind is now KindUnsupported and the scan rejects it by name.
const ( // KindUnsupported is a type codegen cannot map. It is rejected, never guessed. KindUnsupported FieldKind = iota // KindPrimitive is a basic Go type, or a pointer to one. KindPrimitive // KindBytes is []byte. Postgres bytea and SQLite BLOB are not implemented // yet, so it is rejected with its own message rather than silently mapped. KindBytes // KindTime is time.Time or *time.Time, which every driver maps natively. KindTime // KindMultiColVO implements drel.MultiColumnMapper: one field, many columns. KindMultiColVO // KindSingleColVO implements sql.Scanner and driver.Valuer. KindSingleColVO // KindEnum is a named type over a string or integer with declared constants. KindEnum // KindNamedPrimitive is a named type over a basic kind with no constants. KindNamedPrimitive // KindJSON is a slice, map, array or plain struct, stored as JSON. KindJSON )
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 (ModelInfo) IsCompositeKey ¶ added in v0.8.0
IsCompositeKey reports whether the model's primary key spans more than one column.
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 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
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
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.
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".