go-gorm/cli(新しいタブで開きます)
主要言語: Go
Type-safe GORM with SQL templates, model helpers, first-class associations
- star 数
- 109
- watcher 数
- 3
- fork 数
- 15
- issue 数
- 39
- 最終更新
- 2026/03/14
主要言語: Go
Type-safe GORM with SQL templates, model helpers, first-class associations
GORM CLI generates two complementary layers of code for your GORM projects:
Together they deliver compile-time safety and a fluent, discoverable API for all database operations
gorm.io/gorm—no runtime magic, just plain Go.genconfig.Config.Create, CreateInBatch, Update, Unlink, and Delete for associations.go install gorm.io/cli/gorm@latest
// examples/models/user.go type User struct { gorm.Model Name string Age int Pets []Pet `gorm:"many2many:user_pets"` } type Pet struct { gorm.Model Name string } // examples/query.go type Query[T any] interface { // SELECT * FROM @@table WHERE id=@id GetByID(id int) (T, error) }
# Default: strictly typed generics API gorm gen -i ./examples -o ./generated # Standard API (still generics-based, relaxed typing for more flexibility) gorm gen -i ./examples -o ./generated --typed=false
// Type-safe query // SELECT * FROM users WHERE id=123 u, err := generated.Query[User](db).GetByID(ctx, 123) // Field helpers // SELECT * FROM users WHERE age>18 users, _ := gorm.G[User](db).Where(generated.User.Age.Gt(18)).Find(ctx) // Association helpers: create a user with a pet gorm.G[User](db). Set( generated.User.Name.Set("alice"), generated.User.Pets.Create(generated.Pet.Name.Set("fido")), ). Create(ctx)
GORM CLI uses two generators that work together for full developer ergonomics:
Define methods with SQL template comments in Go interfaces to generate concrete, type-safe methods.
Generate strongly-typed helpers from your models to build filters, updates, ordering, and associations without raw SQL.
Supported types & associations (field helpers):
string, bool, time.Time, []bytedatabase/sql.Scanner / driver.Valuer or GORM Serializerhas one **(including polymorphic), has many **(including polymorphic), belongs to, many2manyCommon predicates & setters:
// Predicates generated.User.ID.Eq(1) // id = 1 generated.User.Name.Like("%jinzhu%") // name LIKE '%jinzhu%' generated.User.Age.Between(18, 65) // age BETWEEN 18 AND 65 generated.User.Score.IsNull() // score IS NULL (e.g., sql.NullInt64) // Updates (supports expressions and zero-values) gorm.G[User](db). Where(generated.User.Name.Eq("alice")). Set( generated.User.Name.Set("jinzhu"), generated.User.IsAdult.Set(false), generated.User.Score.Set(sql.NullInt64{}), generated.User.Count.Incr(1), generated.User.Age.SetExpr(clause.Expr{ SQL: "GREATEST(?, ?)", Vars: []any{clause.Column{Name: "age"}, 18}, }), ). Update(ctx) // Create with Set(...) gorm.G[User](db). Set( generated.User.Name.Set("alice"), generated.User.Age.Set(0), generated.User.Status.Set("active"), ). Create(ctx)
Standard API note (
--typed=false) The default output is strictly typed. With the Standard API, you keep generics but gain flexibility to mix raw conditions with helpers:generated.Query[User](db). Where("name = ?", "jinzhu"). // raw condition Where(generated.User.Age.Gt(18)). // typed helper Find(ctx)
Association helpers appear on generated models as field.Struct[T] or field.Slice[T] (e.g. generated.User.Pets, generated.User.Account).
Supported operations (composed into Set(...).Update(ctx) or Set(...).Create(ctx)):
// Create a pet for each matched user gorm.G[User](db). Where(generated.User.ID.Eq(1)). Set(generated.User.Pets.Create(generated.Pet.Name.Set("fido"))). Update(ctx) // Filter on the child before acting gorm.G[User](db). Where(generated.User.ID.Eq(1)). Set(generated.User.Pets.Where(generated.Pet.Name.Eq("old")).Delete()). Update(ctx) // Batch link two pets to an existing user gorm.G[User](db). Where(generated.User.ID.Eq(1)). Set(generated.User.Pets.CreateInBatch([]models.Pet{{Name: "rex"}, {Name: "spot"}})). Update(ctx)
Semantics by association type:
Unlink clears the parent FK; Delete removes associated rowsUnlink clears the child FK; Delete removes child rowsUnlink/Delete remove join rows only (both sides remain)Parent operation semantics:
Create(ctx) inserts new parent rows using your Set(...) values, then applies association opsUpdate(ctx) updates matched parent rows, then applies association opsWrite SQL and light templating in interface comments; parameters bind automatically; implementations are generated and type-safe.
type Query[T any] interface { // SELECT * FROM @@table WHERE id=@id GetByID(id int) (T, error) // SELECT * FROM @@table WHERE @@column=@value FilterWithColumn(column string, value string) (T, error) // SELECT * FROM @@table // {{where}} // {{if user.Name }} name=@user.Name {{end}} // {{if user.Age > 0}} AND age=@user.Age {{end}} // {{end}} SearchUsers(user User) ([]T, error) // UPDATE @@table // {{set}} // {{if user.Name != ""}} name=@user.Name, {{end}} // {{if user.Age > 0}} age=@user.Age, {{end}} // {{if user.Age >= 18}} is_adult=1 {{else}} is_adult=0 {{end}} // {{end}} // WHERE id=@id UpdateUser(user User, id int) error }
Context auto-injection If a method doesn’t include
ctx context.Context, the generated implementation adds it.
Example usage
// SQL: SELECT * FROM users WHERE id=123 user, err := generated.Query[User](db).GetByID(ctx, 123) // SQL: SELECT * FROM users WHERE name="jinzhu" AND age=25 (appended to current builder) users, err := generated.Query[User](db).FilterByNameAndAge("jinzhu", 25).Find(ctx) // SQL UPDATE users SET name="jinzhu", age=20, is_adult=1 WHERE id=1 err := generated.Query[User](db).UpdateUser(ctx, User{Name: "jinzhu", Age: 20}, 1)
| Directive | Purpose | Example |
|---|---|---|
@@table |
Model table name | SELECT * FROM @@table WHERE id=@id |
@@column |
Dynamic column binding | @@column=@value |
@param |
Bind Go params to SQL params | WHERE name=@user.Name |
{{where}} |
Conditional WHERE wrapper | {{where}} age > 18 {{end}} |
{{set}} |
Conditional SET wrapper (UPDATE) | {{set}} name=@name {{end}} |
{{if}} |
Conditional SQL fragment | {{if age > 0}} AND age=@age {{end}} |
{{for}} |
Iterate over a collection | {{for _, t := range tags}} ... {{end}} |
-- Safe parameter binding SELECT * FROM @@table WHERE id=@id AND status=@status -- Dynamic column binding SELECT * FROM @@table WHERE @@column=@value -- Conditional WHERE SELECT * FROM @@table {{where}} {{if name != ""}} name=@name {{end}} {{if age > 0}} AND age=@age {{end}} {{end}} -- Dynamic UPDATE UPDATE @@table {{set}} {{if user.Name != ""}} name=@user.Name, {{end}} {{if user.Email != ""}} email=@user.Email {{end}} {{end}} WHERE id=@id -- Iteration SELECT * FROM @@table {{where}} {{for _, tag := range tags}} {{if tag != ""}} tags LIKE concat('%',@tag,'%') OR {{end}} {{end}} {{end}}
You can generate without any config. For overrides, declare a package-level genconfig.Config in the package you generate:
package examples import ( "database/sql" "gorm.io/cli/gorm/field" "gorm.io/cli/gorm/genconfig" ) var _ = genconfig.Config{ OutPath: "examples/output", // Map Go types to helper kinds FieldTypeMap: map[any]any{ sql.NullTime{}: field.Time{}, }, // Map `gen:"name"` tags to helper kinds FieldNameMap: map[string]any{ "json": JSON{}, // use a custom JSON helper where fields are tagged `gen:"json"` }, // Narrow what gets generated (patterns or type literals) IncludeInterfaces: []any{"Query*", models.Query(nil)}, IncludeStructs: []any{"User", "Account*", models.User{}}, }
package examples import "gorm.io/cli/gorm/genconfig" var _ = genconfig.Config{ OutPath: "examples/output", FieldNameMap: map[string]any{ "json": JSON{}, // map fields with `gen:"json"` tag to custom JSON helper }, }
package models type User struct { // ... other fields ... // Tell the generator to use the custom JSON helper for this column Profile string `gen:"json"` }
// JSON is a field helper for JSON columns that generates different SQL for different databases. type JSON struct{ column clause.Column } func (j JSON) WithColumn(name string) JSON { c := j.column c.Name = name return JSON{column: c} } // Equal builds an expression using database-specific JSON functions to compare func (j JSON) Equal(path string, value any) clause.Expression { return jsonEqualExpr{col: j.column, path: path, val: value} } type jsonEqualExpr struct { col clause.Column path string val any } func (e jsonEqualExpr) Build(builder clause.Builder) { if stmt, ok := builder.(*gorm.Statement); ok { switch stmt.Dialector.Name() { case "mysql": v, _ := json.Marshal(e.val) clause.Expr{SQL: "JSON_EXTRACT(?, ?) = CAST(? AS JSON)", Vars: []any{e.col, e.path, string(v)}}.Build(builder) case "sqlite": clause.Expr{SQL: "json_valid(?) AND json_extract(?, ?) = ?", Vars: []any{e.col, e.col, e.path, e.val}}.Build(builder) default: clause.Expr{SQL: "jsonb_extract_path_text(?, ?) = ?", Vars: []any{e.col, e.path[2:], e.val}}.Build(builder) } } }
// This will generate different SQL depending on the database: // MySQL: "JSON_EXTRACT(`profile`, "$.vip") = CAST("true" AS JSON)" // SQLite: "json_valid(`profile`) AND json_extract(`profile`, "$.vip") = 1" got, err := gorm.G[models.User](db). Where(generated.User.Profile.Equal("$.vip", true)).Take(ctx)