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/requestgen v1.3.0
github.com/c9s/rockhopper v1.2.1-0.20220426104534-f27cbb09846c github.com/c9s/rockhopper v1.2.1-0.20220426104534-f27cbb09846c
github.com/codingconcepts/env v0.0.0-20200821220118-a8fbf8d84482 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/fatih/color v1.13.0
github.com/gertd/go-pluralize v0.2.1
github.com/gin-contrib/cors v1.3.1 github.com/gin-contrib/cors v1.3.1
github.com/gin-gonic/gin v1.7.0 github.com/gin-gonic/gin v1.7.0
github.com/go-redis/redis/v8 v8.8.0 github.com/go-redis/redis/v8 v8.8.0
github.com/go-sql-driver/mysql v1.6.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/google/uuid v1.3.0
github.com/gorilla/websocket v1.5.0 github.com/gorilla/websocket v1.5.0
github.com/jmoiron/sqlx v1.3.4 github.com/jmoiron/sqlx v1.3.4
@ -57,7 +61,6 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/denisenkom/go-mssqldb v0.12.0 // indirect github.com/denisenkom/go-mssqldb v0.12.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // 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/fastly/go-utils v0.0.0-20180712184237-d95a45783239 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/gin-contrib/sse v0.1.0 // 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/universal-translator v0.17.0 // indirect
github.com/go-playground/validator/v10 v10.4.1 // indirect github.com/go-playground/validator/v10 v10.4.1 // indirect
github.com/go-test/deep v1.0.6 // 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/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188 // indirect github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188 // indirect
github.com/golang/protobuf v1.5.2 // 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/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 h1:Ghm4eQYC0nEPnSJdVkTrXpu9KtoVCSo1hg7mtI7G9KU=
github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239/go.mod h1:Gdwt2ce0yfBxPvZrHkprdPPTTS3N5rwmLE8T22KBXlw= 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/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.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= 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.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 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 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/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 h1:doAsuITavI4IOcd0Y19U4B+O0dNWihRyX//nn4sEmgA=
github.com/gin-contrib/cors v1.3.1/go.mod h1:jjEJ4268OPZUcU7k9Pm653S7lXUGcqMADzFA61xsmDk= 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-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-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-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 h1:N2T1jUrTQE9Re6TFF5PhvEHXHCguynGhKjWVsIUt5cY=
golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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= 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" sqlite3Migrations "github.com/c9s/bbgo/pkg/migrations/sqlite3"
) )
// reflect cache for database
var dbCache = NewReflectCache()
type DatabaseService struct { type DatabaseService struct {
Driver string Driver string
DSN string DSN string
@ -40,6 +43,12 @@ func (s *DatabaseService) Connect() error {
return err 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 { func (s *DatabaseService) Close() error {
return s.DB.Close() return s.DB.Close()
} }

View File

@ -13,10 +13,6 @@ type ProfitService struct {
DB *sqlx.DB DB *sqlx.DB
} }
func NewProfitService(db *sqlx.DB) *ProfitService {
return &ProfitService{db}
}
func (s *ProfitService) Load(ctx context.Context, id int64) (*types.Trade, error) { func (s *ProfitService) Load(ctx context.Context, id int64) (*types.Trade, error) {
var trade types.Trade 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 ExchangeSession *bbgo.ExchangeSession
marginBorrowRepay types.MarginBorrowRepay marginBorrowRepay types.MarginBorrowRepayService
} }
func (s *Strategy) ID() string { func (s *Strategy) ID() string {
@ -329,9 +329,9 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
s.ExchangeSession = session s.ExchangeSession = session
marginBorrowRepay, ok := session.Exchange.(types.MarginBorrowRepay) marginBorrowRepay, ok := session.Exchange.(types.MarginBorrowRepayService)
if !ok { 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 s.marginBorrowRepay = marginBorrowRepay

View File

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