diff --git a/Dockerfile b/Dockerfile index 4c1a26b..7110c71 100644 --- a/Dockerfile +++ b/Dockerfile @@ -43,4 +43,4 @@ USER nobody EXPOSE 8080 -CMD ./super-graph +CMD ./super-graph serv diff --git a/config/allow.list b/config/allow.list index 1293087..cd53643 100644 --- a/config/allow.list +++ b/config/allow.list @@ -1,18 +1,5 @@ # http://localhost:8080/ -query { - products(id: $PRODUCT_ID) { - name - } -} - -query { - products(id: $PRODUCT_ID) { - name - image - } -} - variables { "update": { "name": "Hellooooo", @@ -31,6 +18,31 @@ mutation { } } +query { + me { + id + email + full_name + } +} + + +variables { + "update": { + "name": "Hellooooo", + "description": "World" + }, + "user": 123 +} + +mutation { + products(update: $update, where: {id: {eq: 134}}) { + id + name + description + } +} + variables { "update": { "name": "Helloo", @@ -47,50 +59,11 @@ mutation { } } -variables {} - -{ - me { - id - email - } -} - - -variables { - "update": { - "name": "Hellooooo", - "description": "World" - }, - "user": 123 -} - -mutation { - products(update: $update, where: {id: {eq: 134}}) { - id - name - description - } -} - query { me { id + email full_name } } -variables {} - -query { - customers { - id - email - payments { - customer_id - amount - billing_details - } - } -} - diff --git a/config/dev.yml b/config/dev.yml index f6f336d..76294f6 100644 --- a/config/dev.yml +++ b/config/dev.yml @@ -24,6 +24,12 @@ enable_tracing: true # with the new configs when a change is detected reload_on_config_change: true +# File that points to the database seeding script +# seed_file: seed.js + +# Path pointing to where the migrations can be found +migrations_path: ./config/migrations + # Postgres related environment Variables # SG_DATABASE_HOST # SG_DATABASE_PORT diff --git a/config/prod.yml b/config/prod.yml index 06affae..7312bc1 100644 --- a/config/prod.yml +++ b/config/prod.yml @@ -20,6 +20,12 @@ auth_fail_block: always # response enable_tracing: true +# File that points to the database seeding script +# seed_file: seed.js + +# Path pointing to where the migrations can be found +# migrations_path: migrations + # Postgres related environment Variables # SG_DATABASE_HOST # SG_DATABASE_PORT diff --git a/fuzzbuzz.yaml b/fuzzbuzz.yaml index fbf4622..8627c11 100644 --- a/fuzzbuzz.yaml +++ b/fuzzbuzz.yaml @@ -3,7 +3,7 @@ targets: - name: qcode language: go - version: "1.13" + version: "1.11" corpus: ./corpus memory_limit: "100" # in megabytes timeout: "500" # in milliseconds @@ -17,7 +17,7 @@ targets: - name: jsn language: go - version: "1.13" + version: "1.11" corpus: ./corpus memory_limit: "100" # in megabytes timeout: "500" # in milliseconds diff --git a/go.mod b/go.mod index 7c169bd..f75225a 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,6 @@ module github.com/dosco/super-graph require ( - github.com/BurntSushi/toml v0.3.1 // indirect github.com/Masterminds/semver v1.4.2 github.com/OneOfOne/xxhash v1.2.5 // indirect github.com/adjust/gorails v0.0.0-20171013043634-2786ed0c03d3 @@ -17,18 +16,25 @@ require ( github.com/go-pg/pg v8.0.1+incompatible github.com/go-sourcemap/sourcemap v2.1.2+incompatible // indirect github.com/gobuffalo/flect v0.1.1 + github.com/golang-migrate/migrate v3.5.4+incompatible // indirect + github.com/golang-migrate/migrate/v4 v4.6.2 github.com/gorilla/websocket v1.4.0 + github.com/jackc/pgconn v1.0.1 + github.com/jackc/pgx v3.6.0+incompatible + github.com/jackc/pgx/v4 v4.0.1 + github.com/jackc/tern v1.8.2 github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a // indirect - github.com/kr/pretty v0.1.0 // indirect + github.com/lib/pq v1.2.0 github.com/onsi/ginkgo v1.8.0 // indirect github.com/onsi/gomega v1.5.0 // indirect github.com/pkg/errors v0.8.1 - github.com/rs/zerolog v1.14.3 + github.com/rs/zerolog v1.15.0 github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/spf13/viper v1.3.1 + github.com/spf13/cobra v0.0.5 + github.com/spf13/viper v1.3.2 github.com/valyala/fasttemplate v1.0.1 - golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 - golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 // indirect - gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect + github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec + golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7 + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 mellium.im/sasl v0.2.1 // indirect ) diff --git a/go.sum b/go.sum index ced36a8..29abb78 100644 --- a/go.sum +++ b/go.sum @@ -1,75 +1,254 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI= github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/adjust/gorails v0.0.0-20171013043634-2786ed0c03d3 h1:+qz9Ga6l6lKw6fgvk5RMV5HQznSLvI8Zxajwdj4FhFg= github.com/adjust/gorails v0.0.0-20171013043634-2786ed0c03d3/go.mod h1:FlkD11RtgMTYjVuBnb7cxoHmQGqvPpCsr2atC88nl/M= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 h1:rRISKWyXfVxvoa702s91Zl5oREZTrR3yv+tXrrX7G/g= github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= github.com/brianvoe/gofakeit v3.18.0+incompatible h1:wDOmHc9DLG4nRjUVVaxA+CEglKOW72Y5+4WNxUIkjM8= github.com/brianvoe/gofakeit v3.18.0+incompatible/go.mod h1:kfwdRA90vvNhPutZWfH7WPaDzUjz+CZFqG+rPkOjGOc= github.com/cespare/xxhash/v2 v2.0.0 h1:Eb1IiuHmi3FhT12NKfqCQXSXRqc4NTMvgJoREemrSt4= github.com/cespare/xxhash/v2 v2.0.0/go.mod h1:MaMeaVDXZNmTpkOyhVs3/WfjgobkbQgfrVnrr3DyZL0= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= +github.com/containerd/containerd v1.2.7/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8= +github.com/cznic/fileutil v0.0.0-20180108211300-6a051e75936f/go.mod h1:8S58EK26zhXSxzv7NQFpnliaOQsmDUxvoQO3rt154Vg= +github.com/cznic/golex v0.0.0-20170803123110-4ab7c5e190e4/go.mod h1:+bmmJDNmKlhWNG+gwWCkaBoTy39Fs+bzRxVBzoTQbIc= +github.com/cznic/internal v0.0.0-20180608152220-f44710a21d00/go.mod h1:olo7eAdKwJdXxb55TKGLiJ6xt1H0/tiiRCWKVLmtjY4= +github.com/cznic/lldb v1.1.0/go.mod h1:FIZVUmYUVhPwRiPzL8nD/mpFcJ/G7SSXjjXYG4uRI3A= +github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM= +github.com/cznic/ql v1.2.0/go.mod h1:FbpzhyZrqr0PVlK6ury+PoW3T0ODUV22OeWIxcaOrSE= +github.com/cznic/sortutil v0.0.0-20150617083342-4c7342852e65/go.mod h1:q2w6Bg5jeox1B+QkJ6Wp/+Vn0G/bo3f1uY7Fn3vivIQ= +github.com/cznic/strutil v0.0.0-20171016134553-529a34b1c186/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc= +github.com/cznic/zappy v0.0.0-20160723133515-2533cb5b45cc/go.mod h1:Y1SNZ4dRUOKXshKUbwUapqNncRrho4mkjQebgEHZLj8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dhui/dktest v0.3.0/go.mod h1:cyzIUfGsBEbZ6BT7tnXqAShHSXCZhSNmFl70sZ7c1yc= github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk= github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v0.7.3-0.20190103212154-2b7e084dc98b/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v0.7.3-0.20190817195342-4760db040282/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dop251/goja v0.0.0-20190912223329-aa89e6a4c733 h1:cyNc40Dx5YNEO94idePU8rhVd3dn+sd04Arh0kDBAaw= github.com/dop251/goja v0.0.0-20190912223329-aa89e6a4c733/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsouza/fake-gcs-server v1.7.0/go.mod h1:5XIRs4YvwNbNoz+1JF8j6KLAyDh7RHGAyAK3EP2EsNk= github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc= github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-pg/pg v8.0.1+incompatible h1:gi93AxXmqlFGT0os5z2kTnbDqCk6BHXnA9MMApVxAkY= github.com/go-pg/pg v8.0.1+incompatible/go.mod h1:a2oXow+aFOrvwcKs3eIA0lNFmMilrxK2sOkB5NWe0vA= github.com/go-sourcemap/sourcemap v2.1.2+incompatible h1:0b/xya7BKGhXuqFESKM4oIiRo9WOt2ebz7KxfreD6ug= github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gobuffalo/flect v0.1.1 h1:GTZJjJufv9FxgRs1+0Soo3wj+Md3kTUmTER/YE4uINA= github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= +github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang-migrate/migrate v3.5.4+incompatible h1:R7OzwvCJTCgwapPCiX6DyBiu2czIUMDCB118gFTKTUA= +github.com/golang-migrate/migrate v3.5.4+incompatible/go.mod h1:IsVUlFN5puWOmXrqjgGUfIRIbU7mr8oNBE2tyERd9Wk= +github.com/golang-migrate/migrate/v4 v4.6.2 h1:LDDOHo/q1W5UDj6PbkxdCv7lv9yunyZHXvxuwDkGo3k= +github.com/golang-migrate/migrate/v4 v4.6.2/go.mod h1:JYi6reN3+Z734VZ0akNuyOJNcrg45ZL7LDBMW3WGJL0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0 h1:DUwgMQuuPnS0rhMXenUtZpqZqrR/30NWY+qQvTpSvEs= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.0.1 h1:ZANo4pIkeHKIVD1cQMcxu8fwrwIICLblzi9HCjooZeQ= +github.com/jackc/pgconn v1.0.1/go.mod h1:GgY/Lbj1VonNaVdNUHs9AwWom3yP2eymFQ1C8z9r/Lk= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0 h1:FApgMJ/GtaXfI0s8Lvd0kaLaRwMOhs4VH92pwkwQQvU= +github.com/jackc/pgproto3/v2 v2.0.0/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.0.1 h1:7GWB9n3DdnO3TIbj59wMAE9QcHPL4cy/Bbtk5P1Noow= +github.com/jackc/pgtype v1.0.1/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0= +github.com/jackc/pgx v0.0.0-20180217033919-55ca9db5d578/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= +github.com/jackc/pgx v3.2.0+incompatible h1:0Vihzu20St42/UDsvZGdNE6jak7oi/UOeMzwMPHkgFY= +github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= +github.com/jackc/pgx v3.6.0+incompatible h1:bJeo4JdVbDAW8KB2m8XkFeo8CPipREoG37BwEoKGz+Q= +github.com/jackc/pgx v3.6.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.0.1 h1:NNrG0MX2AVEJw1NNDYg+ixSXycCfWWKeqMuQHQkAngc= +github.com/jackc/pgx/v4 v4.0.1/go.mod h1:NeQ64VJooukJGFLX2r01sJL/gRbKlpvsO2giBvjfgrY= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.0.0 h1:rbjAshlgKscNa7j0jAM0uNQflis5o2XUogPMVAwtcsM= +github.com/jackc/puddle v1.0.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/tern v1.8.2 h1:+d9eK83fRS0dbf6nt+2tjILYF4FKG1O5xTFB8Lzc66U= +github.com/jackc/tern v1.8.2/go.mod h1:AMppp2oyCT6rYnJHLLMmPWwahfFvdIVi6mr9gH81Nxs= github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a h1:eeaG9XMUvRBYXJi4pg1ZKM7nxc5AfXfojeLLW7O5J3k= github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +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/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kshvakov/clickhouse v1.3.5/go.mod h1:DMzX7FxRymoNkVgizH0DWAL8Cur7wHLgx3MUnGwJqpE= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= 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/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.0.0-20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.14.3 h1:4EGfSkR2hJDB0s3oFfrlPqjU1e4WLncergLil3nEKW0= github.com/rs/zerolog v1.14.3/go.mod h1:3WXPzbXEEliJ+a6UFE4vhIxV8qR1EML6ngzP9ug4eYg= +github.com/rs/zerolog v1.15.0 h1:uPRuwkWF4J6fGsJ2R0Gn2jB1EQiav9k3S6CSdygQJXY= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -77,55 +256,165 @@ 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/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.0-20160114030619-9c9300901990/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= 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/pflag v0.0.0-20151218134703-7f60f83a2c81/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.1 h1:5+8j8FTpnFV4nEImW/ofkzEt8VoOiLXxdYIDsB73T38= github.com/spf13/viper v1.3.1/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec h1:DGmKwyZwEB8dI7tbLt/I/gQuP559o/0FrAkHKlQM/Ks= +github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec/go.mod h1:owBmyHYMLkxyrugmfwE/DLJyW8Ro9mkphwuVErQ0iUw= +github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE= +go.mongodb.org/mongo-driver v1.1.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20151201002508-7b85b097bf75/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7 h1:0hQKqeLdqlt5iIwVOBErRisrHJAN57yOiPRQItI20fU= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a h1:1n5lsVfiQW3yfsRGu98756EH1YthsFqr/5mxHduZW2A= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190102155601-82a175fd1598/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190426135247-a129542de9ae h1:mQLHiymj/JXKnnjc62tb7nD5pZLs940/sXJu+Xp3DBA= +golang.org/x/sys v0.0.0-20190426135247-a129542de9ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 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/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/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190425222832-ad9eeb80039a/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.3.2/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 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= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +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= mellium.im/sasl v0.2.1 h1:nspKSRg7/SyO0cRGY71OkfHab8tf9kCts6a6oTDut0w= mellium.im/sasl v0.2.1/go.mod h1:ROaEDLQNuf9vjKqE1SrAfnsobm2YKXT1gnN1uDp1PjQ= diff --git a/migrate/connection_settings_test.go.example b/migrate/connection_settings_test.go.example new file mode 100644 index 0000000..4a5e0ae --- /dev/null +++ b/migrate/connection_settings_test.go.example @@ -0,0 +1,12 @@ +package migrate_test + +import ( + "github.com/jackc/pgx" +) + +var defaultConnectionParameters *pgx.ConnConfig = &pgx.ConnConfig{ + // Host: "127.0.0.1", + User: "tern", + Password: "secret", // Password is usually not needed when using socket + Database: "tern_migrate_test", +} diff --git a/migrate/migrate.go b/migrate/migrate.go new file mode 100644 index 0000000..130479b --- /dev/null +++ b/migrate/migrate.go @@ -0,0 +1,373 @@ +package migrate + +import ( + "bytes" + "context" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "regexp" + "strconv" + "strings" + "text/template" + + "github.com/jackc/pgx/v4" + "github.com/pkg/errors" +) + +var migrationPattern = regexp.MustCompile(`\A(\d+)_.+\.sql\z`) + +var ErrNoFwMigration = errors.Errorf("no sql in forward migration step") + +type BadVersionError string + +func (e BadVersionError) Error() string { + return string(e) +} + +type IrreversibleMigrationError struct { + m *Migration +} + +func (e IrreversibleMigrationError) Error() string { + return fmt.Sprintf("Irreversible migration: %d - %s", e.m.Sequence, e.m.Name) +} + +type NoMigrationsFoundError struct { + Path string +} + +func (e NoMigrationsFoundError) Error() string { + return fmt.Sprintf("No migrations found at %s", e.Path) +} + +type MigrationPgError struct { + Sql string + Error error +} + +type Migration struct { + Sequence int32 + Name string + UpSQL string + DownSQL string +} + +type MigratorOptions struct { + // DisableTx causes the Migrator not to run migrations in a transaction. + DisableTx bool + // MigratorFS is the interface used for collecting the migrations. + MigratorFS MigratorFS +} + +type Migrator struct { + conn *pgx.Conn + versionTable string + options *MigratorOptions + Migrations []*Migration + OnStart func(int32, string, string, string) // OnStart is called when a migration is run with the sequence, name, direction, and SQL + Data map[string]interface{} // Data available to use in migrations +} + +func NewMigrator(conn *pgx.Conn, versionTable string) (m *Migrator, err error) { + return NewMigratorEx(conn, versionTable, &MigratorOptions{MigratorFS: defaultMigratorFS{}}) +} + +func NewMigratorEx(conn *pgx.Conn, versionTable string, opts *MigratorOptions) (m *Migrator, err error) { + m = &Migrator{conn: conn, versionTable: versionTable, options: opts} + err = m.ensureSchemaVersionTableExists() + m.Migrations = make([]*Migration, 0) + m.Data = make(map[string]interface{}) + return +} + +type MigratorFS interface { + ReadDir(dirname string) ([]os.FileInfo, error) + ReadFile(filename string) ([]byte, error) + Glob(pattern string) (matches []string, err error) +} + +type defaultMigratorFS struct{} + +func (defaultMigratorFS) ReadDir(dirname string) ([]os.FileInfo, error) { + return ioutil.ReadDir(dirname) +} + +func (defaultMigratorFS) ReadFile(filename string) ([]byte, error) { + return ioutil.ReadFile(filename) +} + +func (defaultMigratorFS) Glob(pattern string) ([]string, error) { + return filepath.Glob(pattern) +} + +func FindMigrationsEx(path string, fs MigratorFS) ([]string, error) { + path = strings.TrimRight(path, string(filepath.Separator)) + + fileInfos, err := fs.ReadDir(path) + if err != nil { + return nil, err + } + + paths := make([]string, 0, len(fileInfos)) + for _, fi := range fileInfos { + if fi.IsDir() { + continue + } + + matches := migrationPattern.FindStringSubmatch(fi.Name()) + if len(matches) != 2 { + continue + } + + n, err := strconv.ParseInt(matches[1], 10, 32) + if err != nil { + // The regexp already validated that the prefix is all digits so this *should* never fail + return nil, err + } + + mcount := len(paths) + 100 + + if n < int64(mcount) { + return nil, fmt.Errorf("Duplicate migration %d", n) + } + + if int64(mcount) < n { + return nil, fmt.Errorf("Missing migration %d", mcount) + } + + paths = append(paths, filepath.Join(path, fi.Name())) + } + + return paths, nil +} + +func FindMigrations(path string) ([]string, error) { + return FindMigrationsEx(path, defaultMigratorFS{}) +} + +func (m *Migrator) LoadMigrations(path string) error { + path = strings.TrimRight(path, string(filepath.Separator)) + + mainTmpl := template.New("main") + sharedPaths, err := m.options.MigratorFS.Glob(filepath.Join(path, "*", "*.sql")) + if err != nil { + return err + } + + for _, p := range sharedPaths { + body, err := m.options.MigratorFS.ReadFile(p) + if err != nil { + return err + } + + name := strings.Replace(p, path+string(filepath.Separator), "", 1) + _, err = mainTmpl.New(name).Parse(string(body)) + if err != nil { + return err + } + } + + paths, err := FindMigrationsEx(path, m.options.MigratorFS) + if err != nil { + return err + } + + if len(paths) == 0 { + return NoMigrationsFoundError{Path: path} + } + + for _, p := range paths { + body, err := m.options.MigratorFS.ReadFile(p) + if err != nil { + return err + } + + pieces := strings.SplitN(string(body), "---- create above / drop below ----", 2) + var upSQL, downSQL string + upSQL = strings.TrimSpace(pieces[0]) + upSQL, err = m.evalMigration(mainTmpl.New(filepath.Base(p)+" up"), upSQL) + if err != nil { + return err + } + // Make sure there is SQL in the forward migration step. + containsSQL := false + for _, v := range strings.Split(upSQL, "\n") { + // Only account for regular single line comment, empty line and space/comment combination + cleanString := strings.TrimSpace(v) + if len(cleanString) != 0 && + !strings.HasPrefix(cleanString, "--") { + containsSQL = true + break + } + } + if !containsSQL { + return ErrNoFwMigration + } + + if len(pieces) == 2 { + downSQL = strings.TrimSpace(pieces[1]) + downSQL, err = m.evalMigration(mainTmpl.New(filepath.Base(p)+" down"), downSQL) + if err != nil { + return err + } + } + + m.AppendMigration(filepath.Base(p), upSQL, downSQL) + } + + return nil +} + +func (m *Migrator) evalMigration(tmpl *template.Template, sql string) (string, error) { + tmpl, err := tmpl.Parse(sql) + if err != nil { + return "", err + } + + var buf bytes.Buffer + err = tmpl.Execute(&buf, m.Data) + if err != nil { + return "", err + } + + return buf.String(), nil +} + +func (m *Migrator) AppendMigration(name, upSQL, downSQL string) { + m.Migrations = append( + m.Migrations, + &Migration{ + Sequence: int32(len(m.Migrations)) + 1, + Name: name, + UpSQL: upSQL, + DownSQL: downSQL, + }) + return +} + +// Migrate runs pending migrations +// It calls m.OnStart when it begins a migration +func (m *Migrator) Migrate() error { + return m.MigrateTo(int32(len(m.Migrations))) +} + +// MigrateTo migrates to targetVersion +func (m *Migrator) MigrateTo(targetVersion int32) (err error) { + ctx := context.Background() + // Lock to ensure multiple migrations cannot occur simultaneously + lockNum := int64(9628173550095224) // arbitrary random number + if _, lockErr := m.conn.Exec(ctx, "select pg_advisory_lock($1)", lockNum); lockErr != nil { + return lockErr + } + defer func() { + _, unlockErr := m.conn.Exec(ctx, "select pg_advisory_unlock($1)", lockNum) + if err == nil && unlockErr != nil { + err = unlockErr + } + }() + + currentVersion, err := m.GetCurrentVersion() + if err != nil { + return err + } + + if targetVersion < 0 || int32(len(m.Migrations)) < targetVersion { + errMsg := fmt.Sprintf("destination version %d is outside the valid versions of 0 to %d", targetVersion, len(m.Migrations)) + return BadVersionError(errMsg) + } + + if currentVersion < 0 || int32(len(m.Migrations)) < currentVersion { + errMsg := fmt.Sprintf("current version %d is outside the valid versions of 0 to %d", currentVersion, len(m.Migrations)) + return BadVersionError(errMsg) + } + + var direction int32 + if currentVersion < targetVersion { + direction = 1 + } else { + direction = -1 + } + + for currentVersion != targetVersion { + var current *Migration + var sql, directionName string + var sequence int32 + if direction == 1 { + current = m.Migrations[currentVersion] + sequence = current.Sequence + sql = current.UpSQL + directionName = "up" + } else { + current = m.Migrations[currentVersion-1] + sequence = current.Sequence - 1 + sql = current.DownSQL + directionName = "down" + if current.DownSQL == "" { + return IrreversibleMigrationError{m: current} + } + } + + ctx := context.Background() + + tx, err := m.conn.Begin(ctx) + if err != nil { + return err + } + defer tx.Rollback(ctx) + + // Fire on start callback + if m.OnStart != nil { + m.OnStart(current.Sequence, current.Name, directionName, sql) + } + + // Execute the migration + _, err = tx.Exec(ctx, sql) + if err != nil { + // if err, ok := err.(pgx.PgError); ok { + // return MigrationPgError{Sql: sql, PgError: err} + // } + return err + } + + // Reset all database connection settings. Important to do before updating version as search_path may have been changed. + tx.Exec(ctx, "reset all") + + // Add one to the version + _, err = tx.Exec(ctx, "update "+m.versionTable+" set version=$1", sequence) + if err != nil { + return err + } + + err = tx.Commit(ctx) + if err != nil { + return err + } + + currentVersion = currentVersion + direction + } + + return nil +} + +func (m *Migrator) GetCurrentVersion() (v int32, err error) { + ctx := context.Background() + + err = m.conn.QueryRow(ctx, "select version from "+m.versionTable).Scan(&v) + return v, err +} + +func (m *Migrator) ensureSchemaVersionTableExists() (err error) { + ctx := context.Background() + + _, err = m.conn.Exec(ctx, fmt.Sprintf(` + create table if not exists %s(version int4 not null); + + insert into %s(version) + select 0 + where 0=(select count(*) from %s); + `, m.versionTable, m.versionTable, m.versionTable)) + + return err +} diff --git a/migrate/migrate_test.go b/migrate/migrate_test.go new file mode 100644 index 0000000..1256062 --- /dev/null +++ b/migrate/migrate_test.go @@ -0,0 +1,354 @@ +package migrate_test + +import ( + "fmt" + "testing" + + "github.com/jackc/pgx" + "github.com/jackc/tern/migrate" + . "gopkg.in/check.v1" +) + +type MigrateSuite struct { + conn *pgx.Conn +} + +func Test(t *testing.T) { TestingT(t) } + +var _ = Suite(&MigrateSuite{}) + +var versionTable string = "schema_version_non_default" + +func (s *MigrateSuite) SetUpTest(c *C) { + var err error + s.conn, err = pgx.Connect(*defaultConnectionParameters) + c.Assert(err, IsNil) + + s.cleanupSampleMigrator(c) +} + +func (s *MigrateSuite) currentVersion(c *C) int32 { + var n int32 + err := s.conn.QueryRow("select version from " + versionTable).Scan(&n) + c.Assert(err, IsNil) + return n +} + +func (s *MigrateSuite) Exec(c *C, sql string, arguments ...interface{}) pgx.CommandTag { + commandTag, err := s.conn.Exec(sql, arguments...) + c.Assert(err, IsNil) + return commandTag +} + +func (s *MigrateSuite) tableExists(c *C, tableName string) bool { + var exists bool + err := s.conn.QueryRow( + "select exists(select 1 from information_schema.tables where table_catalog=$1 and table_name=$2)", + defaultConnectionParameters.Database, + tableName, + ).Scan(&exists) + c.Assert(err, IsNil) + return exists +} + +func (s *MigrateSuite) createEmptyMigrator(c *C) *migrate.Migrator { + var err error + m, err := migrate.NewMigrator(s.conn, versionTable) + c.Assert(err, IsNil) + return m +} + +func (s *MigrateSuite) createSampleMigrator(c *C) *migrate.Migrator { + m := s.createEmptyMigrator(c) + m.AppendMigration("Create t1", "create table t1(id serial);", "drop table t1;") + m.AppendMigration("Create t2", "create table t2(id serial);", "drop table t2;") + m.AppendMigration("Create t3", "create table t3(id serial);", "drop table t3;") + return m +} + +func (s *MigrateSuite) cleanupSampleMigrator(c *C) { + tables := []string{versionTable, "t1", "t2", "t3"} + for _, table := range tables { + s.Exec(c, "drop table if exists "+table) + } +} + +func (s *MigrateSuite) TestNewMigrator(c *C) { + var m *migrate.Migrator + var err error + + // Initial run + m, err = migrate.NewMigrator(s.conn, versionTable) + c.Assert(err, IsNil) + + // Creates version table + schemaVersionExists := s.tableExists(c, versionTable) + c.Assert(schemaVersionExists, Equals, true) + + // Succeeds when version table is already created + m, err = migrate.NewMigrator(s.conn, versionTable) + c.Assert(err, IsNil) + + initialVersion, err := m.GetCurrentVersion() + c.Assert(err, IsNil) + c.Assert(initialVersion, Equals, int32(0)) +} + +func (s *MigrateSuite) TestAppendMigration(c *C) { + m := s.createEmptyMigrator(c) + + name := "Create t" + upSQL := "create t..." + downSQL := "drop t..." + m.AppendMigration(name, upSQL, downSQL) + + c.Assert(len(m.Migrations), Equals, 1) + c.Assert(m.Migrations[0].Name, Equals, name) + c.Assert(m.Migrations[0].UpSQL, Equals, upSQL) + c.Assert(m.Migrations[0].DownSQL, Equals, downSQL) +} + +func (s *MigrateSuite) TestLoadMigrationsMissingDirectory(c *C) { + m := s.createEmptyMigrator(c) + err := m.LoadMigrations("testdata/missing") + c.Assert(err, ErrorMatches, "open testdata/missing: no such file or directory") +} + +func (s *MigrateSuite) TestLoadMigrationsEmptyDirectory(c *C) { + m := s.createEmptyMigrator(c) + err := m.LoadMigrations("testdata/empty") + c.Assert(err, ErrorMatches, "No migrations found at testdata/empty") +} + +func (s *MigrateSuite) TestFindMigrationsWithGaps(c *C) { + _, err := migrate.FindMigrations("testdata/gap") + c.Assert(err, ErrorMatches, "Missing migration 2") +} + +func (s *MigrateSuite) TestFindMigrationsWithDuplicate(c *C) { + _, err := migrate.FindMigrations("testdata/duplicate") + c.Assert(err, ErrorMatches, "Duplicate migration 2") +} + +func (s *MigrateSuite) TestLoadMigrations(c *C) { + m := s.createEmptyMigrator(c) + m.Data = map[string]interface{}{"prefix": "foo"} + err := m.LoadMigrations("testdata/sample") + c.Assert(err, IsNil) + c.Assert(m.Migrations, HasLen, 5) + + c.Check(m.Migrations[0].Name, Equals, "001_create_t1.sql") + c.Check(m.Migrations[0].UpSQL, Equals, `create table t1( + id serial primary key +);`) + c.Check(m.Migrations[0].DownSQL, Equals, "drop table t1;") + + c.Check(m.Migrations[1].Name, Equals, "002_create_t2.sql") + c.Check(m.Migrations[1].UpSQL, Equals, `create table t2( + id serial primary key +);`) + c.Check(m.Migrations[1].DownSQL, Equals, "drop table t2;") + + c.Check(m.Migrations[2].Name, Equals, "003_irreversible.sql") + c.Check(m.Migrations[2].UpSQL, Equals, "drop table t2;") + c.Check(m.Migrations[2].DownSQL, Equals, "") + + c.Check(m.Migrations[3].Name, Equals, "004_data_interpolation.sql") + c.Check(m.Migrations[3].UpSQL, Equals, "create table foo_bar(id serial primary key);") + c.Check(m.Migrations[3].DownSQL, Equals, "drop table foo_bar;") +} + +func (s *MigrateSuite) TestLoadMigrationsNoForward(c *C) { + var err error + m, err := migrate.NewMigrator(s.conn, versionTable) + c.Assert(err, IsNil) + + m.Data = map[string]interface{}{"prefix": "foo"} + err = m.LoadMigrations("testdata/noforward") + c.Assert(err, Equals, migrate.ErrNoFwMigration) +} + +func (s *MigrateSuite) TestMigrate(c *C) { + m := s.createSampleMigrator(c) + + err := m.Migrate() + c.Assert(err, IsNil) + currentVersion := s.currentVersion(c) + c.Assert(currentVersion, Equals, int32(3)) +} + +func (s *MigrateSuite) TestMigrateToLifeCycle(c *C) { + m := s.createSampleMigrator(c) + + var onStartCallUpCount int + var onStartCallDownCount int + m.OnStart = func(_ int32, _, direction, _ string) { + switch direction { + case "up": + onStartCallUpCount++ + case "down": + onStartCallDownCount++ + default: + c.Fatalf("Unexpected direction: %s", direction) + } + } + + // Migrate from 0 up to 1 + err := m.MigrateTo(1) + c.Assert(err, IsNil) + currentVersion := s.currentVersion(c) + c.Assert(currentVersion, Equals, int32(1)) + c.Assert(s.tableExists(c, "t1"), Equals, true) + c.Assert(s.tableExists(c, "t2"), Equals, false) + c.Assert(s.tableExists(c, "t3"), Equals, false) + c.Assert(onStartCallUpCount, Equals, 1) + c.Assert(onStartCallDownCount, Equals, 0) + + // Migrate from 1 up to 3 + err = m.MigrateTo(3) + c.Assert(err, IsNil) + currentVersion = s.currentVersion(c) + c.Assert(currentVersion, Equals, int32(3)) + c.Assert(s.tableExists(c, "t1"), Equals, true) + c.Assert(s.tableExists(c, "t2"), Equals, true) + c.Assert(s.tableExists(c, "t3"), Equals, true) + c.Assert(onStartCallUpCount, Equals, 3) + c.Assert(onStartCallDownCount, Equals, 0) + + // Migrate from 3 to 3 is no-op + err = m.MigrateTo(3) + c.Assert(err, IsNil) + currentVersion = s.currentVersion(c) + c.Assert(s.tableExists(c, "t1"), Equals, true) + c.Assert(s.tableExists(c, "t2"), Equals, true) + c.Assert(s.tableExists(c, "t3"), Equals, true) + c.Assert(onStartCallUpCount, Equals, 3) + c.Assert(onStartCallDownCount, Equals, 0) + + // Migrate from 3 down to 1 + err = m.MigrateTo(1) + c.Assert(err, IsNil) + currentVersion = s.currentVersion(c) + c.Assert(currentVersion, Equals, int32(1)) + c.Assert(s.tableExists(c, "t1"), Equals, true) + c.Assert(s.tableExists(c, "t2"), Equals, false) + c.Assert(s.tableExists(c, "t3"), Equals, false) + c.Assert(onStartCallUpCount, Equals, 3) + c.Assert(onStartCallDownCount, Equals, 2) + + // Migrate from 1 down to 0 + err = m.MigrateTo(0) + c.Assert(err, IsNil) + currentVersion = s.currentVersion(c) + c.Assert(currentVersion, Equals, int32(0)) + c.Assert(s.tableExists(c, "t1"), Equals, false) + c.Assert(s.tableExists(c, "t2"), Equals, false) + c.Assert(s.tableExists(c, "t3"), Equals, false) + c.Assert(onStartCallUpCount, Equals, 3) + c.Assert(onStartCallDownCount, Equals, 3) + + // Migrate back up to 3 + err = m.MigrateTo(3) + c.Assert(err, IsNil) + currentVersion = s.currentVersion(c) + c.Assert(currentVersion, Equals, int32(3)) + c.Assert(s.tableExists(c, "t1"), Equals, true) + c.Assert(s.tableExists(c, "t2"), Equals, true) + c.Assert(s.tableExists(c, "t3"), Equals, true) + c.Assert(onStartCallUpCount, Equals, 6) + c.Assert(onStartCallDownCount, Equals, 3) +} + +func (s *MigrateSuite) TestMigrateToBoundaries(c *C) { + m := s.createSampleMigrator(c) + + // Migrate to -1 is error + err := m.MigrateTo(-1) + c.Assert(err, ErrorMatches, "destination version -1 is outside the valid versions of 0 to 3") + + // Migrate past end is error + err = m.MigrateTo(int32(len(m.Migrations)) + 1) + c.Assert(err, ErrorMatches, "destination version 4 is outside the valid versions of 0 to 3") + + // When schema version says it is negative + s.Exec(c, "update "+versionTable+" set version=-1") + err = m.MigrateTo(int32(1)) + c.Assert(err, ErrorMatches, "current version -1 is outside the valid versions of 0 to 3") + + // When schema version says it is negative + s.Exec(c, "update "+versionTable+" set version=4") + err = m.MigrateTo(int32(1)) + c.Assert(err, ErrorMatches, "current version 4 is outside the valid versions of 0 to 3") +} + +func (s *MigrateSuite) TestMigrateToIrreversible(c *C) { + m := s.createEmptyMigrator(c) + m.AppendMigration("Foo", "drop table if exists t3", "") + + err := m.MigrateTo(1) + c.Assert(err, IsNil) + + err = m.MigrateTo(0) + c.Assert(err, ErrorMatches, "Irreversible migration: 1 - Foo") +} + +func (s *MigrateSuite) TestMigrateToDisableTx(c *C) { + m, err := migrate.NewMigratorEx(s.conn, versionTable, &migrate.MigratorOptions{DisableTx: true}) + c.Assert(err, IsNil) + m.AppendMigration("Create t1", "create table t1(id serial);", "drop table t1;") + m.AppendMigration("Create t2", "create table t2(id serial);", "drop table t2;") + m.AppendMigration("Create t3", "create table t3(id serial);", "drop table t3;") + + tx, err := s.conn.Begin() + c.Assert(err, IsNil) + + err = m.MigrateTo(3) + c.Assert(err, IsNil) + currentVersion := s.currentVersion(c) + c.Assert(currentVersion, Equals, int32(3)) + c.Assert(s.tableExists(c, "t1"), Equals, true) + c.Assert(s.tableExists(c, "t2"), Equals, true) + c.Assert(s.tableExists(c, "t3"), Equals, true) + + err = tx.Rollback() + c.Assert(err, IsNil) + currentVersion = s.currentVersion(c) + c.Assert(currentVersion, Equals, int32(0)) + c.Assert(s.tableExists(c, "t1"), Equals, false) + c.Assert(s.tableExists(c, "t2"), Equals, false) + c.Assert(s.tableExists(c, "t3"), Equals, false) +} + +func Example_OnStartMigrationProgressLogging() { + conn, err := pgx.Connect(*defaultConnectionParameters) + if err != nil { + fmt.Printf("Unable to establish connection: %v", err) + return + } + + // Clear any previous runs + if _, err = conn.Exec("drop table if exists schema_version"); err != nil { + fmt.Printf("Unable to drop schema_version table: %v", err) + return + } + + var m *migrate.Migrator + m, err = migrate.NewMigrator(conn, "schema_version") + if err != nil { + fmt.Printf("Unable to create migrator: %v", err) + return + } + + m.OnStart = func(_ int32, name, direction, _ string) { + fmt.Printf("Migrating %s: %s", direction, name) + } + + m.AppendMigration("create a table", "create temporary table foo(id serial primary key)", "") + + if err = m.Migrate(); err != nil { + fmt.Printf("Unexpected failure migrating: %v", err) + return + } + // Output: + // Migrating up: create a table +} diff --git a/migrate/testdata/duplicate/001_create_t1.sql b/migrate/testdata/duplicate/001_create_t1.sql new file mode 100644 index 0000000..87a05c1 --- /dev/null +++ b/migrate/testdata/duplicate/001_create_t1.sql @@ -0,0 +1,7 @@ +create table t1( + id serial primary key +); + +---- create above / drop below ---- + +drop table t1; diff --git a/migrate/testdata/duplicate/002_create_t2.sql b/migrate/testdata/duplicate/002_create_t2.sql new file mode 100644 index 0000000..352e514 --- /dev/null +++ b/migrate/testdata/duplicate/002_create_t2.sql @@ -0,0 +1,7 @@ +create table t2( + id serial primary key +); + +---- create above / drop below ---- + +drop table t2; diff --git a/migrate/testdata/duplicate/002_duplicate.sql b/migrate/testdata/duplicate/002_duplicate.sql new file mode 100644 index 0000000..8e856ea --- /dev/null +++ b/migrate/testdata/duplicate/002_duplicate.sql @@ -0,0 +1,7 @@ +create table duplicate( + id serial primary key +); + +---- create above / drop below ---- + +drop table duplicate; diff --git a/migrate/testdata/empty/.gitignore b/migrate/testdata/empty/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/migrate/testdata/gap/001_create_people.sql.example b/migrate/testdata/gap/001_create_people.sql.example new file mode 100644 index 0000000..e69de29 diff --git a/migrate/testdata/gap/001_create_t1.sql b/migrate/testdata/gap/001_create_t1.sql new file mode 100644 index 0000000..87a05c1 --- /dev/null +++ b/migrate/testdata/gap/001_create_t1.sql @@ -0,0 +1,7 @@ +create table t1( + id serial primary key +); + +---- create above / drop below ---- + +drop table t1; diff --git a/migrate/testdata/gap/003_irreversible.sql b/migrate/testdata/gap/003_irreversible.sql new file mode 100644 index 0000000..fcb15c7 --- /dev/null +++ b/migrate/testdata/gap/003_irreversible.sql @@ -0,0 +1 @@ +drop table t2; diff --git a/migrate/testdata/noforward/001_create_no_forward.sql b/migrate/testdata/noforward/001_create_no_forward.sql new file mode 100644 index 0000000..50b3e38 --- /dev/null +++ b/migrate/testdata/noforward/001_create_no_forward.sql @@ -0,0 +1,7 @@ +-- no SQL here +-- nor here, just all comments. + -- comment with space before + +---- create above / drop below ---- + +drop table t1; diff --git a/migrate/testdata/sample/001_create_t1.sql b/migrate/testdata/sample/001_create_t1.sql new file mode 100644 index 0000000..87a05c1 --- /dev/null +++ b/migrate/testdata/sample/001_create_t1.sql @@ -0,0 +1,7 @@ +create table t1( + id serial primary key +); + +---- create above / drop below ---- + +drop table t1; diff --git a/migrate/testdata/sample/002_create_t2.sql b/migrate/testdata/sample/002_create_t2.sql new file mode 100644 index 0000000..352e514 --- /dev/null +++ b/migrate/testdata/sample/002_create_t2.sql @@ -0,0 +1,7 @@ +create table t2( + id serial primary key +); + +---- create above / drop below ---- + +drop table t2; diff --git a/migrate/testdata/sample/003_irreversible.sql b/migrate/testdata/sample/003_irreversible.sql new file mode 100644 index 0000000..fcb15c7 --- /dev/null +++ b/migrate/testdata/sample/003_irreversible.sql @@ -0,0 +1 @@ +drop table t2; diff --git a/migrate/testdata/sample/004_data_interpolation.sql b/migrate/testdata/sample/004_data_interpolation.sql new file mode 100644 index 0000000..0811b42 --- /dev/null +++ b/migrate/testdata/sample/004_data_interpolation.sql @@ -0,0 +1,5 @@ +create table {{.prefix}}_bar(id serial primary key); + +---- create above / drop below ---- + +drop table {{.prefix}}_bar; diff --git a/migrate/testdata/sample/005_template_inclusion.sql b/migrate/testdata/sample/005_template_inclusion.sql new file mode 100644 index 0000000..b09a3e5 --- /dev/null +++ b/migrate/testdata/sample/005_template_inclusion.sql @@ -0,0 +1,5 @@ +{{ template "shared/v1_001.sql" . }} + +---- create above / drop below ---- + +drop view {{.prefix}}v1; diff --git a/migrate/testdata/sample/shared/v1_001.sql b/migrate/testdata/sample/shared/v1_001.sql new file mode 100644 index 0000000..c72379a --- /dev/null +++ b/migrate/testdata/sample/shared/v1_001.sql @@ -0,0 +1 @@ +create view {{.prefix}}v1 as select * from t1; diff --git a/migrate/testdata/sample/should_be_ignored.sql b/migrate/testdata/sample/should_be_ignored.sql new file mode 100644 index 0000000..355933c --- /dev/null +++ b/migrate/testdata/sample/should_be_ignored.sql @@ -0,0 +1 @@ +-- This file should be ignored because it does not start with a number. diff --git a/psql/select_test.go b/psql/select_test.go index 47c99c8..80e373c 100644 --- a/psql/select_test.go +++ b/psql/select_test.go @@ -58,45 +58,45 @@ func TestMain(m *testing.M) { columns := [][]*DBColumn{ []*DBColumn{ - &DBColumn{ID: 1, Name: "id", Type: "bigint", NotNull: true, PrimaryKey: true, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 2, Name: "full_name", Type: "character varying", NotNull: true, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 3, Name: "phone", Type: "character varying", NotNull: false, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 4, Name: "email", Type: "character varying", NotNull: true, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 5, Name: "encrypted_password", Type: "character varying", NotNull: true, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 6, Name: "reset_password_token", Type: "character varying", NotNull: false, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 7, Name: "reset_password_sent_at", Type: "timestamp without time zone", NotNull: false, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 8, Name: "remember_created_at", Type: "timestamp without time zone", NotNull: false, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 9, Name: "created_at", Type: "timestamp without time zone", NotNull: true, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 10, Name: "updated_at", Type: "timestamp without time zone", NotNull: true, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}}, + &DBColumn{ID: 1, Name: "id", Type: "bigint", NotNull: true, PrimaryKey: true, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, + &DBColumn{ID: 2, Name: "full_name", Type: "character varying", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, + &DBColumn{ID: 3, Name: "phone", Type: "character varying", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, + &DBColumn{ID: 4, Name: "email", Type: "character varying", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, + &DBColumn{ID: 5, Name: "encrypted_password", Type: "character varying", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, + &DBColumn{ID: 6, Name: "reset_password_token", Type: "character varying", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, + &DBColumn{ID: 7, Name: "reset_password_sent_at", Type: "timestamp without time zone", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, + &DBColumn{ID: 8, Name: "remember_created_at", Type: "timestamp without time zone", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, + &DBColumn{ID: 9, Name: "created_at", Type: "timestamp without time zone", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, + &DBColumn{ID: 10, Name: "updated_at", Type: "timestamp without time zone", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}}, []*DBColumn{ - &DBColumn{ID: 1, Name: "id", Type: "bigint", NotNull: true, PrimaryKey: true, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 2, Name: "full_name", Type: "character varying", NotNull: true, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 3, Name: "phone", Type: "character varying", NotNull: false, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 4, Name: "avatar", Type: "character varying", NotNull: false, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 5, Name: "email", Type: "character varying", NotNull: true, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 6, Name: "encrypted_password", Type: "character varying", NotNull: true, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 7, Name: "reset_password_token", Type: "character varying", NotNull: false, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 8, Name: "reset_password_sent_at", Type: "timestamp without time zone", NotNull: false, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 9, Name: "remember_created_at", Type: "timestamp without time zone", NotNull: false, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 10, Name: "created_at", Type: "timestamp without time zone", NotNull: true, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 11, Name: "updated_at", Type: "timestamp without time zone", NotNull: true, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}}, + &DBColumn{ID: 1, Name: "id", Type: "bigint", NotNull: true, PrimaryKey: true, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, + &DBColumn{ID: 2, Name: "full_name", Type: "character varying", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, + &DBColumn{ID: 3, Name: "phone", Type: "character varying", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, + &DBColumn{ID: 4, Name: "avatar", Type: "character varying", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, + &DBColumn{ID: 5, Name: "email", Type: "character varying", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, + &DBColumn{ID: 6, Name: "encrypted_password", Type: "character varying", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, + &DBColumn{ID: 7, Name: "reset_password_token", Type: "character varying", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, + &DBColumn{ID: 8, Name: "reset_password_sent_at", Type: "timestamp without time zone", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, + &DBColumn{ID: 9, Name: "remember_created_at", Type: "timestamp without time zone", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, + &DBColumn{ID: 10, Name: "created_at", Type: "timestamp without time zone", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, + &DBColumn{ID: 11, Name: "updated_at", Type: "timestamp without time zone", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}}, []*DBColumn{ - &DBColumn{ID: 1, Name: "id", Type: "bigint", NotNull: true, PrimaryKey: true, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 2, Name: "name", Type: "character varying", NotNull: false, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 3, Name: "description", Type: "text", NotNull: false, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 4, Name: "price", Type: "numeric(7,2)", NotNull: false, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 5, Name: "user_id", Type: "bigint", NotNull: false, PrimaryKey: false, Uniquekey: false, FKeyTable: "users", FKeyColID: []int{1}}, - &DBColumn{ID: 6, Name: "created_at", Type: "timestamp without time zone", NotNull: true, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 7, Name: "updated_at", Type: "timestamp without time zone", NotNull: true, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 8, Name: "tsv", Type: "tsvector", NotNull: false, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}}, + &DBColumn{ID: 1, Name: "id", Type: "bigint", NotNull: true, PrimaryKey: true, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, + &DBColumn{ID: 2, Name: "name", Type: "character varying", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, + &DBColumn{ID: 3, Name: "description", Type: "text", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, + &DBColumn{ID: 4, Name: "price", Type: "numeric(7,2)", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, + &DBColumn{ID: 5, Name: "user_id", Type: "bigint", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "users", FKeyColID: []int{1}}, + &DBColumn{ID: 6, Name: "created_at", Type: "timestamp without time zone", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, + &DBColumn{ID: 7, Name: "updated_at", Type: "timestamp without time zone", NotNull: true, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, + &DBColumn{ID: 8, Name: "tsv", Type: "tsvector", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}}, []*DBColumn{ - &DBColumn{ID: 1, Name: "id", Type: "bigint", NotNull: true, PrimaryKey: true, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 2, Name: "customer_id", Type: "bigint", NotNull: false, PrimaryKey: false, Uniquekey: false, FKeyTable: "customers", FKeyColID: []int{1}}, - &DBColumn{ID: 3, Name: "product_id", Type: "bigint", NotNull: false, PrimaryKey: false, Uniquekey: false, FKeyTable: "products", FKeyColID: []int{1}}, - &DBColumn{ID: 4, Name: "sale_type", Type: "character varying", NotNull: false, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 5, Name: "quantity", Type: "integer", NotNull: false, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 6, Name: "due_date", Type: "timestamp without time zone", NotNull: false, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}, - &DBColumn{ID: 7, Name: "returned", Type: "timestamp without time zone", NotNull: false, PrimaryKey: false, Uniquekey: false, FKeyTable: "", FKeyColID: []int(nil)}}, + &DBColumn{ID: 1, Name: "id", Type: "bigint", NotNull: true, PrimaryKey: true, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, + &DBColumn{ID: 2, Name: "customer_id", Type: "bigint", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "customers", FKeyColID: []int{1}}, + &DBColumn{ID: 3, Name: "product_id", Type: "bigint", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "products", FKeyColID: []int{1}}, + &DBColumn{ID: 4, Name: "sale_type", Type: "character varying", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, + &DBColumn{ID: 5, Name: "quantity", Type: "integer", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, + &DBColumn{ID: 6, Name: "due_date", Type: "timestamp without time zone", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}, + &DBColumn{ID: 7, Name: "returned", Type: "timestamp without time zone", NotNull: false, PrimaryKey: false, UniqueKey: false, FKeyTable: "", FKeyColID: []int(nil)}}, } schema := &DBSchema{ diff --git a/psql/tables.go b/psql/tables.go index 2c94230..c2ad44a 100644 --- a/psql/tables.go +++ b/psql/tables.go @@ -1,26 +1,29 @@ package psql import ( + "context" "fmt" "strings" - "github.com/go-pg/pg" "github.com/gobuffalo/flect" + "github.com/jackc/pgx/pgtype" + "github.com/jackc/pgx/v4/pgxpool" ) type DBTable struct { - Name string `sql:"name"` - Type string `sql:"type"` + Name string + Type string } -func GetTables(db *pg.DB) ([]*DBTable, error) { +func GetTables(dbc *pgxpool.Conn) ([]*DBTable, error) { sqlStmt := ` SELECT c.relname as "name", CASE c.relkind WHEN 'r' THEN 'table' - WHEN 'v' THEN 'view' - WHEN 'm' THEN 'materialized view' - WHEN 'f' THEN 'foreign table' END as "type" + WHEN 'v' THEN 'view' + WHEN 'm' THEN 'materialized view' + WHEN 'f' THEN 'foreign table' + END as "type" FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN ('r','v','m','f','') @@ -30,28 +33,39 @@ WHERE c.relkind IN ('r','v','m','f','') AND pg_catalog.pg_table_is_visible(c.oid); ` - var t []*DBTable - _, err := db.Query(&t, sqlStmt) + var tables []*DBTable + rows, err := dbc.Query(context.Background(), sqlStmt) if err != nil { return nil, fmt.Errorf("Error fetching tables: %s", err) } + defer rows.Close() - return t, nil + for rows.Next() { + t := DBTable{} + err = rows.Scan(&t.Name, &t.Type) + if err != nil { + return nil, err + } + tables = append(tables, &t) + } + + return tables, nil } type DBColumn struct { - ID int `sql:"id"` - Name string `sql:"name"` - Type string `sql:"type"` - NotNull bool `sql:"notnull"` - PrimaryKey bool `sql:"primarykey"` - Uniquekey bool `sql:"uniquekey"` - FKeyTable string `sql:"foreignkey"` - FKeyColID []int `sql:"foreignkey_fieldnum,array"` + ID int + Name string + Type string + NotNull bool + PrimaryKey bool + UniqueKey bool + FKeyTable string + FKeyColID []int + fKeyColID pgtype.Int2Array } -func GetColumns(db *pg.DB, schema, table string) ([]*DBColumn, error) { +func GetColumns(dbc *pgxpool.Conn, schema, table string) ([]*DBColumn, error) { sqlStmt := ` SELECT f.attnum AS id, @@ -59,18 +73,20 @@ func GetColumns(db *pg.DB, schema, table string) ([]*DBColumn, error) { f.attnotnull AS notnull, pg_catalog.format_type(f.atttypid,f.atttypmod) AS type, CASE - WHEN p.contype = 'p' THEN 't' - ELSE 'f' + WHEN p.contype = 'p' THEN true + ELSE false END AS primarykey, CASE - WHEN p.contype = 'u' THEN 't' - ELSE 'f' + WHEN p.contype = 'u' THEN true + ELSE false END AS uniquekey, CASE - WHEN p.contype = 'f' THEN g.relname + WHEN p.contype = 'f' THEN g.relname + ELSE ''::text END AS foreignkey, CASE - WHEN p.contype = 'f' THEN p.confkey + WHEN p.contype = 'f' THEN p.confkey + ELSE ARRAY[]::int2[] END AS foreignkey_fieldnum FROM pg_attribute f JOIN pg_class c ON c.oid = f.attrelid @@ -85,18 +101,26 @@ WHERE c.relkind = 'r'::char AND f.attnum > 0 ORDER BY id; ` - stmt, err := db.Prepare(sqlStmt) + var cols []*DBColumn + + rows, err := dbc.Query(context.Background(), sqlStmt, schema, table) if err != nil { return nil, fmt.Errorf("error fetching columns: %s", err) } + defer rows.Close() - var t []*DBColumn - _, err = stmt.Query(&t, schema, table) - if err != nil { - return nil, fmt.Errorf("error fetching columns: %s", err) + for rows.Next() { + c := DBColumn{} + err = rows.Scan(&c.ID, &c.Name, &c.NotNull, &c.Type, &c.PrimaryKey, &c.UniqueKey, + &c.FKeyTable, &c.fKeyColID) + if err != nil { + return nil, err + } + c.fKeyColID.AssignTo(&c.FKeyColID) + cols = append(cols, &c) } - return t, nil + return cols, nil } type DBSchema struct { @@ -131,20 +155,26 @@ type DBRel struct { Col2 string } -func NewDBSchema(db *pg.DB, aliases map[string][]string) (*DBSchema, error) { +func NewDBSchema(db *pgxpool.Pool, aliases map[string][]string) (*DBSchema, error) { schema := &DBSchema{ t: make(map[string]*DBTableInfo), rm: make(map[string]map[string]*DBRel), al: make(map[string]struct{}), } - tables, err := GetTables(db) + dbc, err := db.Acquire(context.Background()) + if err != nil { + return nil, fmt.Errorf("error acquiring connection from pool") + } + defer dbc.Release() + + tables, err := GetTables(dbc) if err != nil { return nil, err } for _, t := range tables { - cols, err := GetColumns(db, "public", t.Name) + cols, err := GetColumns(dbc, "public", t.Name) if err != nil { return nil, err } diff --git a/serv/allow.go b/serv/allow.go index a0f347c..1d5c7b3 100644 --- a/serv/allow.go +++ b/serv/allow.go @@ -1,6 +1,7 @@ package serv import ( + "bytes" "encoding/json" "fmt" "io/ioutil" @@ -206,7 +207,7 @@ func (al *allowList) save(item *allowItem) { f.WriteString(fmt.Sprintf("# %s\n\n", k)) for i := range v { - if len(v[i].vars) != 0 { + if len(v[i].vars) != 0 && bytes.Equal(v[i].vars, []byte("{}")) == false { vj, err := json.MarshalIndent(v[i].vars, "", "\t") if err != nil { logger.Warn().Err(err).Msg("Failed to write allow list 'vars' to file") @@ -215,7 +216,11 @@ func (al *allowList) save(item *allowItem) { f.WriteString(fmt.Sprintf("variables %s\n\n", vj)) } - f.WriteString(fmt.Sprintf("%s\n\n", v[i].gql)) + if v[i].gql[0] == '{' { + f.WriteString(fmt.Sprintf("query %s\n\n", v[i].gql)) + } else { + f.WriteString(fmt.Sprintf("%s\n\n", v[i].gql)) + } } } } diff --git a/serv/cmd.go b/serv/cmd.go index 663fd1f..8eff55b 100644 --- a/serv/cmd.go +++ b/serv/cmd.go @@ -1,17 +1,19 @@ package serv import ( - "errors" - "flag" + "context" "fmt" "os" "strings" "github.com/dosco/super-graph/psql" "github.com/dosco/super-graph/qcode" - "github.com/go-pg/pg" "github.com/gobuffalo/flect" + "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v4/log/zerologadapter" + "github.com/jackc/pgx/v4/pgxpool" "github.com/rs/zerolog" + "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -28,12 +30,109 @@ const ( var ( logger *zerolog.Logger conf *config - db *pg.DB + confPath string + db *pgxpool.Pool qcompile *qcode.Compiler pcompile *psql.Compiler authFailBlock int + + rootCmd *cobra.Command + servCmd *cobra.Command + seedCmd *cobra.Command + migrateCmd *cobra.Command + statusCmd *cobra.Command + newMigrationCmd *cobra.Command ) +func Init() { + var err error + + rootCmd = &cobra.Command{ + Use: "super-graph", + Short: "An instant high-performance GraphQL API. No code needed. https://supergraph.dev", + //Run: cmdServ, + } + + seedCmd = &cobra.Command{ + Use: "seed", + Short: "Run the seed script to seed the database", + Run: cmdSeed, + } + + servCmd = &cobra.Command{ + Use: "serv", + Short: "Run the super-graph service", + Run: cmdServ, + } + + migrateCmd = &cobra.Command{ + Use: "migrate", + Short: "Migrate the database", + Long: `Migrate the database to destination migration version. + +Destination migration version can be one of the following value types: + +An integer: +Migrate to a specific migration. +e.g. tern migrate -d 42 + +"+" and an integer: +Migrate forward N steps. +e.g. tern migrate -d +3 + +"-" and an integer: +Migrate backward N steps. +e.g. tern migrate -d -2 + +"-+" and an integer: +Redo previous N steps (migrate backward N steps then forward N steps). +e.g. tern migrate -d -+1 + +The word "last": +Migrate to the most recent migration. This is the default value, so it is +never needed to specify directly. +e.g. tern migrate +e.g. tern migrate -d last + `, + Run: cmdMigrate, + } + + statusCmd = &cobra.Command{ + Use: "status", + Short: "Print current migration status", + Run: cmdStatus, + } + + newMigrationCmd = &cobra.Command{ + Use: "new NAME", + Short: "Generate a new migration", + Long: "Generate a new migration with the next sequence number and provided name", + Run: cmdNewMigration, + } + + logger = initLog() + + rootCmd.Flags().StringVar(&confPath, + "path", "./config", "path to config files") + + //cmdMigrate.Flags().StringVarP(&cliOptions.destinationVersion, + // "destination", "d", "last", "destination migration version") + + rootCmd.AddCommand(servCmd) + rootCmd.AddCommand(seedCmd) + rootCmd.AddCommand(migrateCmd) + rootCmd.AddCommand(statusCmd) + rootCmd.AddCommand(newMigrationCmd) + + if conf, err = initConf(); err != nil { + logger.Fatal().Err(err).Msg("failed to read config") + } + + if err := rootCmd.Execute(); err != nil { + logger.Fatal().Err(err).Send() + } +} + func initLog() *zerolog.Logger { logger := zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}). With(). @@ -42,24 +141,16 @@ func initLog() *zerolog.Logger { Logger() return &logger - /* - log := logrus.New() - logger.Formatter = new(logrus.TextFormatter) - logger.Formatter.(*logrus.TextFormatter).DisableColors = false - logger.Formatter.(*logrus.TextFormatter).DisableTimestamp = true - logger.Level = logrus.TraceLevel - logger.Out = os.Stdout - */ } -func initConf(path string) (*config, error) { +func initConf() (*config, error) { vi := viper.New() vi.SetEnvPrefix("SG") vi.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) vi.AutomaticEnv() - vi.AddConfigPath(path) + vi.AddConfigPath(confPath) vi.AddConfigPath("./config") vi.SetConfigName(getConfigName()) @@ -73,6 +164,7 @@ func initConf(path string) (*config, error) { vi.SetDefault("database.host", "localhost") vi.SetDefault("database.port", 5432) vi.SetDefault("database.user", "postgres") + vi.SetDefault("database.schema", "public") vi.SetDefault("env", "development") vi.BindEnv("env", "GO_ENV") @@ -103,102 +195,104 @@ func initConf(path string) (*config, error) { authFailBlock = getAuthFailBlock(c) - //fmt.Printf("%#v", c) - - return c, nil -} - -func initDB(c *config) (*pg.DB, error) { - opt := &pg.Options{ - Addr: strings.Join([]string{c.DB.Host, c.DB.Port}, ":"), - User: c.DB.User, - Password: c.DB.Password, - Database: c.DB.DBName, - ApplicationName: c.AppName, - } - - if c.DB.PoolSize != 0 { - opt.PoolSize = conf.DB.PoolSize - } - - if c.DB.MaxRetries != 0 { - opt.MaxRetries = c.DB.MaxRetries - } - - if len(c.DB.Schema) != 0 { - opt.OnConnect = func(conn *pg.Conn) error { - _, err := conn.Exec("set search_path=?", c.DB.Schema) - if err != nil { - return err - } - return nil - } - } - - db := pg.Connect(opt) - if db == nil { - return nil, errors.New("failed to connect to postgres db") - } - - return db, nil -} - -func Init() { - var err error - - path := flag.String("path", "./config", "Path to config files") - flag.Parse() - - logger = initLog() - - conf, err = initConf(*path) - if err != nil { - logger.Fatal().Err(err).Msg("failed to read config") - } - - logLevel, err := zerolog.ParseLevel(conf.LogLevel) + logLevel, err := zerolog.ParseLevel(c.LogLevel) if err != nil { logger.Error().Err(err).Msg("error setting log_level") } zerolog.SetGlobalLevel(logLevel) - db, err = initDB(conf) - if err != nil { - logger.Fatal().Err(err).Msg("failed to connect to database") + //fmt.Printf("%#v", c) + + return c, nil +} + +func initDB(c *config) (*pgx.Conn, error) { + config, _ := pgx.ParseConfig("") + config.Host = c.DB.Host + config.Port = c.DB.Port + config.Database = c.DB.DBName + config.User = c.DB.User + config.Password = c.DB.Password + config.RuntimeParams = map[string]string{ + "application_name": c.AppName, + "search_path": c.DB.Schema, } + switch c.LogLevel { + case "debug": + config.LogLevel = pgx.LogLevelDebug + case "info": + config.LogLevel = pgx.LogLevelInfo + case "warn": + config.LogLevel = pgx.LogLevelWarn + case "error": + config.LogLevel = pgx.LogLevelError + default: + config.LogLevel = pgx.LogLevelNone + } + + config.Logger = zerologadapter.NewLogger(*logger) + + db, err := pgx.ConnectConfig(context.Background(), config) + if err != nil { + return nil, err + } + + return db, nil +} + +func initDBPool(c *config) (*pgxpool.Pool, error) { + config, _ := pgxpool.ParseConfig("") + config.ConnConfig.Host = c.DB.Host + config.ConnConfig.Port = c.DB.Port + config.ConnConfig.Database = c.DB.DBName + config.ConnConfig.User = c.DB.User + config.ConnConfig.Password = c.DB.Password + config.ConnConfig.RuntimeParams = map[string]string{ + "application_name": c.AppName, + "search_path": c.DB.Schema, + } + + switch c.LogLevel { + case "debug": + config.ConnConfig.LogLevel = pgx.LogLevelDebug + case "info": + config.ConnConfig.LogLevel = pgx.LogLevelInfo + case "warn": + config.ConnConfig.LogLevel = pgx.LogLevelWarn + case "error": + config.ConnConfig.LogLevel = pgx.LogLevelError + default: + config.ConnConfig.LogLevel = pgx.LogLevelNone + } + + config.ConnConfig.Logger = zerologadapter.NewLogger(*logger) + + // if c.DB.MaxRetries != 0 { + // opt.MaxRetries = c.DB.MaxRetries + // } + + if c.DB.PoolSize != 0 { + config.MaxConns = conf.DB.PoolSize + } + + db, err := pgxpool.ConnectConfig(context.Background(), config) + if err != nil { + return nil, err + } + + return db, nil +} + +func initCompiler() { + var err error + qcompile, pcompile, err = initCompilers(conf) if err != nil { - logger.Fatal().Err(err).Msg("failed to connect to database") + logger.Fatal().Err(err).Msg("failed to initialize compilers") } if err := initResolvers(); err != nil { logger.Fatal().Err(err).Msg("failed to initialized resolvers") } - - args := flag.Args() - - if len(args) == 0 { - cmdServ(*path) - } - - switch args[0] { - case "seed": - cmdSeed(*path) - - case "serv": - fallthrough - - default: - logger.Fatal().Msg("options: [serve|seed]") - } - -} - -func cmdServ(path string) { - initAllowList(path) - initPreparedList() - initWatcher(path) - - startHTTP() } diff --git a/serv/seed.go b/serv/cmd_seed.go similarity index 96% rename from serv/seed.go rename to serv/cmd_seed.go index 19732d7..30b221b 100644 --- a/serv/seed.go +++ b/serv/cmd_seed.go @@ -11,12 +11,21 @@ import ( "github.com/brianvoe/gofakeit" "github.com/dop251/goja" + "github.com/spf13/cobra" ) -func cmdSeed(cpath string) { +func cmdSeed(cmd *cobra.Command, args []string) { + var err error conf.UseAllowList = false - b, err := ioutil.ReadFile(path.Join(cpath, conf.SeedFile)) + db, err = initDBPool(conf) + if err != nil { + logger.Fatal().Err(err).Msg("failed to connect to database") + } + + initCompiler() + + b, err := ioutil.ReadFile(path.Join(confPath, conf.SeedFile)) if err != nil { logger.Fatal().Err(err).Msg("failed to read file") } diff --git a/serv/cmd_serv.go b/serv/cmd_serv.go new file mode 100644 index 0000000..16ee1c5 --- /dev/null +++ b/serv/cmd_serv.go @@ -0,0 +1,21 @@ +package serv + +import ( + "github.com/spf13/cobra" +) + +func cmdServ(cmd *cobra.Command, args []string) { + var err error + + db, err = initDBPool(conf) + if err != nil { + logger.Fatal().Err(err).Msg("failed to connect to database") + } + + initCompiler() + initAllowList(confPath) + initPreparedList() + initWatcher(confPath) + + startHTTP() +} diff --git a/serv/cmd_tern.go b/serv/cmd_tern.go new file mode 100644 index 0000000..3481f77 --- /dev/null +++ b/serv/cmd_tern.go @@ -0,0 +1,231 @@ +package serv + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/dosco/super-graph/migrate" + "github.com/spf13/cobra" +) + +var sampleMigration = `-- This is a sample migration. + +create table users( + id serial primary key, + fullname varchar not null, + email varchar not null +); + +---- create above / drop below ---- + +drop table users; +` + +var newMigrationText = `-- Write your migrate up statements here + +---- create above / drop below ---- + +-- Write your migrate down statements here. If this migration is irreversible +-- Then delete the separator line above. +` + +func cmdNewMigration(cmd *cobra.Command, args []string) { + if len(args) != 1 { + cmd.Help() + os.Exit(1) + } + + name := args[0] + + m, err := migrate.FindMigrations(conf.MigrationsPath) + if err != nil { + fmt.Fprintf(os.Stderr, "Error loading migrations:\n %v\n", err) + os.Exit(1) + } + + mname := fmt.Sprintf("%03d_%s.sql", len(m)+100, name) + + // Write new migration + mpath := filepath.Join(conf.MigrationsPath, mname) + mfile, err := os.OpenFile(mpath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0666) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + defer mfile.Close() + + _, err = mfile.WriteString(newMigrationText) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + logger.Info().Msgf("created migration '%s'\n", mpath) +} + +func cmdMigrate(cmd *cobra.Command, args []string) { + conn, err := initDB(conf) + if err != nil { + logger.Fatal().Err(err).Msg("failed to connect to database") + } + defer conn.Close(context.Background()) + + m, err := migrate.NewMigrator(conn, "schema_version") + if err != nil { + logger.Fatal().Err(err).Msg("failed to initializing migrator") + } + //m.Data = config.Data + + err = m.LoadMigrations(conf.MigrationsPath) + if err != nil { + logger.Fatal().Err(err).Msg("failed to load migrations") + } + + if len(m.Migrations) == 0 { + logger.Fatal().Msg("No migrations found") + } + + m.OnStart = func(sequence int32, name, direction, sql string) { + logger.Info().Msgf("%s executing %s %s\n%s\n\n", + time.Now().Format("2006-01-02 15:04:05"), name, direction, sql) + } + + var currentVersion int32 + currentVersion, err = m.GetCurrentVersion() + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to get current version:\n %v\n", err) + os.Exit(1) + } + + dest := args[0] + mustParseDestination := func(d string) int32 { + var n int64 + n, err = strconv.ParseInt(d, 10, 32) + if err != nil { + logger.Fatal().Err(err).Msg("invalid destination") + } + return int32(n) + } + + if dest == "last" { + err = m.Migrate() + + } else if len(dest) >= 3 && dest[0:2] == "-+" { + err = m.MigrateTo(currentVersion - mustParseDestination(dest[2:])) + if err == nil { + err = m.MigrateTo(currentVersion) + } + + } else if len(dest) >= 2 && dest[0] == '-' { + err = m.MigrateTo(currentVersion - mustParseDestination(dest[1:])) + + } else if len(dest) >= 2 && dest[0] == '+' { + err = m.MigrateTo(currentVersion + mustParseDestination(dest[1:])) + + } else { + //err = make(type, 0).MigrateTo(mustParseDestination(dest)) + } + + if err != nil { + logger.Info().Err(err).Send() + + // logger.Info().Err(err).Send() + + // if err, ok := err.(m.MigrationPgError); ok { + // if err.Detail != "" { + // logger.Info().Err(err).Msg(err.Detail) + // } + + // if err.Position != 0 { + // ele, err := ExtractErrorLine(err.Sql, int(err.Position)) + // if err != nil { + // logger.Fatal().Err(err).Send() + // } + + // prefix := fmt.Sprintf() + // logger.Info().Msgf("line %d, %s%s", ele.LineNum, prefix, ele.Text) + // } + // } + // os.Exit(1) + } + logger.Info().Msg("migration done") + +} + +func cmdStatus(cmd *cobra.Command, args []string) { + conn, err := initDB(conf) + if err != nil { + logger.Fatal().Err(err).Msg("failed to connect to database") + } + defer conn.Close(context.Background()) + + m, err := migrate.NewMigrator(conn, "schema_version") + if err != nil { + logger.Fatal().Err(err).Msg("failed to initialize migrator") + } + //m.Data = config.Data + + err = m.LoadMigrations(conf.MigrationsPath) + if err != nil { + logger.Fatal().Err(err).Msg("failed to load migrations") + } + + if len(m.Migrations) == 0 { + logger.Fatal().Msg("no migrations found") + } + + mver, err := m.GetCurrentVersion() + if err != nil { + logger.Fatal().Err(err).Msg("failed to retrieve migration") + } + + var status string + behindCount := len(m.Migrations) - int(mver) + if behindCount == 0 { + status = "up to date" + } else { + status = "migration(s) pending" + } + + fmt.Println("status: ", status) + fmt.Println("version: %d of %d\n", mver, len(m.Migrations)) + fmt.Println("host: ", conf.DB.Host) + fmt.Println("database:", conf.DB.DBName) +} + +type ErrorLineExtract struct { + LineNum int // Line number starting with 1 + ColumnNum int // Column number starting with 1 + Text string // Text of the line without a new line character. +} + +// ExtractErrorLine takes source and character position extracts the line +// number, column number, and the line of text. +// +// The first character is position 1. +func ExtractErrorLine(source string, position int) (ErrorLineExtract, error) { + ele := ErrorLineExtract{LineNum: 1} + + if position > len(source) { + return ele, fmt.Errorf("position (%d) is greater than source length (%d)", position, len(source)) + } + + lines := strings.SplitAfter(source, "\n") + for _, ele.Text = range lines { + if position-len(ele.Text) < 1 { + ele.ColumnNum = position + break + } + + ele.LineNum += 1 + position -= len(ele.Text) + } + + ele.Text = strings.TrimSuffix(ele.Text, "\n") + + return ele, nil +} diff --git a/serv/cmd_test.go b/serv/cmd_test.go new file mode 100644 index 0000000..9353985 --- /dev/null +++ b/serv/cmd_test.go @@ -0,0 +1,104 @@ +package serv_test + +import ( + "testing" +) + +func TestErrorLineExtract(t *testing.T) { + tests := []struct { + source string + position int + ele ErrorLineExtract + errMsg string + }{ + { + source: "single line", + position: 3, + ele: ErrorLineExtract{ + LineNum: 1, + ColumnNum: 3, + Text: "single line", + }, + errMsg: "", + }, + { + source: "bad position", + position: 32, + ele: ErrorLineExtract{}, + errMsg: "position (32) is greater than source length (12)", + }, + { + source: `multi +line +text`, + position: 8, + ele: ErrorLineExtract{ + LineNum: 2, + ColumnNum: 2, + Text: "line", + }, + errMsg: "", + }, + { + source: `last +line +error`, + position: 13, + ele: ErrorLineExtract{ + LineNum: 3, + ColumnNum: 3, + Text: "error", + }, + errMsg: "", + }, + { + source: `first +character +first +line +error`, + position: 1, + ele: ErrorLineExtract{ + LineNum: 1, + ColumnNum: 1, + Text: "first", + }, + errMsg: "", + }, + { + source: `last +character +last +line +error`, + position: 30, + ele: ErrorLineExtract{ + LineNum: 5, + ColumnNum: 5, + Text: "error", + }, + errMsg: "", + }, + } + + for i, tt := range tests { + ele, err := ExtractErrorLine(tt.source, tt.position) + if err != nil { + if tt.errMsg == "" { + t.Errorf("%d. Expected success but received err %v", i, err) + } else if err.Error() != tt.errMsg { + t.Errorf("%d. Expected err %v, but received %v", i, tt.errMsg, err) + } + continue + } + + if tt.errMsg != "" { + t.Errorf("%d. Expected err %v, but it succeeded", i, tt.errMsg) + continue + } + + if ele != tt.ele { + t.Errorf("%d. Expected %v, but received %v", i, tt.ele, ele) + } + } +} diff --git a/serv/config.go b/serv/config.go index e4a4a0f..71c8d94 100644 --- a/serv/config.go +++ b/serv/config.go @@ -19,7 +19,9 @@ type config struct { WatchAndReload bool `mapstructure:"reload_on_config_change"` AuthFailBlock string `mapstructure:"auth_fail_block"` SeedFile string `mapstructure:"seed_file"` - Inflections map[string]string + MigrationsPath string `mapstructure:"migrations_path"` + + Inflections map[string]string Auth struct { Type string @@ -49,12 +51,12 @@ type config struct { DB struct { Type string Host string - Port string + Port uint16 DBName string User string Password string Schema string - PoolSize int `mapstructure:"pool_size"` + PoolSize int32 `mapstructure:"pool_size"` MaxRetries int `mapstructure:"max_retries"` LogLevel string `mapstructure:"log_level"` diff --git a/serv/core.go b/serv/core.go index aaf27da..9c7f3ee 100644 --- a/serv/core.go +++ b/serv/core.go @@ -8,7 +8,6 @@ import ( "fmt" "io" "net/http" - "os" "sync" "time" @@ -16,7 +15,6 @@ import ( "github.com/dosco/super-graph/jsn" "github.com/dosco/super-graph/psql" "github.com/dosco/super-graph/qcode" - "github.com/go-pg/pg" "github.com/valyala/fasttemplate" ) @@ -267,45 +265,45 @@ func (c *coreContext) resolvePreparedSQL(gql string) ([]byte, *preparedItem, err return nil, nil, errUnauthorized } - var root json.RawMessage + var root []byte vars := varList(c, ps.args) - tx, err := db.Begin() + tx, err := db.Begin(c) if err != nil { return nil, nil, err } - defer tx.Rollback() + defer tx.Rollback(c) if v := c.Value(userIDKey); v != nil { - _, err = tx.Exec(`SET LOCAL "user.id" = ?`, v) + _, err = tx.Exec(c, fmt.Sprintf(`SET LOCAL "user.id" = %s;`, v)) if err != nil { return nil, nil, err } } - _, err = tx.Stmt(ps.stmt).QueryOne(pg.Scan(&root), vars...) + err = tx.QueryRow(c, ps.stmt.SQL, vars...).Scan(&root) if err != nil { return nil, nil, err } - if err := tx.Commit(); err != nil { + if err := tx.Commit(c); err != nil { return nil, nil, err } fmt.Printf("PRE: %v\n", ps.stmt) - return []byte(root), ps, nil + return root, ps, nil } -func (c *coreContext) resolveSQL(qc *qcode.QCode) ( - []byte, uint32, error) { +func (c *coreContext) resolveSQL(qc *qcode.QCode) ([]byte, uint32, error) { + var vars map[string]json.RawMessage stmt := &bytes.Buffer{} - vars := make(map[string]json.RawMessage) - - if err := json.Unmarshal(c.req.Vars, &vars); err != nil { - return nil, 0, err + if len(c.req.Vars) != 0 { + if err := json.Unmarshal(c.req.Vars, &vars); err != nil { + return nil, 0, err + } } skipped, err := pcompile.Compile(qc, stmt, psql.Variables(vars)) @@ -330,10 +328,10 @@ func (c *coreContext) resolveSQL(qc *qcode.QCode) ( finalSQL := stmt.String() - if conf.LogLevel == "debug" { - os.Stdout.WriteString(finalSQL) - os.Stdout.WriteString("\n\n") - } + // if conf.LogLevel == "debug" { + // os.Stdout.WriteString(finalSQL) + // os.Stdout.WriteString("\n\n") + // } var st time.Time @@ -341,14 +339,14 @@ func (c *coreContext) resolveSQL(qc *qcode.QCode) ( st = time.Now() } - tx, err := db.Begin() + tx, err := db.Begin(c) if err != nil { return nil, 0, err } - defer tx.Rollback() + defer tx.Rollback(c) if v := c.Value(userIDKey); v != nil { - _, err = tx.Exec(`SET LOCAL "user.id" = ?`, v) + _, err = tx.Exec(c, fmt.Sprintf(`SET LOCAL "user.id" = %s;`, v)) if err != nil { return nil, 0, err @@ -357,14 +355,14 @@ func (c *coreContext) resolveSQL(qc *qcode.QCode) ( //fmt.Printf("\nRAW: %#v\n", finalSQL) - var root json.RawMessage - _, err = tx.QueryOne(pg.Scan(&root), finalSQL) + var root []byte + err = tx.QueryRow(c, finalSQL).Scan(&root) if err != nil { return nil, 0, err } - if err := tx.Commit(); err != nil { + if err := tx.Commit(c); err != nil { return nil, 0, err } @@ -379,7 +377,7 @@ func (c *coreContext) resolveSQL(qc *qcode.QCode) ( _allowList.add(&c.req) } - return []byte(root), skipped, nil + return root, skipped, nil } func (c *coreContext) render(w io.Writer, data []byte) error { diff --git a/serv/prepare.go b/serv/prepare.go index 906b55d..bee390c 100644 --- a/serv/prepare.go +++ b/serv/prepare.go @@ -2,18 +2,19 @@ package serv import ( "bytes" + "context" "encoding/json" "fmt" "io" "github.com/dosco/super-graph/psql" "github.com/dosco/super-graph/qcode" - "github.com/go-pg/pg" + "github.com/jackc/pgconn" "github.com/valyala/fasttemplate" ) type preparedItem struct { - stmt *pg.Stmt + stmt *pgconn.StatementDescription args [][]byte skipped uint32 qc *qcode.QCode @@ -75,7 +76,15 @@ func prepareStmt(key, gql string, varBytes json.RawMessage) error { return err } - pstmt, err := db.Prepare(finalSQL) + ctx := context.Background() + + tx, err := db.Begin(ctx) + if err != nil { + return err + } + defer tx.Rollback(ctx) + + pstmt, err := tx.Prepare(ctx, "", finalSQL) if err != nil { return err } @@ -87,5 +96,9 @@ func prepareStmt(key, gql string, varBytes json.RawMessage) error { qc: qc, } + if err := tx.Commit(ctx); err != nil { + return err + } + return nil } diff --git a/serv/serv.go b/serv/serv.go index 54f79a9..f3c0df9 100644 --- a/serv/serv.go +++ b/serv/serv.go @@ -92,9 +92,7 @@ func startHTTP() { }() srv.RegisterOnShutdown(func() { - if err := db.Close(); err != nil { - logger.Error().Err(err).Msg("db closed") - } + db.Close() }) fmt.Printf("%s listening on %s (%s)\n", serverName, hostPort, conf.Env)