mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +00:00
service: use reflect to generate insert sql
This commit is contained in:
parent
2dc825f654
commit
f29e8bd6d2
6
go.mod
6
go.mod
|
@ -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
5
go.sum
|
@ -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=
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
154
pkg/service/reflect.go
Normal 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
|
||||
}
|
71
pkg/service/reflect_test.go
Normal file
71
pkg/service/reflect_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue
Block a user