Table.Columns/Constraints/Indexes/Relationships are Go maps, and every writer, reader, diff, inspector, and merge code path that iterated them directly was subject to Go's randomized map order, so identical input could produce different output (or a different in-report violation/diff order) on every run. Most visibly this showed up as bun/gorm `unique:` struct tags changing order across consecutive `make models` runs with no source change. Fixed by sorting map iteration (by Sequence then Name, or alphabetically for string-keyed maps) everywhere the order affects generated output or first-match tie-break logic, across the bun, gorm, sqlite, dbml, drawdb, pgsql, prisma, graphql, typeorm, drizzle, and dctx writers; the dctx, prisma, and typeorm readers; the shared models.GetPrimaryKey/ GetForeignKeys helpers; pkg/diff, pkg/inspector, and pkg/merge; and the TUI column/relationship pickers in pkg/ui.
GORM Writer
Generates Go source files with GORM model definitions from database schema information.
Overview
The GORM Writer converts RelSpec's internal database model representation into Go source code with GORM struct definitions, complete with proper tags, relationships, and methods.
With --types sqltypes, nullable fields use the pkg/sqltypes package.
Features
- Generates GORM-compatible Go structs
- Creates proper
gormstruct tags - Generates
TableName()methods - Adds relationship fields (belongs-to, has-many)
- Supports both single-file and multi-file output
- Auto-generates helper methods (optional)
- Maps SQL types to Go types
- Handles nullable fields with custom sql_types
Usage
Basic Example
package main
import (
"git.warky.dev/wdevs/relspecgo/pkg/models"
"git.warky.dev/wdevs/relspecgo/pkg/writers"
"git.warky.dev/wdevs/relspecgo/pkg/writers/gorm"
)
func main() {
// Assume db is a *models.Database from a reader
options := &writers.WriterOptions{
OutputPath: "models.go",
PackageName: "models",
}
writer := gorm.NewWriter(options)
err := writer.WriteDatabase(db)
if err != nil {
panic(err)
}
}
CLI Examples
# Generate GORM models from a DBML schema (default: baselib pointer types)
relspec convert --from dbml --from-path schema.dbml \
--to gorm --to-path models.go --package models
# Use standard library database/sql nullable types instead
relspec convert --from dbml --from-path schema.dbml \
--to gorm --to-path models.go --package models \
--types stdlib
# Select sqltypes package types (git.warky.dev/wdevs/relspecgo/pkg/sqltypes)
relspec convert --from pgsql --from-conn "postgres://localhost/mydb" \
--to gorm --to-path models.go --package models \
--types sqltypes
# Multi-file output (one file per table)
relspec convert --from json --from-path schema.json \
--to gorm --to-path models/ --package models
Output Modes
Single File Mode
Generates all models in one file:
relspec --input pgsql --conn "..." --output gorm --out-file models.go
Multi-File Mode
Generates one file per table (auto-detected when output is a directory):
relspec --input pgsql --conn "..." --output gorm --out-file models/
Files are named: sql_{schema}_{table}.go
Generated Code Examples
sqltypes package types (--types sqltypes)
package models
import (
sql_types "git.warky.dev/wdevs/relspecgo/pkg/sqltypes"
)
type ModelUser struct {
ID string `gorm:"column:id;type:uuid;primaryKey" json:"id"`
Username string `gorm:"column:username;type:text;not null" json:"username"`
Email sql_types.SqlString `gorm:"column:email;type:text" json:"email,omitempty"`
Tags sql_types.SqlStringArray `gorm:"column:tags;type:text[];not null;default:'{}'" json:"tags"`
CreatedAt sql_types.SqlTimeStamp `gorm:"column:created_at;type:timestamptz;not null;default:now()" json:"created_at"`
}
func (ModelUser) TableName() string {
return "public.users"
}
Standard library — --types stdlib
package models
import (
"database/sql"
"time"
)
type ModelUser struct {
ID string `gorm:"column:id;type:uuid;primaryKey" json:"id"`
Username string `gorm:"column:username;type:text;not null" json:"username"`
Email sql.NullString `gorm:"column:email;type:text" json:"email,omitempty"`
Tags []string `gorm:"column:tags;type:text[];not null;default:'{}'" json:"tags"`
CreatedAt time.Time `gorm:"column:created_at;type:timestamptz;not null;default:now()" json:"created_at"`
}
func (ModelUser) TableName() string {
return "public.users"
}
Writer Options
NullableTypes
Controls which Go package is used for nullable column types. Set via the --types CLI flag or WriterOptions.NullableTypes:
// Use sqltypes package types
options := &writers.WriterOptions{
OutputPath: "models.go",
PackageName: "models",
NullableTypes: writers.NullableTypeSqlTypes,
}
// Use standard library database/sql types
options := &writers.WriterOptions{
OutputPath: "models.go",
PackageName: "models",
NullableTypes: writers.NullableTypeStdlib,
}
Metadata Options
Configure additional writer behavior using metadata in WriterOptions:
options := &writers.WriterOptions{
OutputPath: "models.go",
PackageName: "models",
Metadata: map[string]any{
"multi_file": true, // Enable multi-file mode
"populate_refs": true, // Populate RefDatabase/RefSchema
"generate_get_id_str": true, // Generate GetIDStr() methods
},
}
Type Mapping
The nullable type package is selected with --types (or WriterOptions.NullableTypes).
| SQL Type | NOT NULL — both | Nullable — sqltypes | Nullable — stdlib |
|---|---|---|---|
bigint |
int64 |
SqlInt64 |
sql.NullInt64 |
integer |
int32 |
SqlInt32 |
sql.NullInt32 |
smallint |
int16 |
SqlInt16 |
sql.NullInt16 |
text, varchar |
string |
SqlString |
sql.NullString |
boolean |
bool |
SqlBool |
sql.NullBool |
timestamp, timestamptz |
time.Time |
SqlTimeStamp |
sql.NullTime |
numeric, decimal |
float64 |
SqlFloat64 |
sql.NullFloat64 |
uuid |
string |
SqlUUID |
sql.NullString |
jsonb |
string |
SqlString |
sql.NullString |
text[] |
SqlStringArray |
SqlStringArray |
[]string |
integer[] |
SqlInt32Array |
SqlInt32Array |
[]int32 |
uuid[] |
SqlUUIDArray |
SqlUUIDArray |
[]string |
vector |
SqlVector |
SqlVector |
[]float32 |
Relationship Generation
The writer automatically generates relationship fields:
- Belongs-to: Generated for tables with foreign keys
- Has-many: Generated for tables referenced by foreign keys
- Relationship field names use 3-letter prefixes
- Includes proper
gormtags withforeignKeyandreferences
Notes
- Model names are prefixed with "Model" (e.g.,
ModelUser) - Nullable columns use
sql_types.SqlString,sql_types.SqlInt64, etc. by default; pass--types stdlibto usesql.NullString,sql.NullInt64, etc. instead - Array columns use
sql_types.SqlStringArray,sql_types.SqlInt32Array, etc. by default;--types stdlibproduces plain Go slices ([]string,[]int32, …) - Generated code is auto-formatted with
go fmt - JSON tags are automatically added
- Supports schema-qualified table names in
TableName()method