mirror of https://github.com/zhufuyi/sponge
style: remove old libary ggorm
This commit is contained in:
parent
a8d69f49e7
commit
4ed9820657
|
@ -1,165 +0,0 @@
|
|||
## ggorm
|
||||
|
||||
`ggorm` library wrapped in [gorm](gorm.io/gorm), with added features such as tracer, paging queries, etc.
|
||||
|
||||
Support `mysql`, `postgresql`, `sqlite`.
|
||||
|
||||
<br>
|
||||
|
||||
## Examples of use
|
||||
|
||||
### mysql
|
||||
|
||||
#### Initializing the connection
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/go-dev-frame/sponge/pkg/ggorm"
|
||||
)
|
||||
|
||||
var dsn = "root:123456@(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
|
||||
|
||||
// (1) connect to the database using the default settings
|
||||
db, err := ggorm.InitMysql(dsn)
|
||||
|
||||
// (2) customised settings to connect to the database
|
||||
db, err := ggorm.InitMysql(
|
||||
dsn,
|
||||
ggorm.WithLogging(logger.Get()), // print log
|
||||
ggorm.WithLogRequestIDKey("request_id"), // print request_id
|
||||
ggorm.WithMaxIdleConns(5),
|
||||
ggorm.WithMaxOpenConns(50),
|
||||
ggorm.WithConnMaxLifetime(time.Minute*3),
|
||||
// ggorm.WithSlowThreshold(time.Millisecond*100), // only print logs that take longer than 100 milliseconds to execute
|
||||
// ggorm.WithEnableTrace(), // enable tracing
|
||||
// ggorm.WithRWSeparation(SlavesDsn, MastersDsn...) // read-write separation
|
||||
// ggorm.WithGormPlugin(yourPlugin) // custom gorm plugin
|
||||
)
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
#### Model
|
||||
|
||||
```go
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/go-dev-frame/sponge/pkg/ggorm"
|
||||
)
|
||||
|
||||
// UserExample object fields mapping table
|
||||
type UserExample struct {
|
||||
ggorm.Model `gorm:"embedded"`
|
||||
|
||||
Name string `gorm:"type:varchar(40);unique_index;not null" json:"name"`
|
||||
Age int `gorm:"not null" json:"age"`
|
||||
Gender string `gorm:"type:varchar(10);not null" json:"gender"`
|
||||
}
|
||||
|
||||
// TableName get table name
|
||||
func (table *UserExample) TableName() string {
|
||||
return ggorm.GetTableName(table)
|
||||
}
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
#### Transaction
|
||||
|
||||
```go
|
||||
func createUser() error {
|
||||
// note that you should use tx as the database handle when you are in a transaction
|
||||
tx := db.Begin()
|
||||
defer func() {
|
||||
if err := recover(); err != nil { // rollback after a panic during transaction execution
|
||||
tx.Rollback()
|
||||
fmt.Printf("transaction failed, err = %v\n", err)
|
||||
}
|
||||
}()
|
||||
|
||||
var err error
|
||||
if err = tx.Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = tx.Where("id = ?", 1).First(table).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
panic("mock panic")
|
||||
|
||||
if err = tx.Create(&userExample{Name: "Mr Li", Age: table.Age + 2, Gender: "male"}).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit().Error
|
||||
}
|
||||
```
|
||||
<br>
|
||||
|
||||
### Postgresql
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/go-dev-frame/sponge/pkg/ggorm"
|
||||
"github.com/go-dev-frame/sponge/pkg/utils"
|
||||
)
|
||||
|
||||
func InitSqlite() {
|
||||
opts := []ggorm.Option{
|
||||
ggorm.WithMaxIdleConns(10),
|
||||
ggorm.WithMaxOpenConns(100),
|
||||
ggorm.WithConnMaxLifetime(time.Duration(10) * time.Minute),
|
||||
ggorm.WithLogging(logger.Get()),
|
||||
ggorm.WithLogRequestIDKey("request_id"),
|
||||
}
|
||||
|
||||
dsn := "root:123456@127.0.0.1:5432/test"
|
||||
dsn = utils.AdaptivePostgresqlDsn(dsn)
|
||||
db, err := ggorm.InitPostgresql(dsn, opts...)
|
||||
if err != nil {
|
||||
panic("ggorm.InitPostgresql error: " + err.Error())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
### Tidb
|
||||
|
||||
Tidb is mysql compatible, just use **InitMysql**.
|
||||
|
||||
<br>
|
||||
|
||||
### Sqlite
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/go-dev-frame/sponge/pkg/ggorm"
|
||||
)
|
||||
|
||||
func InitSqlite() {
|
||||
opts := []ggorm.Option{
|
||||
ggorm.WithMaxIdleConns(10),
|
||||
ggorm.WithMaxOpenConns(100),
|
||||
ggorm.WithConnMaxLifetime(time.Duration(10) * time.Minute),
|
||||
ggorm.WithLogging(logger.Get()),
|
||||
ggorm.WithLogRequestIDKey("request_id"),
|
||||
}
|
||||
|
||||
dbFile: = "test.db"
|
||||
db, err := ggorm.InitSqlite(dbFile, opts...)
|
||||
if err != nil {
|
||||
panic("ggorm.InitSqlite error: " + err.Error())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
### gorm User Guide
|
||||
|
||||
- https://gorm.io/zh_CN/docs/index.html
|
|
@ -1,45 +0,0 @@
|
|||
package ggorm
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/huandu/xstrings"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Model embedded structs, add `gorm: "embedded"` when defining table structs
|
||||
type Model struct {
|
||||
ID uint64 `gorm:"column:id;AUTO_INCREMENT;primary_key" json:"id"`
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updatedAt"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;index" json:"-"`
|
||||
}
|
||||
|
||||
// Model2 embedded structs, json tag named is snake case
|
||||
type Model2 struct {
|
||||
ID uint64 `gorm:"column:id;AUTO_INCREMENT;primary_key" json:"id"`
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;index" json:"-"`
|
||||
}
|
||||
|
||||
// KV map type
|
||||
type KV = map[string]interface{}
|
||||
|
||||
// GetTableName get table name
|
||||
func GetTableName(object interface{}) string {
|
||||
tableName := ""
|
||||
|
||||
typeof := reflect.TypeOf(object)
|
||||
switch typeof.Kind() {
|
||||
case reflect.Ptr:
|
||||
tableName = typeof.Elem().Name()
|
||||
case reflect.Struct:
|
||||
tableName = typeof.Name()
|
||||
default:
|
||||
return tableName
|
||||
}
|
||||
|
||||
return xstrings.ToSnakeCase(tableName)
|
||||
}
|
|
@ -1,248 +0,0 @@
|
|||
// Package ggorm is a library wrapped on top of gorm.io/gorm, with added features such as link tracing, paging queries, etc.
|
||||
package ggorm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/uptrace/opentelemetry-go-extra/otelgorm"
|
||||
mysqlDriver "gorm.io/driver/mysql"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
"gorm.io/gorm/schema"
|
||||
"gorm.io/plugin/dbresolver"
|
||||
)
|
||||
|
||||
type DB = gorm.DB
|
||||
|
||||
const (
|
||||
// DBDriverMysql mysql driver
|
||||
DBDriverMysql = "mysql"
|
||||
// DBDriverPostgresql postgresql driver
|
||||
DBDriverPostgresql = "postgresql"
|
||||
// DBDriverTidb tidb driver
|
||||
DBDriverTidb = "tidb"
|
||||
// DBDriverSqlite sqlite driver
|
||||
DBDriverSqlite = "sqlite"
|
||||
)
|
||||
|
||||
// InitMysql init mysql or tidb
|
||||
func InitMysql(dsn string, opts ...Option) (*gorm.DB, error) {
|
||||
o := defaultOptions()
|
||||
o.apply(opts...)
|
||||
|
||||
sqlDB, err := sql.Open("mysql", dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sqlDB.SetMaxIdleConns(o.maxIdleConns) // set the maximum number of connections in the idle connection pool
|
||||
sqlDB.SetMaxOpenConns(o.maxOpenConns) // set the maximum number of open database connections
|
||||
sqlDB.SetConnMaxLifetime(o.connMaxLifetime) // set the maximum time a connection can be reused
|
||||
|
||||
db, err := gorm.Open(mysqlDriver.New(mysqlDriver.Config{Conn: sqlDB}), gormConfig(o))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db.Set("gorm:table_options", "CHARSET=utf8mb4") // automatic appending of table suffixes when creating tables
|
||||
|
||||
// register trace plugin
|
||||
if o.enableTrace {
|
||||
err = db.Use(otelgorm.NewPlugin())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("using gorm opentelemetry, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// register read-write separation plugin
|
||||
if len(o.slavesDsn) > 0 {
|
||||
err = db.Use(rwSeparationPlugin(o))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// register plugins
|
||||
for _, plugin := range o.plugins {
|
||||
err = db.Use(plugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// InitPostgresql init postgresql
|
||||
func InitPostgresql(dsn string, opts ...Option) (*gorm.DB, error) {
|
||||
o := defaultOptions()
|
||||
o.apply(opts...)
|
||||
|
||||
db, err := gorm.Open(postgres.Open(dsn), gormConfig(o))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// register trace plugin
|
||||
if o.enableTrace {
|
||||
err = db.Use(otelgorm.NewPlugin())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("using gorm opentelemetry, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// register read-write separation plugin
|
||||
if len(o.slavesDsn) > 0 {
|
||||
err = db.Use(rwSeparationPlugin(o))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// register plugins
|
||||
for _, plugin := range o.plugins {
|
||||
err = db.Use(plugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// InitTidb init tidb
|
||||
func InitTidb(dsn string, opts ...Option) (*gorm.DB, error) {
|
||||
return InitMysql(dsn, opts...)
|
||||
}
|
||||
|
||||
// InitSqlite init sqlite
|
||||
func InitSqlite(dbFile string, opts ...Option) (*gorm.DB, error) {
|
||||
o := defaultOptions()
|
||||
o.apply(opts...)
|
||||
|
||||
dsn := fmt.Sprintf("%s?_journal=WAL&_vacuum=incremental", dbFile)
|
||||
db, err := gorm.Open(sqlite.Open(dsn), gormConfig(o))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db.Set("gorm:auto_increment", true)
|
||||
|
||||
// register trace plugin
|
||||
if o.enableTrace {
|
||||
err = db.Use(otelgorm.NewPlugin())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("using gorm opentelemetry, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// register plugins
|
||||
for _, plugin := range o.plugins {
|
||||
err = db.Use(plugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// CloseDB close gorm db
|
||||
func CloseDB(db *gorm.DB) error {
|
||||
if db == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
checkInUse(sqlDB, time.Second*5)
|
||||
|
||||
return sqlDB.Close()
|
||||
}
|
||||
|
||||
func checkInUse(sqlDB *sql.DB, duration time.Duration) {
|
||||
ctx, _ := context.WithTimeout(context.Background(), duration) //nolint
|
||||
for {
|
||||
select {
|
||||
case <-time.After(time.Millisecond * 250):
|
||||
if v := sqlDB.Stats().InUse; v == 0 {
|
||||
return
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CloseSQLDB close sql db
|
||||
func CloseSQLDB(db *gorm.DB) {
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_ = sqlDB.Close()
|
||||
}
|
||||
|
||||
// gorm setting
|
||||
func gormConfig(o *options) *gorm.Config {
|
||||
config := &gorm.Config{
|
||||
// disable foreign key constraints, not recommended for production environments
|
||||
DisableForeignKeyConstraintWhenMigrating: o.disableForeignKey,
|
||||
// removing the plural of an epithet
|
||||
NamingStrategy: schema.NamingStrategy{SingularTable: true},
|
||||
}
|
||||
|
||||
// print SQL
|
||||
if o.isLog {
|
||||
if o.gLog == nil {
|
||||
config.Logger = logger.Default.LogMode(o.logLevel)
|
||||
} else {
|
||||
config.Logger = NewCustomGormLogger(o)
|
||||
}
|
||||
} else {
|
||||
config.Logger = logger.Default.LogMode(logger.Silent)
|
||||
}
|
||||
|
||||
// print only slow queries
|
||||
if o.slowThreshold > 0 {
|
||||
config.Logger = logger.New(
|
||||
log.New(os.Stdout, "\r\n", log.LstdFlags), // use the standard output asWriter
|
||||
logger.Config{
|
||||
SlowThreshold: o.slowThreshold,
|
||||
Colorful: true,
|
||||
LogLevel: logger.Warn, // set the logging level, only above the specified level will output the slow query log
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func rwSeparationPlugin(o *options) gorm.Plugin {
|
||||
slaves := []gorm.Dialector{}
|
||||
for _, dsn := range o.slavesDsn {
|
||||
slaves = append(slaves, mysqlDriver.New(mysqlDriver.Config{
|
||||
DSN: dsn,
|
||||
}))
|
||||
}
|
||||
|
||||
masters := []gorm.Dialector{}
|
||||
for _, dsn := range o.mastersDsn {
|
||||
masters = append(masters, mysqlDriver.New(mysqlDriver.Config{
|
||||
DSN: dsn,
|
||||
}))
|
||||
}
|
||||
|
||||
return dbresolver.Register(dbresolver.Config{
|
||||
Sources: masters,
|
||||
Replicas: slaves,
|
||||
Policy: dbresolver.RandomPolicy{},
|
||||
})
|
||||
}
|
|
@ -1,131 +0,0 @@
|
|||
package ggorm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm/logger"
|
||||
"gorm.io/gorm/utils"
|
||||
)
|
||||
|
||||
type gormLogger struct {
|
||||
gLog *zap.Logger
|
||||
requestIDKey string
|
||||
logLevel logger.LogLevel
|
||||
}
|
||||
|
||||
// NewCustomGormLogger custom gorm logger
|
||||
func NewCustomGormLogger(o *options) logger.Interface {
|
||||
return &gormLogger{
|
||||
gLog: o.gLog,
|
||||
requestIDKey: o.requestIDKey,
|
||||
logLevel: o.logLevel,
|
||||
}
|
||||
}
|
||||
|
||||
// LogMode log mode
|
||||
func (l *gormLogger) LogMode(level logger.LogLevel) logger.Interface {
|
||||
l.logLevel = level
|
||||
return l
|
||||
}
|
||||
|
||||
// Info print info
|
||||
func (l *gormLogger) Info(ctx context.Context, msg string, data ...interface{}) {
|
||||
if l.logLevel >= logger.Info {
|
||||
msg = strings.ReplaceAll(msg, "%v", "")
|
||||
l.gLog.Info(msg, zap.Any("data", data), zap.String("line", utils.FileWithLineNum()), requestIDField(ctx, l.requestIDKey))
|
||||
}
|
||||
}
|
||||
|
||||
// Warn print warn messages
|
||||
func (l *gormLogger) Warn(ctx context.Context, msg string, data ...interface{}) {
|
||||
if l.logLevel >= logger.Warn {
|
||||
msg = strings.ReplaceAll(msg, "%v", "")
|
||||
l.gLog.Warn(msg, zap.Any("data", data), zap.String("line", utils.FileWithLineNum()), requestIDField(ctx, l.requestIDKey))
|
||||
}
|
||||
}
|
||||
|
||||
// Error print error messages
|
||||
func (l *gormLogger) Error(ctx context.Context, msg string, data ...interface{}) {
|
||||
if l.logLevel >= logger.Error {
|
||||
msg = strings.ReplaceAll(msg, "%v", "")
|
||||
l.gLog.Warn(msg, zap.Any("data", data), zap.String("line", utils.FileWithLineNum()), requestIDField(ctx, l.requestIDKey))
|
||||
}
|
||||
}
|
||||
|
||||
// Trace print sql message
|
||||
func (l *gormLogger) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
|
||||
if l.logLevel <= logger.Silent {
|
||||
return
|
||||
}
|
||||
|
||||
elapsed := time.Since(begin)
|
||||
sql, rows := fc()
|
||||
|
||||
var rowsField zap.Field
|
||||
if rows == -1 {
|
||||
rowsField = zap.String("rows", "-")
|
||||
} else {
|
||||
rowsField = zap.Int64("rows", rows)
|
||||
}
|
||||
|
||||
var fileLineField zap.Field
|
||||
fileLine := utils.FileWithLineNum()
|
||||
ss := strings.Split(fileLine, "/internal/")
|
||||
if len(ss) == 2 {
|
||||
fileLineField = zap.String("file_line", ss[1])
|
||||
} else {
|
||||
fileLineField = zap.String("file_line", fileLine)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
l.gLog.Warn("Gorm msg",
|
||||
zap.Error(err),
|
||||
zap.String("sql", sql),
|
||||
rowsField,
|
||||
zap.Float64("ms", float64(elapsed.Nanoseconds())/1e6),
|
||||
fileLineField,
|
||||
requestIDField(ctx, l.requestIDKey),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if l.logLevel >= logger.Info {
|
||||
l.gLog.Info("Gorm msg",
|
||||
zap.String("sql", sql),
|
||||
rowsField,
|
||||
zap.Float64("ms", float64(elapsed.Nanoseconds())/1e6),
|
||||
fileLineField,
|
||||
requestIDField(ctx, l.requestIDKey),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if l.logLevel >= logger.Warn {
|
||||
l.gLog.Warn("Gorm msg",
|
||||
zap.String("sql", sql),
|
||||
rowsField,
|
||||
zap.Float64("ms", float64(elapsed.Nanoseconds())/1e6),
|
||||
fileLineField,
|
||||
requestIDField(ctx, l.requestIDKey),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func requestIDField(ctx context.Context, requestIDKey string) zap.Field {
|
||||
if requestIDKey == "" {
|
||||
return zap.Skip()
|
||||
}
|
||||
|
||||
var field zap.Field
|
||||
if requestIDKey != "" {
|
||||
if v, ok := ctx.Value(requestIDKey).(string); ok {
|
||||
field = zap.String(requestIDKey, v)
|
||||
} else {
|
||||
field = zap.Skip()
|
||||
}
|
||||
}
|
||||
return field
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
package ggorm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
func TestNewCustomGormLogger(t *testing.T) {
|
||||
zapLog, _ := zap.NewDevelopment()
|
||||
l := NewCustomGormLogger(&options{
|
||||
requestIDKey: "request_id",
|
||||
gLog: zapLog,
|
||||
logLevel: logger.Info,
|
||||
})
|
||||
|
||||
l.LogMode(logger.Info)
|
||||
ctx := context.WithValue(context.Background(), "request_id", "123")
|
||||
l.Info(ctx, "info", "foo")
|
||||
l.Warn(ctx, "warn", "bar")
|
||||
l.Error(ctx, "error", "foo bar")
|
||||
|
||||
l.LogMode(logger.Silent)
|
||||
l.Trace(ctx, time.Now(), nil, nil)
|
||||
|
||||
l.LogMode(logger.Info)
|
||||
l.Trace(ctx, time.Now(), func() (string, int64) {
|
||||
return "sql statement", 1
|
||||
}, nil)
|
||||
l.Trace(ctx, time.Now(), func() (string, int64) {
|
||||
return "sql statement", -1
|
||||
}, nil)
|
||||
|
||||
l.Trace(ctx, time.Now(), func() (string, int64) {
|
||||
return "sql statement", 0
|
||||
}, logger.ErrRecordNotFound)
|
||||
|
||||
l.Trace(ctx, time.Now(), func() (string, int64) {
|
||||
return "sql statement", 0
|
||||
}, errors.New("Error 1054: Unknown column 'test_column'"))
|
||||
|
||||
l.LogMode(logger.Warn)
|
||||
l.Trace(ctx, time.Now(), func() (string, int64) {
|
||||
return "sql statement", 0
|
||||
}, logger.ErrRecordNotFound)
|
||||
}
|
||||
|
||||
func Test_requestIDField(t *testing.T) {
|
||||
ctx := context.WithValue(context.Background(), "request_id", "123")
|
||||
field := requestIDField(ctx, "")
|
||||
assert.Equal(t, zap.Skip(), field)
|
||||
field = requestIDField(ctx, "your request id key")
|
||||
assert.Equal(t, zap.Skip(), field)
|
||||
field = requestIDField(ctx, "request_id")
|
||||
assert.Equal(t, zap.String("request_id", "123"), field)
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
package ggorm
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"gorm.io/gorm"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var dsn = "root:123456@(192.168.3.37:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
|
||||
|
||||
func TestInitMysql(t *testing.T) {
|
||||
db, err := InitMysql(dsn, WithEnableTrace())
|
||||
if err != nil {
|
||||
// ignore test error about not being able to connect to real mysql
|
||||
t.Logf(fmt.Sprintf("connect to mysql failed, err=%v, dsn=%s", err, dsn))
|
||||
return
|
||||
}
|
||||
defer CloseDB(db)
|
||||
|
||||
t.Logf("%+v", db.Name())
|
||||
}
|
||||
|
||||
func TestInitTidb(t *testing.T) {
|
||||
db, err := InitTidb(dsn)
|
||||
if err != nil {
|
||||
// ignore test error about not being able to connect to real tidb
|
||||
t.Logf(fmt.Sprintf("connect to mysql failed, err=%v, dsn=%s", err, dsn))
|
||||
return
|
||||
}
|
||||
defer CloseDB(db)
|
||||
|
||||
t.Logf("%+v", db.Name())
|
||||
}
|
||||
|
||||
func TestInitSqlite(t *testing.T) {
|
||||
dbFile := "test_sqlite.db"
|
||||
db, err := InitSqlite(dbFile)
|
||||
if err != nil {
|
||||
// ignore test error about not being able to connect to real sqlite
|
||||
t.Logf(fmt.Sprintf("connect to sqlite failed, err=%v, dbFile=%s", err, dbFile))
|
||||
return
|
||||
}
|
||||
defer CloseDB(db)
|
||||
|
||||
t.Logf("%+v", db.Name())
|
||||
}
|
||||
|
||||
func TestInitPostgresql(t *testing.T) {
|
||||
dsn = "host=192.168.3.37 user=root password=123456 dbname=account port=5432 sslmode=disable TimeZone=Asia/Shanghai"
|
||||
db, err := InitPostgresql(dsn, WithEnableTrace())
|
||||
if err != nil {
|
||||
// ignore test error about not being able to connect to real postgresql
|
||||
t.Logf(fmt.Sprintf("connect to postgresql failed, err=%v, dsn=%s", err, dsn))
|
||||
return
|
||||
}
|
||||
defer CloseDB(db)
|
||||
|
||||
t.Logf("%+v", db.Name())
|
||||
}
|
||||
|
||||
func Test_gormConfig(t *testing.T) {
|
||||
o := defaultOptions()
|
||||
o.apply(
|
||||
WithLogging(nil),
|
||||
WithLogging(nil, 4),
|
||||
WithSlowThreshold(time.Millisecond*100),
|
||||
WithEnableTrace(),
|
||||
WithMaxIdleConns(5),
|
||||
WithMaxOpenConns(50),
|
||||
WithConnMaxLifetime(time.Minute*3),
|
||||
WithEnableForeignKey(),
|
||||
WithLogRequestIDKey("request_id"),
|
||||
WithRWSeparation([]string{
|
||||
"root:123456@(192.168.3.37:3306)/slave1",
|
||||
"root:123456@(192.168.3.37:3306)/slave2"},
|
||||
"root:123456@(192.168.3.37:3306)/master"),
|
||||
WithGormPlugin(nil),
|
||||
)
|
||||
|
||||
c := gormConfig(o)
|
||||
assert.NotNil(t, c)
|
||||
|
||||
err := rwSeparationPlugin(o)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
type userExample struct {
|
||||
Model `gorm:"embedded"`
|
||||
|
||||
Name string `gorm:"type:varchar(40);unique_index;not null" json:"name"`
|
||||
Age int `gorm:"not null" json:"age"`
|
||||
Gender string `gorm:"type:varchar(10);not null" json:"gender"`
|
||||
}
|
||||
|
||||
func TestGetTableName(t *testing.T) {
|
||||
name := GetTableName(&userExample{})
|
||||
assert.NotEmpty(t, name)
|
||||
|
||||
name = GetTableName(userExample{})
|
||||
assert.NotEmpty(t, name)
|
||||
|
||||
name = GetTableName("table")
|
||||
assert.Empty(t, name)
|
||||
}
|
||||
|
||||
func TestCloseDB(t *testing.T) {
|
||||
sqlDB := new(sql.DB)
|
||||
checkInUse(sqlDB, time.Millisecond*100)
|
||||
checkInUse(sqlDB, time.Millisecond*600)
|
||||
db := new(gorm.DB)
|
||||
defer func() { recover() }()
|
||||
_ = CloseDB(db)
|
||||
}
|
||||
|
||||
func TestCloseSqlDB(t *testing.T) {
|
||||
db := new(gorm.DB)
|
||||
defer func() { recover() }()
|
||||
CloseSQLDB(db)
|
||||
}
|
|
@ -1,137 +0,0 @@
|
|||
package ggorm
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
// Option set the mysql options.
|
||||
type Option func(*options)
|
||||
|
||||
type options struct {
|
||||
isLog bool
|
||||
slowThreshold time.Duration
|
||||
|
||||
maxIdleConns int
|
||||
maxOpenConns int
|
||||
connMaxLifetime time.Duration
|
||||
|
||||
disableForeignKey bool
|
||||
enableTrace bool
|
||||
|
||||
requestIDKey string
|
||||
gLog *zap.Logger
|
||||
logLevel logger.LogLevel
|
||||
|
||||
slavesDsn []string
|
||||
mastersDsn []string
|
||||
|
||||
plugins []gorm.Plugin
|
||||
}
|
||||
|
||||
func (o *options) apply(opts ...Option) {
|
||||
for _, opt := range opts {
|
||||
opt(o)
|
||||
}
|
||||
}
|
||||
|
||||
// default settings
|
||||
func defaultOptions() *options {
|
||||
return &options{
|
||||
isLog: false, // whether to output logs, default off
|
||||
slowThreshold: time.Duration(0), // if greater than 0, only print logs that are longer than the threshold, higher priority than isLog
|
||||
|
||||
maxIdleConns: 3, // set the maximum number of connections in the idle connection pool
|
||||
maxOpenConns: 50, // set the maximum number of open database connections
|
||||
connMaxLifetime: 30 * time.Minute, // sets the maximum amount of time a connection can be reused
|
||||
|
||||
disableForeignKey: true, // disables the use of foreign keys, true is recommended for production environments, enabled by default
|
||||
enableTrace: false, // whether to enable link tracing, default is off
|
||||
|
||||
requestIDKey: "", // request id key
|
||||
gLog: nil, // custom logger
|
||||
logLevel: logger.Info, // default logLevel
|
||||
}
|
||||
}
|
||||
|
||||
// WithLogging set log sql, If l=nil, the gorm log library will be used
|
||||
func WithLogging(l *zap.Logger, level ...logger.LogLevel) Option {
|
||||
return func(o *options) {
|
||||
o.isLog = true
|
||||
o.gLog = l
|
||||
if len(level) > 0 {
|
||||
o.logLevel = level[0]
|
||||
}
|
||||
o.logLevel = logger.Info
|
||||
}
|
||||
}
|
||||
|
||||
// WithSlowThreshold Set sql values greater than the threshold
|
||||
func WithSlowThreshold(d time.Duration) Option {
|
||||
return func(o *options) {
|
||||
o.slowThreshold = d
|
||||
}
|
||||
}
|
||||
|
||||
// WithMaxIdleConns set max idle conns
|
||||
func WithMaxIdleConns(size int) Option {
|
||||
return func(o *options) {
|
||||
o.maxIdleConns = size
|
||||
}
|
||||
}
|
||||
|
||||
// WithMaxOpenConns set max open conns
|
||||
func WithMaxOpenConns(size int) Option {
|
||||
return func(o *options) {
|
||||
o.maxOpenConns = size
|
||||
}
|
||||
}
|
||||
|
||||
// WithConnMaxLifetime set conn max lifetime
|
||||
func WithConnMaxLifetime(t time.Duration) Option {
|
||||
return func(o *options) {
|
||||
o.connMaxLifetime = t
|
||||
}
|
||||
}
|
||||
|
||||
// WithEnableForeignKey use foreign keys
|
||||
func WithEnableForeignKey() Option {
|
||||
return func(o *options) {
|
||||
o.disableForeignKey = false
|
||||
}
|
||||
}
|
||||
|
||||
// WithEnableTrace use trace
|
||||
func WithEnableTrace() Option {
|
||||
return func(o *options) {
|
||||
o.enableTrace = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithLogRequestIDKey log request id
|
||||
func WithLogRequestIDKey(key string) Option {
|
||||
return func(o *options) {
|
||||
if key == "" {
|
||||
key = "request_id"
|
||||
}
|
||||
o.requestIDKey = key
|
||||
}
|
||||
}
|
||||
|
||||
// WithRWSeparation setting read-write separation
|
||||
func WithRWSeparation(slavesDsn []string, mastersDsn ...string) Option {
|
||||
return func(o *options) {
|
||||
o.slavesDsn = slavesDsn
|
||||
o.mastersDsn = mastersDsn
|
||||
}
|
||||
}
|
||||
|
||||
// WithGormPlugin setting gorm plugin
|
||||
func WithGormPlugin(plugins ...gorm.Plugin) Option {
|
||||
return func(o *options) {
|
||||
o.plugins = plugins
|
||||
}
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
package query
|
||||
|
||||
import "strings"
|
||||
|
||||
var defaultMaxSize = 1000
|
||||
|
||||
// SetMaxSize change the default maximum number of pages per page
|
||||
func SetMaxSize(maxValue int) {
|
||||
if maxValue < 10 {
|
||||
maxValue = 10
|
||||
}
|
||||
defaultMaxSize = maxValue
|
||||
}
|
||||
|
||||
// Page info
|
||||
type Page struct {
|
||||
page int // page number, starting from page 0
|
||||
limit int // number per page
|
||||
sort string // sort fields, default is id backwards, you can add - sign before the field to indicate reverse order, no - sign to indicate ascending order, multiple fields separated by comma
|
||||
}
|
||||
|
||||
// Page get page value
|
||||
func (p *Page) Page() int {
|
||||
return p.page
|
||||
}
|
||||
|
||||
// Limit number per page
|
||||
func (p *Page) Limit() int {
|
||||
return p.limit
|
||||
}
|
||||
|
||||
// Size number per page
|
||||
// Deprecated: use Limit instead
|
||||
func (p *Page) Size() int {
|
||||
return p.limit
|
||||
}
|
||||
|
||||
// Sort get sort field
|
||||
func (p *Page) Sort() string {
|
||||
return p.sort
|
||||
}
|
||||
|
||||
// Offset get offset value
|
||||
func (p *Page) Offset() int {
|
||||
return p.page * p.limit
|
||||
}
|
||||
|
||||
// DefaultPage default page, number 20 per page, sorted by id backwards
|
||||
func DefaultPage(page int) *Page {
|
||||
if page < 0 {
|
||||
page = 0
|
||||
}
|
||||
return &Page{
|
||||
page: page,
|
||||
limit: 20,
|
||||
sort: "id DESC",
|
||||
}
|
||||
}
|
||||
|
||||
// NewPage custom page, starting from page 0.
|
||||
// the parameter columnNames indicates a sort field, if empty means id descending,
|
||||
// if there are multiple column names, separated by a comma,
|
||||
// a '-' sign in front of each column name indicates descending order, otherwise ascending order.
|
||||
func NewPage(page int, limit int, columnNames string) *Page {
|
||||
if page < 0 {
|
||||
page = 0
|
||||
}
|
||||
if limit > defaultMaxSize || limit < 1 {
|
||||
limit = defaultMaxSize
|
||||
}
|
||||
|
||||
return &Page{
|
||||
page: page,
|
||||
limit: limit,
|
||||
sort: getSort(columnNames),
|
||||
}
|
||||
}
|
||||
|
||||
// convert to mysql sort, each column name preceded by a '-' sign, indicating descending order, otherwise ascending order, example:
|
||||
//
|
||||
// columnNames="name" means sort by name in ascending order,
|
||||
// columnNames="-name" means sort by name descending,
|
||||
// columnNames="name,age" means sort by name in ascending order, otherwise sort by age in ascending order,
|
||||
// columnNames="-name,-age" means sort by name descending before sorting by age descending.
|
||||
func getSort(columnNames string) string {
|
||||
columnNames = strings.Replace(columnNames, " ", "", -1)
|
||||
if columnNames == "" {
|
||||
return "id DESC"
|
||||
}
|
||||
|
||||
names := strings.Split(columnNames, ",")
|
||||
strs := make([]string, 0, len(names))
|
||||
for _, name := range names {
|
||||
if name[0] == '-' && len(name) > 1 {
|
||||
strs = append(strs, name[1:]+" DESC")
|
||||
} else {
|
||||
strs = append(strs, name+" ASC")
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(strs, ", ")
|
||||
}
|
|
@ -1,233 +0,0 @@
|
|||
// Package query is a library of custom condition queries, support for complex conditional paging queries.
|
||||
package query
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// Eq equal
|
||||
Eq = "eq"
|
||||
// Neq not equal
|
||||
Neq = "neq"
|
||||
// Gt greater than
|
||||
Gt = "gt"
|
||||
// Gte greater than or equal
|
||||
Gte = "gte"
|
||||
// Lt less than
|
||||
Lt = "lt"
|
||||
// Lte less than or equal
|
||||
Lte = "lte"
|
||||
// Like fuzzy lookup
|
||||
Like = "like"
|
||||
// In include
|
||||
In = "in"
|
||||
|
||||
// AND logic and
|
||||
AND string = "and"
|
||||
// OR logic or
|
||||
OR string = "or"
|
||||
)
|
||||
|
||||
var expMap = map[string]string{
|
||||
Eq: " = ",
|
||||
Neq: " <> ",
|
||||
Gt: " > ",
|
||||
Gte: " >= ",
|
||||
Lt: " < ",
|
||||
Lte: " <= ",
|
||||
Like: " LIKE ",
|
||||
In: " IN ",
|
||||
|
||||
"=": " = ",
|
||||
"!=": " <> ",
|
||||
">": " > ",
|
||||
">=": " >= ",
|
||||
"<": " < ",
|
||||
"<=": " <= ",
|
||||
}
|
||||
|
||||
var logicMap = map[string]string{
|
||||
AND: " AND ",
|
||||
OR: " OR ",
|
||||
|
||||
"&": " AND ",
|
||||
"&&": " AND ",
|
||||
"|": " OR ",
|
||||
"||": " OR ",
|
||||
"AND": " AND ",
|
||||
"OR": " OR ",
|
||||
}
|
||||
|
||||
// Params query parameters
|
||||
type Params struct {
|
||||
Page int `json:"page" form:"page" binding:"gte=0"`
|
||||
Limit int `json:"limit" form:"limit" binding:"gte=1"`
|
||||
Sort string `json:"sort,omitempty" form:"sort" binding:""`
|
||||
|
||||
Columns []Column `json:"columns,omitempty" form:"columns"` // not required
|
||||
|
||||
// Deprecated: use Limit instead in sponge version v1.8.6, will remove in the future
|
||||
Size int `json:"size" form:"size"`
|
||||
}
|
||||
|
||||
// Column query info
|
||||
type Column struct {
|
||||
Name string `json:"name" form:"name"` // column name
|
||||
Exp string `json:"exp" form:"exp"` // expressions, default value is "=", support =, !=, >, >=, <, <=, like, in
|
||||
Value interface{} `json:"value" form:"value"` // column value
|
||||
Logic string `json:"logic" form:"logic"` // logical type, defaults to and when the value is null, with &(and), ||(or)
|
||||
}
|
||||
|
||||
func (c *Column) checkValid() error {
|
||||
if c.Name == "" {
|
||||
return fmt.Errorf("field 'name' cannot be empty")
|
||||
}
|
||||
if c.Value == nil {
|
||||
return fmt.Errorf("field 'value' cannot be nil")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// converting ExpType to sql expressions and LogicType to sql using characters
|
||||
func (c *Column) convert() error {
|
||||
if c.Exp == "" {
|
||||
c.Exp = Eq
|
||||
}
|
||||
if v, ok := expMap[strings.ToLower(c.Exp)]; ok { //nolint
|
||||
c.Exp = v
|
||||
if c.Exp == " LIKE " {
|
||||
c.Value = fmt.Sprintf("%%%v%%", c.Value)
|
||||
}
|
||||
if c.Exp == " IN " {
|
||||
val, ok := c.Value.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid value type '%s'", c.Value)
|
||||
}
|
||||
iVal := []interface{}{}
|
||||
ss := strings.Split(val, ",")
|
||||
for _, s := range ss {
|
||||
iVal = append(iVal, s)
|
||||
}
|
||||
c.Value = iVal
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("unknown exp type '%s'", c.Exp)
|
||||
}
|
||||
|
||||
if c.Logic == "" {
|
||||
c.Logic = AND
|
||||
}
|
||||
if v, ok := logicMap[strings.ToLower(c.Logic)]; ok { //nolint
|
||||
c.Logic = v
|
||||
} else {
|
||||
return fmt.Errorf("unknown logic type '%s'", c.Logic)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConvertToPage converted to page
|
||||
func (p *Params) ConvertToPage() (order string, limit int, offset int) { //nolint
|
||||
page := NewPage(p.Page, p.Limit, p.Sort)
|
||||
order = page.sort
|
||||
limit = page.limit
|
||||
offset = page.page * page.limit
|
||||
return //nolint
|
||||
}
|
||||
|
||||
// ConvertToGormConditions conversion to gorm-compliant parameters based on the Columns parameter
|
||||
// ignore the logical type of the last column, whether it is a one-column or multi-column query
|
||||
func (p *Params) ConvertToGormConditions() (string, []interface{}, error) {
|
||||
str := ""
|
||||
args := []interface{}{}
|
||||
l := len(p.Columns)
|
||||
if l == 0 {
|
||||
return "", nil, nil
|
||||
}
|
||||
|
||||
isUseIN := true
|
||||
if l == 1 {
|
||||
isUseIN = false
|
||||
}
|
||||
field := p.Columns[0].Name
|
||||
|
||||
for i, column := range p.Columns {
|
||||
if err := column.checkValid(); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
err := column.convert()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
symbol := "?"
|
||||
if column.Exp == " IN " {
|
||||
symbol = "(?)"
|
||||
}
|
||||
if i == l-1 { // ignore the logical type of the last column
|
||||
str += column.Name + column.Exp + symbol
|
||||
} else {
|
||||
str += column.Name + column.Exp + symbol + column.Logic
|
||||
}
|
||||
args = append(args, column.Value)
|
||||
|
||||
// when multiple columns are the same, determine whether the use of IN
|
||||
if isUseIN {
|
||||
if field != column.Name {
|
||||
isUseIN = false
|
||||
continue
|
||||
}
|
||||
if column.Exp != expMap[Eq] {
|
||||
isUseIN = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isUseIN {
|
||||
str = field + " IN (?)"
|
||||
args = []interface{}{args}
|
||||
}
|
||||
|
||||
return str, args, nil
|
||||
}
|
||||
|
||||
// Conditions query conditions
|
||||
type Conditions struct {
|
||||
Columns []Column `json:"columns" form:"columns" binding:"min=1"` // columns info
|
||||
}
|
||||
|
||||
// CheckValid check valid
|
||||
func (c *Conditions) CheckValid() error {
|
||||
if len(c.Columns) == 0 {
|
||||
return fmt.Errorf("field 'columns' cannot be empty")
|
||||
}
|
||||
|
||||
for _, column := range c.Columns {
|
||||
err := column.checkValid()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if column.Exp != "" {
|
||||
if _, ok := expMap[column.Exp]; !ok {
|
||||
return fmt.Errorf("unknown exp type '%s'", column.Exp)
|
||||
}
|
||||
}
|
||||
if column.Logic != "" {
|
||||
if _, ok := logicMap[column.Logic]; !ok {
|
||||
return fmt.Errorf("unknown logic type '%s'", column.Logic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConvertToGorm conversion to gorm-compliant parameters based on the Columns parameter
|
||||
// ignore the logical type of the last column, whether it is a one-column or multi-column query
|
||||
func (c *Conditions) ConvertToGorm() (string, []interface{}, error) {
|
||||
p := &Params{Columns: c.Columns}
|
||||
return p.ConvertToGormConditions()
|
||||
}
|
|
@ -1,541 +0,0 @@
|
|||
package query
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPage(t *testing.T) {
|
||||
page := DefaultPage(-1)
|
||||
t.Log(page.Page(), page.Limit(), page.Sort(), page.Offset())
|
||||
|
||||
SetMaxSize(1)
|
||||
|
||||
page = NewPage(-1, 100, "id")
|
||||
t.Log(page.Page(), page.Limit(), page.Sort(), page.Offset())
|
||||
}
|
||||
|
||||
func TestParams_ConvertToPage(t *testing.T) {
|
||||
p := &Params{
|
||||
Page: 1,
|
||||
Limit: 50,
|
||||
Sort: "age,-name",
|
||||
}
|
||||
order, limit, offset := p.ConvertToPage()
|
||||
t.Logf("order=%s, limit=%d, offset=%d", order, limit, offset)
|
||||
|
||||
}
|
||||
|
||||
func TestParams_ConvertToGormConditions(t *testing.T) {
|
||||
type args struct {
|
||||
columns []Column
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
want1 []interface{}
|
||||
wantErr bool
|
||||
}{
|
||||
// --------------------------- only 1 column query ------------------------------
|
||||
{
|
||||
name: "1 column eq",
|
||||
args: args{
|
||||
columns: []Column{
|
||||
{
|
||||
Name: "name",
|
||||
Value: "ZhangSan",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "name = ?",
|
||||
want1: []interface{}{"ZhangSan"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "1 column neq",
|
||||
args: args{
|
||||
columns: []Column{
|
||||
{
|
||||
Name: "name",
|
||||
Value: "ZhangSan",
|
||||
//Exp: "neq",
|
||||
Exp: "!=",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "name <> ?",
|
||||
want1: []interface{}{"ZhangSan"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "1 column gt",
|
||||
args: args{
|
||||
columns: []Column{
|
||||
{
|
||||
Name: "age",
|
||||
Value: 20,
|
||||
//Exp: Gt,
|
||||
Exp: ">",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "age > ?",
|
||||
want1: []interface{}{20},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "1 column gte",
|
||||
args: args{
|
||||
columns: []Column{
|
||||
{
|
||||
Name: "age",
|
||||
Value: 20,
|
||||
//Exp: Gte,
|
||||
Exp: ">=",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "age >= ?",
|
||||
want1: []interface{}{20},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "1 column lt",
|
||||
args: args{
|
||||
columns: []Column{
|
||||
{
|
||||
Name: "age",
|
||||
Value: 20,
|
||||
//Exp: Lt,
|
||||
Exp: "<",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "age < ?",
|
||||
want1: []interface{}{20},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "1 column lte",
|
||||
args: args{
|
||||
columns: []Column{
|
||||
{
|
||||
Name: "age",
|
||||
Value: 20,
|
||||
//Exp: Lte,
|
||||
Exp: "<=",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "age <= ?",
|
||||
want1: []interface{}{20},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "1 column like",
|
||||
args: args{
|
||||
columns: []Column{
|
||||
{
|
||||
Name: "name",
|
||||
Value: "Li",
|
||||
Exp: Like,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "name LIKE ?",
|
||||
want1: []interface{}{"%Li%"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "1 column IN",
|
||||
args: args{
|
||||
columns: []Column{
|
||||
{
|
||||
Name: "name",
|
||||
Value: "ab,cd,ef",
|
||||
Exp: In,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "name IN (?)",
|
||||
want1: []interface{}{[]interface{}{"ab", "cd", "ef"}},
|
||||
wantErr: false,
|
||||
},
|
||||
|
||||
// --------------------------- query 2 columns ------------------------------
|
||||
{
|
||||
name: "2 columns eq and",
|
||||
args: args{
|
||||
columns: []Column{
|
||||
{
|
||||
Name: "name",
|
||||
Value: "ZhangSan",
|
||||
},
|
||||
{
|
||||
Name: "gender",
|
||||
Value: "male",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "name = ? AND gender = ?",
|
||||
want1: []interface{}{"ZhangSan", "male"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "2 columns neq and",
|
||||
args: args{
|
||||
columns: []Column{
|
||||
{
|
||||
Name: "name",
|
||||
Value: "ZhangSan",
|
||||
//Exp: Neq,
|
||||
Exp: "!=",
|
||||
},
|
||||
{
|
||||
Name: "name",
|
||||
Value: "LiSi",
|
||||
//Exp: Neq,
|
||||
Exp: "!=",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "name <> ? AND name <> ?",
|
||||
want1: []interface{}{"ZhangSan", "LiSi"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "2 columns gt and",
|
||||
args: args{
|
||||
columns: []Column{
|
||||
{
|
||||
Name: "gender",
|
||||
Value: "male",
|
||||
},
|
||||
{
|
||||
Name: "age",
|
||||
Value: 20,
|
||||
//Exp: Gt,
|
||||
Exp: ">",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "gender = ? AND age > ?",
|
||||
want1: []interface{}{"male", 20},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "2 columns gte and",
|
||||
args: args{
|
||||
columns: []Column{
|
||||
{
|
||||
Name: "gender",
|
||||
Value: "male",
|
||||
},
|
||||
{
|
||||
Name: "age",
|
||||
Value: 20,
|
||||
//Exp: Gte,
|
||||
Exp: ">=",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "gender = ? AND age >= ?",
|
||||
want1: []interface{}{"male", 20},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "2 columns lt and",
|
||||
args: args{
|
||||
columns: []Column{
|
||||
{
|
||||
Name: "gender",
|
||||
Value: "female",
|
||||
},
|
||||
{
|
||||
Name: "age",
|
||||
Value: 20,
|
||||
//Exp: Lt,
|
||||
Exp: "<",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "gender = ? AND age < ?",
|
||||
want1: []interface{}{"female", 20},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "2 columns lte and",
|
||||
args: args{
|
||||
columns: []Column{
|
||||
{
|
||||
Name: "gender",
|
||||
Value: "female",
|
||||
},
|
||||
{
|
||||
Name: "age",
|
||||
Value: 20,
|
||||
//Exp: Lte,
|
||||
Exp: "<=",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "gender = ? AND age <= ?",
|
||||
want1: []interface{}{"female", 20},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "2 columns range and",
|
||||
args: args{
|
||||
columns: []Column{
|
||||
{
|
||||
Name: "age",
|
||||
Value: 10,
|
||||
//Exp: Gte,
|
||||
Exp: ">=",
|
||||
},
|
||||
{
|
||||
Name: "age",
|
||||
Value: 20,
|
||||
//Exp: Lte,
|
||||
Exp: "<=",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "age >= ? AND age <= ?",
|
||||
want1: []interface{}{10, 20},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "2 columns eq or",
|
||||
args: args{
|
||||
columns: []Column{
|
||||
{
|
||||
Name: "name",
|
||||
Value: "LiSi",
|
||||
//Logic: OR,
|
||||
Logic: "||",
|
||||
},
|
||||
{
|
||||
Name: "gender",
|
||||
Value: "female",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "name = ? OR gender = ?",
|
||||
want1: []interface{}{"LiSi", "female"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "2 columns neq or",
|
||||
args: args{
|
||||
columns: []Column{
|
||||
{
|
||||
Name: "name",
|
||||
Value: "LiSi",
|
||||
//Logic: OR,
|
||||
Logic: "||",
|
||||
},
|
||||
{
|
||||
Name: "gender",
|
||||
Value: "male",
|
||||
//Exp: Neq,
|
||||
Exp: "!=",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "name = ? OR gender <> ?",
|
||||
want1: []interface{}{"LiSi", "male"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "2 columns eq and in",
|
||||
args: args{
|
||||
columns: []Column{
|
||||
{
|
||||
Name: "gender",
|
||||
Value: "male",
|
||||
//Logic: "&",
|
||||
},
|
||||
{
|
||||
Name: "name",
|
||||
Value: "LiSi,ZhangSan,WangWu",
|
||||
Exp: In,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "gender = ? AND name IN (?)",
|
||||
want1: []interface{}{"male", []interface{}{"LiSi", "ZhangSan", "WangWu"}},
|
||||
wantErr: false,
|
||||
},
|
||||
|
||||
// ------------------------------ IN -------------------------------------------------
|
||||
{
|
||||
name: "3 columns eq and",
|
||||
args: args{
|
||||
columns: []Column{
|
||||
{
|
||||
Name: "name",
|
||||
Value: "LiSi",
|
||||
},
|
||||
{
|
||||
Name: "name",
|
||||
Value: "ZhangSan",
|
||||
},
|
||||
{
|
||||
Name: "name",
|
||||
Value: "WangWu",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "name IN (?)",
|
||||
want1: []interface{}{[]interface{}{"LiSi", "ZhangSan", "WangWu"}},
|
||||
wantErr: false,
|
||||
},
|
||||
|
||||
// ---------------------------- error ----------------------------------------------
|
||||
{
|
||||
name: "exp type err",
|
||||
args: args{
|
||||
columns: []Column{
|
||||
{
|
||||
Name: "gender",
|
||||
Value: "male",
|
||||
Exp: "xxxxxx",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "",
|
||||
want1: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "logic type err",
|
||||
args: args{
|
||||
columns: []Column{
|
||||
{
|
||||
Name: "gender",
|
||||
Value: "male",
|
||||
Logic: "xxxxxx",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "",
|
||||
want1: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
args: args{
|
||||
columns: nil,
|
||||
},
|
||||
want: "",
|
||||
want1: nil,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
params := &Params{
|
||||
Columns: tt.args.columns,
|
||||
}
|
||||
got, got1, err := params.ConvertToGormConditions()
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ConvertToGormConditions() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("ConvertToGormConditions() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
if !reflect.DeepEqual(got1, tt.want1) {
|
||||
t.Errorf("ConvertToGormConditions() got1 = %v, want %v", got1, tt.want1)
|
||||
}
|
||||
|
||||
got = strings.Replace(got, "?", "%v", -1)
|
||||
t.Logf(got, got1...)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConditions_ConvertToGorm(t *testing.T) {
|
||||
c := Conditions{
|
||||
Columns: []Column{
|
||||
{
|
||||
Name: "name",
|
||||
Value: "ZhangSan",
|
||||
},
|
||||
{
|
||||
Name: "gender",
|
||||
Value: "male",
|
||||
},
|
||||
}}
|
||||
str, values, err := c.ConvertToGorm()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, "name = ? AND gender = ?", str)
|
||||
assert.Equal(t, len(values), 2)
|
||||
}
|
||||
|
||||
func TestConditions_checkValid(t *testing.T) {
|
||||
// empty error
|
||||
c := Conditions{}
|
||||
err := c.CheckValid()
|
||||
assert.Error(t, err)
|
||||
|
||||
// value is empty error
|
||||
c = Conditions{
|
||||
Columns: []Column{
|
||||
{
|
||||
Name: "foo",
|
||||
Value: nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
err = c.CheckValid()
|
||||
assert.Error(t, err)
|
||||
|
||||
// exp error
|
||||
c = Conditions{
|
||||
Columns: []Column{
|
||||
{
|
||||
Name: "foo",
|
||||
Value: "bar",
|
||||
Exp: "unknown-exp",
|
||||
},
|
||||
},
|
||||
}
|
||||
err = c.CheckValid()
|
||||
assert.Error(t, err)
|
||||
|
||||
// logic error
|
||||
c = Conditions{
|
||||
Columns: []Column{
|
||||
{
|
||||
Name: "foo",
|
||||
Value: "bar",
|
||||
Logic: "unknown-logic",
|
||||
},
|
||||
},
|
||||
}
|
||||
err = c.CheckValid()
|
||||
assert.Error(t, err)
|
||||
|
||||
// success
|
||||
c = Conditions{
|
||||
Columns: []Column{
|
||||
{
|
||||
Name: "name",
|
||||
Value: "ZhangSan",
|
||||
},
|
||||
{
|
||||
Name: "gender",
|
||||
Value: "male",
|
||||
},
|
||||
}}
|
||||
err = c.CheckValid()
|
||||
assert.NoError(t, err)
|
||||
}
|
Binary file not shown.
Loading…
Reference in New Issue