rapier Guides
gorm-rapier is an assist rapier for gorm.
Overview
- Idiomatic and Reusable API from Dynamic Raw SQL
- 100% Type-safe API without interface{}
- Almost supports all features, plugins, DBMS that GORM supports
- Almost same behavior as gorm you used.
Installation
Use go get.
go get github.com/thinkgos/gorm-rapier
Then import the package into your own code.
import "github.com/thinkgos/gorm-rapier"
Declaring Models
Declaring gorm model
Declaring rapier model
Supported field:
- bool:
Bool
- []byte:
Bytes
- float:
Float32
,Float64
,Decimal
- integer:
Int
,Int8
,Int16
,Int32
,Int64
- unsigned integer:
Uint
,Uint8
,Uint16
,Uint32
,Uint64
- string:
String
- time.Time:
Time
- any:
Field
- raw filed:
Raw
How to define model
model defined for test testdata.
if we have a gorm model follow:
// Dict 字典
type Dict struct {
Id int64 `gorm:"column:id;autoIncrement:true;not null;primaryKey" json:"id,omitempty"`
Key string `gorm:"column:key;type:varchar(64);not null;default:'';uniqueIndex:uk_key" json:"key,omitempty"`
Name string `gorm:"column:name;type:varchar(64);not null;default:''" json:"name,omitempty"`
IsPin bool `gorm:"column:is_pin;type:tinyint(1);not null;default:0" json:"is_pin,omitempty"`
Remark string `gorm:"column:remark;type:varchar(128);not null;default:''" json:"remark,omitempty"`
CreatedAt time.Time `gorm:"column:created_at;type:datetime;not null" json:"created_at,omitempty"`
UpdatedAt time.Time `gorm:"column:updated_at;type:datetime;not null" json:"updated_at,omitempty"`
}
// TableName implement schema.Tabler interface
func (*Dict) TableName() string {
return "dict"
}
then we can define rapier model:
var ref_Dict_Native = New_Dict("dict")
type Dict_Native struct {
refAlias string
refTableName string
ALL rapier.Asterisk
Id rapier.Int64
Key rapier.String
Name rapier.String
IsPin rapier.Bool
Remark rapier.String
CreatedAt rapier.Time
UpdatedAt rapier.Time
}
func new_Dict(tableName, alias string) *Dict_Native {
return &Dict_Native{
refAlias: alias,
refTableName: tableName,
ALL: rapier.NewAsterisk(alias),
Id: rapier.NewInt64(alias, "id"),
Key: rapier.NewString(alias, "key"),
Name: rapier.NewString(alias, "name"),
IsPin: rapier.NewBool(alias, "is_pin"),
Remark: rapier.NewString(alias, "remark"),
CreatedAt: rapier.NewTime(alias, "created_at"),
UpdatedAt: rapier.NewTime(alias, "updated_at"),
}
}
// Ref_Dict model with TableName `dict`.
func Ref_Dict() *Dict_Native { return ref_Dict_Native }
// New_Dict new instance.
func New_Dict(tableName string) *Dict_Native {
return new_Dict(tableName, tableName)
}
// As alias
func (x *Dict_Native) As(alias string) *Dict_Native {
return new_Dict(x.refTableName, alias)
}
// Alias hold alias name when call Dict_Active.As that you defined.
func (x *Dict_Native) Alias() string { return x.refAlias }
// TableName hold table name when call New_Dict that you defined.
func (x *Dict_Native) TableName() string { return x.refTableName }
Connecting to a Database
CRUD interface
Executor[T]
's Where
, Or
, Not
, Having
and the suffix with Expr
method can use field which implement Expr
interface.
Create
Empty record
// empty record
rowsAffected, err := rapier.NewExecutor[testdata.Dict](db).Create()
_ = err // return error
_ = rowsAffected // return row affected
Single record
// single record
newDict := testdata.Dict{
Key: "key1",
Name: "name1",
IsPin: true,
Remark: "remark1",
}
rowsAffected, err := rapier.NewExecutor[testdata.Dict](db).Create(&newDict)
_ = err // return error
_ = rowsAffected // return row affected
// INSERT INTO `dict` (`key`,`name`,`is_pin`,`remark`,`created_at`,`updated_at`) VALUES ("key1","name1",true,"remark1","2024-02-20 07:18:42.135","2024-02-20 07:18:42.135")
Multiple record
// multiple record
newDicts := []*testdata.Dict{
{
Key: "key1",
Name: "name1",
IsPin: true,
Remark: "remark1",
},
{
Key: "key2",
Name: "name2",
IsPin: true,
Remark: "remark2",
},
}
rowsAffected, err := rapier.NewExecutor[testdata.Dict](db).Create(newDicts...)
_ = err // return error
_ = rowsAffected // return row affected
// INSERT INTO `dict` (`key`,`name`,`is_pin`,`remark`,`created_at`,`updated_at`) VALUES ("key1","name1",true,"remark1","2024-02-20 07:18:42.136","2024-02-20 07:18:42.136"),("key2","name2",true,"remark2","2024-02-20 07:18:42.136","2024-02-20 07:18:42.136")
Batch insert multiple record
// batch insert multiple record
newDicts := []*testdata.Dict{
{
Key: "key1",
Name: "name1",
IsPin: true,
Remark: "remark1",
},
{
Key: "key2",
Name: "name2",
IsPin: true,
Remark: "remark2",
},
{
Key: "key3",
Name: "name3",
IsPin: true,
Remark: "remark3",
},
}
rowsAffected, err := rapier.NewExecutor[testdata.Dict](db).CreateInBatches(newDicts, 2)
_ = err // return error
_ = rowsAffected // return row affected
// INSERT INTO `dict` (`key`,`name`,`is_pin`,`remark`,`created_at`,`updated_at`) VALUES ("key1","name1",true,"remark1","2024-02-20 07:18:42.136","2024-02-20 07:18:42.136"),("key2","name2",true,"remark2","2024-02-20 07:18:42.136","2024-02-20 07:18:42.136")
// INSERT INTO `dict` (`key`,`name`,`is_pin`,`remark`,`created_at`,`updated_at`) VALUES ("key3","name3",true,"remark3","2024-02-20 07:18:42.135","2024-02-20 07:18:42.135")
more information gorm Create
Query
Retrieving a single object
var record2 testdata.Dict
// Get the first record ordered by primary key
record1, err := rapier.NewExecutor[testdata.Dict](db).FirstOne()
_ = err // return error
_ = record1 // return record
// Get one record, no specified order
record1, err = rapier.NewExecutor[testdata.Dict](db).TakeOne()
_ = err // return error
_ = record1 // return record
// Get one record, no specified order
record1, err = rapier.NewExecutor[testdata.Dict](db).LastOne()
_ = err // return error
_ = record1 // return record
// Get the first record ordered by primary key with original gorm api
err = rapier.NewExecutor[testdata.Dict](db).First(&record2)
_ = err // return error
_ = record2 // return record
// Get one record, no specified order with original gorm api
err = rapier.NewExecutor[testdata.Dict](db).Take(&record2)
_ = err // return error
_ = record2 // return record
// Get one record, no specified order with original gorm api
err = rapier.NewExecutor[testdata.Dict](db).Last(&record2)
_ = err // return error
_ = record2 // return record
Retrieving a single field
the api like FirstXXX
or TakeXXX
, return follow type: bool
,string
, float32
, float64
, int
, int8
, int16
, int32
, int64
, uint
, uint8
, uint16
, uint32
, uint64
refDict := testdata.Ref_Dict()
// Get the first record ordered returned single field.
_ = err // return error
_, err = rapier.NewExecutor[testdata.Dict](db).SelectExpr(refDict.Key).FirstString()
// SELECT `dict`.`key` FROM `dict` ORDER BY `dict`.`id` LIMIT 1
// Get one record, no specified order returned single field.
_, err = rapier.NewExecutor[testdata.Dict](db).SelectExpr(refDict.Key).TakeString()
// SELECT `dict`.`key` FROM `dict` LIMIT 1
Retrieving multiple objects
// Get the multiple record.
records1, err := rapier.NewExecutor[testdata.Dict](db).
FindAll()
_ = err // return error
_ = records1 // return records
// SELECT * FROM `dict`
var records2 []*testdata.Dict
// Get the multiple record.
err = rapier.NewExecutor[testdata.Dict](db).
SelectExpr(rapier.All).
Find(&records2)
_ = err // return error
_ = records1 // return records
// SELECT * FROM `dict`
Condition
In addition to gorm Conditions usages, there are usable usage related to rapier
refDict := testdata.Ref_Dict()
// =
_, _ = rapier.NewExecutor[testdata.Dict](db).Where(refDict.Key.Eq("key1")).TakeOne()
// SELECT * FROM `dict` WHERE `dict`.`key` = "key1" LIMIT 1
// <>
_, _ = rapier.NewExecutor[testdata.Dict](db).Where(refDict.Key.Neq("key1")).TakeOne()
// SELECT * FROM `dict` WHERE `dict`.`key` <> "key1" LIMIT 1
// IN
_, _ = rapier.NewExecutor[testdata.Dict](db).Where(refDict.Key.In("key1", "key2")).TakeOne()
// SELECT * FROM `dict` WHERE `dict`.`key` IN ("key1","key2") LIMIT 1
// NOT IN
_, _ = rapier.NewExecutor[testdata.Dict](db).Where(refDict.Key.NotIn("key1", "key2")).TakeOne()
// SELECT * FROM `dict` WHERE `dict`.`key` NOT IN ("key1","key2") LIMIT 1
// Fuzzy LIKE
_, _ = rapier.NewExecutor[testdata.Dict](db).Where(refDict.Key.FuzzyLike("key1")).TakeOne()
// SELECT * FROM `dict` WHERE `dict`.`key` LIKE "%key1%" LIMIT 1
// Left LIKE
_, _ = rapier.NewExecutor[testdata.Dict](db).Where(refDict.Key.LeftLike("key1")).TakeOne()
// SELECT * FROM `dict` WHERE `dict`.`key` LIKE "key1%" LIMIT 1
// LIKE
_, _ = rapier.NewExecutor[testdata.Dict](db).Where(refDict.Key.Like("%key1%")).TakeOne()
// SELECT * FROM `dict` WHERE `dict`.`key` LIKE "%key1%" LIMIT 1
// NOT LIKE
_, _ = rapier.NewExecutor[testdata.Dict](db).Where(refDict.Key.NotLike("%key1%")).TakeOne()
// SELECT * FROM `dict` WHERE `dict`.`key` NOT LIKE "%key1%" LIMIT 1
// AND
_, _ = rapier.NewExecutor[testdata.Dict](db).Where(refDict.Key.Eq("key1"), refDict.IsPin.Eq(true)).TakeOne()
// SELECT * FROM `dict` WHERE `dict`.`key` = "key1" AND `dict`.`is_pin` = true LIMIT 1
// >
_, _ = rapier.NewExecutor[testdata.Dict](db).Where(refDict.CreatedAt.Gt(time.Now())).TakeOne()
// SELECT * FROM `dict` WHERE `dict`.`created_at` > "2024-03-07 06:20:47.057" LIMIT 1
// >=
_, _ = rapier.NewExecutor[testdata.Dict](db).Where(refDict.CreatedAt.Gte(time.Now())).TakeOne()
// SELECT * FROM `dict` WHERE `dict`.`created_at` >= "2024-03-07 06:20:47.057" LIMIT 1
// <
_, _ = rapier.NewExecutor[testdata.Dict](db).Where(refDict.CreatedAt.Lt(time.Now())).TakeOne()
// SELECT * FROM `dict` WHERE `dict`.`created_at` < "2024-03-07 06:20:47.057" LIMIT 1
// <=
_, _ = rapier.NewExecutor[testdata.Dict](db).Where(refDict.CreatedAt.Lte(time.Now())).TakeOne()
// SELECT * FROM `dict` WHERE `dict`.`created_at` <= "2024-03-07 06:20:47.057" LIMIT 1
// BETWEEN
_, _ = rapier.NewExecutor[testdata.Dict](db).Where(refDict.CreatedAt.Between(time.Now().Add(time.Hour), time.Now())).TakeOne()
// SELECT * FROM `dict` WHERE `dict`.`created_at` BETWEEN "2024-03-07 07:20:47.057" AND "2024-03-07 06:20:47.057" LIMIT 1
// NOT BETWEEN
_, _ = rapier.NewExecutor[testdata.Dict](db).Where(refDict.CreatedAt.NotBetween(time.Now().Add(time.Hour), time.Now())).TakeOne()
// SELECT * FROM `dict` WHERE NOT (`dict`.`created_at` BETWEEN "2024-03-07 07:20:47.057" AND "2024-03-07 06:20:47.057") LIMIT 1
// not condition
_, _ = rapier.NewExecutor[testdata.Dict](db).Not(refDict.Key.Eq("key1")).TakeOne()
// SELECT * FROM `dict` WHERE NOT `dict`.`key` = "key1" LIMIT 1
_, _ = rapier.NewExecutor[testdata.Dict](db).Where(rapier.Not(refDict.Key.Eq("key1"))).TakeOne()
// SELECT * FROM `dict` WHERE `dict`.`key` <> "key1" LIMIT 1
_, _ = rapier.NewExecutor[testdata.Dict](db).Not(refDict.Key.In("key1", "key2")).TakeOne()
// SELECT * FROM `dict` WHERE NOT `dict`.`key` IN ("key1","key2") LIMIT 1
_, _ = rapier.NewExecutor[testdata.Dict](db).Where(rapier.Not(refDict.Key.In("key1", "key2"))).TakeOne()
// SELECT * FROM `dict` WHERE `dict`.`key` NOT IN ("key1","key2") LIMIT 1
// Or condition
_, _ = rapier.NewExecutor[testdata.Dict](db).Where(refDict.Key.Eq("key1")).Or(refDict.Key.Eq("key2")).TakeOne()
// SELECT * FROM `dict` WHERE `dict`.`key` = "key1" OR `dict`.`key` = "key2" LIMIT 1
_, _ = rapier.NewExecutor[testdata.Dict](db).Where(rapier.Or(refDict.Key.Eq("key1"), refDict.Key.Eq("key2"))).TakeOne()
// SELECT * FROM `dict` WHERE (`dict`.`key` = "key1" OR `dict`.`key` = "key2") LIMIT 1
_, _ = rapier.NewExecutor[testdata.Dict](db).Where(refDict.Key.Eq("key1")).Or(refDict.Key.Eq("key2"), refDict.IsPin.Eq(true)).TakeOne()
// SELECT * FROM `dict` WHERE `dict`.`key` = "key1" OR (`dict`.`key` = "key2" AND `dict`.`is_pin` = true) LIMIT 1
_, _ = rapier.NewExecutor[testdata.Dict](db).Where(rapier.Or(refDict.Key.Eq("key1"), rapier.And(refDict.Key.Eq("key2"), refDict.IsPin.Eq(true)))).TakeOne()
// SELECT * FROM `dict` WHERE (`dict`.`key` = "key1" OR (`dict`.`key` = "key2" AND `dict`.`is_pin` = true))
Selecting Specific Fields
Select
, SelectExpr
allows you to specify the fields that you want to retrieve from database.
var records []*struct {
Key string
IsPin bool
}
refDict := testdata.Ref_Dict()
// with expr
_ = rapier.NewExecutor[testdata.Dict](db).SelectExpr(refDict.Key, refDict.IsPin).Find(&records)
// SELECT `dict`.`key`,`dict`.`is_pin` FROM `dict`
_ = rapier.NewExecutor[testdata.Dict](db).SelectExpr(refDict.Key.Trim("1").As(refDict.Key.ColumnName()), refDict.IsPin).Find(&records)
// SELECT TRIM(BOTH "1" FROM `dict`.`key`) AS `key`,`dict`.`is_pin` FROM `dict`
// with original gorm api
_ = rapier.NewExecutor[testdata.Dict](db).Select("key", "is_pin").Find(&records)
// SELECT `key`,`is_pin` FROM `dict`
Order
Specify order when retrieving records from the database
refDict := testdata.Ref_Dict()
// with expr
_, _ = rapier.NewExecutor[testdata.Dict](db).OrderExpr(refDict.Key.Desc(), refDict.Name).FindAll()
// SELECT * FROM `dict` ORDER BY `dict`.`key` DESC,`dict`.`name`
_, _ = rapier.NewExecutor[testdata.Dict](db).OrderExpr(refDict.Key.Desc()).OrderExpr(refDict.Name).FindAll()
// SELECT * FROM `dict` ORDER BY `dict`.`key` DESC,`dict`.`name`
// with original gorm api
_, _ = rapier.NewExecutor[testdata.Dict](db).Order("`key` DESC,name").FindAll()
// SELECT * FROM `dict` ORDER BY `key` DESC,name
_, _ = rapier.NewExecutor[testdata.Dict](db).Order("`key` DESC").Order("name").FindAll()
// SELECT * FROM `dict` ORDER BY `key` DESC,name
Limit & Offset
Pagination
:
page
: page indexperPage
: per page size (default size is 50, default max size is 500)maxPerPages
: override default max size.
Limit
: specify the max number of records to retrieve.
Offset
: specify the number of records to skip before starting to return the records
// with Pagination
_, _ = rapier.NewExecutor[testdata.Dict](db).Pagination(3, 5).FindAll()
// SELECT * FROM `dict` LIMIT 5 OFFSET 10
// with original gorm api
_, _ = rapier.NewExecutor[testdata.Dict](db).Limit(3).FindAll()
// SELECT * FROM `dict` LIMIT 3
_, _ = rapier.NewExecutor[testdata.Dict](db).Offset(3).FindAll()
// SELECT * FROM `dict` OFFSET 3
_, _ = rapier.NewExecutor[testdata.Dict](db).Limit(10).Offset(5).FindAll()
// SELECT * FROM `dict` LIMIT 10 OFFSET 5
Group By & Having
var result struct {
Name string
Total int
}
refDict := testdata.Ref_Dict()
// with expr
_ = rapier.NewExecutor[testdata.Dict](db).
SelectExpr(
refDict.Name,
rapier.Star.Count().As("total"),
).
Where(refDict.Name.LeftLike("group")).
GroupExpr(refDict.Name).
Take(&result)
// SELECT `dict`.`name`,COUNT(*) AS `total` FROM `dict` WHERE `dict`.`name` LIKE "group%" GROUP BY `dict`.`name`
_ = rapier.NewExecutor[testdata.Dict](db).
SelectExpr(
refDict.Name,
rapier.Star.Count().As("total"),
).
GroupExpr(refDict.Name).
Having(refDict.Name.Eq("group")).
Take(&result)
// SELECT `dict`.`name`,COUNT(*) AS `total` FROM `dict` GROUP BY `dict`.`name` HAVING `dict`.`name` = "group"
// with original gorm api
_ = rapier.NewExecutor[testdata.Dict](db).
SelectExpr(
refDict.Name,
rapier.Star.Count().As("total"),
).
Where(refDict.Name.LeftLike("group")).
Group("name").
Take(&result)
// SELECT `dict`.`name`,COUNT(*) AS `total` FROM `dict` WHERE `dict`.`name` LIKE "group%" GROUP BY `name` LIMIT 1
_ = rapier.NewExecutor[testdata.Dict](db).
SelectExpr(
refDict.Name,
rapier.Star.Count().As("total"),
).
Group("name").
Having("name = ?", "group").
Take(&result)
// SELECT `dict`.`name`,COUNT(*) AS `total` FROM `dict` GROUP BY `name` HAVING name = "group" LIMIT 1
Distinct
refDict := testdata.Ref_Dict()
// with expr
_, _ = rapier.NewExecutor[testdata.Dict](db).
DistinctExpr(
refDict.Name,
refDict.IsPin,
).
FindAll()
// SELECT DISTINCT `dict`.`name`,`dict`.`is_pin` FROM `dict`
// with original gorm api
_, _ = rapier.NewExecutor[testdata.Dict](db).
Distinct("name", "is_pin").
FindAll()
// SELECT DISTINCT `name`,`is_pin` FROM `dict`
Joins
refDict := testdata.Ref_Dict()
refDictItem := testdata.Ref_DictItem()
d := refDict.As("d")
di := refDictItem.As("di")
// join
_ = rapier.NewExecutor[testdata.Dict](db).
SelectExpr(
refDict.Id.As(refDict.Id.FieldName(refDict.TableName())),
refDict.Key.As(refDict.Key.FieldName(refDict.TableName())),
refDictItem.Name.As(refDictItem.Name.FieldName(refDictItem.TableName())),
).
InnerJoinsExpr(refDictItem, refDictItem.DictId.EqCol(refDict.Id), refDictItem.IsEnabled.Eq(true)).
Take(&struct{}{})
// SELECT `dict`.`id` AS `dict_id`,`dict`.`key` AS `dict_key`,`dict_item`.`name` AS `dict_item_name` FROM `dict` INNER JOIN `dict_item` ON `dict_item`.`dict_id` = `dict`.`id` AND `dict_item`.`is_enabled` = true LIMIT 1
// join with alias
_ = rapier.NewExecutor[testdata.Dict](db).
SelectExpr(
refDict.Id.As(refDict.Id.FieldName(refDict.TableName())),
refDict.Key.As(refDict.Key.FieldName(refDict.TableName())),
d.Id.As(d.Id.FieldName(d.Alias())),
).
InnerJoinsExpr(rapier.NewJoinTable(d, d.Alias()), d.Name.EqCol(refDict.Name), d.IsPin.Eq(true)).
Take(&struct{}{})
// SELECT `dict`.`id` AS `dict_id`,`dict`.`key` AS `dict_key`,`d`.`id` AS `d_id` FROM `dict` INNER JOIN `dict` `d` ON `d`.`name` = `dict`.`name` AND `d`.`is_pin` = true LIMIT 1
// join with alias which table implements Alias interface.
// we can directly use it. no need `NewJoinTable`.
_ = rapier.NewExecutor[testdata.Dict](db).
SelectExpr(
refDict.Id.As(refDict.Id.FieldName(refDict.TableName())),
refDict.Key.As(refDict.Key.FieldName(refDict.TableName())),
d.Id.As(d.Id.FieldName(d.Alias())),
).
InnerJoinsExpr(d, d.Name.EqCol(refDict.Name), d.IsPin.Eq(true)).
Take(&struct{}{})
// SELECT `dict`.`id` AS `dict_id`,`dict`.`key` AS `dict_key`,`d`.`id` AS `d_id` FROM `dict` INNER JOIN `dict` `d` ON `d`.`name` = `dict`.`name` AND `d`.`is_pin` = true LIMIT 1
// join with SubQuery
_ = rapier.NewExecutor[testdata.Dict](db).
SelectExpr(
refDict.Id.As(refDict.Id.FieldName(refDict.TableName())),
refDict.Key.As(refDict.Key.FieldName(refDict.TableName())),
di.Sort.As(di.Sort.FieldName(di.Alias())),
).
InnerJoinsExpr(
rapier.NewJoinTableSubQuery(
rapier.NewExecutor[testdata.DictItem](db).
Where(refDictItem.IsEnabled.Eq(true)).
IntoDB(),
"di",
),
di.Name.EqCol(refDict.Name),
di.Sort.Gt(10),
).
Take(&struct{}{})
// SELECT `dict`.`id` AS `dict_id`,`dict`.`key` AS `dict_key`,`di`.`sort` AS `di_sort` FROM `dict` INNER JOIN (SELECT * FROM `dict_item` WHERE `dict_item`.`is_enabled` = true) AS `di` ON `di`.`name` = `dict`.`name` AND `di`.`sort` > 10 LIMIT 1
Scan
Retrieving a single field, the api like ScanXXX
return follow type: bool
,string
, float32
, float64
, int
, int8
, int16
, int32
, int64
, uint
, uint8
, uint16
, uint32
, uint64
.
var record2 testdata.Dict
// Get one record, no specified order
record1, err := rapier.NewExecutor[testdata.Dict](db).ScanOne()
_ = err // return error
_ = record1 // return record
// SELECT * FROM `dict`
// Get one record, no specified order with original gorm api
err = rapier.NewExecutor[testdata.Dict](db).Scan(&record2)
_ = err // return error
_ = record2 // return record
// SELECT * FROM `dict`
more information gorm Query
Advanced Query
more information gorm Advanced Query
Locking
// Basic FOR UPDATE lock
_, _ = rapier.NewExecutor[testdata.Dict](db).
LockingUpdate().
TakeOne()
// SELECT * FROM `dict` LIMIT 1 FOR UPDATE
// Basic FOR UPDATE lock with Clauses api
_, _ = rapier.NewExecutor[testdata.Dict](db).
Clauses(clause.Locking{Strength: "UPDATE"}).
TakeOne()
// SELECT * FROM `dict` LIMIT 1 FOR UPDATE
// Basic FOR SHARE lock
_, _ = rapier.NewExecutor[testdata.Dict](db).
LockingShare().
TakeOne()
// SELECT * FROM `dict` LIMIT 1 FOR SHARE
// Basic FOR SHARE lock with Clauses api
_, _ = rapier.NewExecutor[testdata.Dict](db).
Clauses(clause.Locking{Strength: "SHARE"}).
TakeOne()
// SELECT * FROM `dict` LIMIT 1 FOR SHARE
// Basic FOR UPDATE NOWAIT lock with Clauses api
_, _ = rapier.NewExecutor[testdata.Dict](db).
Clauses(clause.Locking{Strength: "UPDATE",Options: "NOWAIT"}).
TakeOne()
// SELECT * FROM `dict` LIMIT 1 FOR UPDATE NOWAIT
SubQuery
refDict := testdata.Ref_Dict()
_ = rapier.NewExecutor[testdata.Dict](db).
SelectExpr(
refDict.Id,
refDict.Key,
rapier.NewExecutor[testdata.Dict](db).
SelectExpr(rapier.Star.Count()).
Where(
refDict.Name.Eq("kkk"),
).
IntoSubQueryExpr().As("total"),
).
Where(refDict.Key.LeftLike("key")).
Find(&struct{}{})
// SELECT `dict`.`id`,`dict`.`key`,(SELECT COUNT(*) FROM `dict` WHERE `dict`.`name` = "kkk") AS `total` FROM `dict` WHERE `dict`.`key` LIKE "key%"
_, _ = rapier.NewExecutor[testdata.Dict](db).
Where(refDict.Key.EqSubQuery(
rapier.NewExecutor[testdata.Dict](db).
SelectExpr(refDict.Key).
Where(refDict.Id.Eq(1001)).
IntoDB(),
)).
FindAll()
// SELECT * FROM `dict` WHERE `dict`.`key` = (SELECT `dict`.`key` FROM `dict` WHERE `dict`.`id` = 1001)
From SubQuery
refDict := testdata.Ref_Dict()
_, _ = rapier.NewExecutor[testdata.Dict](db).
TableExpr(
rapier.From{
Alias: "u",
SubQuery: rapier.NewExecutor[testdata.Dict](db).
SelectExpr(refDict.Key).
IntoDB(),
},
rapier.From{
Alias: "p",
SubQuery: rapier.NewExecutor[testdata.Dict](db).
SelectExpr(refDict.Key).
IntoDB(),
},
).
FindAll()
// SELECT * FROM (SELECT `dict`.`key` FROM `dict`) AS `u`, (SELECT `dict`.`key` FROM `dict`) AS `p`
IN with multiple columns
supports the IN clause with multiple columns, allowing you to filter data based on multiple field values in a single query.
refDict := testdata.Ref_Dict()
record1, _ := rapier.NewExecutor[testdata.Dict](db).
Where(
rapier.NewColumns(refDict.Name, refDict.IsPin).
In([][]any{{"name1", true}, {"name2", false}}),
).
FindAll()
_ = record1
// SELECT * FROM `dict` WHERE (`dict`.`name`, `dict`.`is_pin`) IN (("name1",true),("name2",false))
record2, _ := rapier.NewExecutor[testdata.Dict](db).
Where(
rapier.NewColumns(refDict.Name, refDict.IsPin).
In(
rapier.NewExecutor[testdata.Dict](db).
SelectExpr(refDict.Name, refDict.IsPin).
Where(refDict.Id.In(10, 11)).
IntoDB(),
),
).
FindAll()
_ = record2
// SELECT * FROM `dict` WHERE (`dict`.`name`,`dict`.`is_pin`) IN (SELECT `dict`.`name`,`dict`.`is_pin` FROM `dict` WHERE `dict`.`id` IN (10,11))
FirstOrInit
FirstOrInit
method is utilized to fetch the first record that matches given conditions, or initialize a new instance if no matching record is found. This method allows additional flexibility with the Attrs
, Assign
, AttrsExpr
, AssignExpr
methods.
Attrs
,AttrsExpr
: When no record is found, you can useAttrs
,AttrsExpr
to initialize a struct with additional attributes. These attributes are included in the new struct but are not used in the SQL query.Assign
,AssignExpr
method allows you to set attributes on the struct regardless of whether the record is found or not. These attributes are set on the struct but are not used to build the SQL query and the final data won’t be saved into the database.
NOTE!!!: if with expr condition will not initialize the field when initializing, so we should use Attrs
, AttrsExpr
, Assign
, AssignExpr
attributes to indicate these fields.
refDict := testdata.Ref_Dict()
// NOTE!!!: if with expr condition will not initialize the field when initializing, so we should use
// `Attrs`, `AttrsExpr`, `Assign`, `AssignExpr` attributes to indicate these fields.
// `Attrs`, `AttrsExpr`
// with expr
result, _ := rapier.NewExecutor[testdata.Dict](db).
Where(refDict.Name.Eq("myname")).
AttrsExpr(refDict.Remark.Value("remark11")).
FirstOrInit()
newdict := result.Data
rowsAffected := result.RowsAffected
_ = newdict
_ = rowsAffected
// NOTE: Condition use expr. here will not initialize the field of the condition when initializing.
// if not found
// newdict -> Dict{ Remark: "remark11" }
//
// if found, `Attrs`, `AttrsExpr` are ignored
// newdict -> Dict{ Id: 11, Name: "myname", Remark: "remark" }
// with original gorm api
result, _ = rapier.NewExecutor[testdata.Dict](db).
Where(&testdata.Dict{
Name: "non_existing",
}).
FirstOrInit()
newdict = result.Data
rowsAffected = result.RowsAffected
_ = newdict
_ = rowsAffected
// NOTE: Condition not use expr, here will initialize the field of the condition when initializing.
// newdict -> Dict{ Name: "non_existing" } if not found
result, _ = rapier.NewExecutor[testdata.Dict](db).
Where(&testdata.Dict{
Name: "myname",
}).
Attrs(&testdata.Dict{Remark: "remark11"}).
FirstOrInit()
newdict = result.Data
rowsAffected = result.RowsAffected
_ = newdict
_ = rowsAffected
// NOTE: Condition not use expr, here will initialize the field of the condition when initializing.
// if not found
// newdict -> Dict{ Name: "myname", Remark: "remark11" }
//
// if found, `Attrs`, `AttrsExpr` are ignored
// newdict -> Dict{ Id: 1, Name: "myname", Remark: "remark" }
// `Assign`, `AssignExpr`
// with expr
result, _ = rapier.NewExecutor[testdata.Dict](db).
Where(refDict.Name.Eq("myname")).
AssignExpr(refDict.Remark.Value("remark11")).
FirstOrInit()
newdict = result.Data
rowsAffected = result.RowsAffected
_ = newdict
_ = rowsAffected
// NOTE: Where condition use expr, here will not initialize the field of the condition when initializing.
// if not found
// newdict -> Dict{ Remark: "remark11" }
//
// if not found
// newdict -> Dict{ Name: "non_existing" }
result, _ = rapier.NewExecutor[testdata.Dict](db).
Where(&testdata.Dict{
Name: "myname",
}).
Assign(&testdata.Dict{Remark: "remark11"}).
FirstOrInit()
newdict = result.Data
rowsAffected = result.RowsAffected
_ = newdict
_ = rowsAffected
// NOTE: condition not use expr, here will initialize the field of the condition when initializing.
// if not found
// newdict -> Dict{ Name: "myname", Remark: "remark11" }
//
// if found, `Assign`, `AssignExpr` are set on the struct
// newdict -> Dict{ Id: 1, Name: "myname", Remark: "remark11" }
FirstOrCreate
FirstOrCreate
is used to fetch the first record that matches given conditions or create a new one if no matching record is found. This method is effective with both struct and map conditions. The RowsAffected property is useful to determine the number of records created or updated.
Attrs
,AttrsExpr
can be used to specify additional attributes for the new record if it is not found. These attributes are used for creation but not in the initial search query.Assign
,AssignExpr
method sets attributes on the record regardless of whether it is found or not, and these attributes are saved back to the database.
NOTE!!!: if with expr condition will not initialize the field when creating, so we should use
Attrs
, AttrsExpr
, Assign
, AssignExpr
attributes to indicate these fields.
refDict := testdata.Ref_Dict()
// NOTE!!!: if with expr condition will not initialize the field when creating, so we should use
// `Attrs`, `AttrsExpr`, `Assign`, `AssignExpr` attributes to indicate these fields.
// `Attrs`, `AttrsExpr`
// with expr
result, _ := rapier.NewExecutor[testdata.Dict](db).
Where(refDict.Name.Eq("myname")).
AttrsExpr(refDict.Remark.Value("remark11")).
FirstOrCreate()
newdict := result.Data
rowsAffected := result.RowsAffected
_ = newdict
_ = rowsAffected
// NOTE: Condition use expr. here will not initialize the field of the condition when creating.
// if not found. initialize with additional attributes
// SELECT * FROM `dict` WHERE `dict`.`name` = "myname" ORDER BY `dict`.`id` LIMIT 1;
// INSERT INTO `dict` (`key`,`name`,`is_pin`,`remark`,`created_at`,`updated_at`) VALUES ("","",false,"remark11","2024-03-08 02:20:10.853","2024-03-08 02:20:10.853");
// newdict -> Dict{ Id: 11, Name: "", Remark: "remark11" } if not found
//
// if found, `Attrs`, `AttrsExpr` are ignored.
// newdict -> Dict{ Id: 11, Name: "myname", Remark: "remark" }
// with original gorm api
result, _ = rapier.NewExecutor[testdata.Dict](db).
Where(&testdata.Dict{
Name: "myname",
}).
Attrs(&testdata.Dict{Remark: "remark11"}).
FirstOrCreate()
newdict = result.Data
rowsAffected = result.RowsAffected
_ = newdict
_ = rowsAffected
// NOTE: Condition not use expr, here will initialize the field of the condition when creating.
// if not found, initialize with given conditions and additional attributes
// SELECT * FROM `dict` WHERE `dict`.`name` = "myname" ORDER BY `dict`.`id` LIMIT 1;
// INSERT INTO `dict` (`key`,`name`,`is_pin`,`remark`,`created_at`,`updated_at`) VALUES ("","myname",false,"remark11","2024-03-08 02:20:10.853","2024-03-08 02:20:10.853");
// newdict -> Dict{ Id: 11, Name: "myname", Remark: "remark11" }
//
// if found, `Attrs`, `AttrsExpr` are ignored
// newdict -> Dict{ Id: 11, Name: "myname", Remark: "remark" }
// `Assign`, `AssignExpr`
// with expr
result, _ = rapier.NewExecutor[testdata.Dict](db).
Where(refDict.Name.Eq("myname")).
AssignExpr(refDict.Remark.Value("remark11")).
FirstOrCreate()
newdict = result.Data
rowsAffected = result.RowsAffected
_ = newdict
_ = rowsAffected
// NOTE: Where condition use expr, here will not initialize the field of the condition when creating.
// whether it is found or not, and `Assign`, `AssignExpr` attributes are saved back to the database.
// if no found
// SELECT * FROM `dict` WHERE `dict`.`name` = "myname" ORDER BY `dict`.`id` LIMIT 1
// INSERT INTO `dict` (`key`,`name`,`is_pin`,`remark`,`created_at`,`updated_at`) VALUES ("","",false,"remark11","2024-03-08 02:26:12.619","2024-03-08 02:26:12.619");
// newdict -> Dict{ Id: 11, Name: "", Remark: "remark11", ... }
//
// if found
// SELECT * FROM `dict` WHERE `dict`.`name` = "myname" ORDER BY `dict`.`id` LIMIT 1
// UPDATE `dict` SET `remark` = "remark11" WHERE id = "11"
// newdict -> Dict{ Id: 11, Name: "myname", Remark: "remark11", ... }
result, _ = rapier.NewExecutor[testdata.Dict](db).
Where(&testdata.Dict{
Name: "myname",
}).
Assign(&testdata.Dict{Remark: "remark11"}).
FirstOrCreate()
newdict = result.Data
rowsAffected = result.RowsAffected
_ = newdict
_ = rowsAffected
// NOTE: condition not use expr, here will initialize the field of the condition when creating.
// whether it is found or not, and `Assign`, `AssignExpr` attributes are saved back to the database.
// if no found
// SELECT * FROM `dict` WHERE `dict`.`name` = "myname" ORDER BY `dict`.`id` LIMIT 1
// INSERT INTO `dict` (`key`,`name`,`is_pin`,`remark`,`created_at`,`updated_at`) VALUES ("","myname",false,"remark11","2024-03-08 02:26:12.619","2024-03-08 02:26:12.619");
// newdict -> Dict{ Id: 11, Name: "myname", Remark: "remark11", ... }
//
// if found
// SELECT * FROM `dict` WHERE `dict`.`name` = "myname" ORDER BY `dict`.`id` LIMIT 1
// UPDATE `dict` SET `remark` = "remark11" WHERE id = "11"
// newdict -> Dict{ Id: 11, Name: "myname", Remark: "remark11", ... }
Pluck
The Pluck
, PluckExpr
method is used to query a single column from the database and scan the result into a slice. This method is ideal for when you need to retrieve specific fields from a model.
If you need to query more than one column, you can use Select with Scan
or Find
instead.
var ids []int64
refDict := testdata.Ref_Dict()
// with expr api
_, _ = rapier.NewExecutor[testdata.Dict](db).PluckExprString(refDict.Name)
// SELECT `name` FROM `dict`
_, _ = rapier.NewExecutor[testdata.Dict](db).PluckExprBool(refDict.IsPin)
// SELECT `is_pin` FROM `dict`
_ = rapier.NewExecutor[testdata.Dict](db).Pluck("id", &ids)
_, _ = rapier.NewExecutor[testdata.Dict](db).PluckExprInt(refDict.Id)
_, _ = rapier.NewExecutor[testdata.Dict](db).PluckExprInt8(refDict.Id)
_, _ = rapier.NewExecutor[testdata.Dict](db).PluckExprInt16(refDict.Id)
_, _ = rapier.NewExecutor[testdata.Dict](db).PluckExprInt32(refDict.Id)
_, _ = rapier.NewExecutor[testdata.Dict](db).PluckExprInt64(refDict.Id)
_, _ = rapier.NewExecutor[testdata.Dict](db).PluckExprUint(refDict.Id)
_, _ = rapier.NewExecutor[testdata.Dict](db).PluckExprUint8(refDict.Id)
_, _ = rapier.NewExecutor[testdata.Dict](db).PluckExprUint16(refDict.Id)
_, _ = rapier.NewExecutor[testdata.Dict](db).PluckExprUint32(refDict.Id)
_, _ = rapier.NewExecutor[testdata.Dict](db).PluckExprUint64(refDict.Id)
// SELECT `id` FROM `dict`
// with original gorm api
_, _ = rapier.NewExecutor[testdata.Dict](db).PluckString("name")
// SELECT `name` FROM `dict`
_, _ = rapier.NewExecutor[testdata.Dict](db).PluckBool("is_pin")
// SELECT `is_pin` FROM `dict`
_ = rapier.NewExecutor[testdata.Dict](db).Pluck("id", &ids)
_, _ = rapier.NewExecutor[testdata.Dict](db).PluckInt("id")
_, _ = rapier.NewExecutor[testdata.Dict](db).PluckInt8("id")
_, _ = rapier.NewExecutor[testdata.Dict](db).PluckInt16("id")
_, _ = rapier.NewExecutor[testdata.Dict](db).PluckInt32("id")
_, _ = rapier.NewExecutor[testdata.Dict](db).PluckInt64("id")
_, _ = rapier.NewExecutor[testdata.Dict](db).PluckUint("id")
_, _ = rapier.NewExecutor[testdata.Dict](db).PluckUint8("id")
_, _ = rapier.NewExecutor[testdata.Dict](db).PluckUint16("id")
_, _ = rapier.NewExecutor[testdata.Dict](db).PluckUint32("id")
_, _ = rapier.NewExecutor[testdata.Dict](db).PluckUint64("id")
// SELECT `id` FROM `dict`
Count
The Count
method is used to retrieve the number of records that match a given query. It’s a useful feature for understanding the size of a dataset, particularly in scenarios involving conditional queries or data analysis.
total, err := rapier.NewExecutor[testdata.Dict](db).Count()
_ = err
_ = total
// SELECT count(*) FROM `dict`
Exist
The Exist
method is used to check whether the exist record that match a given query.
refDict := testdata.Ref_Dict()
b, err := rapier.NewExecutor[testdata.Dict](db).Where(refDict.Id.Eq(100)).Exist()
_ = err
_ = b
// SELECT 1 FROM `dict` WHERE `dict`.`id` = 100 LIMIT 1
Function
Case When
NewCaseWhen().
WhenThen(NewField("", "id1").Gt(100), NewField("", "value1")).
WhenThen(NewField("", "id2").Gt(200), NewField("", "value2")).
Else(NewField("", "result")).
Build()
// (CASE WHEN `id1` > ? THEN `value1` WHEN `id2` > ? THEN `value2` ELSE `result` END)
Concat
ConcatCol(NewString("", "id"), NewString("", "new_id"), NewString("", "new_id2"))
// CONCAT(`id`,`new_id`,`new_id2`)
ConcatWsCol(NewRaw(`'-'`), NewString("", "id"), NewString("", "new_id"), NewString("", "new_id2"))
// CONCAT_WS('-',`id`,`new_id`,`new_id2`)
IF
IF(NewField("", "id1").Gt(100), NewRaw("t"), NewField("", "f").Sub(1))
// IF(`id1` > ?,t,`f`-?)
Update
Save
will save all fields
Save
will save all fields when performing the Updating SQL
rowsAffected, err := rapier.NewExecutor[testdata.Dict](db).
Model().
Save(&testdata.Dict{
Id: 100,
Key: "k1",
Remark: "remark1",
})
_ = err // return error
_ = rowsAffected // return row affected
// UPDATE `dict` SET `key`="k1",`name`="",`is_pin`=false,`remark`="remark1",`created_at`="2024-03-07 01:53:14.633",`updated_at`="2024-03-07 01:53:14.633" WHERE `id` = 100
Update single column
refDict := testdata.Ref_Dict()
// update with expr
rowsAffected, err := rapier.NewExecutor[testdata.Dict](db).
Model().
Where(refDict.Id.Eq(100)).
UpdateExpr(refDict.Key, "k1")
_ = err // return error
_ = rowsAffected // return row affected
// UPDATE `dict` SET `key`="k1",`updated_at`="2024-03-07 02:10:44.258" WHERE `dict`.`id` = 100
// update AssignExpr with expr
rowsAffected, err = rapier.NewExecutor[testdata.Dict](db).
Model().
Where(refDict.Id.Eq(100)).
UpdateExpr(refDict.UpdatedAt, refDict.CreatedAt.Add(time.Second))
_ = err // return error
_ = rowsAffected // return row affected
// UPDATE `dict` SET `updated_at`=DATE_ADD(`dict`.`created_at`, INTERVAL 1000000 MICROSECOND) WHERE `dict`.`id`
// update with original gorm api
rowsAffected, err = rapier.NewExecutor[testdata.Dict](db).
Model().
Where(refDict.Id.Eq(100)).
Update("key", "k1")
_ = err // return error
_ = rowsAffected // return row affected
// UPDATE `dict` SET `key`="k1",`updated_at`="2024-03-07 02:10:44.258" WHERE `dict`.`id` = 100
Updates multiple columns
refDict := testdata.Ref_Dict()
// update with expr
rowsAffected, err := rapier.NewExecutor[testdata.Dict](db).
Model().
Where(refDict.Id.Eq(100)).
UpdatesExpr(
refDict.Key.Value("k1"),
refDict.Remark.Value(""),
)
_ = err // return error
_ = rowsAffected // return row affected
// UPDATE `dict` SET `key`="k1",`remark`="",`updated_at`="2024-03-07 02:19:10.144" WHERE `dict`.`id` = 100
// update use `struct` with original gorm api
rowsAffected, err = rapier.NewExecutor[testdata.Dict](db).
Model().
Where(refDict.Id.Eq(100)).
Updates(&testdata.Dict{
Key: "k1",
Remark: "remark1",
})
_ = err // return error
_ = rowsAffected // return row affected
// UPDATE `dict` SET `key`="k1",`remark`="remark1",`updated_at`="2024-03-07 02:19:10.144" WHERE `dict`.`id` = 100
// update use map with original gorm api
rowsAffected, err = rapier.NewExecutor[testdata.Dict](db).
Model().
Where(refDict.Id.Eq(100)).
UpdatesMap(map[string]any{
"key": "k1",
"remark": "remark1",
})
_ = err // return error
_ = rowsAffected // return row affected
// UPDATE `dict` SET `key`="k1",`remark`="remark1",`updated_at`="2024-03-07 02:19:10.144" WHERE `dict`.`id` = 100
Update from SubQuery
refDict := testdata.Ref_Dict()
// update with expr
rowsAffected, err := rapier.NewExecutor[testdata.Dict](db).
Model().
Where(refDict.Id.Eq(100)).
UpdateExpr(
refDict.Key,
rapier.NewExecutor[testdata.Dict](db).Model().
SelectExpr(refDict.Key).
Where(refDict.Id.Eq(101)).
IntoDB(),
)
_ = err // return error
_ = rowsAffected // return row affected
// UPDATE `dict` SET `key`=(SELECT `dict`.`key` FROM `dict` WHERE `dict`.`id` = 101),`updated_at`="2024-03-07 02:41:40.548" WHERE `dict`.`id` = 100
// update with exprs
rowsAffected, err = rapier.NewExecutor[testdata.Dict](db).
Model().
Where(refDict.Id.Eq(100)).
UpdatesExpr(
refDict.Key.ValueSubQuery(
rapier.NewExecutor[testdata.Dict](db).Model().
SelectExpr(refDict.Key).
Where(refDict.Id.Eq(101)).
IntoDB(),
),
)
_ = err // return error
_ = rowsAffected // return row affected
// UPDATE `dict` SET `key`=(SELECT `dict`.`key` FROM `dict` WHERE `dict`.`id` = 101),`updated_at`="2024-03-07 02:41:40.548" WHERE `dict`.`id` = 100
// update use map with original gorm api
rowsAffected, err = rapier.NewExecutor[testdata.Dict](db).
Model().
Where(refDict.Id.Eq(100)).
UpdatesMap(map[string]any{
"key": rapier.NewExecutor[testdata.Dict](db).Model().
SelectExpr(refDict.Key).
Where(refDict.Id.Eq(101)).
IntoDB(),
})
_ = err // return error
_ = rowsAffected // return row affected
// UPDATE `dict` SET `key`=(SELECT `dict`.`key` FROM `dict` WHERE `dict`.`id` = 101),`updated_at`="2024-03-07 02:41:40.548" WHERE `dict`.`id` = 100
Without Hooks/Time Tracking
refDict := testdata.Ref_Dict()
// update with expr
rowsAffected, err := rapier.NewExecutor[testdata.Dict](db).
Model().
Where(refDict.Id.Eq(100)).
UpdateColumnsExpr(
refDict.Key.Value("k1"),
refDict.Remark.Value(""),
)
_ = err // return error
_ = rowsAffected // return row affected
// UPDATE `dict` SET `key`="k1",`remark`="" WHERE `dict`.`id` = 100
// update with expr
rowsAffected, err = rapier.NewExecutor[testdata.Dict](db).
Model().
Where(refDict.Id.Eq(100)).
UpdateColumnExpr(refDict.Key, "k1")
_ = err // return error
_ = rowsAffected // return row affected
// UPDATE `dict` SET `key`="k1" WHERE `dict`.`id` = 100
// update with original gorm api
rowsAffected, err = rapier.NewExecutor[testdata.Dict](db).
Model().
Where(refDict.Id.Eq(100)).
UpdateColumn("key", "k1")
_ = err // return error
_ = rowsAffected // return row affected
// UPDATE `dict` SET `key`="k1" WHERE `dict`.`id` = 100
// update with original gorm api
rowsAffected, err = rapier.NewExecutor[testdata.Dict](db).
Model().
Where(refDict.Id.Eq(100)).
UpdateColumns(&testdata.Dict{
Key: "k1",
})
_ = err // return error
_ = rowsAffected // return row affected
// UPDATE `dict` SET `key`="k1" WHERE `dict`.`id` = 100
// update with original gorm api
rowsAffected, err = rapier.NewExecutor[testdata.Dict](db).
Model().
Where(refDict.Id.Eq(100)).
UpdateColumnsMap(map[string]any{
"key": "k1",
})
_ = err // return error
_ = rowsAffected // return row affected
// UPDATE `dict` SET `key`="k1" WHERE `dict`.`id` = 100
more information gorm Update
Delete
refDict := testdata.Ref_Dict()
rowsAffected, err := rapier.NewExecutor[testdata.Dict](db).
Model().
Where(refDict.Id.Eq(100)).
Delete()
_ = err // return error
_ = rowsAffected // return row affected
// DELETE FROM `dict` WHERE `dict`.`id` = 100
more information gorm Delete
Raw SQL & SQL Builder
Original gorm db
IntoDB
, IntoRawDB
will get original gorm db.
IntoDB
: with model or tableIntoRawDB
: without model or table
Transaction
Associations
not supported yet, you can use gorm original api's associations topic.