Files
relspecgo/pkg/writers/gorm
warkanum 3b88c386a1 fix(codegen): sort map iteration to make generated output deterministic
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.
2026-08-10 20:54:40 +02:00
..
2025-12-17 20:44:02 +02:00

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 gorm struct 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 gorm tags with foreignKey and references

Notes

  • Model names are prefixed with "Model" (e.g., ModelUser)
  • Nullable columns use sql_types.SqlString, sql_types.SqlInt64, etc. by default; pass --types stdlib to use sql.NullString, sql.NullInt64, etc. instead
  • Array columns use sql_types.SqlStringArray, sql_types.SqlInt32Array, etc. by default; --types stdlib produces 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