A code generator base on GORM

GORM/GEN

GoVersion Release Go.Dev reference Go Report Card MIT license OpenIssue ClosedIssue TODOs

The code generator base on GORM, aims to be developer friendly.

Overview

  • CRUD or DIY query method code generation
  • Auto migration from database to code
  • Transactions, Nested Transactions, Save Point, RollbackTo to Saved Point
  • Competely compatible with GORM
  • Developer Friendly

Contents

Installation

To install Gen package, you need to install Go and set your Go workspace first.

1.The first need Go installed(version 1.14+ is required), then you can use the below Go command to install Gen.

go get -u gorm.io/gen

2.Import it in your code:

import "gorm.io/gen"

Quick start

# assume the following code in generate.go file
$ cat generate.go
package main

import "gorm.io/gen"

// generate code
func main() {
    // specify the output directory (default: "./query")
    g := gen.NewGenerator(gen.Config{OutPath: "../dal/query"})
  
    // reuse the database connection in Project or create a connection here
    // db, _ := gorm.Open(mysql.Open("root:@(127.0.0.1:3306)/demo?charset=utf8mb4&parseTime=True&loc=Local"))
    g.UseDB(db)
  
    // apply basic crud api on structs or table models which is specified by table name with function
    // GenerateModel/GenerateModelAs. And generator will generate table models' code when calling Excute.
    g.ApplyBasic(model.User{}, g.GenerateModel("company"), g.GenerateModelAs("people", "Person"),)
    
    // apply diy interfaces on structs or table models
    g.ApplyInterface(func(method model.Method) {}, model.User{}, g.GenerateModel("company"))

    // execute the action of code generation
    g.Execute()
}

Project Directory

Here is a template for best practices:

demo
├── cmd
│   └── generate
│       └── generate.go # execute it will generate codes
├── dal
│   ├── dal.go # create connections with database server here
│   ├── model
│   │   ├── method.go # DIY method interfaces
│   │   └── model.go  # store struct which corresponding to the database table
│   └── query  # generated code's directory
│       └── gorm_generated.go # generated code
├── biz
│   └── query.go # call function in dal/gorm_generated.go and query databases
├── config
│   └── config.go # DSN for database server
├── generate.sh # a shell to execute cmd/generate
├── go.mod
├── go.sum
└── main.go

API Examples

Field Expression

Create Field

Actually, you're not supposed to create a new field variable, cause it will be accomplished in generated code.

Field Type Detail Type Crerate Function Supported Query Method
generic field NewField IsNull/IsNotNull/Count
int int/int8/.../int64 NewInt/NewInt8/.../NewInt64 Eq/Neq/Gt/Gte/Lt/Lte/In/NotIn/Between/NotBetween/Like/NotLike/Add/Sub/Mul/Div/Mod/FloorDiv/RightShift/LeftShift/BitXor/BitAnd/BitOr/BitFlip
uint uint/uint8/.../uint64 NewUint/NewUint8/.../NewUint64 same with int
float float32/float64 NewFloat32/NewFloat64 Eq/Neq/Gt/Gte/Lt/Lte/In/NotIn/Between/NotBetween/Like/NotLike/Add/Sub/Mul/Div/FloorDiv
string string/[]byte NewString/NewBytes Eq/Neq/Gt/Gte/Lt/Lte/Between/NotBetween/In(val/NotIn(val/Like/NotLike/Regexp/NotRegxp
bool bool NewBool Not/Is/And/Or/Xor/BitXor/BitAnd/BitOr
time time.Time NewTime Eq/Neq/Gt/Gte/Lt/Lte/Between/NotBetween/In/NotIn/Add/Sub

Create field examples:

import "gorm.io/gen/field"

// create a new generic field map to `generic_a`
a := field.NewField("table_name", "generic_a")

// create a field map to `id`
i := field.NewInt("user", "id")

// create a field map to `address`
s := field.NewString("user", "address")

// create a field map to `create_time`
t := field.NewTime("user", "create_time")

CRUD API

Here is a basic struct user and struct DB.

// generated code
// generated code
// generated code
package query

import "gorm.io/gen"

// struct map to table `users` 
type user struct {
    gen.DO
    ID       field.Uint
    Name     field.String
    Age      field.Int
    Address  field.Field
    Birthday field.Time
}

// struct collection
type DB struct {
    db       *gorm.DB
    User     *user
}

Create

Create record
// u refer to query.user
user := model.User{Name: "Modi", Age: 18, Birthday: time.Now()}

u := query.Query.User
err := u.Create(&user) // pass pointer of data to Create

err // returns error
Create record with selected fields

Create a record and assgin a value to the fields specified.

u := query.Query.User
u.Select(u.Name, u.Age).Create(&user)
// INSERT INTO `users` (`name`,`age`) VALUES ("modi", 18)

Create a record and ignore the values for fields passed to omit

u := query.Query.User
u.Omit(u.Name, u.Age).Create(&user)
// INSERT INTO `users` (`Address`, `Birthday`) VALUES ("2021-08-17 20:54:12.000", 18)
Batch Insert

To efficiently insert large number of records, pass a slice to the Create method. GORM will generate a single SQL statement to insert all the data and backfill primary key values.

var users = []model.User{{Name: "modi"}, {Name: "zhangqiang"}, {Name: "songyuan"}}
query.Query.User.Create(&users)

for _, user := range users {
    user.ID // 1,2,3
}

You can specify batch size when creating with CreateInBatches, e.g:

var users = []User{{Name: "modi_1"}, ...., {Name: "modi_10000"}}

// batch size 100
query.Query.User.CreateInBatches(users, 100)

It will works if you set CreateBatchSize in gorm.Config / gorm.Session

db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
    CreateBatchSize: 1000,
})
// OR
db = db.Session(&gorm.Session{CreateBatchSize: 1000})

u := query.NewUser(db)

var users = []User{{Name: "modi_1"}, ...., {Name: "modi_5000"}}

u.Create(&users)
// INSERT INTO users xxx (5 batches)

Query

Retrieving a single object

Generated code provides First, Take, Last methods to retrieve a single object from the database, it adds LIMIT 1 condition when querying the database, and it will return the error ErrRecordNotFound if no record is found.

u := query.Query.User

// Get the first record ordered by primary key
user, err := u.First()
// SELECT * FROM users ORDER BY id LIMIT 1;

// Get one record, no specified order
user, err := u.Take()
// SELECT * FROM users LIMIT 1;

// Get last record, ordered by primary key desc
user, err := u.Last()
// SELECT * FROM users ORDER BY id DESC LIMIT 1;

// check error ErrRecordNotFound
errors.Is(err, gorm.ErrRecordNotFound)
Retrieving objects with primary key
u := query.Query.User

user, err := u.Where(u.ID.Eq(10)).First()
// SELECT * FROM users WHERE id = 10;

users, err := u.Where(u.ID.In(1,2,3)).Find()
// SELECT * FROM users WHERE id IN (1,2,3);

If the primary key is a string (for example, like a uuid), the query will be written as follows:

user, err := u.Where(u.ID.Eq("1b74413f-f3b8-409f-ac47-e8c062e3472a")).First()
// SELECT * FROM users WHERE id = "1b74413f-f3b8-409f-ac47-e8c062e3472a";
Retrieving all objects
u := query.Query.User

// Get all records
users, err := u.Find()
// SELECT * FROM users;
Conditions
String Conditions
'modi'; // IN users, err := u.Where(u.Name.In("modi", "zhangqiang")).Find() // SELECT * FROM users WHERE name IN ('modi','zhangqiang'); // LIKE users, err := u.Where(u.Name.Like("%modi%")).Find() // SELECT * FROM users WHERE name LIKE '%modi%'; // AND users, err := u.Where(u.Name.Eq("modi"), u.Age.Gte(17)).Find() // SELECT * FROM users WHERE name = 'modi' AND age >= 17; // Time users, err := u.Where(u.Birthday.Gt(birthTime).Find() // SELECT * FROM users WHERE birthday > '2000-01-01 00:00:00'; // BETWEEN users, err := u.Where(u.Birthday.Between(lastWeek, today)).Find() // SELECT * FROM users WHERE birthday BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00'; ">
u := query.Query.User

// Get first matched record
user, err := u.Where(u.Name.Eq("modi")).First()
// SELECT * FROM users WHERE name = 'modi' ORDER BY id LIMIT 1;

// Get all matched records
users, err := u.Where(u.Name.Neq("modi")).Find()
// SELECT * FROM users WHERE name <> 'modi';

// IN
users, err := u.Where(u.Name.In("modi", "zhangqiang")).Find()
// SELECT * FROM users WHERE name IN ('modi','zhangqiang');

// LIKE
users, err := u.Where(u.Name.Like("%modi%")).Find()
// SELECT * FROM users WHERE name LIKE '%modi%';

// AND
users, err := u.Where(u.Name.Eq("modi"), u.Age.Gte(17)).Find()
// SELECT * FROM users WHERE name = 'modi' AND age >= 17;

// Time
users, err := u.Where(u.Birthday.Gt(birthTime).Find()
// SELECT * FROM users WHERE birthday > '2000-01-01 00:00:00';

// BETWEEN
users, err := u.Where(u.Birthday.Between(lastWeek, today)).Find()
// SELECT * FROM users WHERE birthday BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00';
Inline Condition
"modi" AND age > 17; ">
u := query.Query.User

// Get by primary key if it were a non-integer type
user, err := u.Where(u.ID.Eq("string_primary_key")).First()
// SELECT * FROM users WHERE id = 'string_primary_key';

// Plain SQL
users, err := u.Where(u.Name.Eq("modi")).Find()
// SELECT * FROM users WHERE name = "modi";

users, err := u.Where(u.Name.Neq("modi"), u.Age.Gt(17)).Find()
// SELECT * FROM users WHERE name <> "modi" AND age > 17;
Not Conditions

Build NOT conditions, works similar to Where

u := query.Query.User

user, err := u.Not(u.Name.Eq("modi")).First()
// SELECT * FROM users WHERE NOT name = "modi" ORDER BY id LIMIT 1;

// Not In
users, err := u.Not(u.Name.In("modi", "zhangqiang")).Find()
// SELECT * FROM users WHERE name NOT IN ("modi", "zhangqiang");

// Not In slice of primary keys
user, err := u.Not(u.ID.In(1,2,3)).First()
// SELECT * FROM users WHERE id NOT IN (1,2,3) ORDER BY id LIMIT 1;
Or Conditions
u := query.Query.User

users, err := u.Where(u.Role.Eq("admin")).Or(u.Role.Eq("super_admin")).Find()
// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';
Group Conditions

Easier to write complicated SQL query with Group Conditions

p := query.Query.Pizza

pizzas, err := p.Where(
    p.Where(p.Pizza.Eq("pepperoni")).Where(p.Where(p.Size.Eq("small")).Or(p.Size.Eq("medium"))),
).Or(
    p.Where(p.Pizza.Eq("hawaiian")).Where(p.Size.Eq("xlarge")),
).Find()

// SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge")
Selecting Specific Fields

Select allows you to specify the fields that you want to retrieve from database. Otherwise, GORM will select all fields by default.

u := query.Query.User

users, err := u.Select(u.Name, u.Age).Find()
// SELECT name, age FROM users;

u.Select(u.Age.Avg()).Rows()
// SELECT Avg(age) FROM users;
Order

Specify order when retrieving records from the database

u := query.Query.User

users, err := u.Order(u.Age.Desc(), u.Name).Find()
// SELECT * FROM users ORDER BY age DESC, name;

// Multiple orders
users, err := u.Order(u.Age.Desc()).Order(u.Name).Find()
// SELECT * FROM users ORDER BY age DESC, name;
Limit & Offset

Limit specify the max number of records to retrieve Offset specify the number of records to skip before starting to return the records

u := query.Query.User

urers, err := u.Limit(3).Find()
// SELECT * FROM users LIMIT 3;

// Cancel limit condition with -1
users, err := u.Limit(10).Limit(-1).Find()
// SELECT * FROM users;

users, err := u.Offset(3).Find()
// SELECT * FROM users OFFSET 3;

users, err := u.Limit(10).Offset(5).Find()
// SELECT * FROM users OFFSET 5 LIMIT 10;

// Cancel offset condition with -1
users, err := u.Offset(10).Offset(-1).Find()
// SELECT * FROM users;
Group By & Having
u := query.Query.User

type Result struct {
    Date  time.Time
    Total int
}

var result Result

err := u.Select(u.Name, u.Age.Sum().As("total")).Where(u.Name.Like("%modi%")).Group(u.Name).Scan(&result)
// SELECT name, sum(age) as total FROM `users` WHERE name LIKE "group%" GROUP BY `name`

err := u.Select(u.Name, u.Age.Sum().As("total")).Group(u.Name).Having(u.Name.Eq("group")).Scan(&result)
// SELECT name, sum(age) as total FROM `users` GROUP BY `name` HAVING name = "group"

rows, err := u.Select(u.Birthday.As("date"), u.Age.Sum().As("total")).Group(u.Birthday).Rows()
for rows.Next() {
  ...
}

o := query.Query.Order

rows, err := o.Select(o.CreateAt.Date().As("date"), o.Amount.Sum().As("total")).Group(o.CreateAt.Date()).Having(u.Amount.Sum().Gt(100)).Rows()
for rows.Next() {
  ...
}

var results []Result

o.Select(o.CreateAt.Date().As("date"), o.Amount.Sum().As("total")).Group(o.CreateAt.Date()).Having(u.Amount.Sum().Gt(100)).Scan(&results)
Distinct

Selecting distinct values from the model

u := query.Query.User

users, err := u.Distinct(u.Name, u.Age).Order(u.Name, u.Age.Desc()).Find()

Distinct works with Pluck and Count too

Joins

Specify Joins conditions

u := query.Query.User
e := query.Query.Email
c := query.Query.CreditCard

type Result struct {
    Name  string
    Email string
}

var result Result

err := u.Select(u.Name, e.Email).LeftJoin(e, e.UserId.EqCol(u.ID)).Scan(&result)
// SELECT users.name, emails.email FROM `users` left join emails on emails.user_id = users.id

rows, err := u.Select(u.Name, e.Email).LeftJoin(e, e.UserId.EqCol(u.ID)).Rows()
for rows.Next() {
  ...
}

var results []Result

err := u.Select(u.Name, e.Email).LeftJoin(e, e.UserId.EqCol(u.ID)).Scan(&results)

// multiple joins with parameter
users := u.Join(e, e.UserId.EqCol(u.id), e.Email.Eq("[email protected]")).Join(c, c.UserId.EqCol(u.ID)).Where(c.Number.Eq("411111111111")).Find()
SubQuery

A subquery can be nested within a query, GEN can generate subquery when using a Dao object as param

(SELECT AVG(amount) FROM "orders"); subQuery := u.Select(u.Age.Avg()).Where(u.Name.Like("name%")) users, err := u.Select(u.Age.Avg().As("avgage")).Group(u.Name).Having(gen.Gt(u.Age.Avg(), subQuery).Find() // SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") ">
o := query.Query.Order
u := query.Query.User

orders, err := o.Where(gen.Gt(o.Amount, o.Select(u.Amount.Avg())).Find()
// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

subQuery := u.Select(u.Age.Avg()).Where(u.Name.Like("name%"))
users, err := u.Select(u.Age.Avg().As("avgage")).Group(u.Name).Having(gen.Gt(u.Age.Avg(), subQuery).Find()
// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%")
From SubQuery

GORM allows you using subquery in FROM clause with method Table, for example:

u := query.Query.User
p := query.Query.Pet

users, err := gen.Table(u.Select(u.Name, u.Age).As("u")).Where(u.Age.Eq(18)).Find()
// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18

subQuery1 := u.Select(u.Name)
subQuery2 := p.Select(p.Name)
users, err := gen.Table(subQuery1.As("u"), subQuery2.As("p")).Find()
db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{})
// SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p
Update from SubQuery

Update a table by using SubQuery

u := query.Query.User
c := query.Query.Company

u.Update(u.CompanyName, c.Select(c.Name).Where(c.ID.EqCol(u.CompanyId)))
// UPDATE "users" SET "company_name" = (SELECT name FROM companies WHERE companies.id = users.company_id);

u.Where(u.Name.Eq("modi")).Update(u.CompanyName, c.Select(c.Name).Where(c.ID.EqCol(u.CompanyId)))
Advanced Query
Iteration

GEN supports iterating through Rows

rows, err := query.Query.User.Where(u.Name.Eq("modi")).Rows()
defer rows.Close()

for rows.Next() {
    var user User
    // ScanRows is a method of `gorm.DB`, it can be used to scan a row into a struct
    db.ScanRows(rows, &user)

    // do something
}
FindInBatches

Query and process records in batch

u := query.Query.User

// batch size 100
err := u.Where(u.ID.Gt(9)).FindInBatches(&results, 100, func(tx gen.Dao, batch int) error {
    for _, result := range results {
      // batch processing found records
    }
  
    // build a new `u` to use it's api
    // queryUsery := query.NewUser(tx.UnderlyingDB())

    tx.Save(&results)

    batch // Batch 1, 2, 3

    // returns error will stop future batches
    return nil
})
Pluck

Query single column from database and scan into a slice, if you want to query multiple columns, use Select with Scan instead

u := query.Query.User

var ages []int64
u.Pluck(u.Age, &ages)

var names []string
u.Pluck(u.Name, &names)

// Distinct Pluck
u.Distinct().Pluck(u.Name, &names)
// SELECT DISTINCT `name` FROM `users`

// Requesting more than one column, use `Scan` or `Find` like this:
db.Select(u.Name, u.Age).Scan(&users)
users, err := db.Select(u.Name, u.Age).Find()
Scopes

Scopes allows you to specify commonly-used queries which can be referenced as method calls

o := query.Query.Order

func AmountGreaterThan1000(tx gen.Dao) gen.Dao {
    return tx.Where(o.Amount.Gt(1000))
}

func PaidWithCreditCard(tx gen.Dao) gen.Dao {
    return tx.Where(o.PayModeSign.Eq("C"))
}

func PaidWithCod(tx gen.Dao) gen.Dao {
    return tx.Where(o.PayModeSign.Eq("C"))
}

func OrderStatus(status []string) func (tx gen.Dao) gen.Dao {
    return func (tx gen.Dao) gen.Dao {
      return tx.Where(o.Status.In(status...))
    }
}

orders, err := o.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find()
// Find all credit card orders and amount greater than 1000

orders, err := o.Scopes(AmountGreaterThan1000, PaidWithCod).Find()
// Find all COD orders and amount greater than 1000

orders, err := o.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find()
// Find all paid, shipped orders that amount greater than 1000
Count

Get matched records count

u := query.Query.User

count, err := u.Where(u.Name.Eq("modi")).Or(u.Name.Eq("zhangqiang")).Count()
// SELECT count(1) FROM users WHERE name = 'modi' OR name = 'zhangqiang'

count, err := u.Where(u.Name.Eq("modi")).Count()
// SELECT count(1) FROM users WHERE name = 'modi'; (count)

// Count with Distinct
u.Distinct(u.Name).Count()
// SELECT COUNT(DISTINCT(`name`)) FROM `users`

Update

Update single column

When updating a single column with Update, it needs to have any conditions or it will raise error ErrMissingWhereClause, for example:

u := query.Query.User

// Update with conditions
u.Where(u.Activate.Is(true)).Update(u.Name, "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true;

// Update with conditions
u.Where(u.Activate.Is(true)).Update(u.Age, u.Age.Add(1))
// UPDATE users SET age=age+1, updated_at='2013-11-17 21:34:10' WHERE active=true;
Updates multiple columns

Updates supports update with struct or map[string]interface{}, when updating with struct it will only update non-zero fields by default

u := query.Query.User

// Update attributes with `map`
u).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

NOTE When update with struct, GEN will only update non-zero fields, you might want to use map to update attributes or use Select to specify fields to update

Update selected fields

If you want to update selected fields or ignore some fields when updating, you can use Select, Omit

u := query.Query.User

// Select with Map
// User's ID is `111`:
u.Select(u.Name).Where(u.ID.Eq(111)).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello' WHERE id=111;

u.Omit(u.Name).Where(u.ID.Eq(111)).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

Delete

Delete record
e := query.Query.Email

// Email's ID is `10`
e.Where(e.ID.Eq(10)).Delete()
// DELETE from emails where id = 10;

// Delete with additional conditions
e.Where(e.ID.Eq(10), e.Name.Eq("modi")).Delete()
// DELETE from emails where id = 10 AND name = "modi";
Delete with primary key

GEN allows to delete objects using primary key(s) with inline condition, it works with numbers.

u.Where(u.ID.In(1,2,3)).Delete()
// DELETE FROM users WHERE id IN (1,2,3);
Batch Delete

The specified value has no primary value, GEN will perform a batch delete, it will delete all matched records

e := query.Query.Email

err := e.Where(e.Name.Like("%modi%")).Delete()
// DELETE from emails where email LIKE "%modi%";
Soft Delete

If your model includes a gorm.DeletedAt field (which is included in gorm.Model), it will get soft delete ability automatically!

When calling Delete, the record WON’T be removed from the database, but GORM will set the DeletedAt‘s value to the current time, and the data is not findable with normal Query methods anymore.

// Batch Delete
err := u.Where(u.Age.Eq(20)).Delete()
// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;

// Soft deleted records will be ignored when querying
users, err := u.Where(u.Age.Eq(20)).Find()
// SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;

If you don’t want to include gorm.Model, you can enable the soft delete feature like:

type User struct {
    ID      int
    Deleted gorm.DeletedAt
    Name    string
}
Find soft deleted records

You can find soft deleted records with Unscoped

users, err := db.Unscoped().Where(u.Age.Eq(20)).Find()
// SELECT * FROM users WHERE age = 20;
Delete permanently

You can delete matched records permanently with Unscoped

o.Unscoped().Where(o.ID.Eq(10)).Delete()
// DELETE FROM orders WHERE id=10;

DIY method

Method interface

Method interface is an abstraction of query methods, all functions it contains are query methods and above comments describe the specific query conditions or logic. SQL supports simple where query or execute raw SQL. Simple query conditions wrapped by where(), and raw SQL wrapped by sql()(not required)

type Method interface {
    // where("name=@name and age=@age")
    SimpleFindByNameAndAge(name string, age int) (gen.T, error)
    
    // sql(select * from users where id=@id)
    FindUserToMap(id int) (gen.M, error)
    
    // insert into users (name,age) values (@name,@age)
    InsertValue(age int, name string) error
}

Return values must contains less than 1 gen.T/gen.M and less than 1 error. You can also use bulitin type (like string/ int) as the return parameter,gen.T represents return a single result struct's pointer, []gen.T represents return an array of result structs' pointer,

Syntax of template
placeholder
  • gen.T represents specified struct or table
  • gen.M represents map[string]interface
  • @@table represents table's name (if method's parameter doesn't contains variable table, GEN will generate table from model struct)
  • @@ represents column's name or table's name
  • @ represents normal query variable
template

Logical operations must be wrapped in {{}},and end must used {{end}}, All templates support nesting

  • if/else if/else the condition accept a bool parameter or operation expression which conforms to Golang syntax.
  • where The where clause will be inserted only if the child elements return something. The key word and or or in front of clause will be removed. And and will be added automatically when there is no junction keyword between query condition clause.
  • Set The set clause will be inserted only if the child elements return something. The , in front of columns array will be removed.And , will be added automatically when there is no junction keyword between query coulmns.
  • ... Coming soon
If clause
{{if cond1}}
    // do something here
{{else if cond2}}
    // do something here
{{else}}
    // do something here
{{end}}

Use case in raw SQL:

// select * from users where {{if name !=""}} name=@name{{end}}
methond(name string) (gen.T,error) 

Use case in raw SQL template:

30}} status="middle-ager" {{else if age>18}} status="younger" {{else}} {{if sex=="male"}} status="boys" {{else}} status="girls" {{end}} {{end}} ">
select * from @@table where
{{if age>60}}
    status="older"
{{else if age>30}}
    status="middle-ager"
{{else if age>18}}
    status="younger"
{{else}}
    {{if sex=="male"}}
        status="boys"
    {{else}}
        status="girls"
    {{end}}
{{end}}
Where clause
{{where}}
    // do something here
{{end}}

Use case in raw SQL

// select * from {{where}}id=@id{{end}}
methond(id int) error

Use case in raw SQL template

select * from @@table 
{{where}}
    {{if cond}}id=@id {{end}}
    {{if name != ""}}@@key=@value{{end}}
{{end}}
Set clause
{{set}}
    // sepecify update expression here
{{end}}

Use case in raw SQL

// update users {{set}}name=@name{{end}}
methond() error

Use case in raw SQL template

0}} age=@age {{end}} {{end}} where id=@id ">
update @@table 
{{set}}
    {{if name!=""}} name=@name {{end}}
    {{if age>0}} age=@age {{end}}
{{end}}
where id=@id
Method interface example
0 // {{if cond}}id=@id {{end}} // {{if key!="" && value != ""}} or @@key=@value{{end}} // {{end}} FindByIDOrCustom(cond bool, id int, key, value string) ([]gen.T, error) // update @@table // {{set}} // update_time=now() // {{if name != ""}} // name=@name // {{end}} // {{end}} // {{where}} // id=@id // {{end}} UpdateName(name string, id int) error } ">
type Method interface {
    // Where("name=@name and age=@age")
    SimpleFindByNameAndAge(name string, age int) (gen.T, error)
    
    // select * from users where id=@id
    FindUserToMap(id int) (gen.M, error)
    
    // sql(insert into @@table (name,age) values (@name,@age) )
    InsertValue(age int, name string) error
    
    // select name from @@table where id=@id
    FindNameById(id int) string
    
    // select * from @@table
    //  {{where}}
    //      id>0
    //      {{if cond}}id=@id {{end}}
    //      {{if key!="" && value != ""}} or @@key=@value{{end}}
    //  {{end}}
    FindByIDOrCustom(cond bool, id int, key, value string) ([]gen.T, error)
    
    // update @@table
    //  {{set}}
    //      update_time=now()
    //      {{if name != ""}}
    //          name=@name
    //      {{end}}
    //  {{end}}
    //  {{where}}
    //      id=@id
    //  {{end}}
    UpdateName(name string, id int) error
}

Smart select fields

GEN allows select specific fields with Select, if you often use this in your application, maybe you want to define a smaller struct for API usage which can select specific fields automatically, for example:

type User struct {
  ID     uint
  Name   string
  Age    int
  Gender string
  // hundreds of fields
}

type APIUser struct {
  ID   uint
  Name string
}

type Method interface{
    // select * from user
    FindSome() ([]APIUser, error)
}

apiusers, err := u.Limit(10).FindSome()
// SELECT `id`, `name` FROM `users` LIMIT 10

Advanced Topics

Hints

Optimizer hints allow to control the query optimizer to choose a certain query execution plan, GORM supports it with gorm.io/hints, e.g:

import "gorm.io/hints"

u := query.Query.User

users, err := u.Hints(hints.New("MAX_EXECUTION_TIME(10000)")).Find()
// SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users`

Index hints allow passing index hints to the database in case the query planner gets confused.

import "gorm.io/hints"

u := query.Query.User

users, err := u.Hints(hints.UseIndex("idx_user_name")).Find()
// SELECT * FROM `users` USE INDEX (`idx_user_name`)

users, err := u.Hints(hints.ForceIndex("idx_user_name", "idx_user_id").ForJoin()).Find()
// SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)"

Contributing

You can help to deliver a better GORM/GEN

License

Released under the MIT License

Comments
  • DateFormat转换出现?占位符,参数没有赋值

    DateFormat转换出现?占位符,参数没有赋值

    GORM Playground Link

    场景,按照订购日期cdate、单据状态state分组,汇总单据总数及总金额,sql如下:

    SELECT
    	count( 1 ) AS num,
    	sum( amount ) AS amount,
    	`status`,
    	DATE_FORMAT( cdate, '%Y-%m-%d' ) AS cdate 
    FROM
    	s3_order_bill 
    GROUP BY
    	DATE_FORMAT( cdate, '%Y-%m-%d' ),
    	`status` 
    HAVING
    	`status` = 3 
    	AND DATE_FORMAT( cdate, '%Y-%m-%d' )>= '2022-01-02' 
    	AND DATE_FORMAT( cdate, '%Y-%m-%d' )<= '2022-08-16' 
    ORDER BY
    	DATE_FORMAT(cdate,'%Y-%m-%d')
    

    执行结果如下: image

    翻译成go代码如下:

    func (*S3Order) ListBillCount(status int, cdate_begin, cdate_end string) ([]*model.S3OrderBill, int64, error) {
    	fmt.Println("ListBillCount参数:", status, cdate_begin, cdate_end)
    	qryBill, qryBillDo := query.S3OrderBillDo()
    	expr := []gen.Condition{}
    	if status > 0 {
    		expr = append(expr, qryBill.Status.Eq(int32(status)))
    	}
    	cdate := qryBill.Cdate.DateFormat("%Y-%m-%d")
    	if cdate_begin != "" {
    		// expr = append(expr, cdate.Gte(cdate_begin))
    		cbegin := cdate_begin + " 00:00:00"
    		fmt.Println(cbegin)
    		begin, err := time.Parse("2006-01-02 15:04:05", cbegin)
    		if err != nil {
    			fmt.Println(err.Error())
    		} else {
    			expr = append(expr, qryBill.Cdate.Gte(begin))
    		}
    	}
    	if cdate_end != "" {
    		// expr = append(expr, cdate.Lte(cdate_end))
    		cend := cdate_end + " 23:59:59"
    		fmt.Println(cend)
    		end, err := time.Parse("2006-01-02 15:04:05", cend)
    		if err != nil {
    			fmt.Println(err.Error())
    		} else {
    			expr = append(expr, qryBill.Cdate.Lte(end))
    		}
    	}
    	action := qryBillDo.Select(qryBill.ID.Count().As("num"), qryBill.Amount.Sum().As("amount"), qryBill.Status, cdate.As("cdate")).
    		Having(expr...).
    		Group(cdate, qryBill.Status).
    		Order(cdate)
    	outList := []*model.S3OrderBill{}
    	err := action.Scan(&outList)
    	return outList, int64(len(outList)), err
    }
    

    执行出现如下错误:

    Filter --------------------> /admin/s3/order/SearchBill7Day /admin/login true
    ListBillCount参数: 3 2022-08-09 2022-08-16
    
    2022/08/16 21:21:24 /Users/zhushuyan/go/pkg/mod/gorm.io/[email protected]/do.go:799 Error 1064: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'AS `cdate`,`s3_order_bill`.`status` HAVING `s3_order_bill`.`status` = ? AND DATE' at line 1
    [2.235ms] [rows:-] SELECT COUNT(`s3_order_bill`.`id`) AS `num`,SUM(`s3_order_bill`.`amount`) AS `amount`,`s3_order_bill`.`status`,DATE_FORMAT(`s3_order_bill`.`cdate`,'%Y-%m-%d') AS `cdate` FROM `s3_order_bill` GROUP BY DATE_FORMAT(`s3_order_bill`.`cdate`,3) AS `cdate`,`s3_order_bill`.`status` HAVING `s3_order_bill`.`status` = '%Y-%m-%d' AND DATE_FORMAT(`s3_order_bill`.`cdate`,'2022-08-09') >= '%Y-%m-%d' AND DATE_FORMAT(`s3_order_bill`.`cdate`,'2022-08-16') <= ? ORDER BY DATE_FORMAT(`s3_order_bill`.`cdate`,?) AS `cdate`
    Error 1064: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'AS `cdate`,`s3_order_bill`.`status` HAVING `s3_order_bill`.`status` = ? AND DATE' at line 1
    

    为了方便查看,将生成的sql格式化如下:

    SELECT
    	COUNT( `s3_order_bill`.`id` ) AS `num`,
    	SUM( `s3_order_bill`.`amount` ) AS `amount`,
    	`s3_order_bill`.`status`,
    	DATE_FORMAT( `s3_order_bill`.`cdate`, '%Y-%m-%d' ) AS `cdate` 
    FROM
    	`s3_order_bill` 
    GROUP BY
    	DATE_FORMAT( `s3_order_bill`.`cdate`, 3 ),
    	`s3_order_bill`.`status` 
    HAVING
    	`s3_order_bill`.`status` = '2022-08-09 00:00:00' 
    	AND `s3_order_bill`.`cdate` >= '2022-08-16 23:59:59' 
    	AND `s3_order_bill`.`cdate` <= ? 
    ORDER BY
    	DATE_FORMAT(
    	`s3_order_bill`.`cdate`,?)
    

    问题一:group by生成错误 问题二:having生成错误 问题三:order by生成错误

    https://github.com/go-gorm/playground/pull/1

    Description

  • 查询是否可以生成计算列

    查询是否可以生成计算列

    查询字段或where条件,是否可以多个字段拼接组合

    如以下sql

    SELECT c.code as bcode,c.cdate,c.status,a.*,(a.sto_qty - a.dist_qty - a.allot_qty + a.retn_qty - a.allot_qty - a.loss_qty - a.borrow_qty + a.retn_qty2) as lft_qty,b.spec,b.type,b.unit,b.mate 
    FROM s3_rec_item a
    LEFT JOIN s3_cat_item b ON a.item_id = b.id
    LEFT JOIN s3_rec_bill c ON a.rec_id = c.id
    WHERE c.`status` = 3 
    AND (a.sto_qty - a.dist_qty - a.allot_qty + a.retn_qty - a.allot_qty - a.loss_qty - a.borrow_qty + a.retn_qty2) > 0
    

    问题1:字段

    (a.sto_qty - a.dist_qty - a.allot_qty + a.retn_qty - a.allot_qty - a.loss_qty - a.borrow_qty + a.retn_qty2) as lft_qty
    

    如何写

    问题2:查询条件

    (a.sto_qty - a.dist_qty - a.allot_qty + a.retn_qty - a.allot_qty - a.loss_qty - a.borrow_qty + a.retn_qty2) > 0
    

    如何写

    Motivation

    Related Issues

  • 使用问题求教~

    使用问题求教~

    Your Question

    1. 比较好奇为什么 ResultInfo 中会有个 error, 比如像 Updates 方法本身就返回了一个 error 了,为什么又将 error 放到 ResultInfo 中呢?
    2. gen 中的 Select 不可以和 struct 一起使用吗?下列代码为什么不能更新 Select 的字段?
    	schedule := query.Use(preload.DB).Schedule
    	_, err := schedule.WithContext(ctx).
    		Select(schedule.Title, schedule.Content, schedule.BeginTime, schedule.EndTime, schedule.UpdatedAt).
    		Where(schedule.ID.Eq(input.ID), schedule.DeletedAt.Eq(0)).
    		Updates(newValue)
    

    对应控制台的输出为:

    UPDATE `tblSchedule` SET `updated_at`=1658389286 WHERE `tblSchedule`.`id` = 1 AND `tblSchedule`.`deleted_at` = 0
    

    麻烦大佬看下~

    Expected answer

  • How to Generate Models and Curd Queries for Mysql tables with foreign keys.

    How to Generate Models and Curd Queries for Mysql tables with foreign keys.

    Want to generate Models and curd queries for Mysql tables with foreign keys, how to call generate function ?

    Mysql Tables

    CREATE TABLE `customers` (
      `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
      `created_at` datetime(3) DEFAULT NULL,
      `updated_at` datetime(3) DEFAULT NULL,
      `deleted_at` datetime(3) DEFAULT NULL,
      `name` Varchar(20) DEFAULT NULL,
      `cc_id` bigint(20) unsigned DEFAULT NULL,
      PRIMARY KEY (`id`),
      CONSTRAINT `credit_card_ref` FOREIGN KEY (`cc_id`) REFERENCES `credit_cards` (`id`),
      KEY `idx_customers_deleted_at` (`deleted_at`)
    ) ENGINE=InnoDB;
    
    CREATE TABLE `credit_cards` (
      `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
      `created_at` datetime(3) DEFAULT NULL,
      `updated_at` datetime(3) DEFAULT NULL,
      `deleted_at` datetime(3) DEFAULT NULL,
      `number` longtext,
      PRIMARY KEY (`id`),
      KEY `idx_credit_cards_deleted_at` (`deleted_at`)
    ) ENGINE=InnoDB;
    
    

    Gen code

    customer := g.GenerateModel("customers")
    g.GenerateModel("credit_cards")
    //g.ApplyBasic(card, customer)
    g.Execute()
    

    and also tried calling it as below

    card := g.GenerateModel("credit_cards")
    g.GenerateModel("customers", gen.FieldRelate(field.HasOne, "credit_cards", card,
    	&field.RelateConfig{
    		// RelateSlice: true,
    		GORMTag: "foreignKey:credit_card_ref",
    	}))
    //g.ApplyBasic(card, customer)
    g.Execute()
    

    but it returns Models as

    type Customer struct {
    	ID           int64          `gorm:"column:id;type:bigint(20) unsigned;primaryKey;autoIncrement:true" json:"id"`
    	CreatedAt    *time.Time     `gorm:"column:created_at;type:datetime(3)" json:"created_at"`
    	UpdatedAt    *time.Time     `gorm:"column:updated_at;type:datetime(3)" json:"updated_at"`
    	DeletedAt    gorm.DeletedAt `gorm:"column:deleted_at;type:datetime(3);index:idx_customers_deleted_at,priority:1" json:"deleted_at"`
    	Name         *string        `gorm:"column:name;type:varchar(20)" json:"name"`
    	CcID         *int64         `gorm:"column:cc_id;type:bigint(20) unsigned;index:credit_card_ref,priority:1" json:"cc_id"`
    	credit_cards CreditCard     `gorm:"foreignKey:credit_card_ref" json:"credit_cards"`
    }
    
    type CreditCard struct {
    	ID        int64          `gorm:"column:id;type:bigint(20) unsigned;primaryKey;autoIncrement:true" json:"id"`
    	CreatedAt *time.Time     `gorm:"column:created_at;type:datetime(3)" json:"created_at"`
    	UpdatedAt *time.Time     `gorm:"column:updated_at;type:datetime(3)" json:"updated_at"`
    	DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:datetime(3);index:idx_credit_cards_deleted_at,priority:1" json:"deleted_at"`
    	Number    *string        `gorm:"column:number;type:longtext" json:"number"`
    }
    

    version used v0.2.28

  • 能否支持下字段属性的枚举生成

    能否支持下字段属性的枚举生成

    在编码过程中,经常会遇到用某个数值来表示某种状态、类型或者阶段的情况,比如有这样一个枚举:

    public enum ComputerState {
        OPEN(10),         //开启
        CLOSE(11),         //关闭
        OFF_LINE(12),     //离线
        FAULT(200),     //故障
        UNKNOWN(255);     //未知
    
        private int code;
        ComputerState(int code) { this.code = code; }
    }
    
    

    通常我们希望将表示状态的数值存入数据库,即ComputerState.OPEN存入数据库取值为10。

    能否通过field comment的中备注,来生成对应枚举值

    比如可以约定注释检查规则的正则表达式如下

    REMARKS_PATTERN = ".*\\s*\\[\\s*(\\w+\\s*\\(\\s*[\\u4e00-\\u9fa5_\\-a-zA-Z0-9]+\\s*\\)\\s*:\\s*[\\u4e00-\\u9fa5_\\-a-zA-Z0-9]+\\s*\\,?\\s*)+\\s*\\]\\s*.*";
    
    
    
    CREATE TABLE `tb` (
      `type` smallint(3) COMMENT '注释[success(0):成功, fail(1):失败]',                       
    );
    

    提取出字段属性相应的枚举

    type Type int
    
    const (
            Success  Type = 0 //成功
            Fail Type = 1 //失败
    )
    
    
    
  • 如何在事务中删除关联的行

    如何在事务中删除关联的行

    Your Question

    比如现在我有这么两个表:

    type User struct {
        ID                   int	
        UserInfo             UserInfo
        CreatedAt            time.Time
        UpdatedAt            time.Time
    }
    
    type UserInfo struct {
        ID           int
        UserID       int
        DisplayName  string
        Address      string
        Age          int
        CreatedAt    time.Time
        UpdatedAt    time.Time
    }
    
    

    我需要在事务中删除User及其关联的UserInfo,代码需要怎么写呢? 下面👇这个代码只会删除掉User,但UserInfo没有删除

    q := query.Use(db)
    
    q.Transaction(func(tx *query.Query) error {
      if _, err := tx.User.WithContext(ctx).Where(tx.User.ID.Eq(100)).Delete(); err != nil {
        return err
      }
      return nil
    })
    

    另外,如果在删除的时候,需要根据 join 后的过滤条件进行删除(这里我需要删除的是 user),那么,根据下面的代码但生成的 sql 会报错,正确的做法是什么?

    q := query.Use(db)
    
    // 这里生成的 sql 有误,大概长这样:
    // DELETE FROM `users` INNER JOIN `user_infos` ON `user_infos`.`user_id` = `users`.`id` WHERE `user_infos`.`id` = 100 
    q.Transaction(func(tx *query.Query) error {
    	if _, err := tx.User.WithContext(ctx).
    		Join(tx.UserInfo, tx.UserInfo.UserID.EqCol(tx.User.ID)).
    		Where(tx.UserInfo.ID.Eq(100)).
    		Delete(); err != nil {
    		return err
    	}
    	return nil
    })
    
    

    The document you expected this should be explained

    Expected answer

  • FindOne API

    FindOne API

    Describe the feature

    FindOne => db.Limit(1).Find(&user)

    https://gorm.io/docs/query.html

    If you want to avoid the ErrRecordNotFound error, you could use Find like db.Limit(1).Find(&user), the Find method accepts both struct and slice data

    Motivation

    Related Issues

  • 期望增加配置分片参数、支持分表模型

    期望增加配置分片参数、支持分表模型

    水平分表创建模型需要建立多张表,期望收敛在一个模型中,能定制模型的TableName

    ###DDL

    CREATE TABLE IF NOT EXISTS `mytables_[0-127]` (
    	`ID` int(11) NOT NULL," 
    	`username` varchar(16) DEFAULT NULL,
    	`age` int(8) NOT NULL," 
    	`phone` varchar(11) NOT NULL,
    	INDEX `idx_username` (`username`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
    

    生成模型

    g.GenerateModelAs("mytables_0", "Mytable")
    

    model

    package model
    
    const TableNameMytable = "mytables_0"
    
    // Mytable mapped from table <mytables_[0-127]>
    type Mytable struct {
    	ID       int32   `gorm:"column:ID;type:int(11);not null" json:"ID_example"`
    	Username *string `gorm:"column:username;type:varchar(16);index:idx_username,priority:1;default:NULL" json:"username_example"`
    	Age      int32   `gorm:"column:age;type:int(8);not null" json:"age_example"`
    	Phone    string  `gorm:"column:phone;type:varchar(11);not null" json:"phone_example"`
    }
    
    // TableName Mytable's table name
    func (*Mytable) TableName() string {
    	return TableNameMytable
    }
    
    

    期望在模板中增加一个分片参数、通过分片参数可以自定义TableName()方法。

    image

    image

    预期想要生成的模型

    package model
    
    import (
    	"fmt"
    )
    
    const TableNameMytable = "mytables"
    const TableNameMytableShardNumber = 128
    
    // Mytable mapped from table <mytables_[0-127]>
    type Mytable struct {
    	ID       int32   `gorm:"column:ID;type:int(11);not null" json:"ID_example"`
    	Username *string `gorm:"column:username;type:varchar(16);index:idx_username,priority:1;default:NULL" json:"username_example"`
    	Age      int32   `gorm:"column:age;type:int(8);not null" json:"age_example"`
    	Phone    string  `gorm:"column:phone;type:varchar(11);not null" json:"phone_example"`
    }
    
    // TableName Mytable's table name
    func (*Mytable) TableName(id int32) string {
    	return TableNameMytable + fmt.Sprintf("_%v", id%TableNameMytableShardNumber) // 自定义方法
    
  • Mock with interface style

    Mock with interface style

    Describe the feature

    Interface style

    Motivation

    Why this project doesn't write in interface style ?

    Related Issues

    It's impossible to mock xxxDo struct when importing dao in other packages, the only way to write unit test for these packages is to launch a sqlite database then fill some fake records. Is there any way to mock method of xxxDo struct? if not, I think it's not appropriate for serious project, only suitable for project that is not in need for unit test.

  • Config Struct OutFile field describe error

    Config Struct OutFile field describe error

    Description

    OutFile      string // query code file name, default: gen.go
    

    OutFile field is describe use for query code file name.

    but the real behavior is query code file generate PATH and the file name always gen.go

    // config.go#122L
    if cfg.OutFile == "" {
    	cfg.OutFile = cfg.OutPath + "/gen.go"
    }
    
    // generator.go#321L
    err = g.output(g.OutFile, buf.Bytes())
    if err != nil {
    	return err
    }
    g.successInfo("generate query file: " + g.OutFile)
    

    this comment mislead a lot...

  • Return interface instead of private struct for query objects

    Return interface instead of private struct for query objects

    When I write

    dto := query.Use(db).MyDataTransferObject
    

    I hope that the type of dto is exported so that I can use it as a function input/output.

    Motivation

    I need to process dto in another function to improve code reusability.

    Related Issues

    None.

  • How to get correct Primary Key, when we use CreateInBatches() in case of mysql running in Cluster

    How to get correct Primary Key, when we use CreateInBatches() in case of mysql running in Cluster

    How to get correct Primary Key, when we use CreateInBatches() in case of mysql running in Cluster (more than 1 node)

    The document you expected this should be explained

    Expected answer

    When we use Mysql Percona XtraDB Cluster of 3 nodes, (setup using k8 percona operator), what should be GEN configuration to get correct Primary Keys in CreateInBatches()

    e.g:

    Response of CreateInBatches() : 
    {"id":10002,"name":"Element 1"},
    {"id":10003,"name":"Element 2"},
    {"id":10004,"name":"Element 3"},
    
    
    But when checked in Mysql Cluster they are inserted as:
    id,name
    10002,"Element 1"
    10005,"Element 2"
    10008,"Element 3"
    
    Here Primary Keys are not Sequential due to use of Mysql Cluster
    
    
  • DIY method call model Method?

    DIY method call model Method?

    Your Question

    can i call the model's method in the diy method template?

    The document you expected this should be explained

    type User struct {
    	Id         uint64 `gorm:"column:id;type:bigint(20) unsigned;primary_key;AUTO_INCREMENT" json:"id"`
    	CreateTime int64  `gorm:"column:create_time;type:bigint(20) unsigned;NOT NULL" json:"create_time"`
    }
    
    func (w User) Now() int64 {
    	return time.Now().Unix()
    }
    
    type Method interface {
    	// UPDATE @@table SET [email protected]() WHERE id = @wrong.Id
    	UpdateByID(user *gen.T) error
    }
    

    Expected answer

    now is this

    // UPDATE @@table SET [email protected]() WHERE id = @wrong.Id
    func (u userDo) UpdateByID(wrong *model.User) (err error) {
    	var params []interface{}
    
    	var generateSQL strings.Builder
    	params = append(params, wrong.Now)
    	params = append(params, wrong.Id)
    	generateSQL.WriteString("UPDATE users SET modify_time=?() WHERE id = ? ")
    
    	var executeSQL *gorm.DB
    
    	executeSQL = u.UnderlyingDB().Exec(generateSQL.String(), params...)
    	err = executeSQL.Error
    	return
    }
    

    want this

    // UPDATE @@table SET [email protected]() WHERE id = @wrong.Id
    func (u userDo) UpdateByID(wrong *model.User) (err error) {
    	var params []interface{}
    
    	var generateSQL strings.Builder
    	params = append(params, wrong.Now())
    	params = append(params, wrong.Id)
    	generateSQL.WriteString("UPDATE users SET modify_time=? WHERE id = ? ")
    
    	var executeSQL *gorm.DB
    
    	executeSQL = u.UnderlyingDB().Exec(generateSQL.String(), params...)
    	err = executeSQL.Error
    	return
    }
    
  • chore(deps): bump golang.org/x/tools from 0.1.12 to 0.4.0

    chore(deps): bump golang.org/x/tools from 0.1.12 to 0.4.0

    Bumps golang.org/x/tools from 0.1.12 to 0.4.0.

    Release notes

    Sourced from golang.org/x/tools's releases.

    gopls/v0.4.0

    • Improved support for working with modules (@​ridersofrohan). A detailed walk-through of the new features can be found here. A quick summary:
      • Use the -modfile flag to suggest which modules should be added/removed from the go.mod file, rather than editing it automatically.
      • Suggest dependency upgrades in-editor and provide additional language features, such as formatting, for the go.mod file.
    • Inverse implementations (@​muirdm). "Go to implementations" on a concrete type will show the interfaces it implements.
    • Completion improvements (@​muirdm). Specifically, improved completion for keywords. Also, offer if err != nil { return err } as a completion item.
    • Jumping to definition on an import statement returns all files as definition locations (@​danishprakash).
    • Support for running go generate through the editor, via a code lens (@​marwan-at-work).
    • Command-line support for workspace symbols (@​daisuzu).

    Opt-in:

    • Code actions suggesting gofmt -s-style simplifications (@​ridersofrohan). To get these on-save, add the following setting:
    "[go]": {
    	"editor.codeActionsOnSave": {
    		"source.fixAll": true,
    	}
    }
    
    • Code actions suggesting fixes for type errors, such as missing return values (goreturns-style), undeclared names, unused parameters, and assignment statements that should be converted from := to = (@​ridersofrohan). Add the following to your gopls settings to opt-in to these analyzers. In the future, they will be on by default and high-confidence suggested fixes may be applied on save. See additional documentation on analyzers here.
    "gopls": {
    	"analyses": {
    		"fillreturns": true,
                    "undeclaredname": true,
                    "unusedparams": true,
                    "nonewvars": true,
    	}
    }
    
    • Further improvements in the support for multiple concurrent clients (@​findleyr). See #34111 for all details.

    For a complete list of the issues resolved, see the gopls/v0.4.0 milestone.

    gopls/v0.3.4

    gopls/v0.3.3

    • Support for workspace symbols. (@​daisuzu)
    • Various completion improvements, including fixes for completion in code that doesn't parse. (@​muirdm)
    • Limit diagnostic concurrency, preventing huge spikes in memory usage that some users encountered. (@​heschik)
    • Improved handling for URIs containing escaped characters. (@​heschik)
    • Module versions from "go list" in pkg.go.dev links. (@​ridersofrohan)

    ... (truncated)

    Commits
    • aee3994 gopls/internal/lsp/fake: in (*Workdir).RenameFile, fall back to read + write
    • fe60148 go.mod: update golang.org/x dependencies
    • c9ea9a7 gopls/internal/regtest: add a test for the case when the renaming package's p...
    • bf5db81 gopls/internal/lsp/cache: improve ad-hoc warning for nested modules
    • aa9f4b2 go/analysis: document that facts are gob encoded in one gulp
    • bdcd082 internal/gcimporter: skip tests earlier when 'go build' is not available
    • 2ad6325 gopls/internal/lsp/cache: expand ImportPath!=PackagePath comment
    • 52c7b88 gopls/internal/robustio: only define ERROR_SHARING_VIOLATION on Windows
    • 4f69bf3 gopls/internal/lsp/cache: narrow reloadOrphanedFiles to open files
    • 6002d6e gopls/internal/regtest/misc: test Implementations + vendor
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
  • feat:support trim clause

    feat:support trim clause

    • [x] Do only one thing
    • [x] Non breaking API changes
    • [x] Tested

    What did this pull request do?

    You can use trim clause to remove the keywords at the beginning or end of a SQL segment, including and,or,xor,,

    User Case Description

    type TrimTest interface {
    // TestTrim
    //
    // select * from @@table where
    //    {{trim}}
    //        {{for _, name :=range list}}
    //            name = @name or
    //        {{end}}
    //    {{end}}
    TestTrim(list []string)[]gen.T
    }
    
  • chore(deps): bump gorm.io/plugin/dbresolver from 1.3.0 to 1.4.0 in /tests

    chore(deps): bump gorm.io/plugin/dbresolver from 1.3.0 to 1.4.0 in /tests

    Bumps gorm.io/plugin/dbresolver from 1.3.0 to 1.4.0.

    Commits
    • 58a1b2a feat: trace resolver mode (#89)
    • 4a7eece Merge pull request #86 from go-gorm/dependabot/go_modules/gorm.io/driver/mysq...
    • 719b633 Bump gorm.io/driver/mysql from 1.3.2 to 1.4.3
    • d82f8d3 Merge pull request #83 from nine-third-repo/master
    • 17116ba fix: new PreparedStmtDB error
    • See full diff in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Related tags
100% type-safe ORM for Go (Golang) with code generation and MySQL, PostgreSQL, Sqlite3, SQL Server support. GORM under the hood.

go-queryset 100% type-safe ORM for Go (Golang) with code generation and MySQL, PostgreSQL, Sqlite3, SQL Server support. GORM under the hood. Contents

Dec 30, 2022
opentracing integration with GORM
opentracing integration with GORM

gorm-opentracing opentracing support for gorm2. Features Record SQL in span logs. Record Result in span logs. Record Table in span tags. Record Error

Nov 27, 2022
Converts a database into gorm structs and RESTful api

gen The gen tool produces a CRUD (Create, read, update and delete) REST api project template from a given database. The gen tool will connect to the d

Dec 28, 2022
Scope function for GORM queries provides easy filtering with query parameters

Gin GORM filter Scope function for GORM queries provides easy filtering with query parameters Usage go get github.com/ActiveChooN/gin-gorm-filter Mod

Dec 28, 2022
A plugin to allow telemetry by NewRelic Go Agent for GORM

GORM NewRelic Telemetry Plugin A plugin to allow telemetry by NewRelic Go Agent for GORM Overview Plugin implementation to add datastore segments on a

Aug 19, 2022
Gorm firebird driver

gorm-firebird GORM firebird driver import: "github.com/flylink888/gorm-firebird" Example: var products []Product dsn := "SYSDBA:[email protected]/sy

Sep 27, 2022
OpenTelemetry plugin for GORM v2

gorm-opentelemetry OpenTelemetry plugin for GORM v2 Traces all queries along with the query SQL. Usage Example: // Copyright The OpenTelemetry Authors

Jan 11, 2022
CURD using go fiber - gorm - mysql

GO Fiber - CRUD - GORM - Mysql Folder Structure - database | database config |- migration | migration config - middleware | mid

Nov 13, 2022
A example of a join table using liquibase and gorm

A example of a join table using liquibase and gorm. Additionally the join table's composite key is used as a composite foreign key for another table.

Feb 4, 2022
Fiber Clean Architecture With GORM

Fiber Clean Architecture With GORM I offer this repository as a proposal for a c

Oct 30, 2022
Ormtool - 将数据库表转换为golang的结构体,自定义生成tag,如json,gorm,xorm和简单的db信息

数据库表转换为golang 结构体 1. 获取方式 2. 配置说明 # 保存路径 SavePath: "./models/test.go", # 是

May 30, 2022
Simple-crm-system - Simple CRM system CRUD backend using Go, Fiber, SQLite, Gorm

Simple CRM system CRUD backend using GO, Fiber, Gorm, SQLite Developent go mod t

Nov 13, 2022
Api-project - Api project with Golang, Gorm, Gorilla-Mux, Postgresql

TECHNOLOGIES GOLANG 1.14 GORM GORILLA-MUX POSTGRESQL API's PATHS For Product Ser

Nov 23, 2022
This library is a complementary library for Gorm (v2) which resolves the first available pool passed to it.

This library is a complementary library for Gorm (v2) which resolves the first available pool passed to it.

Feb 2, 2022
EZCoin is a control panel for Bitfinex funding, backend is build by Golang, Gin and GORM, frontend is build by angular

EZCoin server is backend for Bitfinex funding, it build by Golang, Gin and GORM.

Feb 7, 2022
RestAPI Starter Template Using Go (Gin + Gorm)

go-gin-gorm-restapi-template Go (Gin + Gorm) を使用した RestAPI 開発のスターターテンプレート RestAPI Starter Template Using Golang (Gin + Gorm) 主要な依存ライブラリ Gin (Web フレームワ

Apr 3, 2022
A simple CRUD API made with Go, Postgres, FIber, Gorm and Docker.

golang-test-api A simple CRUD API made with Go, Postgres, FIber, Gorm and Docker. Cloning the repository To clone the repository run the following com

Dec 14, 2022
A better ORM for Go, based on non-empty interfaces and code generation.

reform A better ORM for Go and database/sql. It uses non-empty interfaces, code generation (go generate), and initialization-time reflection as oppose

Dec 31, 2022
A better ORM for Go, based on non-empty interfaces and code generation.
A better ORM for Go, based on non-empty interfaces and code generation.

A better ORM for Go and database/sql. It uses non-empty interfaces, code generation (go generate), and initialization-time reflection as opposed to interface{}, type system sidestepping, and runtime reflection. It will be kept simple.

Dec 29, 2022