service: use reflect to generate insert sql

This commit is contained in:
c9s 2022-05-30 18:08:35 +08:00
parent 2dc825f654
commit f29e8bd6d2
No known key found for this signature in database
GPG Key ID: 7385E7E464CB0A54
8 changed files with 246 additions and 13 deletions

6
go.mod
View File

@ -10,11 +10,15 @@ require (
github.com/c9s/requestgen v1.3.0
github.com/c9s/rockhopper v1.2.1-0.20220426104534-f27cbb09846c
github.com/codingconcepts/env v0.0.0-20200821220118-a8fbf8d84482
github.com/evanphx/json-patch/v5 v5.6.0
github.com/fatih/camelcase v1.0.0
github.com/fatih/color v1.13.0
github.com/gertd/go-pluralize v0.2.1
github.com/gin-contrib/cors v1.3.1
github.com/gin-gonic/gin v1.7.0
github.com/go-redis/redis/v8 v8.8.0
github.com/go-sql-driver/mysql v1.6.0
github.com/gofrs/flock v0.8.1
github.com/google/uuid v1.3.0
github.com/gorilla/websocket v1.5.0
github.com/jmoiron/sqlx v1.3.4
@ -57,7 +61,6 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/denisenkom/go-mssqldb v0.12.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
@ -65,7 +68,6 @@ require (
github.com/go-playground/universal-translator v0.17.0 // indirect
github.com/go-playground/validator/v10 v10.4.1 // indirect
github.com/go-test/deep v1.0.6 // indirect
github.com/gofrs/flock v0.8.1 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188 // indirect
github.com/golang/protobuf v1.5.2 // indirect

5
go.sum
View File

@ -116,6 +116,7 @@ github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJ
github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 h1:Ghm4eQYC0nEPnSJdVkTrXpu9KtoVCSo1hg7mtI7G9KU=
github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239/go.mod h1:Gdwt2ce0yfBxPvZrHkprdPPTTS3N5rwmLE8T22KBXlw=
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
@ -125,6 +126,8 @@ github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gertd/go-pluralize v0.2.1 h1:M3uASbVjMnTsPb0PNqg+E/24Vwigyo/tvyMTtAlLgiA=
github.com/gertd/go-pluralize v0.2.1/go.mod h1:rbYaKDbsXxmRfr8uygAEKhOWsjyrrqrkHVpZvoOp8zk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/cors v1.3.1 h1:doAsuITavI4IOcd0Y19U4B+O0dNWihRyX//nn4sEmgA=
github.com/gin-contrib/cors v1.3.1/go.mod h1:jjEJ4268OPZUcU7k9Pm653S7lXUGcqMADzFA61xsmDk=
@ -686,8 +689,6 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc=
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a h1:N2T1jUrTQE9Re6TFF5PhvEHXHCguynGhKjWVsIUt5cY=
golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=

View File

@ -11,6 +11,9 @@ import (
sqlite3Migrations "github.com/c9s/bbgo/pkg/migrations/sqlite3"
)
// reflect cache for database
var dbCache = NewReflectCache()
type DatabaseService struct {
Driver string
DSN string
@ -40,6 +43,12 @@ func (s *DatabaseService) Connect() error {
return err
}
func (s *DatabaseService) Insert(record interface{}) error {
sql := dbCache.InsertSqlOf(record)
_, err := s.DB.NamedExec(sql, record)
return err
}
func (s *DatabaseService) Close() error {
return s.DB.Close()
}

View File

@ -13,10 +13,6 @@ type ProfitService struct {
DB *sqlx.DB
}
func NewProfitService(db *sqlx.DB) *ProfitService {
return &ProfitService{db}
}
func (s *ProfitService) Load(ctx context.Context, id int64) (*types.Trade, error) {
var trade types.Trade

154
pkg/service/reflect.go Normal file
View File

@ -0,0 +1,154 @@
package service
import (
"reflect"
"strings"
"github.com/fatih/camelcase"
gopluralize "github.com/gertd/go-pluralize"
)
var pluralize = gopluralize.NewClient()
func tableNameOf(record interface{}) string {
rt := reflect.TypeOf(record)
if rt.Kind() == reflect.Ptr {
rt = rt.Elem()
}
typeName := rt.Name()
tableName := strings.Join(camelcase.Split(typeName), "_")
tableName = strings.ToLower(tableName)
return pluralize.Plural(tableName)
}
func placeholdersOf(record interface{}) []string {
rt := reflect.TypeOf(record)
if rt.Kind() == reflect.Ptr {
rt = rt.Elem()
}
if rt.Kind() != reflect.Struct {
return nil
}
var dbFields []string
for i := 0; i < rt.NumField(); i++ {
fieldType := rt.Field(i)
if tag, ok := fieldType.Tag.Lookup("db"); ok {
dbFields = append(dbFields, ":"+tag)
}
}
return dbFields
}
func fieldsNamesOf(record interface{}) []string {
rt := reflect.TypeOf(record)
if rt.Kind() == reflect.Ptr {
rt = rt.Elem()
}
if rt.Kind() != reflect.Struct {
return nil
}
var dbFields []string
for i := 0; i < rt.NumField(); i++ {
fieldType := rt.Field(i)
if tag, ok := fieldType.Tag.Lookup("db"); ok {
dbFields = append(dbFields, tag)
}
}
return dbFields
}
type ReflectCache struct {
tableNames map[string]string
fields map[string][]string
placeholders map[string][]string
insertSqls map[string]string
}
func NewReflectCache() *ReflectCache {
return &ReflectCache{
tableNames: make(map[string]string),
fields: make(map[string][]string),
placeholders: make(map[string][]string),
insertSqls: make(map[string]string),
}
}
func (c *ReflectCache) InsertSqlOf(t interface{}) string {
rt := reflect.TypeOf(t)
if rt.Kind() == reflect.Ptr {
rt = rt.Elem()
}
typeName := rt.Name()
sql, ok := c.insertSqls[typeName]
if ok {
return sql
}
tableName := dbCache.TableNameOf(t)
fields := dbCache.FieldsOf(t)
placeholders := dbCache.PlaceholderOf(t)
fieldClause := strings.Join(fields, ", ")
placeholderClause := strings.Join(placeholders, ", ")
sql = `INSERT INTO ` + tableName + ` (` + fieldClause + `) VALUES (` + placeholderClause + `)`
c.insertSqls[typeName] = sql
return sql
}
func (c *ReflectCache) TableNameOf(t interface{}) string {
rt := reflect.TypeOf(t)
if rt.Kind() == reflect.Ptr {
rt = rt.Elem()
}
typeName := rt.Name()
tableName, ok := c.tableNames[typeName]
if ok {
return tableName
}
tableName = tableNameOf(t)
c.tableNames[typeName] = tableName
return tableName
}
func (c *ReflectCache) PlaceholderOf(t interface{}) []string {
rt := reflect.TypeOf(t)
if rt.Kind() == reflect.Ptr {
rt = rt.Elem()
}
typeName := rt.Name()
placeholders, ok := c.placeholders[typeName]
if ok {
return placeholders
}
placeholders = placeholdersOf(t)
c.placeholders[typeName] = placeholders
return placeholders
}
func (c *ReflectCache) FieldsOf(t interface{}) []string {
rt := reflect.TypeOf(t)
if rt.Kind() == reflect.Ptr {
rt = rt.Elem()
}
typeName := rt.Name()
fields, ok := c.fields[typeName]
if ok {
return fields
}
fields = fieldsNamesOf(t)
c.fields[typeName] = fields
return fields
}

View File

@ -0,0 +1,71 @@
package service
import (
"reflect"
"testing"
"github.com/c9s/bbgo/pkg/types"
)
func Test_tableNameOf(t *testing.T) {
type args struct {
record interface{}
}
tests := []struct {
name string
args args
want string
}{
{
name: "MarginInterest",
args: args{record: &types.MarginInterest{}},
want: "margin_interests",
},
{
name: "MarginLoan",
args: args{record: &types.MarginLoan{}},
want: "margin_loans",
},
{
name: "MarginRepay",
args: args{record: &types.MarginRepay{}},
want: "margin_repays",
},
{
name: "MarginLiquidation",
args: args{record: &types.MarginLiquidation{}},
want: "margin_liquidations",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tableNameOf(tt.args.record); got != tt.want {
t.Errorf("tableNameOf() = %v, want %v", got, tt.want)
}
})
}
}
func Test_fieldsNamesOf(t *testing.T) {
type args struct {
record interface{}
}
tests := []struct {
name string
args args
want []string
}{
{
name: "MarginInterest",
args: args{record: &types.MarginInterest{}},
want: []string{"asset", "principle", "interest", "interest_rate", "isolated_symbol", "time"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := fieldsNamesOf(tt.args.record); !reflect.DeepEqual(got, tt.want) {
t.Errorf("fieldsNamesOf() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -61,7 +61,7 @@ type Strategy struct {
ExchangeSession *bbgo.ExchangeSession
marginBorrowRepay types.MarginBorrowRepay
marginBorrowRepay types.MarginBorrowRepayService
}
func (s *Strategy) ID() string {
@ -329,9 +329,9 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
s.ExchangeSession = session
marginBorrowRepay, ok := session.Exchange.(types.MarginBorrowRepay)
marginBorrowRepay, ok := session.Exchange.(types.MarginBorrowRepayService)
if !ok {
return fmt.Errorf("exchange %s does not implement types.MarginBorrowRepay", session.ExchangeName)
return fmt.Errorf("exchange %s does not implement types.MarginBorrowRepayService", session.ExchangeName)
}
s.marginBorrowRepay = marginBorrowRepay

View File

@ -52,8 +52,8 @@ type MarginExchange interface {
GetMarginSettings() MarginSettings
}
// MarginBorrowRepay provides repay and borrow actions of an crypto exchange
type MarginBorrowRepay interface {
// MarginBorrowRepayService provides repay and borrow actions of an crypto exchange
type MarginBorrowRepayService interface {
RepayMarginAsset(ctx context.Context, asset string, amount fixedpoint.Value) error
BorrowMarginAsset(ctx context.Context, asset string, amount fixedpoint.Value) error
QueryMarginAssetMaxBorrowable(ctx context.Context, asset string) (amount fixedpoint.Value, err error)