mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-24 15:55:14 +00:00
add rockhopper
This commit is contained in:
parent
8af89160e3
commit
2699c32b38
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -17,6 +17,7 @@
|
|||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
/.mod
|
||||
|
||||
/.env.local
|
||||
/.env.*.local
|
||||
|
|
29
go.mod
29
go.mod
|
@ -7,7 +7,9 @@ go 1.13
|
|||
require (
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0
|
||||
github.com/adshao/go-binance/v2 v2.2.1-0.20210108025425-9a582c63144e
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 // indirect
|
||||
github.com/c9s/goose v0.0.0-20200415105707-8da682162a5b
|
||||
github.com/c9s/rockhopper v1.0.0 // indirect
|
||||
github.com/codingconcepts/env v0.0.0-20200821220118-a8fbf8d84482
|
||||
github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 // indirect
|
||||
github.com/go-redis/redis/v8 v8.4.0
|
||||
|
@ -21,27 +23,42 @@ require (
|
|||
github.com/leekchan/accounting v0.0.0-20191218023648-17a4ce5f94d4
|
||||
github.com/lestrrat-go/file-rotatelogs v2.2.0+incompatible
|
||||
github.com/lestrrat-go/strftime v1.0.0 // indirect
|
||||
github.com/lib/pq v1.9.0
|
||||
github.com/magiconair/properties v1.8.4 // indirect
|
||||
github.com/markbates/pkger v0.17.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.2 // indirect
|
||||
github.com/mattn/go-isatty v0.0.12 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
||||
github.com/pelletier/go-toml v1.8.1 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pquerna/otp v1.3.0
|
||||
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5
|
||||
github.com/robfig/cron/v3 v3.0.0
|
||||
github.com/shopspring/decimal v1.2.0 // indirect
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
github.com/slack-go/slack v0.6.6-0.20200602212211-b04b8521281b
|
||||
github.com/spf13/cobra v1.0.0
|
||||
github.com/spf13/pflag v1.0.3
|
||||
github.com/spf13/viper v1.7.0
|
||||
github.com/spf13/afero v1.5.1 // indirect
|
||||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/spf13/cobra v1.1.1
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.7.1
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/tebeka/strftime v0.1.3 // indirect
|
||||
github.com/ugorji/go v1.1.4 // indirect
|
||||
github.com/valyala/fastjson v1.5.1
|
||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 // indirect
|
||||
github.com/ziutek/mymysql v1.5.4
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
|
||||
golang.org/x/sys v0.0.0-20210113131315-ba0562f347e0 // indirect
|
||||
golang.org/x/text v0.3.5 // indirect
|
||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324
|
||||
gonum.org/v1/gonum v0.8.1
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||
gopkg.in/tucnak/telebot.v2 v2.3.5
|
||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
)
|
||||
|
|
50
go.sum
50
go.sum
|
@ -43,6 +43,8 @@ github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8
|
|||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/c9s/goose v0.0.0-20200415105707-8da682162a5b h1:4qsZTw8wHHTzFnwrfs3zLwz+cU2diGBdwoKRKiWOMvc=
|
||||
github.com/c9s/goose v0.0.0-20200415105707-8da682162a5b/go.mod h1:RaBe6PIVbQRqwrnjjSoHhlLM601JWdT7KZ0p6rhgI7I=
|
||||
github.com/c9s/rockhopper v1.0.0 h1:IQnLETldYLhUtFhtahnb1WlYMkJQtpSWo8vdw13I7VE=
|
||||
github.com/c9s/rockhopper v1.0.0/go.mod h1:FnLRi5pmIdXj+K+oxL/ocLKc74xMk+hdD5W3GQW6qJQ=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
|
@ -64,6 +66,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/denisenkom/go-mssqldb v0.9.0 h1:RSohk2RsiZqLZ0zCjtfn3S4Gp4exhpBWHyQ7D0yGjAk=
|
||||
github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
|
@ -96,6 +100,7 @@ github.com/gobuffalo/here v0.6.0 h1:hYrd0a6gDmWxBM4TnrGw8mQg24iSVoIkHEk7FodQcBI=
|
|||
github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
|
@ -181,6 +186,7 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
|||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
|
@ -199,9 +205,13 @@ github.com/lestrrat-go/strftime v1.0.0/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR7
|
|||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
|
||||
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8=
|
||||
github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.4 h1:8KGKTcQQGm0Kv7vEbKFErAoAOFyyacLStRtQSeYtvkY=
|
||||
github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/markbates/pkger v0.17.1 h1:/MKEtWqtc0mZvu9OinB9UzVN9iYCwLWuyUv4Bw+PCno=
|
||||
github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
|
@ -213,11 +223,16 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
|
|||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
|
||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v2.0.2+incompatible h1:qzw9c2GNT8UFrgWNDhCTqRqYUSmu/Dav/9Z58LGpk7U=
|
||||
github.com/mattn/go-sqlite3 v2.0.2+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
|
@ -228,6 +243,8 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
|
|||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
|
@ -248,10 +265,13 @@ github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDs
|
|||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
|
||||
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
|
@ -284,6 +304,8 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
|
|||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/slack-go/slack v0.6.6-0.20200602212211-b04b8521281b h1:4NIpokK7Rg/k6lSzNQzvGLphpHtfAAaLw9AWHxHQn0w=
|
||||
github.com/slack-go/slack v0.6.6-0.20200602212211-b04b8521281b/go.mod h1:FGqNzJBmxIsZURAxh2a8D21AnOVvvXZvGligs4npPUM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
|
@ -294,17 +316,28 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k
|
|||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.5.1 h1:VHu76Lk0LSP1x254maIu2bplkWpfBWI+B+6fdoZprcg=
|
||||
github.com/spf13/afero v1.5.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
|
||||
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
|
||||
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||
github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM=
|
||||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||
github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
|
||||
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
|
@ -327,6 +360,7 @@ github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJ
|
|||
github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
|
||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
|
@ -342,8 +376,11 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
|||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
|
@ -414,18 +451,24 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/
|
|||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210113131315-ba0562f347e0 h1:rTJ72jiMIolMfCaiZLkdlJLN2Og7LKF6xE1cgwQnwzQ=
|
||||
golang.org/x/sys v0.0.0-20210113131315-ba0562f347e0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE=
|
||||
|
@ -500,6 +543,8 @@ gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
|||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
|
||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
|
@ -511,12 +556,17 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
-- +goose Up
|
||||
-- +up
|
||||
CREATE TABLE `trades` (
|
||||
`gid` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
||||
|
|
|
@ -10,14 +10,15 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/datatype"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
type PnLReporterConfig struct {
|
||||
AverageCostBySymbols StringSlice `json:"averageCostBySymbols" yaml:"averageCostBySymbols"`
|
||||
Of StringSlice `json:"of" yaml:"of"`
|
||||
When StringSlice `json:"when" yaml:"when"`
|
||||
AverageCostBySymbols datatype.StringSlice `json:"averageCostBySymbols" yaml:"averageCostBySymbols"`
|
||||
Of datatype.StringSlice `json:"of" yaml:"of"`
|
||||
When datatype.StringSlice `json:"when" yaml:"when"`
|
||||
}
|
||||
|
||||
// ExchangeStrategyMount wraps the SingleExchangeStrategy with the Session name for mounting
|
||||
|
@ -189,7 +190,6 @@ func Load(configFile string, loadStrategies bool) (*Config, error) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -1,57 +1,2 @@
|
|||
package bbgo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type StringSlice []string
|
||||
|
||||
func (s *StringSlice) decode(a interface{}) error {
|
||||
switch d := a.(type) {
|
||||
case string:
|
||||
*s = append(*s, d)
|
||||
|
||||
case []string:
|
||||
*s = append(*s, d...)
|
||||
|
||||
case []interface{}:
|
||||
for _, de := range d {
|
||||
if err := s.decode(de); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unexpected type %T for StringSlice: %+v", d, d)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StringSlice) UnmarshalYAML(unmarshal func(interface{}) error) (err error) {
|
||||
var ss []string
|
||||
err = unmarshal(&ss)
|
||||
if err == nil {
|
||||
*s = ss
|
||||
return
|
||||
}
|
||||
|
||||
var as string
|
||||
err = unmarshal(&as)
|
||||
if err == nil {
|
||||
*s = append(*s, as)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *StringSlice) UnmarshalJSON(b []byte) error {
|
||||
var a interface{}
|
||||
var err = json.Unmarshal(b, &a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.decode(a)
|
||||
}
|
||||
|
|
|
@ -6,8 +6,9 @@ import (
|
|||
_ "github.com/c9s/bbgo/pkg/strategy/buyandhold"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/flashcrash"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/grid"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/trailingstop"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/mirrormaker"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/pricealert"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/swing"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/trailingstop"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/xpuremaker"
|
||||
)
|
||||
|
|
|
@ -44,6 +44,15 @@ func init() {
|
|||
RootCmd.AddCommand(RunCmd)
|
||||
}
|
||||
|
||||
var RunCmd = &cobra.Command{
|
||||
Use: "run",
|
||||
Short: "run strategies from config file",
|
||||
|
||||
// SilenceUsage is an option to silence usage when an error occurs.
|
||||
SilenceUsage: true,
|
||||
RunE: run,
|
||||
}
|
||||
|
||||
var wrapperTemplate = template.Must(template.New("main").Parse(`package main
|
||||
// DO NOT MODIFY THIS FILE. THIS FILE IS GENERATED FOR IMPORTING STRATEGIES
|
||||
import (
|
||||
|
@ -253,14 +262,7 @@ func runConfig(basectx context.Context, userConfig *bbgo.Config) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
var RunCmd = &cobra.Command{
|
||||
Use: "run",
|
||||
Short: "run strategies from config file",
|
||||
|
||||
// SilenceUsage is an option to silence usage when an error occurs.
|
||||
SilenceUsage: true,
|
||||
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
func run(cmd *cobra.Command, args []string) error {
|
||||
configFile, err := cmd.Flags().GetString("config")
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -333,7 +335,6 @@ var RunCmd = &cobra.Command{
|
|||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func compile(buildDir string, userConfig *bbgo.Config) error {
|
||||
|
|
57
pkg/datatype/string_slice.go
Normal file
57
pkg/datatype/string_slice.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
package datatype
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type StringSlice []string
|
||||
|
||||
func (s *StringSlice) decode(a interface{}) error {
|
||||
switch d := a.(type) {
|
||||
case string:
|
||||
*s = append(*s, d)
|
||||
|
||||
case []string:
|
||||
*s = append(*s, d...)
|
||||
|
||||
case []interface{}:
|
||||
for _, de := range d {
|
||||
if err := s.decode(de); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unexpected type %T for StringSlice: %+v", d, d)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StringSlice) UnmarshalYAML(unmarshal func(interface{}) error) (err error) {
|
||||
var ss []string
|
||||
err = unmarshal(&ss)
|
||||
if err == nil {
|
||||
*s = ss
|
||||
return
|
||||
}
|
||||
|
||||
var as string
|
||||
err = unmarshal(&as)
|
||||
if err == nil {
|
||||
*s = append(*s, as)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *StringSlice) UnmarshalJSON(b []byte) error {
|
||||
var a interface{}
|
||||
var err = json.Unmarshal(b, &a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.decode(a)
|
||||
}
|
5
pkg/migration/cmd/gopackmigration/main.go
Normal file
5
pkg/migration/cmd/gopackmigration/main.go
Normal file
|
@ -0,0 +1,5 @@
|
|||
package main
|
||||
|
||||
func main() {
|
||||
|
||||
}
|
285
pkg/migration/dialect.go
Normal file
285
pkg/migration/dialect.go
Normal file
|
@ -0,0 +1,285 @@
|
|||
package migration
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// SQLDialect abstracts the details of specific SQL dialects
|
||||
// for goose's few SQL specific statements
|
||||
type SQLDialect interface {
|
||||
createVersionTableSQL() string // sql string to create the db version table
|
||||
insertVersionSQL() string // sql string to insert the initial version table row
|
||||
deleteVersionSQL() string // sql string to delete version
|
||||
migrationSQL() string // sql string to retrieve migrations
|
||||
dbVersionQuery(db *sql.DB) (*sql.Rows, error)
|
||||
}
|
||||
|
||||
var dialect SQLDialect = &PostgresDialect{}
|
||||
|
||||
// GetDialect gets the SQLDialect
|
||||
func GetDialect() SQLDialect {
|
||||
return dialect
|
||||
}
|
||||
|
||||
// SetDialect sets the SQLDialect
|
||||
func SetDialect(d string) error {
|
||||
switch d {
|
||||
case "postgres":
|
||||
dialect = &PostgresDialect{}
|
||||
case "mysql":
|
||||
dialect = &MySQLDialect{}
|
||||
case "sqlite3":
|
||||
dialect = &Sqlite3Dialect{}
|
||||
case "mssql":
|
||||
dialect = &SqlServerDialect{}
|
||||
case "redshift":
|
||||
dialect = &RedshiftDialect{}
|
||||
case "tidb":
|
||||
dialect = &TiDBDialect{}
|
||||
default:
|
||||
return fmt.Errorf("%q: unknown dialect", d)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
////////////////////////////
|
||||
// Postgres
|
||||
////////////////////////////
|
||||
|
||||
// PostgresDialect struct.
|
||||
type PostgresDialect struct{}
|
||||
|
||||
func (pg PostgresDialect) createVersionTableSQL() string {
|
||||
return fmt.Sprintf(`CREATE TABLE %s (
|
||||
id serial NOT NULL,
|
||||
version_id bigint NOT NULL,
|
||||
is_applied boolean NOT NULL,
|
||||
tstamp timestamp NULL default now(),
|
||||
PRIMARY KEY(id)
|
||||
);`, TableName())
|
||||
}
|
||||
|
||||
func (pg PostgresDialect) insertVersionSQL() string {
|
||||
return fmt.Sprintf("INSERT INTO %s (version_id, is_applied) VALUES ($1, $2);", TableName())
|
||||
}
|
||||
|
||||
func (pg PostgresDialect) dbVersionQuery(db *sql.DB) (*sql.Rows, error) {
|
||||
rows, err := db.Query(fmt.Sprintf("SELECT version_id, is_applied from %s ORDER BY id DESC", TableName()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rows, err
|
||||
}
|
||||
|
||||
func (m PostgresDialect) migrationSQL() string {
|
||||
return fmt.Sprintf("SELECT tstamp, is_applied FROM %s WHERE version_id=$1 ORDER BY tstamp DESC LIMIT 1", TableName())
|
||||
}
|
||||
|
||||
func (pg PostgresDialect) deleteVersionSQL() string {
|
||||
return fmt.Sprintf("DELETE FROM %s WHERE version_id=$1;", TableName())
|
||||
}
|
||||
|
||||
////////////////////////////
|
||||
// MySQL
|
||||
////////////////////////////
|
||||
|
||||
// MySQLDialect struct.
|
||||
type MySQLDialect struct{}
|
||||
|
||||
func (m MySQLDialect) createVersionTableSQL() string {
|
||||
return fmt.Sprintf(`CREATE TABLE %s (
|
||||
id serial NOT NULL,
|
||||
version_id bigint NOT NULL,
|
||||
is_applied boolean NOT NULL,
|
||||
tstamp timestamp NULL default now(),
|
||||
PRIMARY KEY(id)
|
||||
);`, TableName())
|
||||
}
|
||||
|
||||
func (m MySQLDialect) insertVersionSQL() string {
|
||||
return fmt.Sprintf("INSERT INTO %s (version_id, is_applied) VALUES (?, ?);", TableName())
|
||||
}
|
||||
|
||||
func (m MySQLDialect) dbVersionQuery(db *sql.DB) (*sql.Rows, error) {
|
||||
rows, err := db.Query(fmt.Sprintf("SELECT version_id, is_applied from %s ORDER BY id DESC", TableName()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rows, err
|
||||
}
|
||||
|
||||
func (m MySQLDialect) migrationSQL() string {
|
||||
return fmt.Sprintf("SELECT tstamp, is_applied FROM %s WHERE version_id=? ORDER BY tstamp DESC LIMIT 1", TableName())
|
||||
}
|
||||
|
||||
func (m MySQLDialect) deleteVersionSQL() string {
|
||||
return fmt.Sprintf("DELETE FROM %s WHERE version_id=?;", TableName())
|
||||
}
|
||||
|
||||
////////////////////////////
|
||||
// MSSQL
|
||||
////////////////////////////
|
||||
|
||||
// SqlServerDialect struct.
|
||||
type SqlServerDialect struct{}
|
||||
|
||||
func (m SqlServerDialect) createVersionTableSQL() string {
|
||||
return fmt.Sprintf(`CREATE TABLE %s (
|
||||
id INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
|
||||
version_id BIGINT NOT NULL,
|
||||
is_applied BIT NOT NULL,
|
||||
tstamp DATETIME NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);`, TableName())
|
||||
}
|
||||
|
||||
func (m SqlServerDialect) insertVersionSQL() string {
|
||||
return fmt.Sprintf("INSERT INTO %s (version_id, is_applied) VALUES (@p1, @p2);", TableName())
|
||||
}
|
||||
|
||||
func (m SqlServerDialect) dbVersionQuery(db *sql.DB) (*sql.Rows, error) {
|
||||
rows, err := db.Query(fmt.Sprintf("SELECT version_id, is_applied FROM %s ORDER BY id DESC", TableName()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rows, err
|
||||
}
|
||||
|
||||
func (m SqlServerDialect) migrationSQL() string {
|
||||
const tpl = `
|
||||
WITH Migrations AS
|
||||
(
|
||||
SELECT tstamp, is_applied,
|
||||
ROW_NUMBER() OVER (ORDER BY tstamp) AS 'RowNumber'
|
||||
FROM %s
|
||||
WHERE version_id=@p1
|
||||
)
|
||||
SELECT tstamp, is_applied
|
||||
FROM Migrations
|
||||
WHERE RowNumber BETWEEN 1 AND 2
|
||||
ORDER BY tstamp DESC
|
||||
`
|
||||
return fmt.Sprintf(tpl, TableName())
|
||||
}
|
||||
|
||||
func (m SqlServerDialect) deleteVersionSQL() string {
|
||||
return fmt.Sprintf("DELETE FROM %s WHERE version_id=@p1;", TableName())
|
||||
}
|
||||
|
||||
////////////////////////////
|
||||
// sqlite3
|
||||
////////////////////////////
|
||||
|
||||
// Sqlite3Dialect struct.
|
||||
type Sqlite3Dialect struct{}
|
||||
|
||||
func (m Sqlite3Dialect) createVersionTableSQL() string {
|
||||
return fmt.Sprintf(`CREATE TABLE %s (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
version_id INTEGER NOT NULL,
|
||||
is_applied INTEGER NOT NULL,
|
||||
tstamp TIMESTAMP DEFAULT (datetime('now'))
|
||||
);`, TableName())
|
||||
}
|
||||
|
||||
func (m Sqlite3Dialect) insertVersionSQL() string {
|
||||
return fmt.Sprintf("INSERT INTO %s (version_id, is_applied) VALUES (?, ?);", TableName())
|
||||
}
|
||||
|
||||
func (m Sqlite3Dialect) dbVersionQuery(db *sql.DB) (*sql.Rows, error) {
|
||||
rows, err := db.Query(fmt.Sprintf("SELECT version_id, is_applied from %s ORDER BY id DESC", TableName()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rows, err
|
||||
}
|
||||
|
||||
func (m Sqlite3Dialect) migrationSQL() string {
|
||||
return fmt.Sprintf("SELECT tstamp, is_applied FROM %s WHERE version_id=? ORDER BY tstamp DESC LIMIT 1", TableName())
|
||||
}
|
||||
|
||||
func (m Sqlite3Dialect) deleteVersionSQL() string {
|
||||
return fmt.Sprintf("DELETE FROM %s WHERE version_id=?;", TableName())
|
||||
}
|
||||
|
||||
////////////////////////////
|
||||
// Redshift
|
||||
////////////////////////////
|
||||
|
||||
// RedshiftDialect struct.
|
||||
type RedshiftDialect struct{}
|
||||
|
||||
func (rs RedshiftDialect) createVersionTableSQL() string {
|
||||
return fmt.Sprintf(`CREATE TABLE %s (
|
||||
id integer NOT NULL identity(1, 1),
|
||||
version_id bigint NOT NULL,
|
||||
is_applied boolean NOT NULL,
|
||||
tstamp timestamp NULL default sysdate,
|
||||
PRIMARY KEY(id)
|
||||
);`, TableName())
|
||||
}
|
||||
|
||||
func (rs RedshiftDialect) insertVersionSQL() string {
|
||||
return fmt.Sprintf("INSERT INTO %s (version_id, is_applied) VALUES ($1, $2);", TableName())
|
||||
}
|
||||
|
||||
func (rs RedshiftDialect) dbVersionQuery(db *sql.DB) (*sql.Rows, error) {
|
||||
rows, err := db.Query(fmt.Sprintf("SELECT version_id, is_applied from %s ORDER BY id DESC", TableName()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rows, err
|
||||
}
|
||||
|
||||
func (m RedshiftDialect) migrationSQL() string {
|
||||
return fmt.Sprintf("SELECT tstamp, is_applied FROM %s WHERE version_id=$1 ORDER BY tstamp DESC LIMIT 1", TableName())
|
||||
}
|
||||
|
||||
func (rs RedshiftDialect) deleteVersionSQL() string {
|
||||
return fmt.Sprintf("DELETE FROM %s WHERE version_id=$1;", TableName())
|
||||
}
|
||||
|
||||
////////////////////////////
|
||||
// TiDB
|
||||
////////////////////////////
|
||||
|
||||
// TiDBDialect struct.
|
||||
type TiDBDialect struct{}
|
||||
|
||||
func (m TiDBDialect) createVersionTableSQL() string {
|
||||
return fmt.Sprintf(`CREATE TABLE %s (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
|
||||
version_id bigint NOT NULL,
|
||||
is_applied boolean NOT NULL,
|
||||
tstamp timestamp NULL default now(),
|
||||
PRIMARY KEY(id)
|
||||
);`, TableName())
|
||||
}
|
||||
|
||||
func (m TiDBDialect) insertVersionSQL() string {
|
||||
return fmt.Sprintf("INSERT INTO %s (version_id, is_applied) VALUES (?, ?);", TableName())
|
||||
}
|
||||
|
||||
func (m TiDBDialect) dbVersionQuery(db *sql.DB) (*sql.Rows, error) {
|
||||
rows, err := db.Query(fmt.Sprintf("SELECT version_id, is_applied from %s ORDER BY id DESC", TableName()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rows, err
|
||||
}
|
||||
|
||||
func (m TiDBDialect) migrationSQL() string {
|
||||
return fmt.Sprintf("SELECT tstamp, is_applied FROM %s WHERE version_id=? ORDER BY tstamp DESC LIMIT 1", TableName())
|
||||
}
|
||||
|
||||
func (m TiDBDialect) deleteVersionSQL() string {
|
||||
return fmt.Sprintf("DELETE FROM %s WHERE version_id=?;", TableName())
|
||||
}
|
||||
|
492
pkg/migration/migrate.go
Normal file
492
pkg/migration/migrate.go
Normal file
|
@ -0,0 +1,492 @@
|
|||
package migration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const VERSION = "v2.7.0-rc3"
|
||||
|
||||
var (
|
||||
duplicateCheckOnce sync.Once
|
||||
minVersion = int64(0)
|
||||
maxVersion = int64((1 << 63) - 1)
|
||||
timestampFormat = "20060102150405"
|
||||
verbose = false
|
||||
)
|
||||
|
||||
// SetVerbose set the goose verbosity mode
|
||||
func SetVerbose(v bool) {
|
||||
verbose = v
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrNoCurrentVersion when a current migration version is not found.
|
||||
ErrNoCurrentVersion = errors.New("no current version found")
|
||||
// ErrNoNextVersion when the next migration version is not found.
|
||||
ErrNoNextVersion = errors.New("no next version found")
|
||||
// MaxVersion is the maximum allowed version.
|
||||
MaxVersion int64 = 9223372036854775807 // max(int64)
|
||||
|
||||
registeredGoMigrations = map[int64]*Migration{}
|
||||
)
|
||||
|
||||
// MigrationRecord struct.
|
||||
type MigrationRecord struct {
|
||||
VersionID int64
|
||||
TStamp time.Time
|
||||
IsApplied bool // was this a result of up() or down()
|
||||
}
|
||||
|
||||
// Migration struct.
|
||||
type Migration struct {
|
||||
Version int64
|
||||
Next int64 // next version, or -1 if none
|
||||
Previous int64 // previous version, -1 if none
|
||||
Source string // path to .sql script
|
||||
Registered bool
|
||||
UpFn func(*sql.Tx) error // Up go migration function
|
||||
DownFn func(*sql.Tx) error // Down go migration function
|
||||
}
|
||||
|
||||
func (m *Migration) String() string {
|
||||
return fmt.Sprintf(m.Source)
|
||||
}
|
||||
|
||||
// Up runs an up migration.
|
||||
func (m *Migration) Up(ctx context.Context, db *sql.DB) error {
|
||||
if err := m.run(ctx, db, true); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Down runs a down migration.
|
||||
func (m *Migration) Down(ctx context.Context, db *sql.DB) error {
|
||||
if err := m.run(ctx, db, false); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Migration) run(ctx context.Context, db *sql.DB, direction bool) error {
|
||||
switch filepath.Ext(m.Source) {
|
||||
case ".sql":
|
||||
f, err := os.Open(m.Source)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "ERROR %v: failed to open SQL migration file", filepath.Base(m.Source))
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
statements, useTx, err := parseSQLMigration(f, direction)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "ERROR %v: failed to parse SQL migration file", filepath.Base(m.Source))
|
||||
}
|
||||
|
||||
if err := runSQLMigration(ctx, db, statements, useTx, m.Version, direction); err != nil {
|
||||
return errors.Wrapf(err, "ERROR %v: failed to run SQL migration", filepath.Base(m.Source))
|
||||
}
|
||||
|
||||
if len(statements) > 0 {
|
||||
log.Println("OK ", filepath.Base(m.Source))
|
||||
} else {
|
||||
log.Println("EMPTY", filepath.Base(m.Source))
|
||||
}
|
||||
|
||||
case ".go":
|
||||
if !m.Registered {
|
||||
return errors.Errorf("ERROR %v: failed to run Go migration: Go functions must be registered and built into a custom binary (see https://github.com/c9s/goose/tree/master/examples/go-migrations)", m.Source)
|
||||
}
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "ERROR failed to begin transaction")
|
||||
}
|
||||
|
||||
fn := m.UpFn
|
||||
if !direction {
|
||||
fn = m.DownFn
|
||||
}
|
||||
|
||||
if fn != nil {
|
||||
// Run Go migration function.
|
||||
if err := fn(tx); err != nil {
|
||||
tx.Rollback()
|
||||
return errors.Wrapf(err, "ERROR %v: failed to run Go migration function %T", filepath.Base(m.Source), fn)
|
||||
}
|
||||
}
|
||||
|
||||
if direction {
|
||||
if _, err := tx.Exec(GetDialect().insertVersionSQL(), m.Version, direction); err != nil {
|
||||
tx.Rollback()
|
||||
return errors.Wrap(err, "ERROR failed to execute transaction")
|
||||
}
|
||||
} else {
|
||||
if _, err := tx.Exec(GetDialect().deleteVersionSQL(), m.Version); err != nil {
|
||||
tx.Rollback()
|
||||
return errors.Wrap(err, "ERROR failed to execute transaction")
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return errors.Wrap(err, "ERROR failed to commit transaction")
|
||||
}
|
||||
|
||||
if fn != nil {
|
||||
log.Println("OK ", filepath.Base(m.Source))
|
||||
} else {
|
||||
log.Println("EMPTY", filepath.Base(m.Source))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExtractNumericComponent looks for migration scripts with names in the form:
|
||||
// XXX_descriptivename.ext where XXX specifies the version number
|
||||
// and ext specifies the type of migration
|
||||
func ExtractNumericComponent(name string) (int64, error) {
|
||||
base := filepath.Base(name)
|
||||
|
||||
if ext := filepath.Ext(base); ext != ".go" && ext != ".sql" {
|
||||
return 0, errors.New("not a recognized migration file type")
|
||||
}
|
||||
|
||||
idx := strings.Index(base, "_")
|
||||
if idx < 0 {
|
||||
return 0, errors.New("no separator found")
|
||||
}
|
||||
|
||||
n, e := strconv.ParseInt(base[:idx], 10, 64)
|
||||
if e == nil && n <= 0 {
|
||||
return 0, errors.New("migration IDs must be greater than zero")
|
||||
}
|
||||
|
||||
return n, e
|
||||
}
|
||||
|
||||
// Migrations slice.
|
||||
type Migrations []*Migration
|
||||
|
||||
// helpers so we can use pkg sort
|
||||
func (ms Migrations) Len() int { return len(ms) }
|
||||
func (ms Migrations) Swap(i, j int) { ms[i], ms[j] = ms[j], ms[i] }
|
||||
func (ms Migrations) Less(i, j int) bool {
|
||||
if ms[i].Version == ms[j].Version {
|
||||
panic(fmt.Sprintf("goose: duplicate version %v detected:\n%v\n%v", ms[i].Version, ms[i].Source, ms[j].Source))
|
||||
}
|
||||
return ms[i].Version < ms[j].Version
|
||||
}
|
||||
|
||||
// Current gets the current migration.
|
||||
func (ms Migrations) Current(current int64) (*Migration, error) {
|
||||
for i, migration := range ms {
|
||||
if migration.Version == current {
|
||||
return ms[i], nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ErrNoCurrentVersion
|
||||
}
|
||||
|
||||
// Next gets the next migration.
|
||||
func (ms Migrations) Next(current int64) (*Migration, error) {
|
||||
for i, migration := range ms {
|
||||
if migration.Version > current {
|
||||
return ms[i], nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ErrNoNextVersion
|
||||
}
|
||||
|
||||
// Previous : Get the previous migration.
|
||||
func (ms Migrations) Previous(current int64) (*Migration, error) {
|
||||
for i := len(ms) - 1; i >= 0; i-- {
|
||||
if ms[i].Version < current {
|
||||
return ms[i], nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ErrNoNextVersion
|
||||
}
|
||||
|
||||
// Last gets the last migration.
|
||||
func (ms Migrations) Last() (*Migration, error) {
|
||||
if len(ms) == 0 {
|
||||
return nil, ErrNoNextVersion
|
||||
}
|
||||
|
||||
return ms[len(ms)-1], nil
|
||||
}
|
||||
|
||||
// Versioned gets versioned migrations.
|
||||
func (ms Migrations) versioned() (Migrations, error) {
|
||||
var migrations Migrations
|
||||
|
||||
// assume that the user will never have more than 19700101000000 migrations
|
||||
for _, m := range ms {
|
||||
// parse version as timestmap
|
||||
versionTime, err := time.Parse(timestampFormat, fmt.Sprintf("%d", m.Version))
|
||||
|
||||
if versionTime.Before(time.Unix(0, 0)) || err != nil {
|
||||
migrations = append(migrations, m)
|
||||
}
|
||||
}
|
||||
|
||||
return migrations, nil
|
||||
}
|
||||
|
||||
// Timestamped gets the timestamped migrations.
|
||||
func (ms Migrations) timestamped() (Migrations, error) {
|
||||
var migrations Migrations
|
||||
|
||||
// assume that the user will never have more than 19700101000000 migrations
|
||||
for _, m := range ms {
|
||||
// parse version as timestmap
|
||||
versionTime, err := time.Parse(timestampFormat, fmt.Sprintf("%d", m.Version))
|
||||
if err != nil {
|
||||
// probably not a timestamp
|
||||
continue
|
||||
}
|
||||
|
||||
if versionTime.After(time.Unix(0, 0)) {
|
||||
migrations = append(migrations, m)
|
||||
}
|
||||
}
|
||||
return migrations, nil
|
||||
}
|
||||
|
||||
func (ms Migrations) String() string {
|
||||
str := ""
|
||||
for _, m := range ms {
|
||||
str += fmt.Sprintln(m)
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// AddMigration adds a migration.
|
||||
func AddMigration(up func(*sql.Tx) error, down func(*sql.Tx) error) {
|
||||
_, filename, _, _ := runtime.Caller(1)
|
||||
AddNamedMigration(filename, up, down)
|
||||
}
|
||||
|
||||
// AddNamedMigration : Add a named migration.
|
||||
func AddNamedMigration(filename string, up func(*sql.Tx) error, down func(*sql.Tx) error) {
|
||||
v, _ := ExtractNumericComponent(filename)
|
||||
migration := &Migration{Version: v, Next: -1, Previous: -1, Registered: true, UpFn: up, DownFn: down, Source: filename}
|
||||
|
||||
if existing, ok := registeredGoMigrations[v]; ok {
|
||||
panic(fmt.Sprintf("failed to add migration %q: version conflicts with %q", filename, existing.Source))
|
||||
}
|
||||
|
||||
registeredGoMigrations[v] = migration
|
||||
}
|
||||
|
||||
// CollectMigrationsFromDir returns all the valid looking migration scripts in the
|
||||
// migrations folder and go func registry, and key them by version.
|
||||
func CollectMigrationsFromDir(dirpath string, current, target int64) (Migrations, error) {
|
||||
if _, err := os.Stat(dirpath); os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("%s directory does not exists", dirpath)
|
||||
}
|
||||
|
||||
var migrations Migrations
|
||||
|
||||
// SQL migration files.
|
||||
sqlMigrationFiles, err := filepath.Glob(dirpath + "/**.sql")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, file := range sqlMigrationFiles {
|
||||
v, err := ExtractNumericComponent(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if versionFilter(v, current, target) {
|
||||
migration := &Migration{Version: v, Next: -1, Previous: -1, Source: file}
|
||||
migrations = append(migrations, migration)
|
||||
}
|
||||
}
|
||||
|
||||
// Go migrations registered via goose.AddMigration().
|
||||
for _, migration := range registeredGoMigrations {
|
||||
v, err := ExtractNumericComponent(migration.Source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if versionFilter(v, current, target) {
|
||||
migrations = append(migrations, migration)
|
||||
}
|
||||
}
|
||||
|
||||
// Go migration files
|
||||
goMigrationFiles, err := filepath.Glob(dirpath + "/**.go")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, file := range goMigrationFiles {
|
||||
v, err := ExtractNumericComponent(file)
|
||||
if err != nil {
|
||||
continue // Skip any files that don't have version prefix.
|
||||
}
|
||||
|
||||
// Skip migrations already existing migrations registered via goose.AddMigration().
|
||||
if _, ok := registeredGoMigrations[v]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if versionFilter(v, current, target) {
|
||||
migration := &Migration{Version: v, Next: -1, Previous: -1, Source: file, Registered: false}
|
||||
migrations = append(migrations, migration)
|
||||
}
|
||||
}
|
||||
|
||||
migrations = sortAndConnectMigrations(migrations)
|
||||
|
||||
return migrations, nil
|
||||
}
|
||||
|
||||
func sortAndConnectMigrations(migrations Migrations) Migrations {
|
||||
sort.Sort(migrations)
|
||||
|
||||
// now that we're sorted in the appropriate direction,
|
||||
// populate next and previous for each migration
|
||||
for i, m := range migrations {
|
||||
prev := int64(-1)
|
||||
if i > 0 {
|
||||
prev = migrations[i-1].Version
|
||||
migrations[i-1].Next = m.Version
|
||||
}
|
||||
migrations[i].Previous = prev
|
||||
}
|
||||
|
||||
return migrations
|
||||
}
|
||||
|
||||
func versionFilter(v, current, target int64) bool {
|
||||
|
||||
if target > current {
|
||||
return v > current && v <= target
|
||||
}
|
||||
|
||||
if target < current {
|
||||
return v <= current && v > target
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
// Create the db version table
|
||||
// and insert the initial 0 value into it
|
||||
func createVersionTable(db *sql.DB) error {
|
||||
txn, err := db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d := GetDialect()
|
||||
|
||||
if _, err := txn.Exec(d.createVersionTableSQL()); err != nil {
|
||||
txn.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
version := 0
|
||||
applied := true
|
||||
if _, err := txn.Exec(d.insertVersionSQL(), version, applied); err != nil {
|
||||
txn.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
return txn.Commit()
|
||||
}
|
||||
|
||||
|
||||
// Run a migration specified in raw SQL.
|
||||
//
|
||||
// Sections of the script can be annotated with a special comment,
|
||||
// starting with "-- +goose" to specify whether the section should
|
||||
// be applied during an Up or Down migration
|
||||
//
|
||||
// All statements following an Up or Down directive are grouped together
|
||||
// until another direction directive is found.
|
||||
func runSQLMigration(ctx context.Context, db *sql.DB, statements []string, useTx bool, v int64, direction bool) error {
|
||||
if useTx {
|
||||
// TRANSACTION.
|
||||
|
||||
verboseInfo("Begin transaction")
|
||||
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to begin transaction")
|
||||
}
|
||||
|
||||
for _, query := range statements {
|
||||
verboseInfo("Executing statement: %s\n", clearStatement(query))
|
||||
if _, err = tx.Exec(query); err != nil {
|
||||
verboseInfo("Rollback transaction")
|
||||
tx.Rollback()
|
||||
return errors.Wrapf(err, "failed to execute SQL query %q", clearStatement(query))
|
||||
}
|
||||
}
|
||||
|
||||
if direction {
|
||||
if _, err := tx.Exec(GetDialect().insertVersionSQL(), v, direction); err != nil {
|
||||
verboseInfo("Rollback transaction")
|
||||
tx.Rollback()
|
||||
return errors.Wrap(err, "failed to insert new goose version")
|
||||
}
|
||||
} else {
|
||||
if _, err := tx.Exec(GetDialect().deleteVersionSQL(), v); err != nil {
|
||||
verboseInfo("Rollback transaction")
|
||||
tx.Rollback()
|
||||
return errors.Wrap(err, "failed to delete goose version")
|
||||
}
|
||||
}
|
||||
|
||||
verboseInfo("Commit transaction")
|
||||
if err := tx.Commit(); err != nil {
|
||||
return errors.Wrap(err, "failed to commit transaction")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NO TRANSACTION.
|
||||
for _, query := range statements {
|
||||
verboseInfo("Executing statement: %s", clearStatement(query))
|
||||
if _, err := db.Exec(query); err != nil {
|
||||
return errors.Wrapf(err, "failed to execute SQL query %q", clearStatement(query))
|
||||
}
|
||||
}
|
||||
if _, err := db.Exec(GetDialect().insertVersionSQL(), v, direction); err != nil {
|
||||
return errors.Wrap(err, "failed to insert new goose version")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
grayColor = "\033[90m"
|
||||
resetColor = "\033[00m"
|
||||
)
|
||||
|
||||
func verboseInfo(s string, args ...interface{}) {
|
||||
log.Printf(grayColor+s+resetColor, args...)
|
||||
}
|
||||
|
5
pkg/migration/pack.go
Normal file
5
pkg/migration/pack.go
Normal file
|
@ -0,0 +1,5 @@
|
|||
package migration
|
||||
|
||||
//go:generate gopackmigration -dir ../../migrations
|
||||
|
||||
|
13
pkg/migration/sql.go
Normal file
13
pkg/migration/sql.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package migration
|
||||
|
||||
import "regexp"
|
||||
|
||||
var (
|
||||
matchSQLComments = regexp.MustCompile(`(?m)^--.*$[\r\n]*`)
|
||||
matchEmptyEOL = regexp.MustCompile(`(?m)^$[\r\n]*`) // TODO: Duplicate
|
||||
)
|
||||
|
||||
func clearStatement(s string) string {
|
||||
s = matchSQLComments.ReplaceAllString(s, ``)
|
||||
return matchEmptyEOL.ReplaceAllString(s, ``)
|
||||
}
|
212
pkg/migration/sql_parser.go
Normal file
212
pkg/migration/sql_parser.go
Normal file
|
@ -0,0 +1,212 @@
|
|||
package migration
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type parserState int
|
||||
|
||||
const (
|
||||
start parserState = iota // 0
|
||||
upStatement // 1
|
||||
upStatementBegin // 2
|
||||
upStatementEnd // 3
|
||||
downStatement // 4
|
||||
downStatementBegin // 5
|
||||
downStatementEnd // 6
|
||||
)
|
||||
|
||||
type stateMachine parserState
|
||||
|
||||
func (s *stateMachine) Get() parserState {
|
||||
return parserState(*s)
|
||||
}
|
||||
func (s *stateMachine) Set(new parserState) {
|
||||
*s = stateMachine(new)
|
||||
}
|
||||
|
||||
const scanBufSize = 4 * 1024 * 1024
|
||||
|
||||
var matchEmptyLines = regexp.MustCompile(`^\s*$`)
|
||||
|
||||
var bufferPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return make([]byte, scanBufSize)
|
||||
},
|
||||
}
|
||||
|
||||
// Split given SQL script into individual statements and return
|
||||
// SQL statements for given direction (up=true, down=false).
|
||||
//
|
||||
// The base case is to simply split on semicolons, as these
|
||||
// naturally terminate a statement.
|
||||
//
|
||||
// However, more complex cases like pl/pgsql can have semicolons
|
||||
// within a statement. For these cases, we provide the explicit annotations
|
||||
// 'StatementBegin' and 'StatementEnd' to allow the script to
|
||||
// tell us to ignore semicolons.
|
||||
func parseSQLMigration(r io.Reader, direction bool) (stmts []string, useTx bool, err error) {
|
||||
var buf bytes.Buffer
|
||||
scanBuf := bufferPool.Get().([]byte)
|
||||
defer bufferPool.Put(scanBuf)
|
||||
|
||||
scanner := bufio.NewScanner(r)
|
||||
scanner.Buffer(scanBuf, scanBufSize)
|
||||
|
||||
stateMachine := stateMachine(start)
|
||||
useTx = true
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.HasPrefix(line, "--") {
|
||||
cmd := strings.TrimSpace(strings.TrimPrefix(line, "--"))
|
||||
|
||||
switch cmd {
|
||||
case "+goose Up":
|
||||
switch stateMachine.Get() {
|
||||
case start:
|
||||
stateMachine.Set(upStatement)
|
||||
default:
|
||||
return nil, false, errors.Errorf("duplicate '-- +goose Up' annotations; stateMachine=%v, see https://github.com/c9s/goose#sql-migrations", stateMachine)
|
||||
}
|
||||
continue
|
||||
|
||||
case "+goose Down":
|
||||
switch stateMachine.Get() {
|
||||
case upStatement, upStatementEnd:
|
||||
stateMachine.Set(downStatement)
|
||||
default:
|
||||
return nil, false, errors.Errorf("must start with '-- +goose Up' annotation, stateMachine=%v, see https://github.com/c9s/goose#sql-migrations", stateMachine)
|
||||
}
|
||||
continue
|
||||
|
||||
case "+goose StatementBegin":
|
||||
switch stateMachine.Get() {
|
||||
case upStatement, upStatementEnd:
|
||||
stateMachine.Set(upStatementBegin)
|
||||
case downStatement, downStatementEnd:
|
||||
stateMachine.Set(downStatementBegin)
|
||||
default:
|
||||
return nil, false, errors.Errorf("'-- +goose StatementBegin' must be defined after '-- +goose Up' or '-- +goose Down' annotation, stateMachine=%v, see https://github.com/c9s/goose#sql-migrations", stateMachine)
|
||||
}
|
||||
continue
|
||||
|
||||
case "+goose StatementEnd":
|
||||
switch stateMachine.Get() {
|
||||
case upStatementBegin:
|
||||
stateMachine.Set(upStatementEnd)
|
||||
case downStatementBegin:
|
||||
stateMachine.Set(downStatementEnd)
|
||||
default:
|
||||
return nil, false, errors.New("'-- +goose StatementEnd' must be defined after '-- +goose StatementBegin', see https://github.com/c9s/goose#sql-migrations")
|
||||
}
|
||||
|
||||
case "+goose NO TRANSACTION":
|
||||
useTx = false
|
||||
continue
|
||||
|
||||
default:
|
||||
// Ignore comments.
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore empty lines.
|
||||
if matchEmptyLines.MatchString(line) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Write SQL line to a buffer.
|
||||
if _, err := buf.WriteString(line + "\n"); err != nil {
|
||||
return nil, false, errors.Wrap(err, "failed to write to buf")
|
||||
}
|
||||
|
||||
// Read SQL body one by line, if we're in the right direction.
|
||||
//
|
||||
// 1) basic query with semicolon; 2) psql statement
|
||||
//
|
||||
// Export statement once we hit end of statement.
|
||||
switch stateMachine.Get() {
|
||||
case upStatement, upStatementBegin, upStatementEnd:
|
||||
if !direction /*down*/ {
|
||||
buf.Reset()
|
||||
continue
|
||||
}
|
||||
case downStatement, downStatementBegin, downStatementEnd:
|
||||
if direction /*up*/ {
|
||||
buf.Reset()
|
||||
continue
|
||||
}
|
||||
default:
|
||||
return nil, false, errors.Errorf("failed to parse migration: unexpected state %q on line %q, see https://github.com/c9s/goose#sql-migrations", stateMachine, line)
|
||||
}
|
||||
|
||||
switch stateMachine.Get() {
|
||||
case upStatement:
|
||||
if endsWithSemicolon(line) {
|
||||
stmts = append(stmts, buf.String())
|
||||
buf.Reset()
|
||||
}
|
||||
case downStatement:
|
||||
if endsWithSemicolon(line) {
|
||||
stmts = append(stmts, buf.String())
|
||||
buf.Reset()
|
||||
}
|
||||
case upStatementEnd:
|
||||
stmts = append(stmts, buf.String())
|
||||
buf.Reset()
|
||||
stateMachine.Set(upStatement)
|
||||
case downStatementEnd:
|
||||
stmts = append(stmts, buf.String())
|
||||
buf.Reset()
|
||||
stateMachine.Set(downStatement)
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, false, errors.Wrap(err, "failed to scan migration")
|
||||
}
|
||||
// EOF
|
||||
|
||||
switch stateMachine.Get() {
|
||||
case start:
|
||||
return nil, false, errors.New("failed to parse migration: must start with '-- +goose Up' annotation, see https://github.com/c9s/goose#sql-migrations")
|
||||
case upStatementBegin, downStatementBegin:
|
||||
return nil, false, errors.New("failed to parse migration: missing '-- +goose StatementEnd' annotation")
|
||||
}
|
||||
|
||||
if bufferRemaining := strings.TrimSpace(buf.String()); len(bufferRemaining) > 0 {
|
||||
return nil, false, errors.Errorf("failed to parse migration: state %q, direction: %v: unexpected unfinished SQL query: %q: missing semicolon?", stateMachine, direction, bufferRemaining)
|
||||
}
|
||||
|
||||
return stmts, useTx, nil
|
||||
}
|
||||
|
||||
// Checks the line to see if the line has a statement-ending semicolon
|
||||
// or if the line contains a double-dash comment.
|
||||
func endsWithSemicolon(line string) bool {
|
||||
scanBuf := bufferPool.Get().([]byte)
|
||||
defer bufferPool.Put(scanBuf)
|
||||
|
||||
prev := ""
|
||||
scanner := bufio.NewScanner(strings.NewReader(line))
|
||||
scanner.Buffer(scanBuf, scanBufSize)
|
||||
scanner.Split(bufio.ScanWords)
|
||||
|
||||
for scanner.Scan() {
|
||||
word := scanner.Text()
|
||||
if strings.HasPrefix(word, "--") {
|
||||
break
|
||||
}
|
||||
prev = word
|
||||
}
|
||||
|
||||
return strings.HasSuffix(prev, ";")
|
||||
}
|
||||
|
68
pkg/migration/up.go
Normal file
68
pkg/migration/up.go
Normal file
|
@ -0,0 +1,68 @@
|
|||
package migration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"log"
|
||||
)
|
||||
|
||||
// UpTo migrates up to a specific version.
|
||||
func UpTo(ctx context.Context, db *sql.DB, dir string, version int64) error {
|
||||
migrations, err := CollectMigrationsFromDir(dir, minVersion, version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
current, err := GetDBVersion(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
next, err := migrations.Next(current)
|
||||
if err != nil {
|
||||
if err == ErrNoNextVersion {
|
||||
log.Printf("no migrations to run. current version: %d\n", current)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if err = next.Up(ctx, db); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Up applies all available migrations.
|
||||
func Up(ctx context.Context, db *sql.DB, dir string) error {
|
||||
return UpTo(ctx, db, dir, maxVersion)
|
||||
}
|
||||
|
||||
// UpByOne migrates up by a single version.
|
||||
func UpByOne(ctx context.Context, db *sql.DB, dir string) error {
|
||||
migrations, err := CollectMigrationsFromDir(dir, minVersion, maxVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currentVersion, err := GetDBVersion(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
next, err := migrations.Next(currentVersion)
|
||||
if err != nil {
|
||||
if err == ErrNoNextVersion {
|
||||
log.Printf("no migrations to run. current version: %d\n", currentVersion)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if err = next.Up(ctx, db); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
91
pkg/migration/version.go
Normal file
91
pkg/migration/version.go
Normal file
|
@ -0,0 +1,91 @@
|
|||
package migration
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Version prints the current version of the database.
|
||||
func Version(db *sql.DB, dir string) error {
|
||||
current, err := GetDBVersion(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("cmd: version %v\n", current)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDBVersion is an alias for EnsureDBVersion, but returns -1 in error.
|
||||
func GetDBVersion(db *sql.DB) (int64, error) {
|
||||
version, err := EnsureDBVersion(db)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return version, nil
|
||||
}
|
||||
|
||||
// EnsureDBVersion retrieves the current version for this DB.
|
||||
// Create and initialize the DB version table if it doesn't exist.
|
||||
func EnsureDBVersion(db *sql.DB) (int64, error) {
|
||||
rows, err := GetDialect().dbVersionQuery(db)
|
||||
if err != nil {
|
||||
return 0, createVersionTable(db)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
// The most recent record for each migration specifies
|
||||
// whether it has been applied or rolled back.
|
||||
// The first version we find that has been applied is the current version.
|
||||
|
||||
toSkip := make([]int64, 0)
|
||||
|
||||
for rows.Next() {
|
||||
var row MigrationRecord
|
||||
if err = rows.Scan(&row.VersionID, &row.IsApplied); err != nil {
|
||||
return 0, errors.Wrap(err, "failed to scan row")
|
||||
}
|
||||
|
||||
// have we already marked this version to be skipped?
|
||||
skip := false
|
||||
for _, v := range toSkip {
|
||||
if v == row.VersionID {
|
||||
skip = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if skip {
|
||||
continue
|
||||
}
|
||||
|
||||
// if version has been applied we're done
|
||||
if row.IsApplied {
|
||||
return row.VersionID, nil
|
||||
}
|
||||
|
||||
// latest version of migration has not been applied.
|
||||
toSkip = append(toSkip, row.VersionID)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return 0, errors.Wrap(err, "failed to get next row")
|
||||
}
|
||||
|
||||
return 0, ErrNoNextVersion
|
||||
}
|
||||
|
||||
|
||||
var tableName = "goose_db_version"
|
||||
|
||||
// TableName returns goose db version table name
|
||||
func TableName() string {
|
||||
return tableName
|
||||
}
|
||||
|
||||
// SetTableName set goose db version table name
|
||||
func SetTableName(n string) {
|
||||
tableName = n
|
||||
}
|
Loading…
Reference in New Issue
Block a user