diff --git a/config.yml.sample b/config.yml.sample index 30b149a566..e6175b32ec 100644 --- a/config.yml.sample +++ b/config.yml.sample @@ -112,6 +112,8 @@ log: standard: "stdout" # Whether or not to log database queries. Useful for debugging. Possible values are stdout, stderr, file or off to disable database logging. database: "off" + # The log level for database log messages. Possible values are CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG. + databaselevel: "DEBUG" # Whether to log http requests or not. Possible values are stdout, stderr, file or off to disable http logging. http: "stdout" # Echo has its own logging which usually is unnessecary, which is why it is disabled by default. Possible values are stdout, stderr, file or off to disable standard logging. diff --git a/docs/content/doc/setup/config.md b/docs/content/doc/setup/config.md index c78dedc490..5519d39f51 100644 --- a/docs/content/doc/setup/config.md +++ b/docs/content/doc/setup/config.md @@ -155,6 +155,8 @@ log: standard: "stdout" # Whether or not to log database queries. Useful for debugging. Possible values are stdout, stderr, file or off to disable database logging. database: "off" + # The log level for database log messages. Possible values are CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG. + databaselevel: "DEBUG" # Whether to log http requests or not. Possible values are stdout, stderr, file or off to disable http logging. http: "stdout" # Echo has its own logging which usually is unnessecary, which is why it is disabled by default. Possible values are stdout, stderr, file or off to disable standard logging. diff --git a/go.mod b/go.mod index 156947f207..4fee4cb41d 100644 --- a/go.mod +++ b/go.mod @@ -30,20 +30,22 @@ require ( github.com/cweill/gotests v1.5.3 github.com/d4l3k/messagediff v1.2.1 // indirect github.com/dgrijalva/jwt-go v3.2.0+incompatible + github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835 github.com/garyburd/redigo v1.6.0 // indirect github.com/go-openapi/jsonreference v0.19.3 // indirect github.com/go-openapi/spec v0.19.4 // indirect - github.com/go-redis/redis v6.14.0+incompatible - github.com/go-redis/redis/v7 v7.2.0 // indirect + github.com/go-redis/redis/v7 v7.2.0 github.com/go-sql-driver/mysql v1.5.0 github.com/go-testfixtures/testfixtures/v3 v3.1.1 github.com/go-xorm/core v0.6.2 // indirect github.com/go-xorm/xorm v0.7.9 // indirect + github.com/golang/protobuf v1.3.5 // indirect github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 github.com/imdario/mergo v0.3.9 github.com/jgautheron/goconst v0.0.0-20200227150835-cda7ea3bf591 + github.com/kr/text v0.2.0 // indirect github.com/labstack/echo/v4 v4.1.16 github.com/labstack/gommon v0.3.0 github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef @@ -51,7 +53,10 @@ require ( github.com/mailru/easyjson v0.7.0 // indirect github.com/mattn/go-sqlite3 v2.0.3+incompatible github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/olekukonko/tablewriter v0.0.4 + github.com/onsi/ginkgo v1.12.0 // indirect + github.com/onsi/gomega v1.9.0 // indirect github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/pelletier/go-toml v1.4.0 // indirect github.com/prometheus/client_golang v0.9.4 @@ -64,21 +69,31 @@ require ( github.com/spf13/viper v1.6.3 github.com/stretchr/testify v1.5.1 github.com/swaggo/swag v1.6.3 - github.com/ulule/limiter/v3 v3.3.0 + github.com/ulule/limiter/v3 v3.5.0 github.com/urfave/cli v1.22.2 // indirect golang.org/x/crypto v0.0.0-20200406173513-056763e48d71 golang.org/x/lint v0.0.0-20200302205851-738671d3881b + golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e // indirect + golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa // indirect + golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect + gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/d4l3k/messagediff.v1 v1.2.1 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a src.techknowlogick.com/xgo v0.0.0-20200408234745-bb0faa361273 - src.techknowlogick.com/xormigrate v1.1.0 - xorm.io/builder v0.3.6 + src.techknowlogick.com/xormigrate v1.2.0 + xorm.io/builder v0.3.7 xorm.io/core v0.7.3 - xorm.io/xorm v0.8.1 + xorm.io/xorm v1.0.1 ) -replace github.com/samedi/caldav-go => github.com/kolaente/caldav-go v3.0.1-0.20190524174923-9e5cd1688227+incompatible // Branch: feature/dynamic-supported-components, PR: https://github.com/samedi/caldav-go/pull/6 and https://github.com/samedi/caldav-go/pull/7 +replace ( + github.com/coreos/bbolt => go.etcd.io/bbolt v1.3.4 + github.com/coreos/go-systemd => github.com/coreos/go-systemd/v22 v22.0.0 + github.com/hpcloud/tail => github.com/jeffbean/tail v0.0.0-20180825121900-988c412e1aaa923bd3f9dcb7c8a75b1b58818f4f // See https://github.com/hpcloud/tail/pull/159 + github.com/samedi/caldav-go => github.com/kolaente/caldav-go v3.0.1-0.20190524174923-9e5cd1688227+incompatible // Branch: feature/dynamic-supported-components, PR: https://github.com/samedi/caldav-go/pull/6 and https://github.com/samedi/caldav-go/pull/7 + gopkg.in/fsnotify.v1 => github.com/kolaente/fsnotify v1.4.10-0.20200411160148-1bc3c8ff4048 // See https://github.com/fsnotify/fsnotify/issues/328 and https://github.com/golang/go/issues/26904 +) go 1.13 diff --git a/go.sum b/go.sum index eaf918ccc3..38411e1c65 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,8 @@ cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU= cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= code.vikunja.io/web v0.0.0-20200208214421-c90649369427 h1:6ps5r0OxZNRdmCavh1k/xMwftN27hHauo+EtdTGxLug= code.vikunja.io/web v0.0.0-20200208214421-c90649369427/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo= +gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= +gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= gitea.com/xorm/tests v0.5.6 h1:bm5SwZD5B6LI4VinKf5bqOCJ8z3z5l0h43HkVuxZG3k= gitea.com/xorm/tests v0.5.6/go.mod h1:53b8exJwT/5JBCf5n5gMqKrIZAqIErdJCRtzS1AuiMM= gitea.com/xorm/xorm-redis-cache v0.0.0-20191113062523-5a6a9e2ab9f2 h1:85jEhrFlzlDrJ+CXoCQ24WtkZ7MEtt8yOYZuC9ewKyk= @@ -36,7 +38,6 @@ github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/astaxie/beego v1.10.0/go.mod h1:0R4++1tUqERR0WYFWdfkcrsyoVBCG4DgpDGokT3yb+U= github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= @@ -50,15 +51,15 @@ github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJ 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/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 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/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cweill/gotests v1.5.3 h1:k3t4wW/x/YNixWZJhUIn+mivmK5iV1tJVOwVYkx0UcU= github.com/cweill/gotests v1.5.3/go.mod h1:XZYOJkGVkCRoymaIzmp9Wyi3rUgfA3oOnkuljYrjFV8= github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U= @@ -79,6 +80,8 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1 github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= 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/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835 h1:roDmqJ4Qes7hrDOsWsMCce0vQHz3xiMPjJ9m4c2eeNs= github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835/go.mod h1:BjL/N0+C+j9uNX+1xcNuM9vdSIcXCZrQZUYbXOFbgN8= github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc= @@ -91,7 +94,7 @@ github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NB github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= -github.com/go-chi/chi v3.3.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= 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-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= @@ -115,10 +118,11 @@ github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/ github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-redis/redis v6.14.0+incompatible h1:AMPZkM7PbsJbilelrJUAyC4xQbGROTOLSuDd7fnMXCI= -github.com/go-redis/redis v6.14.0+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= -github.com/go-redis/redis v6.15.7+incompatible h1:3skhDh95XQMpnqeqNftPkQD9jL9e5e36z/1SUm6dy1U= -github.com/go-redis/redis v6.15.7+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-redis/redis/v7 v7.2.0 h1:CrCexy/jYWZjW0AyVoHlcJUeZN19VWlbepTh1Vq6dJs= github.com/go-redis/redis/v7 v7.2.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -133,6 +137,7 @@ github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM= github.com/go-xorm/xorm v0.7.9 h1:LZze6n1UvRmM5gpL9/U9Gucwqo6aWlFVlfcHKH10qA0= github.com/go-xorm/xorm v0.7.9/go.mod h1:XiVxrMMIhFkwSkh96BW7PACl7UhLtx2iJIHMdmjh5sQ= +github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 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= @@ -147,9 +152,14 @@ 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 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +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/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= @@ -171,8 +181,6 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t github.com/hashicorp/golang-lru v0.5.0/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/iancoleman/strcase v0.0.0-20191112232945-16388991a334 h1:VHgatEHNcBFEB7inlalqfNqw65aNkM1lGX2yt3NmbS8= github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= @@ -182,12 +190,13 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc= github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= github.com/jackc/pgx v3.6.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= +github.com/jeffbean/tail v0.0.0-20180825121900-988c412e1aaa923bd3f9dcb7c8a75b1b58818f4f h1:UgkcqE2r+hkV2Pn+1JTUTaH4iEsrUFTwI2OiDOM//rE= +github.com/jeffbean/tail v0.0.0-20180825121900-988c412e1aaa923bd3f9dcb7c8a75b1b58818f4f/go.mod h1:+MhJ+VPZMpv8Ui6WRzpJFuWFKxBCZgVOo5HAmlw1sFc= github.com/jgautheron/goconst v0.0.0-20200227150835-cda7ea3bf591 h1:x/BpEhm6aL26o4TLtcU0loJ7B3+69jielrGc70V7Yb4= github.com/jgautheron/goconst v0.0.0-20200227150835-cda7ea3bf591/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -197,8 +206,12 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 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/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.9.6/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/kolaente/caldav-go v3.0.1-0.20190524174923-9e5cd1688227+incompatible h1:PkEEpmbrFXlMul8cOplR8nkcIM/NDbx+H6fq2+vaKAA= github.com/kolaente/caldav-go v3.0.1-0.20190524174923-9e5cd1688227+incompatible/go.mod h1:y1UhTNI4g0hVymJrI6yJ5/ohy09hNBeU8iJEZjgdDOw= +github.com/kolaente/fsnotify v1.4.10-0.20200411160148-1bc3c8ff4048/go.mod h1:dv6KyzAg9UuJWiE1pwkvvB2i0TvcQM6QhdsXLZ7K5KI= github.com/konsorten/go-windows-terminal-sequences v1.0.1/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= @@ -207,8 +220,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= 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/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= -github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/labstack/echo/v4 v4.1.7-0.20190627175217-8fb7b5be270f h1:fNJtR+TNyxTdYCZU40fc8Or8RyBqMOKYNv+Zay5gjvk= github.com/labstack/echo/v4 v4.1.7-0.20190627175217-8fb7b5be270f/go.mod h1:kU/7PwzgNxZH4das4XNsSpBSOD09XIF5YEPzjpkGnGE= github.com/labstack/echo/v4 v4.1.16 h1:8swiwjE5Jkai3RPfZoahp8kjVCRNq+y7Q0hPji2Kz0o= @@ -219,6 +232,7 @@ github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0 github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef h1:RZnRnSID1skF35j/15KJ6hKZkdIC/teQClJK5wP5LU4= github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef/go.mod h1:4LATl0uhhtytR6p9n1AlktDyIz4u2iUnWEdI3L/hXiw= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU= @@ -265,19 +279,24 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= +github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg= +github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= @@ -290,6 +309,8 @@ github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= 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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.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= @@ -373,34 +394,40 @@ github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05 github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y= github.com/swaggo/swag v1.6.3 h1:N+uVPGP4H2hXoss2pt5dctoSUPKKRInr6qcTMOm0usI= github.com/swaggo/swag v1.6.3/go.mod h1:wcc83tB4Mb2aNiL/HP4MFeQdpHUrca+Rp/DRNgWAUio= +github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= +github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI= -github.com/ulule/limiter/v3 v3.3.0 h1:DuMRthpkl1wW9Em6xOVw5HMHnbDumSIDydiMqP0PTXs= -github.com/ulule/limiter/v3 v3.3.0/go.mod h1:E6sfg3hfRgW+yFvkE/rZf6YLqXYFMWTmZaZKvdEiQsA= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/ulule/limiter/v3 v3.5.0 h1:QRAebbswjlezHIfiSQgM8+jMxaz/zsrxGRuiUJ43MHo= +github.com/ulule/limiter/v3 v3.5.0/go.mod h1:TgOUQZKZ2KHjemqrC8UHUbKPqpTmSY43/2wbQ7YN1h8= github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 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/fasthttp v1.9.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.1.0 h1:RZqt0yGBsps8NGvLSGW804QQqCUYYLsaOjTVHy1Ocw4= github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= 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.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 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= @@ -420,12 +447,13 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58 h1:otZG8yDCO4LVps5+9bxOeNiCvgmOyt96J3roHTYs7oE= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/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= @@ -446,6 +474,8 @@ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -454,6 +484,7 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ 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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/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= @@ -472,10 +503,17 @@ golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa h1:mQTN3ECqfsViCNBgq+A40vdwhkGykrrQlYe3mPj6BoU= +golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa/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= @@ -492,15 +530,20 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190608022120-eacb66d2a7c3/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628034336-212fb13d595e h1:ZlQjfVdpDxeqxRfmO30CdqWWzTvgRCj0MxaUVfxEG1k= golang.org/x/tools v0.0.0-20190628034336-212fb13d595e/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7 h1:EBZoQjiKKPaLbPrbpssUfuHtwM6KV/vb4U85g/cigFY= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef h1:RHORRhs540cYZYrzgU2CPUyykkwZM78hGdzocOo9P8A= +golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk= @@ -508,8 +551,6 @@ google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.0 h1:Tfd7cKwKbFRsI8RMAD3oqqw7JPFRrvFlOsfbgVkjOOw= google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 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= @@ -523,12 +564,11 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+ 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/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/d4l3k/messagediff.v1 v1.2.1 h1:70AthpjunwzUiarMHyED52mj9UwtAnE89l1Gmrt3EU0= gopkg.in/d4l3k/messagediff.v1 v1.2.1/go.mod h1:EUzikiKadqXWcD1AzJLagx0j/BeeWGtn++04Xniyg44= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= @@ -546,6 +586,8 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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= @@ -553,14 +595,14 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a h1:LJwr7TCTghdatWv40WobzlK honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= src.techknowlogick.com/xgo v0.0.0-20200408234745-bb0faa361273 h1:dE6ry9rVwDn3soD4wPCXqEG60AZTuhniZzHdnj3c+74= src.techknowlogick.com/xgo v0.0.0-20200408234745-bb0faa361273/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU= -src.techknowlogick.com/xormigrate v1.1.0 h1:Ob79c1pOO+voMB9roa2eHZByT+TODwC51+Mn9e3HoTI= -src.techknowlogick.com/xormigrate v1.1.0/go.mod h1:IMdvIk60uPX+IUsaXbdtqFzl3n7PfRg/cSZxxsiCWf8= +src.techknowlogick.com/xormigrate v1.2.0 h1:bq9JaI48bxB+OddMghicjmV7sGmBUogJq4HmTN0DOcw= +src.techknowlogick.com/xormigrate v1.2.0/go.mod h1:7so27LAfBRqAxbma5jKYeL4ykVG1Jhsv9ncSq1KBCs4= xorm.io/builder v0.3.6 h1:ha28mQ2M+TFx96Hxo+iq6tQgnkC9IZkM6D8w9sKHHF8= xorm.io/builder v0.3.6/go.mod h1:LEFAPISnRzG+zxaxj2vPicRwz67BdhFreKg8yv8/TgU= +xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI= +xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= xorm.io/core v0.7.2-0.20190928055935-90aeac8d08eb/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM= -xorm.io/core v0.7.2 h1:mEO22A2Z7a3fPaZMk6gKL/jMD80iiyNwRrX5HOv3XLw= -xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM= xorm.io/core v0.7.3 h1:W8ws1PlrnkS1CZU1YWaYLMQcQilwAmQXU0BJDJon+H0= xorm.io/core v0.7.3/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM= -xorm.io/xorm v0.8.1 h1:4f2KXuQxVdaX3RdI3Fw81NzMiSpZeyCZt8m3sEVeIkQ= -xorm.io/xorm v0.8.1/go.mod h1:ZkJLEYLoVyg7amJK/5r779bHyzs2AU8f8VMiP6BM7uY= +xorm.io/xorm v1.0.1 h1:/lITxpJtkZauNpdzj+L9CN/3OQxZaABrbergMcJu+Cw= +xorm.io/xorm v1.0.1/go.mod h1:o4vnEsQ5V2F1/WK6w4XTwmiWJeGj82tqjAnHe44wVHY= diff --git a/pkg/config/config.go b/pkg/config/config.go index 6c092a9a6f..2f122ce1f8 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -78,13 +78,14 @@ const ( RedisPassword Key = `redis.password` RedisDB Key = `redis.db` - LogEnabled Key = `log.enabled` - LogErrors Key = `log.errors` - LogStandard Key = `log.standard` - LogDatabase Key = `log.database` - LogHTTP Key = `log.http` - LogEcho Key = `log.echo` - LogPath Key = `log.path` + LogEnabled Key = `log.enabled` + LogErrors Key = `log.errors` + LogStandard Key = `log.standard` + LogDatabase Key = `log.database` + LogDatabaseLevel Key = `log.databaselevel` + LogHTTP Key = `log.http` + LogEcho Key = `log.echo` + LogPath Key = `log.path` RateLimitEnabled Key = `ratelimit.enabled` RateLimitKind Key = `ratelimit.kind` @@ -214,6 +215,7 @@ func InitDefaultConfig() { LogErrors.setDefault("stdout") LogStandard.setDefault("stdout") LogDatabase.setDefault("off") + LogDatabaseLevel.setDefault("DEBUG") LogHTTP.setDefault("stdout") LogEcho.setDefault("off") LogPath.setDefault(ServiceRootpath.GetString() + "/logs") diff --git a/pkg/db/db.go b/pkg/db/db.go index aee4a8dfc9..bf54d533ee 100644 --- a/pkg/db/db.go +++ b/pkg/db/db.go @@ -21,14 +21,14 @@ import ( "code.vikunja.io/api/pkg/log" "encoding/gob" "fmt" + xrc "gitea.com/xorm/xorm-redis-cache" "net/url" "strconv" "strings" "time" "xorm.io/core" "xorm.io/xorm" - - xrc "gitea.com/xorm/xorm-redis-cache" + "xorm.io/xorm/caches" _ "github.com/go-sql-driver/mysql" // Because. _ "github.com/lib/pq" // Because. @@ -70,8 +70,7 @@ func CreateDBEngine() (engine *xorm.Engine, err error) { } engine.SetMapper(core.GonicMapper{}) - logger := xorm.NewSimpleLogger(log.GetLogWriter("database")) - logger.ShowSQL(config.LogDatabase.GetString() != "off") + logger := log.NewXormLogger() engine.SetLogger(logger) // Cache @@ -79,10 +78,10 @@ func CreateDBEngine() (engine *xorm.Engine, err error) { if config.CacheEnabled.GetBool() { switch config.CacheType.GetString() { case "memory": - cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), config.CacheMaxElementSize.GetInt()) + cacher := caches.NewLRUCacher(caches.NewMemoryStore(), config.CacheMaxElementSize.GetInt()) engine.SetDefaultCacher(cacher) case "redis": - cacher := xrc.NewRedisCacher(config.RedisEnabled.GetString(), config.RedisPassword.GetString(), xrc.DEFAULT_EXPIRATION, engine.Logger()) + cacher := xrc.NewRedisCacher(config.RedisEnabled.GetString(), config.RedisPassword.GetString(), xrc.DEFAULT_EXPIRATION, log.GetXormLoggerForRedis(logger)) engine.SetDefaultCacher(cacher) default: log.Info("Did not find a valid cache type. Caching disabled. Please refer to the docs for poosible cache types.") diff --git a/pkg/db/fixtures/tasks.yml b/pkg/db/fixtures/tasks.yml index 40742e33e7..72eb30185d 100644 --- a/pkg/db/fixtures/tasks.yml +++ b/pkg/db/fixtures/tasks.yml @@ -1,6 +1,7 @@ - id: 1 text: 'task #1' description: 'Lorem Ipsum' + done: false created_by_id: 1 list_id: 1 index: 1 @@ -16,6 +17,7 @@ updated: 1543626724 - id: 3 text: 'task #3 high prio' + done: false created_by_id: 1 list_id: 1 index: 3 @@ -24,6 +26,7 @@ priority: 100 - id: 4 text: 'task #4 low prio' + done: false created_by_id: 1 list_id: 1 index: 4 @@ -32,6 +35,7 @@ priority: 1 - id: 5 text: 'task #5 higher due date' + done: false created_by_id: 1 list_id: 1 index: 5 @@ -40,6 +44,7 @@ due_date_unix: 1543636724 - id: 6 text: 'task #6 lower due date' + done: false created_by_id: 1 list_id: 1 index: 6 @@ -48,6 +53,7 @@ due_date_unix: 1543616724 - id: 7 text: 'task #7 with start date' + done: false created_by_id: 1 list_id: 1 index: 7 @@ -56,6 +62,7 @@ start_date_unix: 1544600000 - id: 8 text: 'task #8 with end date' + done: false created_by_id: 1 list_id: 1 index: 8 @@ -64,6 +71,7 @@ end_date_unix: 1544700000 - id: 9 text: 'task #9 with start and end date' + done: false created_by_id: 1 list_id: 1 index: 9 @@ -73,6 +81,7 @@ end_date_unix: 1544700000 - id: 10 text: 'task #10 basic' + done: false created_by_id: 1 list_id: 1 index: 10 @@ -80,6 +89,7 @@ updated: 1543626724 - id: 11 text: 'task #11 basic' + done: false created_by_id: 1 list_id: 1 index: 11 @@ -87,6 +97,7 @@ updated: 1543626724 - id: 12 text: 'task #12 basic' + done: false created_by_id: 1 list_id: 1 index: 12 @@ -94,6 +105,7 @@ updated: 1543626724 - id: 13 text: 'task #13 basic other list' + done: false created_by_id: 1 list_id: 2 index: 1 @@ -101,6 +113,7 @@ updated: 1543626724 - id: 14 text: 'task #14 basic other list' + done: false created_by_id: 5 list_id: 5 index: 1 @@ -108,6 +121,7 @@ updated: 1543626724 - id: 15 text: 'task #15' + done: false created_by_id: 6 list_id: 6 index: 1 @@ -115,6 +129,7 @@ updated: 1543626724 - id: 16 text: 'task #16' + done: false created_by_id: 6 list_id: 7 index: 1 @@ -122,6 +137,7 @@ updated: 1543626724 - id: 17 text: 'task #17' + done: false created_by_id: 6 list_id: 8 index: 1 @@ -129,6 +145,7 @@ updated: 1543626724 - id: 18 text: 'task #18' + done: false created_by_id: 6 list_id: 9 index: 1 @@ -136,6 +153,7 @@ updated: 1543626724 - id: 19 text: 'task #19' + done: false created_by_id: 6 list_id: 10 index: 1 @@ -143,6 +161,7 @@ updated: 1543626724 - id: 20 text: 'task #20' + done: false created_by_id: 6 list_id: 11 index: 1 @@ -150,6 +169,7 @@ updated: 1543626724 - id: 21 text: 'task #21' + done: false created_by_id: 6 list_id: 12 index: 1 @@ -157,6 +177,7 @@ updated: 1543626724 - id: 22 text: 'task #22' + done: false created_by_id: 6 list_id: 13 index: 1 @@ -164,6 +185,7 @@ updated: 1543626724 - id: 23 text: 'task #23' + done: false created_by_id: 6 list_id: 14 index: 1 @@ -171,6 +193,7 @@ updated: 1543626724 - id: 24 text: 'task #24' + done: false created_by_id: 6 list_id: 15 index: 1 @@ -178,6 +201,7 @@ updated: 1543626724 - id: 25 text: 'task #25' + done: false created_by_id: 6 list_id: 16 index: 1 @@ -185,6 +209,7 @@ updated: 1543626724 - id: 26 text: 'task #26' + done: false created_by_id: 6 list_id: 17 index: 1 @@ -192,6 +217,7 @@ updated: 1543626724 - id: 27 text: 'task #27 with reminders' + done: false created_by_id: 1 list_id: 1 index: 12 @@ -208,6 +234,7 @@ updated: 1543626724 - id: 29 text: 'task #29 with parent task (1)' + done: false created_by_id: 1 list_id: 1 index: 14 @@ -215,6 +242,7 @@ updated: 1543626724 - id: 30 text: 'task #30 with assignees' + done: false created_by_id: 1 list_id: 1 index: 15 @@ -222,6 +250,7 @@ updated: 1543626724 - id: 31 text: 'task #31 with color' + done: false created_by_id: 1 list_id: 1 index: 16 @@ -230,6 +259,7 @@ updated: 1543626724 - id: 32 text: 'task #32' + done: false created_by_id: 1 list_id: 3 index: 1 @@ -237,6 +267,7 @@ updated: 1543626724 - id: 33 text: 'task #33 with percent done' + done: false created_by_id: 1 list_id: 1 index: 17 @@ -246,6 +277,7 @@ # This task is forbidden for user1 - id: 34 text: 'task #34' + done: false created_by_id: 13 list_id: 20 index: 20 @@ -253,6 +285,7 @@ updated: 1543626724 - id: 35 text: 'task #35' + done: false created_by_id: 1 list_id: 21 index: 1 @@ -260,6 +293,7 @@ updated: 1543626724 - id: 36 text: 'task #36' + done: false created_by_id: 1 list_id: 22 index: 1 diff --git a/pkg/db/test.go b/pkg/db/test.go index 27a54b553a..8752dd47ba 100644 --- a/pkg/db/test.go +++ b/pkg/db/test.go @@ -46,7 +46,7 @@ func CreateTestEngine() (engine *xorm.Engine, err error) { } engine.SetMapper(core.GonicMapper{}) - logger := xorm.NewSimpleLogger(log.GetLogWriter("database")) + logger := log.NewXormLogger() logger.ShowSQL(os.Getenv("UNIT_TESTS_VERBOSE") == "1") engine.SetLogger(logger) x = engine diff --git a/pkg/db/test_fixtures.go b/pkg/db/test_fixtures.go index 90c6c4d5bd..0808b9db38 100644 --- a/pkg/db/test_fixtures.go +++ b/pkg/db/test_fixtures.go @@ -24,6 +24,7 @@ import ( "github.com/stretchr/testify/assert" "path/filepath" "testing" + "xorm.io/xorm/schemas" ) var fixtures *testfixtures.Loader @@ -73,7 +74,7 @@ func LoadFixtures() error { // Copied from https://github.com/go-gitea/gitea/blob/master/models/test_fixtures.go#L39 // Now if we're running postgres we need to tell it to update the sequences - if x.Dialect().DriverName() == "postgres" { + if x.Dialect().URI().DBType == schemas.POSTGRES { results, err := x.QueryString(`SELECT 'SELECT SETVAL(' || quote_literal(quote_ident(PGT.schemaname) || '.' || quote_ident(S.relname)) || ', COALESCE(MAX(' ||quote_ident(C.attname)|| '), 1) ) FROM ' || diff --git a/pkg/log/xorm_logger.go b/pkg/log/xorm_logger.go new file mode 100644 index 0000000000..da97e1d78f --- /dev/null +++ b/pkg/log/xorm_logger.go @@ -0,0 +1,188 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-2020 Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package log + +import ( + "code.vikunja.io/api/pkg/config" + "github.com/op/go-logging" + "time" + "xorm.io/core" + "xorm.io/xorm/log" +) + +// XormFmt defines the format for xorm logging strings +const XormFmt = `%{color}%{time:` + time.RFC3339Nano + `}: %{level}` + "\t" + `▶ [DATABASE] %{id:03x}%{color:reset} %{message}` + +const xormLogModule = `vikunja_database` + +// XormLogger holds an implementation of the xorm logger interface. +type XormLogger struct { + logger *logging.Logger + level log.LogLevel + showSQL bool +} + +// NewXormLogger creates and initializes a new xorm logger +func NewXormLogger() *XormLogger { + level, err := logging.LogLevel(config.LogDatabaseLevel.GetString()) + if err != nil { + Critical("Error setting database log level: %s", err.Error()) + } + + xormLogger := &XormLogger{ + logger: logging.MustGetLogger(xormLogModule), + } + + logBackend := logging.NewLogBackend(GetLogWriter("database"), "", 0) + backend := logging.NewBackendFormatter(logBackend, logging.MustStringFormatter(XormFmt+"\n")) + + backendLeveled := logging.AddModuleLevel(backend) + backendLeveled.SetLevel(level, xormLogModule) + + xormLogger.logger.SetBackend(backendLeveled) + + switch level { + case logging.CRITICAL: + case logging.ERROR: + xormLogger.level = log.LOG_ERR + case logging.WARNING: + case logging.NOTICE: + xormLogger.level = log.LOG_WARNING + case logging.INFO: + xormLogger.level = log.LOG_INFO + case logging.DEBUG: + xormLogger.level = log.LOG_DEBUG + default: + xormLogger.level = log.LOG_OFF + } + + xormLogger.showSQL = true + + return xormLogger +} + +// Debug logs a debug string +func (x *XormLogger) Debug(v ...interface{}) { + x.logger.Debug(v...) +} + +// Debugf logs a debug string +func (x *XormLogger) Debugf(format string, v ...interface{}) { + x.logger.Debugf(format, v...) +} + +// Error logs a debug string +func (x *XormLogger) Error(v ...interface{}) { + x.logger.Error(v...) +} + +// Errorf logs a debug string +func (x *XormLogger) Errorf(format string, v ...interface{}) { + x.logger.Errorf(format, v...) +} + +// Info logs an info string +func (x *XormLogger) Info(v ...interface{}) { + x.logger.Info(v...) +} + +// Infof logs an info string +func (x *XormLogger) Infof(format string, v ...interface{}) { + x.logger.Infof(format, v...) +} + +// Warn logs a warning string +func (x *XormLogger) Warn(v ...interface{}) { + x.logger.Warning(v...) +} + +// Warnf logs a warning string +func (x *XormLogger) Warnf(format string, v ...interface{}) { + x.logger.Warningf(format, v...) +} + +// Level returns the current set log level +func (x *XormLogger) Level() log.LogLevel { + return x.level +} + +// SetLevel sets the log level +func (x *XormLogger) SetLevel(l log.LogLevel) { + x.level = l +} + +// ShowSQL sets whether to show the log level or not +func (x *XormLogger) ShowSQL(show ...bool) { + if len(show) > 0 { + x.showSQL = show[0] + } +} + +// IsShowSQL returns if sql queries should be shown +func (x *XormLogger) IsShowSQL() bool { + return x.showSQL +} + +// XormRedisCacherLogger is used as a compatibility layer to be able to re-use the same logger from xorm until +// the redis cacher module accepts the same logger. +// See https://gitea.com/xorm/xorm-redis-cache/issues/10 +type XormRedisCacherLogger struct { + XormLogger +} + +// GetXormLoggerForRedis creates a new xorm logger which can be used with redis +func GetXormLoggerForRedis(x *XormLogger) *XormRedisCacherLogger { + return &XormRedisCacherLogger{*x} +} + +// Level returns the currently set log level +func (x *XormRedisCacherLogger) Level() core.LogLevel { + switch x.level { + case log.LOG_DEBUG: + return core.LOG_DEBUG + case log.LOG_INFO: + return core.LOG_INFO + case log.LOG_WARNING: + return core.LOG_WARNING + case log.LOG_ERR: + return core.LOG_ERR + case log.LOG_OFF: + return core.LOG_OFF + case log.LOG_UNKNOWN: + return core.LOG_UNKNOWN + default: + return core.LOG_UNKNOWN + } +} + +// SetLevel sets the log level +func (x *XormRedisCacherLogger) SetLevel(l core.LogLevel) { + switch l { + case core.LOG_DEBUG: + x.level = log.LOG_DEBUG + case core.LOG_INFO: + x.level = log.LOG_INFO + case core.LOG_WARNING: + x.level = log.LOG_WARNING + case core.LOG_ERR: + x.level = log.LOG_ERR + case core.LOG_OFF: + x.level = log.LOG_OFF + default: + x.level = log.LOG_UNKNOWN + } +} diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index e336652f3e..335da531da 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -20,7 +20,7 @@ import ( "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/red" - "github.com/go-redis/redis" + "github.com/go-redis/redis/v7" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" ) diff --git a/pkg/migration/20190922205826.go b/pkg/migration/20190922205826.go index 01b0966047..4babe9194e 100644 --- a/pkg/migration/20190922205826.go +++ b/pkg/migration/20190922205826.go @@ -60,7 +60,7 @@ func init() { // Get all current subtasks and put them in a new table tasks := []*task20190922205826{} - err = tx.Where("parent_task_id != null OR parent_task_id != 0").Find(&tasks) + err = tx.Where("parent_task_id is not null OR parent_task_id != 0").Find(&tasks) if err != nil { return err } diff --git a/pkg/models/label_rights.go b/pkg/models/label_rights.go index de1b940462..d3ced45a41 100644 --- a/pkg/models/label_rights.go +++ b/pkg/models/label_rights.go @@ -76,7 +76,7 @@ func (l *Label) hasAccessToLabel(a web.Auth) (bool, error) { has, err := x.Table("labels"). Select("labels.*"). Join("LEFT", "label_task", "label_task.label_id = labels.id"). - Where("label_task.label_id != null OR labels.created_by_id = ?", a.GetID()). + Where("label_task.label_id is not null OR labels.created_by_id = ?", a.GetID()). Or(builder.In("label_task.task_id", taskIDs)). And("labels.id = ?", l.ID). Exist(&labels) diff --git a/pkg/models/label_task.go b/pkg/models/label_task.go index c9705a45a3..f8ade2d131 100644 --- a/pkg/models/label_task.go +++ b/pkg/models/label_task.go @@ -162,20 +162,27 @@ func getLabelsByTaskIDs(opts *LabelByTaskIDsOptions) (ls []*labelWithTaskID, res // Get all labels associated with these tasks var labels []*labelWithTaskID - cond := builder.And(builder.In("label_task.task_id", opts.TaskIDs), builder.NotNull{"label_task.label_id"}) + cond := builder.And(builder.NotNull{"label_task.label_id"}) + if len(opts.TaskIDs) > 0 { + cond = builder.And(builder.In("label_task.task_id", opts.TaskIDs), cond) + } if opts.GetUnusedLabels { cond = builder.Or(cond, builder.Eq{"labels.created_by_id": opts.User.ID}) } - err = x.Table("labels"). + limit, start := getLimitFromPageIndex(opts.Page, opts.PerPage) + + query := x.Table("labels"). Select(selectStmt). Join("LEFT", "label_task", "label_task.label_id = labels.id"). Where(cond). And("labels.title LIKE ?", "%"+opts.Search+"%"). GroupBy(groupBy). - OrderBy("labels.id ASC"). - Limit(getLimitFromPageIndex(opts.Page, opts.PerPage)). - Find(&labels) + OrderBy("labels.id ASC") + if limit > 0 { + query = query.Limit(limit, start) + } + err = query.Find(&labels) if err != nil { return nil, 0, 0, err } diff --git a/pkg/models/link_sharing.go b/pkg/models/link_sharing.go index 7501872ad3..4e6e91046c 100644 --- a/pkg/models/link_sharing.go +++ b/pkg/models/link_sharing.go @@ -155,11 +155,15 @@ func (share *LinkSharing) ReadAll(a web.Auth, search string, page int, perPage i return nil, 0, 0, ErrGenericForbidden{} } + limit, start := getLimitFromPageIndex(page, perPage) + var shares []*LinkSharing - err = x. - Where("list_id = ? AND hash LIKE ?", share.ListID, "%"+search+"%"). - Limit(getLimitFromPageIndex(page, perPage)). - Find(&shares) + query := x. + Where("list_id = ? AND hash LIKE ?", share.ListID, "%"+search+"%") + if limit > 0 { + query = query.Limit(limit, start) + } + err = query.Find(&shares) if err != nil { return nil, 0, 0, err } diff --git a/pkg/models/list.go b/pkg/models/list.go index 3659494444..23b1ccec99 100644 --- a/pkg/models/list.go +++ b/pkg/models/list.go @@ -235,9 +235,11 @@ func getRawListsForUser(opts *listOptions) (lists []*List, resultCount int, tota ) } + limit, start := getLimitFromPageIndex(opts.page, opts.perPage) + // Gets all Lists where the user is either owner or in a team which has access to the list // Or in a team which has namespace read access - err = x.Select("l.*"). + query := x.Select("l.*"). Table("list"). Alias("l"). Join("INNER", []string{"namespaces", "n"}, "l.namespace_id = n.id"). @@ -247,16 +249,20 @@ func getRawListsForUser(opts *listOptions) (lists []*List, resultCount int, tota Join("LEFT", []string{"team_members", "tm2"}, "tm2.team_id = tl.team_id"). Join("LEFT", []string{"users_list", "ul"}, "ul.list_id = l.id"). Join("LEFT", []string{"users_namespace", "un"}, "un.namespace_id = l.namespace_id"). - Where("tm.user_id = ?", fullUser.ID). - Or("tm2.user_id = ?", fullUser.ID). - Or("l.owner_id = ?", fullUser.ID). - Or("ul.user_id = ?", fullUser.ID). - Or("un.user_id = ?", fullUser.ID). + Where(builder.Or( + builder.Eq{"tm.user_id": fullUser.ID}, + builder.Eq{"tm2.user_id": fullUser.ID}, + builder.Eq{"ul.user_id": fullUser.ID}, + builder.Eq{"un.user_id": fullUser.ID}, + builder.Eq{"l.owner_id": fullUser.ID}, + )). GroupBy("l.id"). - Limit(getLimitFromPageIndex(opts.page, opts.perPage)). Where("l.title LIKE ?", "%"+opts.search+"%"). - Where(isArchivedCond). - Find(&lists) + Where(isArchivedCond) + if limit > 0 { + query = query.Limit(limit, start) + } + err = query.Find(&lists) if err != nil { return nil, 0, 0, err } @@ -271,13 +277,14 @@ func getRawListsForUser(opts *listOptions) (lists []*List, resultCount int, tota Join("LEFT", []string{"team_members", "tm2"}, "tm2.team_id = tl.team_id"). Join("LEFT", []string{"users_list", "ul"}, "ul.list_id = l.id"). Join("LEFT", []string{"users_namespace", "un"}, "un.namespace_id = l.namespace_id"). - Where("tm.user_id = ?", fullUser.ID). - Or("tm2.user_id = ?", fullUser.ID). - Or("l.owner_id = ?", fullUser.ID). - Or("ul.user_id = ?", fullUser.ID). - Or("un.user_id = ?", fullUser.ID). + Where(builder.Or( + builder.Eq{"tm.user_id": fullUser.ID}, + builder.Eq{"tm2.user_id": fullUser.ID}, + builder.Eq{"ul.user_id": fullUser.ID}, + builder.Eq{"un.user_id": fullUser.ID}, + builder.Eq{"l.owner_id": fullUser.ID}, + )). GroupBy("l.id"). - Limit(getLimitFromPageIndex(opts.page, opts.perPage)). Where("l.title LIKE ?", "%"+opts.search+"%"). Where(isArchivedCond). Count(&List{}) diff --git a/pkg/models/list_team.go b/pkg/models/list_team.go index f3896fe517..e78f8c6f0f 100644 --- a/pkg/models/list_team.go +++ b/pkg/models/list_team.go @@ -176,15 +176,19 @@ func (tl *TeamList) ReadAll(a web.Auth, search string, page int, perPage int) (r return nil, 0, 0, ErrNeedToHaveListReadAccess{ListID: tl.ListID, UserID: a.GetID()} } + limit, start := getLimitFromPageIndex(page, perPage) + // Get the teams all := []*TeamWithRight{} - err = x. + query := x. Table("teams"). Join("INNER", "team_list", "team_id = teams.id"). Where("team_list.list_id = ?", tl.ListID). - Limit(getLimitFromPageIndex(page, perPage)). - Where("teams.name LIKE ?", "%"+search+"%"). - Find(&all) + Where("teams.name LIKE ?", "%"+search+"%") + if limit > 0 { + query = query.Limit(limit, start) + } + err = query.Find(&all) if err != nil { return nil, 0, 0, err } diff --git a/pkg/models/list_users.go b/pkg/models/list_users.go index fb3edda337..d322d36663 100644 --- a/pkg/models/list_users.go +++ b/pkg/models/list_users.go @@ -182,14 +182,18 @@ func (lu *ListUser) ReadAll(a web.Auth, search string, page int, perPage int) (r return nil, 0, 0, ErrNeedToHaveListReadAccess{UserID: a.GetID(), ListID: lu.ListID} } + limit, start := getLimitFromPageIndex(page, perPage) + // Get all users all := []*UserWithRight{} - err = x. + query := x. Join("INNER", "users_list", "user_id = users.id"). Where("users_list.list_id = ?", lu.ListID). - Limit(getLimitFromPageIndex(page, perPage)). - Where("users.username LIKE ?", "%"+search+"%"). - Find(&all) + Where("users.username LIKE ?", "%"+search+"%") + if limit > 0 { + query = query.Limit(limit, start) + } + err = query.Find(&all) if err != nil { return nil, 0, 0, err } diff --git a/pkg/models/models.go b/pkg/models/models.go index 7a0e22efa4..5ad208bf0d 100644 --- a/pkg/models/models.go +++ b/pkg/models/models.go @@ -72,8 +72,8 @@ func SetEngine() (err error) { func getLimitFromPageIndex(page int, perPage int) (limit, start int) { - // Get everything when page index is -1 - if page < 0 { + // Get everything when page index is -1 or 0 (= not set) + if page < 1 { return 0, 0 } diff --git a/pkg/models/namespace.go b/pkg/models/namespace.go index 552f800592..977df99b3a 100644 --- a/pkg/models/namespace.go +++ b/pkg/models/namespace.go @@ -190,7 +190,9 @@ func (n *Namespace) ReadAll(a web.Auth, search string, page int, perPage int) (r []*List{}, }) - err = x.Select("namespaces.*"). + limit, start := getLimitFromPageIndex(page, perPage) + + query := x.Select("namespaces.*"). Table("namespaces"). Join("LEFT", "team_namespaces", "namespaces.id = team_namespaces.namespace_id"). Join("LEFT", "team_members", "team_members.team_id = team_namespaces.team_id"). @@ -199,10 +201,12 @@ func (n *Namespace) ReadAll(a web.Auth, search string, page int, perPage int) (r Or("namespaces.owner_id = ?", doer.ID). Or("users_namespace.user_id = ?", doer.ID). GroupBy("namespaces.id"). - Limit(getLimitFromPageIndex(page, perPage)). Where("namespaces.name LIKE ?", "%"+search+"%"). - Where(isArchivedCond). - Find(&all) + Where(isArchivedCond) + if limit > 0 { + query = query.Limit(limit, start) + } + err = query.Find(&all) if err != nil { return all, 0, 0, err } diff --git a/pkg/models/namespace_team.go b/pkg/models/namespace_team.go index 3a498cd2db..f7f3852a0d 100644 --- a/pkg/models/namespace_team.go +++ b/pkg/models/namespace_team.go @@ -164,12 +164,16 @@ func (tn *TeamNamespace) ReadAll(a web.Auth, search string, page int, perPage in // Get the teams all := []*TeamWithRight{} - err = x.Table("teams"). + limit, start := getLimitFromPageIndex(page, perPage) + + query := x.Table("teams"). Join("INNER", "team_namespaces", "team_id = teams.id"). Where("team_namespaces.namespace_id = ?", tn.NamespaceID). - Limit(getLimitFromPageIndex(page, perPage)). - Where("teams.name LIKE ?", "%"+search+"%"). - Find(&all) + Where("teams.name LIKE ?", "%"+search+"%") + if limit > 0 { + query = query.Limit(limit, start) + } + err = query.Find(&all) if err != nil { return nil, 0, 0, err } diff --git a/pkg/models/namespace_users.go b/pkg/models/namespace_users.go index 1f988ad3f1..5885d05e61 100644 --- a/pkg/models/namespace_users.go +++ b/pkg/models/namespace_users.go @@ -170,12 +170,17 @@ func (nu *NamespaceUser) ReadAll(a web.Auth, search string, page int, perPage in // Get all users all := []*UserWithRight{} - err = x. + + limit, start := getLimitFromPageIndex(page, perPage) + + query := x. Join("INNER", "users_namespace", "user_id = users.id"). Where("users_namespace.namespace_id = ?", nu.NamespaceID). - Limit(getLimitFromPageIndex(page, perPage)). - Where("users.username LIKE ?", "%"+search+"%"). - Find(&all) + Where("users.username LIKE ?", "%"+search+"%") + if limit > 0 { + query = query.Limit(limit, start) + } + err = query.Find(&all) if err != nil { return nil, 0, 0, err } diff --git a/pkg/models/task_assignees.go b/pkg/models/task_assignees.go index ba3e9be0ae..1d92473070 100644 --- a/pkg/models/task_assignees.go +++ b/pkg/models/task_assignees.go @@ -253,14 +253,17 @@ func (la *TaskAssginee) ReadAll(a web.Auth, search string, page int, perPage int if !can { return nil, 0, 0, ErrGenericForbidden{} } + limit, start := getLimitFromPageIndex(page, perPage) var taskAssignees []*user.User - err = x.Table("task_assignees"). + query := x.Table("task_assignees"). Select("users.*"). Join("INNER", "users", "task_assignees.user_id = users.id"). - Where("task_id = ? AND users.username LIKE ?", la.TaskID, "%"+search+"%"). - Limit(getLimitFromPageIndex(page, perPage)). - Find(&taskAssignees) + Where("task_id = ? AND users.username LIKE ?", la.TaskID, "%"+search+"%") + if limit > 0 { + query = query.Limit(limit, start) + } + err = query.Find(&taskAssignees) if err != nil { return nil, 0, 0, err } diff --git a/pkg/models/task_attachment.go b/pkg/models/task_attachment.go index b351b6bc56..eccbc89ea0 100644 --- a/pkg/models/task_attachment.go +++ b/pkg/models/task_attachment.go @@ -112,10 +112,14 @@ func (ta *TaskAttachment) ReadOne() (err error) { func (ta *TaskAttachment) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) { attachments := []*TaskAttachment{} - err = x. - Limit(getLimitFromPageIndex(page, perPage)). - Where("task_id = ?", ta.TaskID). - Find(&attachments) + limit, start := getLimitFromPageIndex(page, perPage) + + query := x. + Where("task_id = ?", ta.TaskID) + if limit > 0 { + query = query.Limit(limit, start) + } + err = query.Find(&attachments) if err != nil { return nil, 0, 0, err } diff --git a/pkg/models/task_comments.go b/pkg/models/task_comments.go index aa689f8023..1c2a4b4309 100644 --- a/pkg/models/task_comments.go +++ b/pkg/models/task_comments.go @@ -180,12 +180,16 @@ func (tc *TaskComment) ReadAll(auth web.Auth, search string, page int, perPage i AuthorFromDB *user.User `xorm:"extends" json:"-"` } + limit, start := getLimitFromPageIndex(page, perPage) + comments := []*TaskComment{} - err = x. + query := x. Where("task_id = ? AND comment like ?", tc.TaskID, "%"+search+"%"). - Join("LEFT", "users", "users.id = task_comments.author_id"). - Limit(getLimitFromPageIndex(page, perPage)). - Find(&comments) + Join("LEFT", "users", "users.id = task_comments.author_id") + if limit > 0 { + query = query.Limit(limit, start) + } + err = query.Find(&comments) if err != nil { return } diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index e88231f66b..ba0717973f 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -39,7 +39,7 @@ type Task struct { // The task description. Description string `xorm:"longtext null" json:"description"` // Whether a task is done or not. - Done bool `xorm:"INDEX null default false" json:"done"` + Done bool `xorm:"INDEX null" json:"done"` // The time when a task was marked as done. DoneAt timeutil.TimeStamp `xorm:"INDEX null 'done_at_unix'" json:"doneAt"` // The time when the task is due. @@ -184,41 +184,38 @@ func getRawTasksForLists(lists []*List, opts *taskOptions) (taskMap map[int64]*T taskMap = make(map[int64]*Task) // Then return all tasks for that lists - if len(filters) > 0 { + query := x. + OrderBy(orderby) - err := x.In("list_id", listIDs). - Where("text LIKE ?", "%"+opts.search+"%"). - Where(builder.Or(filters...)). - OrderBy(orderby). - Limit(getLimitFromPageIndex(opts.page, opts.perPage)). - Find(&taskMap) - if err != nil { - return nil, 0, 0, err - } - - totalItems, err = x.In("list_id", listIDs). - Where("text LIKE ?", "%"+opts.search+"%"). - Where(builder.Or(filters...)). - Count(&Task{}) - if err != nil { - return nil, 0, 0, err - } - } else { - err := x.In("list_id", listIDs). - Where("text LIKE ?", "%"+opts.search+"%"). - OrderBy(orderby). - Limit(getLimitFromPageIndex(opts.page, opts.perPage)). - Find(&taskMap) - if err != nil { - return nil, 0, 0, err - } - totalItems, err = x.In("list_id", listIDs). - Where("text LIKE ?", "%"+opts.search+"%"). - Count(&Task{}) - if err != nil { - return nil, 0, 0, err - } + if len(opts.search) > 0 { + query = query.Where("text LIKE ?", "%"+opts.search+"%") } + + if len(listIDs) > 0 { + query = query.In("list_id", listIDs) + } + + if len(filters) > 0 { + query = query.Where(builder.Or(filters...)) + } + + limit, start := getLimitFromPageIndex(opts.page, opts.perPage) + + if limit > 0 { + query = query.Limit(limit, start) + } + + err = query.Find(&taskMap) + if err != nil { + return nil, 0, 0, err + } + + totalItems, err = query. + Count(&Task{}) + if err != nil { + return nil, 0, 0, err + } + return taskMap, len(taskMap), totalItems, nil } diff --git a/pkg/models/teams.go b/pkg/models/teams.go index 4c515c5022..9f79877f1d 100644 --- a/pkg/models/teams.go +++ b/pkg/models/teams.go @@ -203,14 +203,18 @@ func (t *Team) ReadAll(a web.Auth, search string, page int, perPage int) (result return nil, 0, 0, ErrGenericForbidden{} } + limit, start := getLimitFromPageIndex(page, perPage) + all := []*Team{} - err = x.Select("teams.*"). + query := x.Select("teams.*"). Table("teams"). Join("INNER", "team_members", "team_members.team_id = teams.id"). Where("team_members.user_id = ?", a.GetID()). - Limit(getLimitFromPageIndex(page, perPage)). - Where("teams.name LIKE ?", "%"+search+"%"). - Find(&all) + Where("teams.name LIKE ?", "%"+search+"%") + if limit > 0 { + query = query.Limit(limit, start) + } + err = query.Find(&all) if err != nil { return nil, 0, 0, err } diff --git a/pkg/red/redis.go b/pkg/red/redis.go index 2ac0e613ab..016fb58731 100644 --- a/pkg/red/redis.go +++ b/pkg/red/redis.go @@ -19,7 +19,7 @@ package red import ( "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/log" - "github.com/go-redis/redis" + "github.com/go-redis/redis/v7" ) var r *redis.Client diff --git a/pkg/user/user.go b/pkg/user/user.go index 9df167e7cc..fa1bba9f80 100644 --- a/pkg/user/user.go +++ b/pkg/user/user.go @@ -309,7 +309,7 @@ func UpdateUser(user *User) (updatedUser *User, err error) { user.Password = theUser.Password // set the password to the one in the database to not accedently resetting it // Update it - _, err = x.Id(user.ID).Update(user) + _, err = x.ID(user.ID).Update(user) if err != nil { return &User{}, err } @@ -344,7 +344,7 @@ func UpdateUserPassword(user *User, newPassword string) (err error) { theUser.Password = hashed // Update it - _, err = x.Id(user.ID).Update(theUser) + _, err = x.ID(user.ID).Update(theUser) if err != nil { return err } diff --git a/vendor/github.com/fsnotify/fsnotify/.editorconfig b/vendor/github.com/fsnotify/fsnotify/.editorconfig index ba49e3c234..fad895851e 100644 --- a/vendor/github.com/fsnotify/fsnotify/.editorconfig +++ b/vendor/github.com/fsnotify/fsnotify/.editorconfig @@ -1,5 +1,12 @@ root = true -[*] +[*.go] indent_style = tab indent_size = 4 +insert_final_newline = true + +[*.{yml,yaml}] +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/vendor/github.com/fsnotify/fsnotify/.gitattributes b/vendor/github.com/fsnotify/fsnotify/.gitattributes new file mode 100644 index 0000000000..32f1001be0 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/.gitattributes @@ -0,0 +1 @@ +go.sum linguist-generated diff --git a/vendor/github.com/fsnotify/fsnotify/.travis.yml b/vendor/github.com/fsnotify/fsnotify/.travis.yml index 981d1bb813..a9c30165cd 100644 --- a/vendor/github.com/fsnotify/fsnotify/.travis.yml +++ b/vendor/github.com/fsnotify/fsnotify/.travis.yml @@ -2,29 +2,35 @@ sudo: false language: go go: - - 1.8.x - - 1.9.x - - tip + - "stable" + - "1.11.x" + - "1.10.x" + - "1.9.x" matrix: + include: + - go: "stable" + env: GOLINT=true allow_failures: - go: tip fast_finish: true -before_script: - - go get -u github.com/golang/lint/golint + +before_install: + - if [ ! -z "${GOLINT}" ]; then go get -u golang.org/x/lint/golint; fi script: - - go test -v --race ./... + - go test --race ./... after_script: - test -z "$(gofmt -s -l -w . | tee /dev/stderr)" - - test -z "$(golint ./... | tee /dev/stderr)" + - if [ ! -z "${GOLINT}" ]; then echo running golint; golint --set_exit_status ./...; else echo skipping golint; fi - go vet ./... os: - linux - osx + - windows notifications: email: false diff --git a/vendor/github.com/fsnotify/fsnotify/LICENSE b/vendor/github.com/fsnotify/fsnotify/LICENSE index f21e540800..e180c8fb05 100644 --- a/vendor/github.com/fsnotify/fsnotify/LICENSE +++ b/vendor/github.com/fsnotify/fsnotify/LICENSE @@ -1,5 +1,5 @@ Copyright (c) 2012 The Go Authors. All rights reserved. -Copyright (c) 2012 fsnotify Authors. All rights reserved. +Copyright (c) 2012-2019 fsnotify Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are diff --git a/vendor/github.com/fsnotify/fsnotify/README.md b/vendor/github.com/fsnotify/fsnotify/README.md index 3993207413..b2629e5229 100644 --- a/vendor/github.com/fsnotify/fsnotify/README.md +++ b/vendor/github.com/fsnotify/fsnotify/README.md @@ -10,16 +10,16 @@ go get -u golang.org/x/sys/... Cross platform: Windows, Linux, BSD and macOS. -|Adapter |OS |Status | -|----------|----------|----------| -|inotify |Linux 2.6.27 or later, Android\*|Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify)| -|kqueue |BSD, macOS, iOS\*|Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify)| -|ReadDirectoryChangesW|Windows|Supported [![Build status](https://ci.appveyor.com/api/projects/status/ivwjubaih4r0udeh/branch/master?svg=true)](https://ci.appveyor.com/project/NathanYoungman/fsnotify/branch/master)| -|FSEvents |macOS |[Planned](https://github.com/fsnotify/fsnotify/issues/11)| -|FEN |Solaris 11 |[In Progress](https://github.com/fsnotify/fsnotify/issues/12)| -|fanotify |Linux 2.6.37+ | | -|USN Journals |Windows |[Maybe](https://github.com/fsnotify/fsnotify/issues/53)| -|Polling |*All* |[Maybe](https://github.com/fsnotify/fsnotify/issues/9)| +| Adapter | OS | Status | +| --------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| inotify | Linux 2.6.27 or later, Android\* | Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify) | +| kqueue | BSD, macOS, iOS\* | Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify) | +| ReadDirectoryChangesW | Windows | Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify) | +| FSEvents | macOS | [Planned](https://github.com/fsnotify/fsnotify/issues/11) | +| FEN | Solaris 11 | [In Progress](https://github.com/fsnotify/fsnotify/issues/12) | +| fanotify | Linux 2.6.37+ | [Planned](https://github.com/fsnotify/fsnotify/issues/114) | +| USN Journals | Windows | [Maybe](https://github.com/fsnotify/fsnotify/issues/53) | +| Polling | *All* | [Maybe](https://github.com/fsnotify/fsnotify/issues/9) | \* Android and iOS are untested. @@ -33,6 +33,53 @@ All [releases](https://github.com/fsnotify/fsnotify/releases) are tagged based o Go 1.6 supports dependencies located in the `vendor/` folder. Unless you are creating a library, it is recommended that you copy fsnotify into `vendor/github.com/fsnotify/fsnotify` within your project, and likewise for `golang.org/x/sys`. +## Usage + +```go +package main + +import ( + "log" + + "github.com/fsnotify/fsnotify" +) + +func main() { + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Fatal(err) + } + defer watcher.Close() + + done := make(chan bool) + go func() { + for { + select { + case event, ok := <-watcher.Events: + if !ok { + return + } + log.Println("event:", event) + if event.Op&fsnotify.Write == fsnotify.Write { + log.Println("modified file:", event.Name) + } + case err, ok := <-watcher.Errors: + if !ok { + return + } + log.Println("error:", err) + } + } + }() + + err = watcher.Add("/tmp/foo") + if err != nil { + log.Fatal(err) + } + <-done +} +``` + ## Contributing Please refer to [CONTRIBUTING][] before opening an issue or pull request. @@ -65,6 +112,10 @@ There are OS-specific limits as to how many watches can be created: * Linux: /proc/sys/fs/inotify/max_user_watches contains the limit, reaching this limit results in a "no space left on device" error. * BSD / OSX: sysctl variables "kern.maxfiles" and "kern.maxfilesperproc", reaching these limits results in a "too many open files" error. +**Why don't notifications work with NFS filesystems or filesystem in userspace (FUSE)?** + +fsnotify requires support from underlying OS to work. The current NFS protocol does not provide network level support for file notifications. + [#62]: https://github.com/howeyc/fsnotify/issues/62 [#18]: https://github.com/fsnotify/fsnotify/issues/18 [#11]: https://github.com/fsnotify/fsnotify/issues/11 diff --git a/vendor/github.com/fsnotify/fsnotify/fsnotify.go b/vendor/github.com/fsnotify/fsnotify/fsnotify.go index 190bf0de57..89cab046d1 100644 --- a/vendor/github.com/fsnotify/fsnotify/fsnotify.go +++ b/vendor/github.com/fsnotify/fsnotify/fsnotify.go @@ -63,4 +63,6 @@ func (e Event) String() string { } // Common errors that can be reported by a watcher -var ErrEventOverflow = errors.New("fsnotify queue overflow") +var ( + ErrEventOverflow = errors.New("fsnotify queue overflow") +) diff --git a/vendor/github.com/fsnotify/fsnotify/go.mod b/vendor/github.com/fsnotify/fsnotify/go.mod new file mode 100644 index 0000000000..ff11e13f22 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/go.mod @@ -0,0 +1,5 @@ +module github.com/fsnotify/fsnotify + +go 1.13 + +require golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9 diff --git a/vendor/github.com/fsnotify/fsnotify/go.sum b/vendor/github.com/fsnotify/fsnotify/go.sum new file mode 100644 index 0000000000..f60af9855d --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/go.sum @@ -0,0 +1,2 @@ +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9 h1:L2auWcuQIvxz9xSEqzESnV/QN/gNRXNApHi3fYwl2w0= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/vendor/github.com/fsnotify/fsnotify/inotify_poller.go b/vendor/github.com/fsnotify/fsnotify/inotify_poller.go index cc7db4b22e..b33f2b4d4b 100644 --- a/vendor/github.com/fsnotify/fsnotify/inotify_poller.go +++ b/vendor/github.com/fsnotify/fsnotify/inotify_poller.go @@ -40,12 +40,12 @@ func newFdPoller(fd int) (*fdPoller, error) { poller.fd = fd // Create epoll fd - poller.epfd, errno = unix.EpollCreate1(0) + poller.epfd, errno = unix.EpollCreate1(unix.EPOLL_CLOEXEC) if poller.epfd == -1 { return nil, errno } // Create pipe; pipe[0] is the read end, pipe[1] the write end. - errno = unix.Pipe2(poller.pipe[:], unix.O_NONBLOCK) + errno = unix.Pipe2(poller.pipe[:], unix.O_NONBLOCK|unix.O_CLOEXEC) if errno != nil { return nil, errno } diff --git a/vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go b/vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go index 7d8de14513..2306c4620b 100644 --- a/vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go +++ b/vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go @@ -8,4 +8,4 @@ package fsnotify import "golang.org/x/sys/unix" -const openMode = unix.O_NONBLOCK | unix.O_RDONLY +const openMode = unix.O_NONBLOCK | unix.O_RDONLY | unix.O_CLOEXEC diff --git a/vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go b/vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go index 9139e17161..870c4d6d18 100644 --- a/vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go +++ b/vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go @@ -9,4 +9,4 @@ package fsnotify import "golang.org/x/sys/unix" // note: this constant is not defined on BSD -const openMode = unix.O_EVTONLY +const openMode = unix.O_EVTONLY | unix.O_CLOEXEC diff --git a/vendor/github.com/go-redis/redis/.travis.yml b/vendor/github.com/go-redis/redis/.travis.yml deleted file mode 100644 index 39ffc2becb..0000000000 --- a/vendor/github.com/go-redis/redis/.travis.yml +++ /dev/null @@ -1,20 +0,0 @@ -sudo: false -language: go - -services: - - redis-server - -go: - - 1.7.x - - 1.8.x - - 1.9.x - - 1.10.x - - tip - -matrix: - allow_failures: - - go: tip - -install: - - go get github.com/onsi/ginkgo - - go get github.com/onsi/gomega diff --git a/vendor/github.com/go-redis/redis/CHANGELOG.md b/vendor/github.com/go-redis/redis/CHANGELOG.md deleted file mode 100644 index 7c40d5e38d..0000000000 --- a/vendor/github.com/go-redis/redis/CHANGELOG.md +++ /dev/null @@ -1,21 +0,0 @@ -# Changelog - -## 6.14 - -- Added Options.MinIdleConns. -- Added Options.MaxConnAge. -- PoolStats.FreeConns is renamed to PoolStats.IdleConns. -- Add Client.Do to simplify creating custom commands. -- Add Cmd.String, Cmd.Int, Cmd.Int64, Cmd.Uint64, Cmd.Float64, and Cmd.Bool helpers. -- Lower memory usage. - -## v6.13 - -- Ring got new options called `HashReplicas` and `Hash`. It is recommended to set `HashReplicas = 1000` for better keys distribution between shards. -- Cluster client was optimized to use much less memory when reloading cluster state. -- PubSub.ReceiveMessage is re-worked to not use ReceiveTimeout so it does not lose data when timeout occurres. In most cases it is recommended to use PubSub.Channel instead. -- Dialer.KeepAlive is set to 5 minutes by default. - -## v6.12 - -- ClusterClient got new option called `ClusterSlots` which allows to build cluster of normal Redis Servers that don't have cluster mode enabled. See https://godoc.org/github.com/go-redis/redis#example-NewClusterClient--ManualSetup diff --git a/vendor/github.com/go-redis/redis/internal/log.go b/vendor/github.com/go-redis/redis/internal/log.go deleted file mode 100644 index fd14222eee..0000000000 --- a/vendor/github.com/go-redis/redis/internal/log.go +++ /dev/null @@ -1,15 +0,0 @@ -package internal - -import ( - "fmt" - "log" -) - -var Logger *log.Logger - -func Logf(s string, args ...interface{}) { - if Logger == nil { - return - } - Logger.Output(2, fmt.Sprintf(s, args...)) -} diff --git a/vendor/github.com/go-redis/redis/internal/pool/conn.go b/vendor/github.com/go-redis/redis/internal/pool/conn.go deleted file mode 100644 index 1095bfe59b..0000000000 --- a/vendor/github.com/go-redis/redis/internal/pool/conn.go +++ /dev/null @@ -1,93 +0,0 @@ -package pool - -import ( - "net" - "sync/atomic" - "time" - - "github.com/go-redis/redis/internal/proto" -) - -var noDeadline = time.Time{} - -type Conn struct { - netConn net.Conn - - rd *proto.Reader - rdLocked bool - wr *proto.Writer - - InitedAt time.Time - pooled bool - usedAt atomic.Value -} - -func NewConn(netConn net.Conn) *Conn { - cn := &Conn{ - netConn: netConn, - } - cn.rd = proto.NewReader(netConn) - cn.wr = proto.NewWriter(netConn) - cn.SetUsedAt(time.Now()) - return cn -} - -func (cn *Conn) UsedAt() time.Time { - return cn.usedAt.Load().(time.Time) -} - -func (cn *Conn) SetUsedAt(tm time.Time) { - cn.usedAt.Store(tm) -} - -func (cn *Conn) SetNetConn(netConn net.Conn) { - cn.netConn = netConn - cn.rd.Reset(netConn) - cn.wr.Reset(netConn) -} - -func (cn *Conn) setReadTimeout(timeout time.Duration) error { - now := time.Now() - cn.SetUsedAt(now) - if timeout > 0 { - return cn.netConn.SetReadDeadline(now.Add(timeout)) - } - return cn.netConn.SetReadDeadline(noDeadline) -} - -func (cn *Conn) setWriteTimeout(timeout time.Duration) error { - now := time.Now() - cn.SetUsedAt(now) - if timeout > 0 { - return cn.netConn.SetWriteDeadline(now.Add(timeout)) - } - return cn.netConn.SetWriteDeadline(noDeadline) -} - -func (cn *Conn) Write(b []byte) (int, error) { - return cn.netConn.Write(b) -} - -func (cn *Conn) RemoteAddr() net.Addr { - return cn.netConn.RemoteAddr() -} - -func (cn *Conn) WithReader(timeout time.Duration, fn func(rd *proto.Reader) error) error { - _ = cn.setReadTimeout(timeout) - return fn(cn.rd) -} - -func (cn *Conn) WithWriter(timeout time.Duration, fn func(wr *proto.Writer) error) error { - _ = cn.setWriteTimeout(timeout) - - firstErr := fn(cn.wr) - err := cn.wr.Flush() - if err != nil && firstErr == nil { - firstErr = err - } - return firstErr -} - -func (cn *Conn) Close() error { - return cn.netConn.Close() -} diff --git a/vendor/github.com/go-redis/redis/internal/pool/pool_single.go b/vendor/github.com/go-redis/redis/internal/pool/pool_single.go deleted file mode 100644 index b35b78afbd..0000000000 --- a/vendor/github.com/go-redis/redis/internal/pool/pool_single.go +++ /dev/null @@ -1,53 +0,0 @@ -package pool - -type SingleConnPool struct { - cn *Conn -} - -var _ Pooler = (*SingleConnPool)(nil) - -func NewSingleConnPool(cn *Conn) *SingleConnPool { - return &SingleConnPool{ - cn: cn, - } -} - -func (p *SingleConnPool) NewConn() (*Conn, error) { - panic("not implemented") -} - -func (p *SingleConnPool) CloseConn(*Conn) error { - panic("not implemented") -} - -func (p *SingleConnPool) Get() (*Conn, error) { - return p.cn, nil -} - -func (p *SingleConnPool) Put(cn *Conn) { - if p.cn != cn { - panic("p.cn != cn") - } -} - -func (p *SingleConnPool) Remove(cn *Conn) { - if p.cn != cn { - panic("p.cn != cn") - } -} - -func (p *SingleConnPool) Len() int { - return 1 -} - -func (p *SingleConnPool) IdleLen() int { - return 0 -} - -func (p *SingleConnPool) Stats() *Stats { - return nil -} - -func (p *SingleConnPool) Close() error { - return nil -} diff --git a/vendor/github.com/go-redis/redis/internal/singleflight/singleflight.go b/vendor/github.com/go-redis/redis/internal/singleflight/singleflight.go deleted file mode 100644 index 3b17417245..0000000000 --- a/vendor/github.com/go-redis/redis/internal/singleflight/singleflight.go +++ /dev/null @@ -1,64 +0,0 @@ -/* -Copyright 2013 Google Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package singleflight provides a duplicate function call suppression -// mechanism. -package singleflight - -import "sync" - -// call is an in-flight or completed Do call -type call struct { - wg sync.WaitGroup - val interface{} - err error -} - -// Group represents a class of work and forms a namespace in which -// units of work can be executed with duplicate suppression. -type Group struct { - mu sync.Mutex // protects m - m map[string]*call // lazily initialized -} - -// Do executes and returns the results of the given function, making -// sure that only one execution is in-flight for a given key at a -// time. If a duplicate comes in, the duplicate caller waits for the -// original to complete and receives the same results. -func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) { - g.mu.Lock() - if g.m == nil { - g.m = make(map[string]*call) - } - if c, ok := g.m[key]; ok { - g.mu.Unlock() - c.wg.Wait() - return c.val, c.err - } - c := new(call) - c.wg.Add(1) - g.m[key] = c - g.mu.Unlock() - - c.val, c.err = fn() - c.wg.Done() - - g.mu.Lock() - delete(g.m, key) - g.mu.Unlock() - - return c.val, c.err -} diff --git a/vendor/github.com/go-redis/redis/internal/util.go b/vendor/github.com/go-redis/redis/internal/util.go deleted file mode 100644 index ffd2353e0e..0000000000 --- a/vendor/github.com/go-redis/redis/internal/util.go +++ /dev/null @@ -1,29 +0,0 @@ -package internal - -import "github.com/go-redis/redis/internal/util" - -func ToLower(s string) string { - if isLower(s) { - return s - } - - b := make([]byte, len(s)) - for i := range b { - c := s[i] - if c >= 'A' && c <= 'Z' { - c += 'a' - 'A' - } - b[i] = c - } - return util.BytesToString(b) -} - -func isLower(s string) bool { - for i := 0; i < len(s); i++ { - c := s[i] - if c >= 'A' && c <= 'Z' { - return false - } - } - return true -} diff --git a/vendor/github.com/go-redis/redis/redis.go b/vendor/github.com/go-redis/redis/redis.go deleted file mode 100644 index 3e72bf060f..0000000000 --- a/vendor/github.com/go-redis/redis/redis.go +++ /dev/null @@ -1,519 +0,0 @@ -package redis - -import ( - "context" - "fmt" - "log" - "os" - "time" - - "github.com/go-redis/redis/internal" - "github.com/go-redis/redis/internal/pool" - "github.com/go-redis/redis/internal/proto" -) - -// Nil reply Redis returns when key does not exist. -const Nil = proto.Nil - -func init() { - SetLogger(log.New(os.Stderr, "redis: ", log.LstdFlags|log.Lshortfile)) -} - -func SetLogger(logger *log.Logger) { - internal.Logger = logger -} - -type baseClient struct { - opt *Options - connPool pool.Pooler - - process func(Cmder) error - processPipeline func([]Cmder) error - processTxPipeline func([]Cmder) error - - onClose func() error // hook called when client is closed -} - -func (c *baseClient) init() { - c.process = c.defaultProcess - c.processPipeline = c.defaultProcessPipeline - c.processTxPipeline = c.defaultProcessTxPipeline -} - -func (c *baseClient) String() string { - return fmt.Sprintf("Redis<%s db:%d>", c.getAddr(), c.opt.DB) -} - -func (c *baseClient) newConn() (*pool.Conn, error) { - cn, err := c.connPool.NewConn() - if err != nil { - return nil, err - } - - if cn.InitedAt.IsZero() { - if err := c.initConn(cn); err != nil { - _ = c.connPool.CloseConn(cn) - return nil, err - } - } - - return cn, nil -} - -func (c *baseClient) getConn() (*pool.Conn, error) { - cn, err := c.connPool.Get() - if err != nil { - return nil, err - } - - if cn.InitedAt.IsZero() { - err := c.initConn(cn) - if err != nil { - c.connPool.Remove(cn) - return nil, err - } - } - - return cn, nil -} - -func (c *baseClient) releaseConn(cn *pool.Conn, err error) bool { - if internal.IsBadConn(err, false) { - c.connPool.Remove(cn) - return false - } - - c.connPool.Put(cn) - return true -} - -func (c *baseClient) initConn(cn *pool.Conn) error { - cn.InitedAt = time.Now() - - if c.opt.Password == "" && - c.opt.DB == 0 && - !c.opt.readOnly && - c.opt.OnConnect == nil { - return nil - } - - conn := newConn(c.opt, cn) - _, err := conn.Pipelined(func(pipe Pipeliner) error { - if c.opt.Password != "" { - pipe.Auth(c.opt.Password) - } - - if c.opt.DB > 0 { - pipe.Select(c.opt.DB) - } - - if c.opt.readOnly { - pipe.ReadOnly() - } - - return nil - }) - if err != nil { - return err - } - - if c.opt.OnConnect != nil { - return c.opt.OnConnect(conn) - } - return nil -} - -// Do creates a Cmd from the args and processes the cmd. -func (c *baseClient) Do(args ...interface{}) *Cmd { - cmd := NewCmd(args...) - c.Process(cmd) - return cmd -} - -// WrapProcess wraps function that processes Redis commands. -func (c *baseClient) WrapProcess( - fn func(oldProcess func(cmd Cmder) error) func(cmd Cmder) error, -) { - c.process = fn(c.process) -} - -func (c *baseClient) Process(cmd Cmder) error { - return c.process(cmd) -} - -func (c *baseClient) defaultProcess(cmd Cmder) error { - for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ { - if attempt > 0 { - time.Sleep(c.retryBackoff(attempt)) - } - - cn, err := c.getConn() - if err != nil { - cmd.setErr(err) - if internal.IsRetryableError(err, true) { - continue - } - return err - } - - err = cn.WithWriter(c.opt.WriteTimeout, func(wr *proto.Writer) error { - return writeCmd(wr, cmd) - }) - if err != nil { - c.releaseConn(cn, err) - cmd.setErr(err) - if internal.IsRetryableError(err, true) { - continue - } - return err - } - - err = cn.WithReader(c.cmdTimeout(cmd), func(rd *proto.Reader) error { - return cmd.readReply(rd) - }) - c.releaseConn(cn, err) - if err != nil && internal.IsRetryableError(err, cmd.readTimeout() == nil) { - continue - } - - return err - } - - return cmd.Err() -} - -func (c *baseClient) retryBackoff(attempt int) time.Duration { - return internal.RetryBackoff(attempt, c.opt.MinRetryBackoff, c.opt.MaxRetryBackoff) -} - -func (c *baseClient) cmdTimeout(cmd Cmder) time.Duration { - if timeout := cmd.readTimeout(); timeout != nil { - return readTimeout(*timeout) - } - return c.opt.ReadTimeout -} - -// Close closes the client, releasing any open resources. -// -// It is rare to Close a Client, as the Client is meant to be -// long-lived and shared between many goroutines. -func (c *baseClient) Close() error { - var firstErr error - if c.onClose != nil { - if err := c.onClose(); err != nil && firstErr == nil { - firstErr = err - } - } - if err := c.connPool.Close(); err != nil && firstErr == nil { - firstErr = err - } - return firstErr -} - -func (c *baseClient) getAddr() string { - return c.opt.Addr -} - -func (c *baseClient) WrapProcessPipeline( - fn func(oldProcess func([]Cmder) error) func([]Cmder) error, -) { - c.processPipeline = fn(c.processPipeline) - c.processTxPipeline = fn(c.processTxPipeline) -} - -func (c *baseClient) defaultProcessPipeline(cmds []Cmder) error { - return c.generalProcessPipeline(cmds, c.pipelineProcessCmds) -} - -func (c *baseClient) defaultProcessTxPipeline(cmds []Cmder) error { - return c.generalProcessPipeline(cmds, c.txPipelineProcessCmds) -} - -type pipelineProcessor func(*pool.Conn, []Cmder) (bool, error) - -func (c *baseClient) generalProcessPipeline(cmds []Cmder, p pipelineProcessor) error { - for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ { - if attempt > 0 { - time.Sleep(c.retryBackoff(attempt)) - } - - cn, err := c.getConn() - if err != nil { - setCmdsErr(cmds, err) - return err - } - - canRetry, err := p(cn, cmds) - - if err == nil || internal.IsRedisError(err) { - c.connPool.Put(cn) - break - } - c.connPool.Remove(cn) - - if !canRetry || !internal.IsRetryableError(err, true) { - break - } - } - return cmdsFirstErr(cmds) -} - -func (c *baseClient) pipelineProcessCmds(cn *pool.Conn, cmds []Cmder) (bool, error) { - err := cn.WithWriter(c.opt.WriteTimeout, func(wr *proto.Writer) error { - return writeCmd(wr, cmds...) - }) - if err != nil { - setCmdsErr(cmds, err) - return true, err - } - - err = cn.WithReader(c.opt.ReadTimeout, func(rd *proto.Reader) error { - return pipelineReadCmds(rd, cmds) - }) - return true, err -} - -func pipelineReadCmds(rd *proto.Reader, cmds []Cmder) error { - for _, cmd := range cmds { - err := cmd.readReply(rd) - if err != nil && !internal.IsRedisError(err) { - return err - } - } - return nil -} - -func (c *baseClient) txPipelineProcessCmds(cn *pool.Conn, cmds []Cmder) (bool, error) { - err := cn.WithWriter(c.opt.WriteTimeout, func(wr *proto.Writer) error { - return txPipelineWriteMulti(wr, cmds) - }) - if err != nil { - setCmdsErr(cmds, err) - return true, err - } - - err = cn.WithReader(c.opt.ReadTimeout, func(rd *proto.Reader) error { - err := txPipelineReadQueued(rd, cmds) - if err != nil { - setCmdsErr(cmds, err) - return err - } - return pipelineReadCmds(rd, cmds) - }) - return false, err -} - -func txPipelineWriteMulti(wr *proto.Writer, cmds []Cmder) error { - multiExec := make([]Cmder, 0, len(cmds)+2) - multiExec = append(multiExec, NewStatusCmd("MULTI")) - multiExec = append(multiExec, cmds...) - multiExec = append(multiExec, NewSliceCmd("EXEC")) - return writeCmd(wr, multiExec...) -} - -func txPipelineReadQueued(rd *proto.Reader, cmds []Cmder) error { - // Parse queued replies. - var statusCmd StatusCmd - err := statusCmd.readReply(rd) - if err != nil { - return err - } - - for _ = range cmds { - err = statusCmd.readReply(rd) - if err != nil && !internal.IsRedisError(err) { - return err - } - } - - // Parse number of replies. - line, err := rd.ReadLine() - if err != nil { - if err == Nil { - err = TxFailedErr - } - return err - } - - switch line[0] { - case proto.ErrorReply: - return proto.ParseErrorReply(line) - case proto.ArrayReply: - // ok - default: - err := fmt.Errorf("redis: expected '*', but got line %q", line) - return err - } - - return nil -} - -//------------------------------------------------------------------------------ - -// Client is a Redis client representing a pool of zero or more -// underlying connections. It's safe for concurrent use by multiple -// goroutines. -type Client struct { - baseClient - cmdable - - ctx context.Context -} - -// NewClient returns a client to the Redis Server specified by Options. -func NewClient(opt *Options) *Client { - opt.init() - - c := Client{ - baseClient: baseClient{ - opt: opt, - connPool: newConnPool(opt), - }, - } - c.baseClient.init() - c.init() - - return &c -} - -func (c *Client) init() { - c.cmdable.setProcessor(c.Process) -} - -func (c *Client) Context() context.Context { - if c.ctx != nil { - return c.ctx - } - return context.Background() -} - -func (c *Client) WithContext(ctx context.Context) *Client { - if ctx == nil { - panic("nil context") - } - c2 := c.copy() - c2.ctx = ctx - return c2 -} - -func (c *Client) copy() *Client { - cp := *c - cp.init() - return &cp -} - -// Options returns read-only Options that were used to create the client. -func (c *Client) Options() *Options { - return c.opt -} - -type PoolStats pool.Stats - -// PoolStats returns connection pool stats. -func (c *Client) PoolStats() *PoolStats { - stats := c.connPool.Stats() - return (*PoolStats)(stats) -} - -func (c *Client) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) { - return c.Pipeline().Pipelined(fn) -} - -func (c *Client) Pipeline() Pipeliner { - pipe := Pipeline{ - exec: c.processPipeline, - } - pipe.statefulCmdable.setProcessor(pipe.Process) - return &pipe -} - -func (c *Client) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) { - return c.TxPipeline().Pipelined(fn) -} - -// TxPipeline acts like Pipeline, but wraps queued commands with MULTI/EXEC. -func (c *Client) TxPipeline() Pipeliner { - pipe := Pipeline{ - exec: c.processTxPipeline, - } - pipe.statefulCmdable.setProcessor(pipe.Process) - return &pipe -} - -func (c *Client) pubSub() *PubSub { - pubsub := &PubSub{ - opt: c.opt, - - newConn: func(channels []string) (*pool.Conn, error) { - return c.newConn() - }, - closeConn: c.connPool.CloseConn, - } - pubsub.init() - return pubsub -} - -// Subscribe subscribes the client to the specified channels. -// Channels can be omitted to create empty subscription. -func (c *Client) Subscribe(channels ...string) *PubSub { - pubsub := c.pubSub() - if len(channels) > 0 { - _ = pubsub.Subscribe(channels...) - } - return pubsub -} - -// PSubscribe subscribes the client to the given patterns. -// Patterns can be omitted to create empty subscription. -func (c *Client) PSubscribe(channels ...string) *PubSub { - pubsub := c.pubSub() - if len(channels) > 0 { - _ = pubsub.PSubscribe(channels...) - } - return pubsub -} - -//------------------------------------------------------------------------------ - -// Conn is like Client, but its pool contains single connection. -type Conn struct { - baseClient - statefulCmdable -} - -func newConn(opt *Options, cn *pool.Conn) *Conn { - c := Conn{ - baseClient: baseClient{ - opt: opt, - connPool: pool.NewSingleConnPool(cn), - }, - } - c.baseClient.init() - c.statefulCmdable.setProcessor(c.Process) - return &c -} - -func (c *Conn) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) { - return c.Pipeline().Pipelined(fn) -} - -func (c *Conn) Pipeline() Pipeliner { - pipe := Pipeline{ - exec: c.processPipeline, - } - pipe.statefulCmdable.setProcessor(pipe.Process) - return &pipe -} - -func (c *Conn) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) { - return c.TxPipeline().Pipelined(fn) -} - -// TxPipeline acts like Pipeline, but wraps queued commands with MULTI/EXEC. -func (c *Conn) TxPipeline() Pipeliner { - pipe := Pipeline{ - exec: c.processTxPipeline, - } - pipe.statefulCmdable.setProcessor(pipe.Process) - return &pipe -} diff --git a/vendor/github.com/go-redis/redis/sentinel.go b/vendor/github.com/go-redis/redis/sentinel.go deleted file mode 100644 index c5f71493d7..0000000000 --- a/vendor/github.com/go-redis/redis/sentinel.go +++ /dev/null @@ -1,347 +0,0 @@ -package redis - -import ( - "crypto/tls" - "errors" - "net" - "strings" - "sync" - "time" - - "github.com/go-redis/redis/internal" - "github.com/go-redis/redis/internal/pool" -) - -//------------------------------------------------------------------------------ - -// FailoverOptions are used to configure a failover client and should -// be passed to NewFailoverClient. -type FailoverOptions struct { - // The master name. - MasterName string - // A seed list of host:port addresses of sentinel nodes. - SentinelAddrs []string - - // Following options are copied from Options struct. - - OnConnect func(*Conn) error - - Password string - DB int - - MaxRetries int - MinRetryBackoff time.Duration - MaxRetryBackoff time.Duration - - DialTimeout time.Duration - ReadTimeout time.Duration - WriteTimeout time.Duration - - PoolSize int - MinIdleConns int - MaxConnAge time.Duration - PoolTimeout time.Duration - IdleTimeout time.Duration - IdleCheckFrequency time.Duration - - TLSConfig *tls.Config -} - -func (opt *FailoverOptions) options() *Options { - return &Options{ - Addr: "FailoverClient", - - OnConnect: opt.OnConnect, - - DB: opt.DB, - Password: opt.Password, - - MaxRetries: opt.MaxRetries, - - DialTimeout: opt.DialTimeout, - ReadTimeout: opt.ReadTimeout, - WriteTimeout: opt.WriteTimeout, - - PoolSize: opt.PoolSize, - PoolTimeout: opt.PoolTimeout, - IdleTimeout: opt.IdleTimeout, - IdleCheckFrequency: opt.IdleCheckFrequency, - - TLSConfig: opt.TLSConfig, - } -} - -// NewFailoverClient returns a Redis client that uses Redis Sentinel -// for automatic failover. It's safe for concurrent use by multiple -// goroutines. -func NewFailoverClient(failoverOpt *FailoverOptions) *Client { - opt := failoverOpt.options() - opt.init() - - failover := &sentinelFailover{ - masterName: failoverOpt.MasterName, - sentinelAddrs: failoverOpt.SentinelAddrs, - - opt: opt, - } - - c := Client{ - baseClient: baseClient{ - opt: opt, - connPool: failover.Pool(), - - onClose: func() error { - return failover.Close() - }, - }, - } - c.baseClient.init() - c.cmdable.setProcessor(c.Process) - - return &c -} - -//------------------------------------------------------------------------------ - -type SentinelClient struct { - baseClient -} - -func NewSentinelClient(opt *Options) *SentinelClient { - opt.init() - c := &SentinelClient{ - baseClient: baseClient{ - opt: opt, - connPool: newConnPool(opt), - }, - } - c.baseClient.init() - return c -} - -func (c *SentinelClient) PubSub() *PubSub { - pubsub := &PubSub{ - opt: c.opt, - - newConn: func(channels []string) (*pool.Conn, error) { - return c.newConn() - }, - closeConn: c.connPool.CloseConn, - } - pubsub.init() - return pubsub -} - -func (c *SentinelClient) GetMasterAddrByName(name string) *StringSliceCmd { - cmd := NewStringSliceCmd("SENTINEL", "get-master-addr-by-name", name) - c.Process(cmd) - return cmd -} - -func (c *SentinelClient) Sentinels(name string) *SliceCmd { - cmd := NewSliceCmd("SENTINEL", "sentinels", name) - c.Process(cmd) - return cmd -} - -type sentinelFailover struct { - sentinelAddrs []string - - opt *Options - - pool *pool.ConnPool - poolOnce sync.Once - - mu sync.RWMutex - masterName string - _masterAddr string - sentinel *SentinelClient -} - -func (d *sentinelFailover) Close() error { - return d.resetSentinel() -} - -func (d *sentinelFailover) Pool() *pool.ConnPool { - d.poolOnce.Do(func() { - d.opt.Dialer = d.dial - d.pool = newConnPool(d.opt) - }) - return d.pool -} - -func (d *sentinelFailover) dial() (net.Conn, error) { - addr, err := d.MasterAddr() - if err != nil { - return nil, err - } - return net.DialTimeout("tcp", addr, d.opt.DialTimeout) -} - -func (d *sentinelFailover) MasterAddr() (string, error) { - d.mu.Lock() - defer d.mu.Unlock() - - addr, err := d.masterAddr() - if err != nil { - return "", err - } - d._switchMaster(addr) - - return addr, nil -} - -func (d *sentinelFailover) masterAddr() (string, error) { - // Try last working sentinel. - if d.sentinel != nil { - addr, err := d.sentinel.GetMasterAddrByName(d.masterName).Result() - if err == nil { - addr := net.JoinHostPort(addr[0], addr[1]) - return addr, nil - } - - internal.Logf("sentinel: GetMasterAddrByName name=%q failed: %s", - d.masterName, err) - d._resetSentinel() - } - - for i, sentinelAddr := range d.sentinelAddrs { - sentinel := NewSentinelClient(&Options{ - Addr: sentinelAddr, - - DialTimeout: d.opt.DialTimeout, - ReadTimeout: d.opt.ReadTimeout, - WriteTimeout: d.opt.WriteTimeout, - - PoolSize: d.opt.PoolSize, - PoolTimeout: d.opt.PoolTimeout, - IdleTimeout: d.opt.IdleTimeout, - }) - - masterAddr, err := sentinel.GetMasterAddrByName(d.masterName).Result() - if err != nil { - internal.Logf("sentinel: GetMasterAddrByName master=%q failed: %s", - d.masterName, err) - sentinel.Close() - continue - } - - // Push working sentinel to the top. - d.sentinelAddrs[0], d.sentinelAddrs[i] = d.sentinelAddrs[i], d.sentinelAddrs[0] - d.setSentinel(sentinel) - - addr := net.JoinHostPort(masterAddr[0], masterAddr[1]) - return addr, nil - } - - return "", errors.New("redis: all sentinels are unreachable") -} - -func (c *sentinelFailover) switchMaster(addr string) { - c.mu.Lock() - c._switchMaster(addr) - c.mu.Unlock() -} - -func (c *sentinelFailover) _switchMaster(addr string) { - if c._masterAddr == addr { - return - } - - internal.Logf("sentinel: new master=%q addr=%q", - c.masterName, addr) - _ = c.Pool().Filter(func(cn *pool.Conn) bool { - return cn.RemoteAddr().String() != addr - }) - c._masterAddr = addr -} - -func (d *sentinelFailover) setSentinel(sentinel *SentinelClient) { - d.discoverSentinels(sentinel) - d.sentinel = sentinel - go d.listen(sentinel) -} - -func (d *sentinelFailover) resetSentinel() error { - var err error - d.mu.Lock() - if d.sentinel != nil { - err = d._resetSentinel() - } - d.mu.Unlock() - return err -} - -func (d *sentinelFailover) _resetSentinel() error { - err := d.sentinel.Close() - d.sentinel = nil - return err -} - -func (d *sentinelFailover) discoverSentinels(sentinel *SentinelClient) { - sentinels, err := sentinel.Sentinels(d.masterName).Result() - if err != nil { - internal.Logf("sentinel: Sentinels master=%q failed: %s", d.masterName, err) - return - } - for _, sentinel := range sentinels { - vals := sentinel.([]interface{}) - for i := 0; i < len(vals); i += 2 { - key := vals[i].(string) - if key == "name" { - sentinelAddr := vals[i+1].(string) - if !contains(d.sentinelAddrs, sentinelAddr) { - internal.Logf( - "sentinel: discovered new sentinel=%q for master=%q", - sentinelAddr, d.masterName, - ) - d.sentinelAddrs = append(d.sentinelAddrs, sentinelAddr) - } - } - } - } -} - -func (d *sentinelFailover) listen(sentinel *SentinelClient) { - pubsub := sentinel.PubSub() - defer pubsub.Close() - - err := pubsub.Subscribe("+switch-master") - if err != nil { - internal.Logf("sentinel: Subscribe failed: %s", err) - d.resetSentinel() - return - } - - for { - msg, err := pubsub.ReceiveMessage() - if err != nil { - if err == pool.ErrClosed { - d.resetSentinel() - return - } - internal.Logf("sentinel: ReceiveMessage failed: %s", err) - continue - } - - switch msg.Channel { - case "+switch-master": - parts := strings.Split(msg.Payload, " ") - if parts[0] != d.masterName { - internal.Logf("sentinel: ignore addr for master=%q", parts[0]) - continue - } - addr := net.JoinHostPort(parts[3], parts[4]) - d.switchMaster(addr) - } - } -} - -func contains(slice []string, str string) bool { - for _, s := range slice { - if s == str { - return true - } - } - return false -} diff --git a/vendor/github.com/go-redis/redis/.gitignore b/vendor/github.com/go-redis/redis/v7/.gitignore similarity index 100% rename from vendor/github.com/go-redis/redis/.gitignore rename to vendor/github.com/go-redis/redis/v7/.gitignore diff --git a/vendor/github.com/go-redis/redis/v7/.golangci.yml b/vendor/github.com/go-redis/redis/v7/.golangci.yml new file mode 100644 index 0000000000..912dab1ef3 --- /dev/null +++ b/vendor/github.com/go-redis/redis/v7/.golangci.yml @@ -0,0 +1,15 @@ +run: + concurrency: 8 + deadline: 5m + tests: false +linters: + enable-all: true + disable: + - funlen + - gochecknoglobals + - gocognit + - goconst + - godox + - gosec + - maligned + - wsl diff --git a/vendor/github.com/go-redis/redis/v7/.travis.yml b/vendor/github.com/go-redis/redis/v7/.travis.yml new file mode 100644 index 0000000000..0c1eef9188 --- /dev/null +++ b/vendor/github.com/go-redis/redis/v7/.travis.yml @@ -0,0 +1,24 @@ +dist: xenial +sudo: false +language: go + +services: + - redis-server + +go: + - 1.11.x + - 1.12.x + - 1.13.x + - tip + +matrix: + allow_failures: + - go: tip + +env: + - GO111MODULE=on + +go_import_path: github.com/go-redis/redis + +before_install: + - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.21.0 diff --git a/vendor/github.com/go-redis/redis/v7/CHANGELOG.md b/vendor/github.com/go-redis/redis/v7/CHANGELOG.md new file mode 100644 index 0000000000..88701cfd5a --- /dev/null +++ b/vendor/github.com/go-redis/redis/v7/CHANGELOG.md @@ -0,0 +1,42 @@ +# Changelog + +## v7.2 + +- Existing `HMSet` is renamed to `HSet` and old deprecated `HMSet` is restored for Redis 3 users. + +## v7 + +- *Important*. Tx.Pipeline now returns a non-transactional pipeline. Use Tx.TxPipeline for a transactional pipeline. +- WrapProcess is replaced with more convenient AddHook that has access to context.Context. +- WithContext now can not be used to create a shallow copy of the client. +- New methods ProcessContext, DoContext, and ExecContext. +- Client respects Context.Deadline when setting net.Conn deadline. +- Client listens on Context.Done while waiting for a connection from the pool and returns an error when context context is cancelled. +- Add PubSub.ChannelWithSubscriptions that sends `*Subscription` in addition to `*Message` to allow detecting reconnections. +- `time.Time` is now marshalled in RFC3339 format. `rdb.Get("foo").Time()` helper is added to parse the time. +- `SetLimiter` is removed and added `Options.Limiter` instead. +- `HMSet` is deprecated as of Redis v4. + +## v6.15 + +- Cluster and Ring pipelines process commands for each node in its own goroutine. + +## 6.14 + +- Added Options.MinIdleConns. +- Added Options.MaxConnAge. +- PoolStats.FreeConns is renamed to PoolStats.IdleConns. +- Add Client.Do to simplify creating custom commands. +- Add Cmd.String, Cmd.Int, Cmd.Int64, Cmd.Uint64, Cmd.Float64, and Cmd.Bool helpers. +- Lower memory usage. + +## v6.13 + +- Ring got new options called `HashReplicas` and `Hash`. It is recommended to set `HashReplicas = 1000` for better keys distribution between shards. +- Cluster client was optimized to use much less memory when reloading cluster state. +- PubSub.ReceiveMessage is re-worked to not use ReceiveTimeout so it does not lose data when timeout occurres. In most cases it is recommended to use PubSub.Channel instead. +- Dialer.KeepAlive is set to 5 minutes by default. + +## v6.12 + +- ClusterClient got new option called `ClusterSlots` which allows to build cluster of normal Redis Servers that don't have cluster mode enabled. See https://godoc.org/github.com/go-redis/redis#example-NewClusterClient--ManualSetup diff --git a/vendor/github.com/go-redis/redis/LICENSE b/vendor/github.com/go-redis/redis/v7/LICENSE similarity index 100% rename from vendor/github.com/go-redis/redis/LICENSE rename to vendor/github.com/go-redis/redis/v7/LICENSE diff --git a/vendor/github.com/go-redis/redis/Makefile b/vendor/github.com/go-redis/redis/v7/Makefile similarity index 65% rename from vendor/github.com/go-redis/redis/Makefile rename to vendor/github.com/go-redis/redis/v7/Makefile index 1fbdac91c5..421993ef28 100644 --- a/vendor/github.com/go-redis/redis/Makefile +++ b/vendor/github.com/go-redis/redis/v7/Makefile @@ -1,8 +1,9 @@ all: testdeps go test ./... go test ./... -short -race + go test ./... -run=NONE -bench=. -benchmem env GOOS=linux GOARCH=386 go test ./... - go vet + golangci-lint run testdeps: testdata/redis/src/redis-server @@ -13,8 +14,7 @@ bench: testdeps testdata/redis: mkdir -p $@ - wget -qO- https://github.com/antirez/redis/archive/unstable.tar.gz | tar xvz --strip-components=1 -C $@ + wget -qO- http://download.redis.io/releases/redis-5.0.7.tar.gz | tar xvz --strip-components=1 -C $@ testdata/redis/src/redis-server: testdata/redis - sed -i.bak 's/libjemalloc.a/libjemalloc.a -lrt/g' $ threshold { @@ -560,10 +562,13 @@ func (c *clusterState) slotClosestNode(slot int) (*clusterNode, error) { return node, nil } -func (c *clusterState) slotRandomNode(slot int) *clusterNode { +func (c *clusterState) slotRandomNode(slot int) (*clusterNode, error) { nodes := c.slotNodes(slot) + if len(nodes) == 0 { + return c.nodes.Random() + } n := rand.Intn(len(nodes)) - return nodes[n] + return nodes[n], nil } func (c *clusterState) slotNodes(slot int) []*clusterNode { @@ -580,23 +585,12 @@ func (c *clusterState) slotNodes(slot int) []*clusterNode { return nil } -func (c *clusterState) IsConsistent() bool { - if c.nodes.opt.ClusterSlots != nil { - return true - } - return len(c.Masters) <= len(c.Slaves) -} - //------------------------------------------------------------------------------ type clusterStateHolder struct { load func() (*clusterState, error) - state atomic.Value - - firstErrMu sync.RWMutex - firstErr error - + state atomic.Value reloading uint32 // atomic } @@ -607,24 +601,8 @@ func newClusterStateHolder(fn func() (*clusterState, error)) *clusterStateHolder } func (c *clusterStateHolder) Reload() (*clusterState, error) { - state, err := c.reload() - if err != nil { - return nil, err - } - if !state.IsConsistent() { - time.AfterFunc(time.Second, c.LazyReload) - } - return state, nil -} - -func (c *clusterStateHolder) reload() (*clusterState, error) { state, err := c.load() if err != nil { - c.firstErrMu.Lock() - if c.firstErr == nil { - c.firstErr = err - } - c.firstErrMu.Unlock() return nil, err } c.state.Store(state) @@ -638,16 +616,11 @@ func (c *clusterStateHolder) LazyReload() { go func() { defer atomic.StoreUint32(&c.reloading, 0) - for { - state, err := c.reload() - if err != nil { - return - } - time.Sleep(100 * time.Millisecond) - if state.IsConsistent() { - return - } + _, err := c.Reload() + if err != nil { + return } + time.Sleep(100 * time.Millisecond) }() } @@ -660,15 +633,7 @@ func (c *clusterStateHolder) Get() (*clusterState, error) { } return state, nil } - - c.firstErrMu.RLock() - err := c.firstErr - c.firstErrMu.RUnlock() - if err != nil { - return nil, err - } - - return nil, errors.New("redis: cluster has no state") + return c.Reload() } func (c *clusterStateHolder) ReloadOrGet() (*clusterState, error) { @@ -681,22 +646,21 @@ func (c *clusterStateHolder) ReloadOrGet() (*clusterState, error) { //------------------------------------------------------------------------------ +type clusterClient struct { + opt *ClusterOptions + nodes *clusterNodes + state *clusterStateHolder //nolint:structcheck + cmdsInfoCache *cmdsInfoCache //nolint:structcheck +} + // ClusterClient is a Redis Cluster client representing a pool of zero // or more underlying connections. It's safe for concurrent use by // multiple goroutines. type ClusterClient struct { + *clusterClient cmdable - + hooks ctx context.Context - - opt *ClusterOptions - nodes *clusterNodes - state *clusterStateHolder - cmdsInfoCache *cmdsInfoCache - - process func(Cmder) error - processPipeline func([]Cmder) error - processTxPipeline func([]Cmder) error } // NewClusterClient returns a Redis Cluster client as described in @@ -705,20 +669,15 @@ func NewClusterClient(opt *ClusterOptions) *ClusterClient { opt.init() c := &ClusterClient{ - opt: opt, - nodes: newClusterNodes(opt), + clusterClient: &clusterClient{ + opt: opt, + nodes: newClusterNodes(opt), + }, + ctx: context.Background(), } c.state = newClusterStateHolder(c.loadState) c.cmdsInfoCache = newCmdsInfoCache(c.cmdsInfo) - - c.process = c.defaultProcess - c.processPipeline = c.defaultProcessPipeline - c.processTxPipeline = c.defaultProcessTxPipeline - - c.init() - - _, _ = c.state.Reload() - _, _ = c.cmdsInfoCache.Get() + c.cmdable = c.Process if opt.IdleCheckFrequency > 0 { go c.reaper(opt.IdleCheckFrequency) @@ -727,37 +686,19 @@ func NewClusterClient(opt *ClusterOptions) *ClusterClient { return c } -// ReloadState reloads cluster state. It calls ClusterSlots func -// to get cluster slots information. -func (c *ClusterClient) ReloadState() error { - _, err := c.state.Reload() - return err -} - -func (c *ClusterClient) init() { - c.cmdable.setProcessor(c.Process) -} - func (c *ClusterClient) Context() context.Context { - if c.ctx != nil { - return c.ctx - } - return context.Background() + return c.ctx } func (c *ClusterClient) WithContext(ctx context.Context) *ClusterClient { if ctx == nil { panic("nil context") } - c2 := c.copy() - c2.ctx = ctx - return c2 -} - -func (c *ClusterClient) copy() *ClusterClient { - cp := *c - cp.init() - return &cp + clone := *c + clone.cmdable = clone.Process + clone.hooks.lock() + clone.ctx = ctx + return &clone } // Options returns read-only Options that were used to create the client. @@ -765,158 +706,10 @@ func (c *ClusterClient) Options() *ClusterOptions { return c.opt } -func (c *ClusterClient) retryBackoff(attempt int) time.Duration { - return internal.RetryBackoff(attempt, c.opt.MinRetryBackoff, c.opt.MaxRetryBackoff) -} - -func (c *ClusterClient) cmdsInfo() (map[string]*CommandInfo, error) { - addrs, err := c.nodes.Addrs() - if err != nil { - return nil, err - } - - var firstErr error - for _, addr := range addrs { - node, err := c.nodes.Get(addr) - if err != nil { - return nil, err - } - if node == nil { - continue - } - - info, err := node.Client.Command().Result() - if err == nil { - return info, nil - } - if firstErr == nil { - firstErr = err - } - } - return nil, firstErr -} - -func (c *ClusterClient) cmdInfo(name string) *CommandInfo { - cmdsInfo, err := c.cmdsInfoCache.Get() - if err != nil { - return nil - } - - info := cmdsInfo[name] - if info == nil { - internal.Logf("info for cmd=%s not found", name) - } - return info -} - -func cmdSlot(cmd Cmder, pos int) int { - if pos == 0 { - return hashtag.RandomSlot() - } - firstKey := cmd.stringArg(pos) - return hashtag.Slot(firstKey) -} - -func (c *ClusterClient) cmdSlot(cmd Cmder) int { - cmdInfo := c.cmdInfo(cmd.Name()) - return cmdSlot(cmd, cmdFirstKeyPos(cmd, cmdInfo)) -} - -func (c *ClusterClient) cmdSlotAndNode(cmd Cmder) (int, *clusterNode, error) { - state, err := c.state.Get() - if err != nil { - return 0, nil, err - } - - cmdInfo := c.cmdInfo(cmd.Name()) - slot := cmdSlot(cmd, cmdFirstKeyPos(cmd, cmdInfo)) - - if cmdInfo != nil && cmdInfo.ReadOnly && c.opt.ReadOnly { - if c.opt.RouteByLatency { - node, err := state.slotClosestNode(slot) - return slot, node, err - } - - if c.opt.RouteRandomly { - node := state.slotRandomNode(slot) - return slot, node, nil - } - - node, err := state.slotSlaveNode(slot) - return slot, node, err - } - - node, err := state.slotMasterNode(slot) - return slot, node, err -} - -func (c *ClusterClient) slotMasterNode(slot int) (*clusterNode, error) { - state, err := c.state.Get() - if err != nil { - return nil, err - } - - nodes := state.slotNodes(slot) - if len(nodes) > 0 { - return nodes[0], nil - } - return c.nodes.Random() -} - -func (c *ClusterClient) Watch(fn func(*Tx) error, keys ...string) error { - if len(keys) == 0 { - return fmt.Errorf("redis: Watch requires at least one key") - } - - slot := hashtag.Slot(keys[0]) - for _, key := range keys[1:] { - if hashtag.Slot(key) != slot { - err := fmt.Errorf("redis: Watch requires all keys to be in the same slot") - return err - } - } - - node, err := c.slotMasterNode(slot) - if err != nil { - return err - } - - for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { - if attempt > 0 { - time.Sleep(c.retryBackoff(attempt)) - } - - err = node.Client.Watch(fn, keys...) - if err == nil { - break - } - - if internal.IsRetryableError(err, true) { - c.state.LazyReload() - continue - } - - moved, ask, addr := internal.IsMovedError(err) - if moved || ask { - c.state.LazyReload() - node, err = c.nodes.GetOrCreate(addr) - if err != nil { - return err - } - continue - } - - if err == pool.ErrClosed { - node, err = c.slotMasterNode(slot) - if err != nil { - return err - } - continue - } - - return err - } - +// ReloadState reloads cluster state. If available it calls ClusterSlots func +// to get cluster slots information. +func (c *ClusterClient) ReloadState() error { + _, err := c.state.Reload() return err } @@ -930,99 +723,111 @@ func (c *ClusterClient) Close() error { // Do creates a Cmd from the args and processes the cmd. func (c *ClusterClient) Do(args ...interface{}) *Cmd { + return c.DoContext(c.ctx, args...) +} + +func (c *ClusterClient) DoContext(ctx context.Context, args ...interface{}) *Cmd { cmd := NewCmd(args...) - c.Process(cmd) + _ = c.ProcessContext(ctx, cmd) return cmd } -func (c *ClusterClient) WrapProcess( - fn func(oldProcess func(Cmder) error) func(Cmder) error, -) { - c.process = fn(c.process) -} - func (c *ClusterClient) Process(cmd Cmder) error { - return c.process(cmd) + return c.ProcessContext(c.ctx, cmd) } -func (c *ClusterClient) defaultProcess(cmd Cmder) error { +func (c *ClusterClient) ProcessContext(ctx context.Context, cmd Cmder) error { + return c.hooks.process(ctx, cmd, c.process) +} + +func (c *ClusterClient) process(ctx context.Context, cmd Cmder) error { + err := c._process(ctx, cmd) + if err != nil { + cmd.SetErr(err) + return err + } + return nil +} + +func (c *ClusterClient) _process(ctx context.Context, cmd Cmder) error { + cmdInfo := c.cmdInfo(cmd.Name()) + slot := c.cmdSlot(cmd) + var node *clusterNode var ask bool + var lastErr error for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { if attempt > 0 { - time.Sleep(c.retryBackoff(attempt)) + if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil { + return err + } } if node == nil { var err error - _, node, err = c.cmdSlotAndNode(cmd) + node, err = c.cmdNode(cmdInfo, slot) if err != nil { - cmd.setErr(err) - break + return err } } - var err error if ask { pipe := node.Client.Pipeline() - _ = pipe.Process(NewCmd("ASKING")) + _ = pipe.Process(NewCmd("asking")) _ = pipe.Process(cmd) - _, err = pipe.Exec() + _, lastErr = pipe.ExecContext(ctx) _ = pipe.Close() ask = false } else { - err = node.Client.Process(cmd) + lastErr = node.Client.ProcessContext(ctx, cmd) } // If there is no error - we are done. - if err == nil { - break + if lastErr == nil { + return nil } - - // If slave is loading - read from master. - if c.opt.ReadOnly && internal.IsLoadingError(err) { - node.MarkAsLoading() + if lastErr != Nil { + c.state.LazyReload() + } + if lastErr == pool.ErrClosed || isReadOnlyError(lastErr) { + node = nil continue } - if internal.IsRetryableError(err, true) { - c.state.LazyReload() - - // First retry the same node. - if attempt == 0 { - continue - } - - // Second try random node. - node, err = c.nodes.Random() - if err != nil { - break - } + // If slave is loading - pick another node. + if c.opt.ReadOnly && isLoadingError(lastErr) { + node.MarkAsFailing() + node = nil continue } var moved bool var addr string - moved, ask, addr = internal.IsMovedError(err) + moved, ask, addr = isMovedError(lastErr) if moved || ask { - c.state.LazyReload() - - node, err = c.nodes.GetOrCreate(addr) + var err error + node, err = c.nodes.Get(addr) if err != nil { - break + return err } continue } - if err == pool.ErrClosed { + if isRetryableError(lastErr, cmd.readTimeout() == nil) { + // First retry the same node. + if attempt == 0 { + continue + } + + // Second try another node. + node.MarkAsFailing() node = nil continue } - break + return lastErr } - - return cmd.Err() + return lastErr } // ForEachMaster concurrently calls the fn on each master node in the cluster. @@ -1035,6 +840,7 @@ func (c *ClusterClient) ForEachMaster(fn func(client *Client) error) error { var wg sync.WaitGroup errCh := make(chan error, 1) + for _, master := range state.Masters { wg.Add(1) go func(node *clusterNode) { @@ -1048,6 +854,7 @@ func (c *ClusterClient) ForEachMaster(fn func(client *Client) error) error { } }(master) } + wg.Wait() select { @@ -1068,6 +875,7 @@ func (c *ClusterClient) ForEachSlave(fn func(client *Client) error) error { var wg sync.WaitGroup errCh := make(chan error, 1) + for _, slave := range state.Slaves { wg.Add(1) go func(node *clusterNode) { @@ -1081,6 +889,7 @@ func (c *ClusterClient) ForEachSlave(fn func(client *Client) error) error { } }(slave) } + wg.Wait() select { @@ -1101,6 +910,7 @@ func (c *ClusterClient) ForEachNode(fn func(client *Client) error) error { var wg sync.WaitGroup errCh := make(chan error, 1) + worker := func(node *clusterNode) { defer wg.Done() err := fn(node.Client) @@ -1122,6 +932,7 @@ func (c *ClusterClient) ForEachNode(fn func(client *Client) error) error { } wg.Wait() + select { case err := <-errCh: return err @@ -1180,7 +991,7 @@ func (c *ClusterClient) loadState() (*clusterState, error) { var firstErr error for _, addr := range addrs { - node, err := c.nodes.GetOrCreate(addr) + node, err := c.nodes.Get(addr) if err != nil { if firstErr == nil { firstErr = err @@ -1216,7 +1027,7 @@ func (c *ClusterClient) reaper(idleCheckFrequency time.Duration) { for _, node := range nodes { _, err := node.Client.connPool.(*pool.ConnPool).ReapStaleConns() if err != nil { - internal.Logf("ReapStaleConns failed: %s", err) + internal.Logger.Printf("ReapStaleConns failed: %s", err) } } } @@ -1224,9 +1035,10 @@ func (c *ClusterClient) reaper(idleCheckFrequency time.Duration) { func (c *ClusterClient) Pipeline() Pipeliner { pipe := Pipeline{ + ctx: c.ctx, exec: c.processPipeline, } - pipe.statefulCmdable.setProcessor(pipe.Process) + pipe.init() return &pipe } @@ -1234,14 +1046,13 @@ func (c *ClusterClient) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) { return c.Pipeline().Pipelined(fn) } -func (c *ClusterClient) WrapProcessPipeline( - fn func(oldProcess func([]Cmder) error) func([]Cmder) error, -) { - c.processPipeline = fn(c.processPipeline) +func (c *ClusterClient) processPipeline(ctx context.Context, cmds []Cmder) error { + return c.hooks.processPipeline(ctx, cmds, c._processPipeline) } -func (c *ClusterClient) defaultProcessPipeline(cmds []Cmder) error { - cmdsMap, err := c.mapCmdsByNode(cmds) +func (c *ClusterClient) _processPipeline(ctx context.Context, cmds []Cmder) error { + cmdsMap := newCmdsMap() + err := c.mapCmdsByNode(cmdsMap, cmds) if err != nil { setCmdsErr(cmds, err) return err @@ -1249,31 +1060,36 @@ func (c *ClusterClient) defaultProcessPipeline(cmds []Cmder) error { for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { if attempt > 0 { - time.Sleep(c.retryBackoff(attempt)) + if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil { + setCmdsErr(cmds, err) + return err + } } - failedCmds := make(map[*clusterNode][]Cmder) + failedCmds := newCmdsMap() + var wg sync.WaitGroup - for node, cmds := range cmdsMap { - cn, err := node.Client.getConn() - if err != nil { - if err == pool.ErrClosed { - c.remapCmds(cmds, failedCmds) + for node, cmds := range cmdsMap.m { + wg.Add(1) + go func(node *clusterNode, cmds []Cmder) { + defer wg.Done() + + err := c._processPipelineNode(ctx, node, cmds, failedCmds) + if err == nil { + return + } + if attempt < c.opt.MaxRedirects { + if err := c.mapCmdsByNode(failedCmds, cmds); err != nil { + setCmdsErr(cmds, err) + } } else { setCmdsErr(cmds, err) } - continue - } - - err = c.pipelineProcessCmds(node, cn, cmds, failedCmds) - if err == nil || internal.IsRedisError(err) { - node.Client.connPool.Put(cn) - } else { - node.Client.connPool.Remove(cn) - } + }(node, cmds) } - if len(failedCmds) == 0 { + wg.Wait() + if len(failedCmds.m) == 0 { break } cmdsMap = failedCmds @@ -1282,30 +1098,33 @@ func (c *ClusterClient) defaultProcessPipeline(cmds []Cmder) error { return cmdsFirstErr(cmds) } -func (c *ClusterClient) mapCmdsByNode(cmds []Cmder) (map[*clusterNode][]Cmder, error) { +func (c *ClusterClient) mapCmdsByNode(cmdsMap *cmdsMap, cmds []Cmder) error { state, err := c.state.Get() if err != nil { - setCmdsErr(cmds, err) - return nil, err + return err } - cmdsMap := make(map[*clusterNode][]Cmder) - cmdsAreReadOnly := c.cmdsAreReadOnly(cmds) - for _, cmd := range cmds { - var node *clusterNode - var err error - if cmdsAreReadOnly { - _, node, err = c.cmdSlotAndNode(cmd) - } else { + if c.opt.ReadOnly && c.cmdsAreReadOnly(cmds) { + for _, cmd := range cmds { slot := c.cmdSlot(cmd) - node, err = state.slotMasterNode(slot) + node, err := c.slotReadOnlyNode(state, slot) + if err != nil { + return err + } + cmdsMap.Add(node, cmd) } - if err != nil { - return nil, err - } - cmdsMap[node] = append(cmdsMap[node], cmd) + return nil } - return cmdsMap, nil + + for _, cmd := range cmds { + slot := c.cmdSlot(cmd) + node, err := state.slotMasterNode(slot) + if err != nil { + return err + } + cmdsMap.Add(node, cmd) + } + return nil } func (c *ClusterClient) cmdsAreReadOnly(cmds []Cmder) bool { @@ -1318,94 +1137,83 @@ func (c *ClusterClient) cmdsAreReadOnly(cmds []Cmder) bool { return true } -func (c *ClusterClient) remapCmds(cmds []Cmder, failedCmds map[*clusterNode][]Cmder) { - remappedCmds, err := c.mapCmdsByNode(cmds) - if err != nil { - setCmdsErr(cmds, err) - return - } - - for node, cmds := range remappedCmds { - failedCmds[node] = cmds - } -} - -func (c *ClusterClient) pipelineProcessCmds( - node *clusterNode, cn *pool.Conn, cmds []Cmder, failedCmds map[*clusterNode][]Cmder, +func (c *ClusterClient) _processPipelineNode( + ctx context.Context, node *clusterNode, cmds []Cmder, failedCmds *cmdsMap, ) error { - err := cn.WithWriter(c.opt.WriteTimeout, func(wr *proto.Writer) error { - return writeCmd(wr, cmds...) - }) - if err != nil { - setCmdsErr(cmds, err) - failedCmds[node] = cmds - return err - } + return node.Client.hooks.processPipeline(ctx, cmds, func(ctx context.Context, cmds []Cmder) error { + return node.Client.withConn(ctx, func(ctx context.Context, cn *pool.Conn) error { + err := cn.WithWriter(ctx, c.opt.WriteTimeout, func(wr *proto.Writer) error { + return writeCmds(wr, cmds) + }) + if err != nil { + return err + } - err = cn.WithReader(c.opt.ReadTimeout, func(rd *proto.Reader) error { - return c.pipelineReadCmds(rd, cmds, failedCmds) + return cn.WithReader(ctx, c.opt.ReadTimeout, func(rd *proto.Reader) error { + return c.pipelineReadCmds(node, rd, cmds, failedCmds) + }) + }) }) - return err } func (c *ClusterClient) pipelineReadCmds( - rd *proto.Reader, cmds []Cmder, failedCmds map[*clusterNode][]Cmder, + node *clusterNode, rd *proto.Reader, cmds []Cmder, failedCmds *cmdsMap, ) error { for _, cmd := range cmds { err := cmd.readReply(rd) if err == nil { continue } - if c.checkMovedErr(cmd, err, failedCmds) { continue } - if internal.IsRedisError(err) { + if c.opt.ReadOnly && isLoadingError(err) { + node.MarkAsFailing() + return err + } + if isRedisError(err) { continue } - return err } return nil } func (c *ClusterClient) checkMovedErr( - cmd Cmder, err error, failedCmds map[*clusterNode][]Cmder, + cmd Cmder, err error, failedCmds *cmdsMap, ) bool { - moved, ask, addr := internal.IsMovedError(err) + moved, ask, addr := isMovedError(err) + if !moved && !ask { + return false + } + + node, err := c.nodes.Get(addr) + if err != nil { + return false + } if moved { c.state.LazyReload() - - node, err := c.nodes.GetOrCreate(addr) - if err != nil { - return false - } - - failedCmds[node] = append(failedCmds[node], cmd) + failedCmds.Add(node, cmd) return true } if ask { - node, err := c.nodes.GetOrCreate(addr) - if err != nil { - return false - } - - failedCmds[node] = append(failedCmds[node], NewCmd("ASKING"), cmd) + failedCmds.Add(node, NewCmd("asking"), cmd) return true } - return false + panic("not reached") } // TxPipeline acts like Pipeline, but wraps queued commands with MULTI/EXEC. func (c *ClusterClient) TxPipeline() Pipeliner { pipe := Pipeline{ + ctx: c.ctx, exec: c.processTxPipeline, } - pipe.statefulCmdable.setProcessor(pipe.Process) + pipe.init() return &pipe } @@ -1413,9 +1221,14 @@ func (c *ClusterClient) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) { return c.TxPipeline().Pipelined(fn) } -func (c *ClusterClient) defaultProcessTxPipeline(cmds []Cmder) error { +func (c *ClusterClient) processTxPipeline(ctx context.Context, cmds []Cmder) error { + return c.hooks.processPipeline(ctx, cmds, c._processTxPipeline) +} + +func (c *ClusterClient) _processTxPipeline(ctx context.Context, cmds []Cmder) error { state, err := c.state.Get() if err != nil { + setCmdsErr(cmds, err) return err } @@ -1426,38 +1239,43 @@ func (c *ClusterClient) defaultProcessTxPipeline(cmds []Cmder) error { setCmdsErr(cmds, err) continue } - cmdsMap := map[*clusterNode][]Cmder{node: cmds} + cmdsMap := map[*clusterNode][]Cmder{node: cmds} for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { if attempt > 0 { - time.Sleep(c.retryBackoff(attempt)) + if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil { + setCmdsErr(cmds, err) + return err + } } - failedCmds := make(map[*clusterNode][]Cmder) + failedCmds := newCmdsMap() + var wg sync.WaitGroup for node, cmds := range cmdsMap { - cn, err := node.Client.getConn() - if err != nil { - if err == pool.ErrClosed { - c.remapCmds(cmds, failedCmds) + wg.Add(1) + go func(node *clusterNode, cmds []Cmder) { + defer wg.Done() + + err := c._processTxPipelineNode(ctx, node, cmds, failedCmds) + if err == nil { + return + } + if attempt < c.opt.MaxRedirects { + if err := c.mapCmdsByNode(failedCmds, cmds); err != nil { + setCmdsErr(cmds, err) + } } else { setCmdsErr(cmds, err) } - continue - } - - err = c.txPipelineProcessCmds(node, cn, cmds, failedCmds) - if err == nil || internal.IsRedisError(err) { - node.Client.connPool.Put(cn) - } else { - node.Client.connPool.Remove(cn) - } + }(node, cmds) } - if len(failedCmds) == 0 { + wg.Wait() + if len(failedCmds.m) == 0 { break } - cmdsMap = failedCmds + cmdsMap = failedCmds.m } } @@ -1473,48 +1291,51 @@ func (c *ClusterClient) mapCmdsBySlot(cmds []Cmder) map[int][]Cmder { return cmdsMap } -func (c *ClusterClient) txPipelineProcessCmds( - node *clusterNode, cn *pool.Conn, cmds []Cmder, failedCmds map[*clusterNode][]Cmder, +func (c *ClusterClient) _processTxPipelineNode( + ctx context.Context, node *clusterNode, cmds []Cmder, failedCmds *cmdsMap, ) error { - err := cn.WithWriter(c.opt.WriteTimeout, func(wr *proto.Writer) error { - return txPipelineWriteMulti(wr, cmds) - }) - if err != nil { - setCmdsErr(cmds, err) - failedCmds[node] = cmds - return err - } + return node.Client.hooks.processTxPipeline(ctx, cmds, func(ctx context.Context, cmds []Cmder) error { + return node.Client.withConn(ctx, func(ctx context.Context, cn *pool.Conn) error { + err := cn.WithWriter(ctx, c.opt.WriteTimeout, func(wr *proto.Writer) error { + return writeCmds(wr, cmds) + }) + if err != nil { + return err + } - err = cn.WithReader(c.opt.ReadTimeout, func(rd *proto.Reader) error { - err := c.txPipelineReadQueued(rd, cmds, failedCmds) - if err != nil { - setCmdsErr(cmds, err) - return err - } - return pipelineReadCmds(rd, cmds) + return cn.WithReader(ctx, c.opt.ReadTimeout, func(rd *proto.Reader) error { + statusCmd := cmds[0].(*StatusCmd) + // Trim multi and exec. + cmds = cmds[1 : len(cmds)-1] + + err := c.txPipelineReadQueued(rd, statusCmd, cmds, failedCmds) + if err != nil { + moved, ask, addr := isMovedError(err) + if moved || ask { + return c.cmdsMoved(cmds, moved, ask, addr, failedCmds) + } + return err + } + + return pipelineReadCmds(rd, cmds) + }) + }) }) - return err } func (c *ClusterClient) txPipelineReadQueued( - rd *proto.Reader, cmds []Cmder, failedCmds map[*clusterNode][]Cmder, + rd *proto.Reader, statusCmd *StatusCmd, cmds []Cmder, failedCmds *cmdsMap, ) error { // Parse queued replies. - var statusCmd StatusCmd if err := statusCmd.readReply(rd); err != nil { return err } for _, cmd := range cmds { err := statusCmd.readReply(rd) - if err == nil { + if err == nil || c.checkMovedErr(cmd, err, failedCmds) || isRedisError(err) { continue } - - if c.checkMovedErr(cmd, err, failedCmds) || internal.IsRedisError(err) { - continue - } - return err } @@ -1529,57 +1350,151 @@ func (c *ClusterClient) txPipelineReadQueued( switch line[0] { case proto.ErrorReply: - err := proto.ParseErrorReply(line) - for _, cmd := range cmds { - if !c.checkMovedErr(cmd, err, failedCmds) { - break - } - } - return err + return proto.ParseErrorReply(line) case proto.ArrayReply: // ok default: - err := fmt.Errorf("redis: expected '*', but got line %q", line) - return err + return fmt.Errorf("redis: expected '*', but got line %q", line) } return nil } -func (c *ClusterClient) pubSub(channels []string) *PubSub { +func (c *ClusterClient) cmdsMoved( + cmds []Cmder, moved, ask bool, addr string, failedCmds *cmdsMap, +) error { + node, err := c.nodes.Get(addr) + if err != nil { + return err + } + + if moved { + c.state.LazyReload() + for _, cmd := range cmds { + failedCmds.Add(node, cmd) + } + return nil + } + + if ask { + for _, cmd := range cmds { + failedCmds.Add(node, NewCmd("asking"), cmd) + } + return nil + } + + return nil +} + +func (c *ClusterClient) Watch(fn func(*Tx) error, keys ...string) error { + return c.WatchContext(c.ctx, fn, keys...) +} + +func (c *ClusterClient) WatchContext(ctx context.Context, fn func(*Tx) error, keys ...string) error { + if len(keys) == 0 { + return fmt.Errorf("redis: Watch requires at least one key") + } + + slot := hashtag.Slot(keys[0]) + for _, key := range keys[1:] { + if hashtag.Slot(key) != slot { + err := fmt.Errorf("redis: Watch requires all keys to be in the same slot") + return err + } + } + + node, err := c.slotMasterNode(slot) + if err != nil { + return err + } + + for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { + if attempt > 0 { + if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil { + return err + } + } + + err = node.Client.WatchContext(ctx, fn, keys...) + if err == nil { + break + } + if err != Nil { + c.state.LazyReload() + } + + moved, ask, addr := isMovedError(err) + if moved || ask { + node, err = c.nodes.Get(addr) + if err != nil { + return err + } + continue + } + + if err == pool.ErrClosed || isReadOnlyError(err) { + node, err = c.slotMasterNode(slot) + if err != nil { + return err + } + continue + } + + if isRetryableError(err, true) { + continue + } + + return err + } + + return err +} + +func (c *ClusterClient) pubSub() *PubSub { var node *clusterNode pubsub := &PubSub{ opt: c.opt.clientOptions(), newConn: func(channels []string) (*pool.Conn, error) { - if node == nil { - var slot int - if len(channels) > 0 { - slot = hashtag.Slot(channels[0]) - } else { - slot = -1 - } - - masterNode, err := c.slotMasterNode(slot) - if err != nil { - return nil, err - } - node = masterNode + if node != nil { + panic("node != nil") } - return node.Client.newConn() + + var err error + if len(channels) > 0 { + slot := hashtag.Slot(channels[0]) + node, err = c.slotMasterNode(slot) + } else { + node, err = c.nodes.Random() + } + if err != nil { + return nil, err + } + + cn, err := node.Client.newConn(context.TODO()) + if err != nil { + node = nil + + return nil, err + } + + return cn, nil }, closeConn: func(cn *pool.Conn) error { - return node.Client.connPool.CloseConn(cn) + err := node.Client.connPool.CloseConn(cn) + node = nil + return err }, } pubsub.init() + return pubsub } // Subscribe subscribes the client to the specified channels. // Channels can be omitted to create empty subscription. func (c *ClusterClient) Subscribe(channels ...string) *PubSub { - pubsub := c.pubSub(channels) + pubsub := c.pubSub() if len(channels) > 0 { _ = pubsub.Subscribe(channels...) } @@ -1589,13 +1504,105 @@ func (c *ClusterClient) Subscribe(channels ...string) *PubSub { // PSubscribe subscribes the client to the given patterns. // Patterns can be omitted to create empty subscription. func (c *ClusterClient) PSubscribe(channels ...string) *PubSub { - pubsub := c.pubSub(channels) + pubsub := c.pubSub() if len(channels) > 0 { _ = pubsub.PSubscribe(channels...) } return pubsub } +func (c *ClusterClient) retryBackoff(attempt int) time.Duration { + return internal.RetryBackoff(attempt, c.opt.MinRetryBackoff, c.opt.MaxRetryBackoff) +} + +func (c *ClusterClient) cmdsInfo() (map[string]*CommandInfo, error) { + addrs, err := c.nodes.Addrs() + if err != nil { + return nil, err + } + + var firstErr error + for _, addr := range addrs { + node, err := c.nodes.Get(addr) + if err != nil { + return nil, err + } + if node == nil { + continue + } + + info, err := node.Client.Command().Result() + if err == nil { + return info, nil + } + if firstErr == nil { + firstErr = err + } + } + return nil, firstErr +} + +func (c *ClusterClient) cmdInfo(name string) *CommandInfo { + cmdsInfo, err := c.cmdsInfoCache.Get() + if err != nil { + return nil + } + + info := cmdsInfo[name] + if info == nil { + internal.Logger.Printf("info for cmd=%s not found", name) + } + return info +} + +func (c *ClusterClient) cmdSlot(cmd Cmder) int { + args := cmd.Args() + if args[0] == "cluster" && args[1] == "getkeysinslot" { + return args[2].(int) + } + + cmdInfo := c.cmdInfo(cmd.Name()) + return cmdSlot(cmd, cmdFirstKeyPos(cmd, cmdInfo)) +} + +func cmdSlot(cmd Cmder, pos int) int { + if pos == 0 { + return hashtag.RandomSlot() + } + firstKey := cmd.stringArg(pos) + return hashtag.Slot(firstKey) +} + +func (c *ClusterClient) cmdNode(cmdInfo *CommandInfo, slot int) (*clusterNode, error) { + state, err := c.state.Get() + if err != nil { + return nil, err + } + + if c.opt.ReadOnly && cmdInfo != nil && cmdInfo.ReadOnly { + return c.slotReadOnlyNode(state, slot) + } + return state.slotMasterNode(slot) +} + +func (c *clusterClient) slotReadOnlyNode(state *clusterState, slot int) (*clusterNode, error) { + if c.opt.RouteByLatency { + return state.slotClosestNode(slot) + } + if c.opt.RouteRandomly { + return state.slotRandomNode(slot) + } + return state.slotSlaveNode(slot) +} + +func (c *ClusterClient) slotMasterNode(slot int) (*clusterNode, error) { + state, err := c.state.Get() + if err != nil { + return nil, err + } + return state.slotMasterNode(slot) +} + func appendUniqueNode(nodes []*clusterNode, node *clusterNode) []*clusterNode { for _, n := range nodes { if n == node { @@ -1632,3 +1639,22 @@ func remove(ss []string, es ...string) []string { } return ss } + +//------------------------------------------------------------------------------ + +type cmdsMap struct { + mu sync.Mutex + m map[*clusterNode][]Cmder +} + +func newCmdsMap() *cmdsMap { + return &cmdsMap{ + m: make(map[*clusterNode][]Cmder), + } +} + +func (m *cmdsMap) Add(node *clusterNode, cmds ...Cmder) { + m.mu.Lock() + m.m[node] = append(m.m[node], cmds...) + m.mu.Unlock() +} diff --git a/vendor/github.com/go-redis/redis/cluster_commands.go b/vendor/github.com/go-redis/redis/v7/cluster_commands.go similarity index 95% rename from vendor/github.com/go-redis/redis/cluster_commands.go rename to vendor/github.com/go-redis/redis/v7/cluster_commands.go index dff62c902d..c9b9b9de24 100644 --- a/vendor/github.com/go-redis/redis/cluster_commands.go +++ b/vendor/github.com/go-redis/redis/v7/cluster_commands.go @@ -14,7 +14,7 @@ func (c *ClusterClient) DBSize() *IntCmd { return nil }) if err != nil { - cmd.setErr(err) + cmd.SetErr(err) return cmd } cmd.val = size diff --git a/vendor/github.com/go-redis/redis/command.go b/vendor/github.com/go-redis/redis/v7/command.go similarity index 65% rename from vendor/github.com/go-redis/redis/command.go rename to vendor/github.com/go-redis/redis/v7/command.go index ca44d7c8b8..266e389232 100644 --- a/vendor/github.com/go-redis/redis/command.go +++ b/vendor/github.com/go-redis/redis/v7/command.go @@ -7,27 +7,28 @@ import ( "strings" "time" - "github.com/go-redis/redis/internal" - "github.com/go-redis/redis/internal/proto" + "github.com/go-redis/redis/v7/internal" + "github.com/go-redis/redis/v7/internal/proto" + "github.com/go-redis/redis/v7/internal/util" ) type Cmder interface { Name() string Args() []interface{} + String() string stringArg(int) string - readReply(rd *proto.Reader) error - setErr(error) - readTimeout() *time.Duration + readReply(rd *proto.Reader) error + SetErr(error) Err() error } func setCmdsErr(cmds []Cmder, e error) { for _, cmd := range cmds { if cmd.Err() == nil { - cmd.setErr(e) + cmd.SetErr(e) } } } @@ -41,18 +42,21 @@ func cmdsFirstErr(cmds []Cmder) error { return nil } -func writeCmd(wr *proto.Writer, cmds ...Cmder) error { +func writeCmds(wr *proto.Writer, cmds []Cmder) error { for _, cmd := range cmds { - err := wr.WriteArgs(cmd.Args()) - if err != nil { + if err := writeCmd(wr, cmd); err != nil { return err } } return nil } +func writeCmd(wr *proto.Writer, cmd Cmder) error { + return wr.WriteArgs(cmd.Args()) +} + func cmdString(cmd Cmder, val interface{}) string { - var ss []string + ss := make([]string, 0, len(cmd.Args())) for _, arg := range cmd.Args() { ss = append(ss, fmt.Sprint(arg)) } @@ -69,7 +73,6 @@ func cmdString(cmd Cmder, val interface{}) string { } } return s - } func cmdFirstKeyPos(cmd Cmder, info *CommandInfo) int { @@ -92,38 +95,40 @@ func cmdFirstKeyPos(cmd Cmder, info *CommandInfo) int { //------------------------------------------------------------------------------ type baseCmd struct { - _args []interface{} - err error + args []interface{} + err error _readTimeout *time.Duration } var _ Cmder = (*Cmd)(nil) -func (cmd *baseCmd) Err() error { - return cmd.err +func (cmd *baseCmd) Name() string { + if len(cmd.args) == 0 { + return "" + } + // Cmd name must be lower cased. + return internal.ToLower(cmd.stringArg(0)) } func (cmd *baseCmd) Args() []interface{} { - return cmd._args + return cmd.args } func (cmd *baseCmd) stringArg(pos int) string { - if pos < 0 || pos >= len(cmd._args) { + if pos < 0 || pos >= len(cmd.args) { return "" } - s, _ := cmd._args[pos].(string) + s, _ := cmd.args[pos].(string) return s } -func (cmd *baseCmd) Name() string { - if len(cmd._args) > 0 { - // Cmd name must be lower cased. - s := internal.ToLower(cmd.stringArg(0)) - cmd._args[0] = s - return s - } - return "" +func (cmd *baseCmd) SetErr(e error) { + cmd.err = e +} + +func (cmd *baseCmd) Err() error { + return cmd.err } func (cmd *baseCmd) readTimeout() *time.Duration { @@ -134,10 +139,6 @@ func (cmd *baseCmd) setReadTimeout(d time.Duration) { cmd._readTimeout = &d } -func (cmd *baseCmd) setErr(e error) { - cmd.err = e -} - //------------------------------------------------------------------------------ type Cmd struct { @@ -148,10 +149,14 @@ type Cmd struct { func NewCmd(args ...interface{}) *Cmd { return &Cmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } +func (cmd *Cmd) String() string { + return cmdString(cmd, cmd.val) +} + func (cmd *Cmd) Val() interface{} { return cmd.val } @@ -160,7 +165,7 @@ func (cmd *Cmd) Result() (interface{}, error) { return cmd.val, cmd.err } -func (cmd *Cmd) String() (string, error) { +func (cmd *Cmd) Text() (string, error) { if cmd.err != nil { return "", cmd.err } @@ -183,7 +188,7 @@ func (cmd *Cmd) Int() (int, error) { case string: return strconv.Atoi(val) default: - err := fmt.Errorf("redis: unexpected type=%T for Int64", val) + err := fmt.Errorf("redis: unexpected type=%T for Int", val) return 0, err } } @@ -218,6 +223,25 @@ func (cmd *Cmd) Uint64() (uint64, error) { } } +func (cmd *Cmd) Float32() (float32, error) { + if cmd.err != nil { + return 0, cmd.err + } + switch val := cmd.val.(type) { + case int64: + return float32(val), nil + case string: + f, err := strconv.ParseFloat(val, 32) + if err != nil { + return 0, err + } + return float32(f), nil + default: + err := fmt.Errorf("redis: unexpected type=%T for Float32", val) + return 0, err + } +} + func (cmd *Cmd) Float64() (float64, error) { if cmd.err != nil { return 0, cmd.err @@ -255,27 +279,21 @@ func (cmd *Cmd) readReply(rd *proto.Reader) error { // Implements proto.MultiBulkParse func sliceParser(rd *proto.Reader, n int64) (interface{}, error) { - vals := make([]interface{}, 0, n) - for i := int64(0); i < n; i++ { + vals := make([]interface{}, n) + for i := 0; i < len(vals); i++ { v, err := rd.ReadReply(sliceParser) if err != nil { if err == Nil { - vals = append(vals, nil) + vals[i] = nil continue } if err, ok := err.(proto.RedisError); ok { - vals = append(vals, err) + vals[i] = err continue } return nil, err } - - switch v := v.(type) { - case string: - vals = append(vals, v) - default: - vals = append(vals, v) - } + vals[i] = v } return vals, nil } @@ -292,7 +310,7 @@ var _ Cmder = (*SliceCmd)(nil) func NewSliceCmd(args ...interface{}) *SliceCmd { return &SliceCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -330,7 +348,7 @@ var _ Cmder = (*StatusCmd)(nil) func NewStatusCmd(args ...interface{}) *StatusCmd { return &StatusCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -363,7 +381,7 @@ var _ Cmder = (*IntCmd)(nil) func NewIntCmd(args ...interface{}) *IntCmd { return &IntCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -375,6 +393,10 @@ func (cmd *IntCmd) Result() (int64, error) { return cmd.val, cmd.err } +func (cmd *IntCmd) Uint64() (uint64, error) { + return uint64(cmd.val), cmd.err +} + func (cmd *IntCmd) String() string { return cmdString(cmd, cmd.val) } @@ -386,6 +408,49 @@ func (cmd *IntCmd) readReply(rd *proto.Reader) error { //------------------------------------------------------------------------------ +type IntSliceCmd struct { + baseCmd + + val []int64 +} + +var _ Cmder = (*IntSliceCmd)(nil) + +func NewIntSliceCmd(args ...interface{}) *IntSliceCmd { + return &IntSliceCmd{ + baseCmd: baseCmd{args: args}, + } +} + +func (cmd *IntSliceCmd) Val() []int64 { + return cmd.val +} + +func (cmd *IntSliceCmd) Result() ([]int64, error) { + return cmd.val, cmd.err +} + +func (cmd *IntSliceCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *IntSliceCmd) readReply(rd *proto.Reader) error { + _, cmd.err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make([]int64, n) + for i := 0; i < len(cmd.val); i++ { + num, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + cmd.val[i] = num + } + return nil, nil + }) + return cmd.err +} + +//------------------------------------------------------------------------------ + type DurationCmd struct { baseCmd @@ -397,7 +462,7 @@ var _ Cmder = (*DurationCmd)(nil) func NewDurationCmd(precision time.Duration, args ...interface{}) *DurationCmd { return &DurationCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, precision: precision, } } @@ -420,7 +485,14 @@ func (cmd *DurationCmd) readReply(rd *proto.Reader) error { if cmd.err != nil { return cmd.err } - cmd.val = time.Duration(n) * cmd.precision + switch n { + // -2 if the key does not exist + // -1 if the key exists but has no associated expire + case -2, -1: + cmd.val = time.Duration(n) + default: + cmd.val = time.Duration(n) * cmd.precision + } return nil } @@ -436,7 +508,7 @@ var _ Cmder = (*TimeCmd)(nil) func NewTimeCmd(args ...interface{}) *TimeCmd { return &TimeCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -453,32 +525,25 @@ func (cmd *TimeCmd) String() string { } func (cmd *TimeCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(timeParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.(time.Time) - return nil -} + _, cmd.err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + if n != 2 { + return nil, fmt.Errorf("got %d elements, expected 2", n) + } -// Implements proto.MultiBulkParse -func timeParser(rd *proto.Reader, n int64) (interface{}, error) { - if n != 2 { - return nil, fmt.Errorf("got %d elements, expected 2", n) - } + sec, err := rd.ReadInt() + if err != nil { + return nil, err + } - sec, err := rd.ReadInt() - if err != nil { - return nil, err - } + microsec, err := rd.ReadInt() + if err != nil { + return nil, err + } - microsec, err := rd.ReadInt() - if err != nil { - return nil, err - } - - return time.Unix(sec, microsec*1000), nil + cmd.val = time.Unix(sec, microsec*1000) + return nil, nil + }) + return cmd.err } //------------------------------------------------------------------------------ @@ -493,7 +558,7 @@ var _ Cmder = (*BoolCmd)(nil) func NewBoolCmd(args ...interface{}) *BoolCmd { return &BoolCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -514,7 +579,6 @@ func (cmd *BoolCmd) readReply(rd *proto.Reader) error { v, cmd.err = rd.ReadReply(nil) // `SET key value NX` returns nil when key already exists. But // `SETNX key value` returns bool (0/1). So convert nil to bool. - // TODO: is this okay? if cmd.err == Nil { cmd.val = false cmd.err = nil @@ -548,7 +612,7 @@ var _ Cmder = (*StringCmd)(nil) func NewStringCmd(args ...interface{}) *StringCmd { return &StringCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -561,7 +625,7 @@ func (cmd *StringCmd) Result() (string, error) { } func (cmd *StringCmd) Bytes() ([]byte, error) { - return []byte(cmd.val), cmd.err + return util.StringToBytes(cmd.val), cmd.err } func (cmd *StringCmd) Int() (int, error) { @@ -585,6 +649,17 @@ func (cmd *StringCmd) Uint64() (uint64, error) { return strconv.ParseUint(cmd.Val(), 10, 64) } +func (cmd *StringCmd) Float32() (float32, error) { + if cmd.err != nil { + return 0, cmd.err + } + f, err := strconv.ParseFloat(cmd.Val(), 32) + if err != nil { + return 0, err + } + return float32(f), nil +} + func (cmd *StringCmd) Float64() (float64, error) { if cmd.err != nil { return 0, cmd.err @@ -592,6 +667,13 @@ func (cmd *StringCmd) Float64() (float64, error) { return strconv.ParseFloat(cmd.Val(), 64) } +func (cmd *StringCmd) Time() (time.Time, error) { + if cmd.err != nil { + return time.Time{}, cmd.err + } + return time.Parse(time.RFC3339, cmd.Val()) +} + func (cmd *StringCmd) Scan(val interface{}) error { if cmd.err != nil { return cmd.err @@ -620,7 +702,7 @@ var _ Cmder = (*FloatCmd)(nil) func NewFloatCmd(args ...interface{}) *FloatCmd { return &FloatCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -653,7 +735,7 @@ var _ Cmder = (*StringSliceCmd)(nil) func NewStringSliceCmd(args ...interface{}) *StringSliceCmd { return &StringSliceCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -674,29 +756,21 @@ func (cmd *StringSliceCmd) ScanSlice(container interface{}) error { } func (cmd *StringSliceCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(stringSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]string) - return nil -} - -// Implements proto.MultiBulkParse -func stringSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - ss := make([]string, 0, n) - for i := int64(0); i < n; i++ { - s, err := rd.ReadString() - if err == Nil { - ss = append(ss, "") - } else if err != nil { - return nil, err - } else { - ss = append(ss, s) + _, cmd.err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make([]string, n) + for i := 0; i < len(cmd.val); i++ { + switch s, err := rd.ReadString(); { + case err == Nil: + cmd.val[i] = "" + case err != nil: + return nil, err + default: + cmd.val[i] = s + } } - } - return ss, nil + return nil, nil + }) + return cmd.err } //------------------------------------------------------------------------------ @@ -711,7 +785,7 @@ var _ Cmder = (*BoolSliceCmd)(nil) func NewBoolSliceCmd(args ...interface{}) *BoolSliceCmd { return &BoolSliceCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -728,26 +802,18 @@ func (cmd *BoolSliceCmd) String() string { } func (cmd *BoolSliceCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(boolSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]bool) - return nil -} - -// Implements proto.MultiBulkParse -func boolSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - bools := make([]bool, 0, n) - for i := int64(0); i < n; i++ { - n, err := rd.ReadIntReply() - if err != nil { - return nil, err + _, cmd.err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make([]bool, n) + for i := 0; i < len(cmd.val); i++ { + n, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + cmd.val[i] = n == 1 } - bools = append(bools, n == 1) - } - return bools, nil + return nil, nil + }) + return cmd.err } //------------------------------------------------------------------------------ @@ -762,7 +828,7 @@ var _ Cmder = (*StringStringMapCmd)(nil) func NewStringStringMapCmd(args ...interface{}) *StringStringMapCmd { return &StringStringMapCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -779,32 +845,24 @@ func (cmd *StringStringMapCmd) String() string { } func (cmd *StringStringMapCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(stringStringMapParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.(map[string]string) - return nil -} + _, cmd.err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make(map[string]string, n/2) + for i := int64(0); i < n; i += 2 { + key, err := rd.ReadString() + if err != nil { + return nil, err + } -// Implements proto.MultiBulkParse -func stringStringMapParser(rd *proto.Reader, n int64) (interface{}, error) { - m := make(map[string]string, n/2) - for i := int64(0); i < n; i += 2 { - key, err := rd.ReadString() - if err != nil { - return nil, err + value, err := rd.ReadString() + if err != nil { + return nil, err + } + + cmd.val[key] = value } - - value, err := rd.ReadString() - if err != nil { - return nil, err - } - - m[key] = value - } - return m, nil + return nil, nil + }) + return cmd.err } //------------------------------------------------------------------------------ @@ -819,7 +877,7 @@ var _ Cmder = (*StringIntMapCmd)(nil) func NewStringIntMapCmd(args ...interface{}) *StringIntMapCmd { return &StringIntMapCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -836,32 +894,24 @@ func (cmd *StringIntMapCmd) String() string { } func (cmd *StringIntMapCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(stringIntMapParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.(map[string]int64) - return nil -} + _, cmd.err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make(map[string]int64, n/2) + for i := int64(0); i < n; i += 2 { + key, err := rd.ReadString() + if err != nil { + return nil, err + } -// Implements proto.MultiBulkParse -func stringIntMapParser(rd *proto.Reader, n int64) (interface{}, error) { - m := make(map[string]int64, n/2) - for i := int64(0); i < n; i += 2 { - key, err := rd.ReadString() - if err != nil { - return nil, err + n, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + + cmd.val[key] = n } - - n, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - - m[key] = n - } - return m, nil + return nil, nil + }) + return cmd.err } //------------------------------------------------------------------------------ @@ -876,7 +926,7 @@ var _ Cmder = (*StringStructMapCmd)(nil) func NewStringStructMapCmd(args ...interface{}) *StringStructMapCmd { return &StringStructMapCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -893,27 +943,18 @@ func (cmd *StringStructMapCmd) String() string { } func (cmd *StringStructMapCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(stringStructMapParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.(map[string]struct{}) - return nil -} - -// Implements proto.MultiBulkParse -func stringStructMapParser(rd *proto.Reader, n int64) (interface{}, error) { - m := make(map[string]struct{}, n) - for i := int64(0); i < n; i++ { - key, err := rd.ReadString() - if err != nil { - return nil, err + _, cmd.err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make(map[string]struct{}, n) + for i := int64(0); i < n; i++ { + key, err := rd.ReadString() + if err != nil { + return nil, err + } + cmd.val[key] = struct{}{} } - - m[key] = struct{}{} - } - return m, nil + return nil, nil + }) + return cmd.err } //------------------------------------------------------------------------------ @@ -933,7 +974,7 @@ var _ Cmder = (*XMessageSliceCmd)(nil) func NewXMessageSliceCmd(args ...interface{}) *XMessageSliceCmd { return &XMessageSliceCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -961,23 +1002,30 @@ func (cmd *XMessageSliceCmd) readReply(rd *proto.Reader) error { // Implements proto.MultiBulkParse func xMessageSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - msgs := make([]XMessage, 0, n) - for i := int64(0); i < n; i++ { + msgs := make([]XMessage, n) + for i := 0; i < len(msgs); i++ { + i := i _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { id, err := rd.ReadString() if err != nil { return nil, err } + var values map[string]interface{} + v, err := rd.ReadArrayReply(stringInterfaceMapParser) if err != nil { - return nil, err + if err != proto.Nil { + return nil, err + } + } else { + values = v.(map[string]interface{}) } - msgs = append(msgs, XMessage{ + msgs[i] = XMessage{ ID: id, - Values: v.(map[string]interface{}), - }) + Values: values, + } return nil, nil }) if err != nil { @@ -1023,7 +1071,7 @@ var _ Cmder = (*XStreamSliceCmd)(nil) func NewXStreamSliceCmd(args ...interface{}) *XStreamSliceCmd { return &XStreamSliceCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -1040,45 +1088,38 @@ func (cmd *XStreamSliceCmd) String() string { } func (cmd *XStreamSliceCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(xStreamSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]XStream) - return nil -} + _, cmd.err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make([]XStream, n) + for i := 0; i < len(cmd.val); i++ { + i := i + _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + if n != 2 { + return nil, fmt.Errorf("got %d, wanted 2", n) + } -// Implements proto.MultiBulkParse -func xStreamSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - ret := make([]XStream, 0, n) - for i := int64(0); i < n; i++ { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - if n != 2 { - return nil, fmt.Errorf("got %d, wanted 2", n) - } + stream, err := rd.ReadString() + if err != nil { + return nil, err + } - stream, err := rd.ReadString() - if err != nil { - return nil, err - } + v, err := rd.ReadArrayReply(xMessageSliceParser) + if err != nil { + return nil, err + } - v, err := rd.ReadArrayReply(xMessageSliceParser) - if err != nil { - return nil, err - } - - ret = append(ret, XStream{ - Stream: stream, - Messages: v.([]XMessage), + cmd.val[i] = XStream{ + Stream: stream, + Messages: v.([]XMessage), + } + return nil, nil }) - return nil, nil - }) - if err != nil { - return nil, err + if err != nil { + return nil, err + } } - } - return ret, nil + return nil, nil + }) + return cmd.err } //------------------------------------------------------------------------------ @@ -1099,7 +1140,7 @@ var _ Cmder = (*XPendingCmd)(nil) func NewXPendingCmd(args ...interface{}) *XPendingCmd { return &XPendingCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -1116,81 +1157,74 @@ func (cmd *XPendingCmd) String() string { } func (cmd *XPendingCmd) readReply(rd *proto.Reader) error { - var info interface{} - info, cmd.err = rd.ReadArrayReply(xPendingParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = info.(*XPending) - return nil -} - -func xPendingParser(rd *proto.Reader, n int64) (interface{}, error) { - if n != 4 { - return nil, fmt.Errorf("got %d, wanted 4", n) - } - - count, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - - lower, err := rd.ReadString() - if err != nil && err != Nil { - return nil, err - } - - higher, err := rd.ReadString() - if err != nil && err != Nil { - return nil, err - } - - pending := &XPending{ - Count: count, - Lower: lower, - Higher: higher, - } - _, err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - for i := int64(0); i < n; i++ { - _, err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - if n != 2 { - return nil, fmt.Errorf("got %d, wanted 2", n) - } - - consumerName, err := rd.ReadString() - if err != nil { - return nil, err - } - - consumerPending, err := rd.ReadInt() - if err != nil { - return nil, err - } - - if pending.Consumers == nil { - pending.Consumers = make(map[string]int64) - } - pending.Consumers[consumerName] = consumerPending - - return nil, nil - }) - if err != nil { - return nil, err - } + _, cmd.err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + if n != 4 { + return nil, fmt.Errorf("got %d, wanted 4", n) } + + count, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + + lower, err := rd.ReadString() + if err != nil && err != Nil { + return nil, err + } + + higher, err := rd.ReadString() + if err != nil && err != Nil { + return nil, err + } + + cmd.val = &XPending{ + Count: count, + Lower: lower, + Higher: higher, + } + _, err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + for i := int64(0); i < n; i++ { + _, err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + if n != 2 { + return nil, fmt.Errorf("got %d, wanted 2", n) + } + + consumerName, err := rd.ReadString() + if err != nil { + return nil, err + } + + consumerPending, err := rd.ReadInt() + if err != nil { + return nil, err + } + + if cmd.val.Consumers == nil { + cmd.val.Consumers = make(map[string]int64) + } + cmd.val.Consumers[consumerName] = consumerPending + + return nil, nil + }) + if err != nil { + return nil, err + } + } + return nil, nil + }) + if err != nil && err != Nil { + return nil, err + } + return nil, nil }) - if err != nil && err != Nil { - return nil, err - } - - return pending, nil + return cmd.err } //------------------------------------------------------------------------------ type XPendingExt struct { - Id string + ID string Consumer string Idle time.Duration RetryCount int64 @@ -1205,7 +1239,7 @@ var _ Cmder = (*XPendingExtCmd)(nil) func NewXPendingExtCmd(args ...interface{}) *XPendingExtCmd { return &XPendingExtCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -1222,62 +1256,143 @@ func (cmd *XPendingExtCmd) String() string { } func (cmd *XPendingExtCmd) readReply(rd *proto.Reader) error { - var info interface{} - info, cmd.err = rd.ReadArrayReply(xPendingExtSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = info.([]XPendingExt) - return nil -} + _, cmd.err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make([]XPendingExt, 0, n) + for i := int64(0); i < n; i++ { + _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + if n != 4 { + return nil, fmt.Errorf("got %d, wanted 4", n) + } -func xPendingExtSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - ret := make([]XPendingExt, 0, n) - for i := int64(0); i < n; i++ { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - if n != 4 { - return nil, fmt.Errorf("got %d, wanted 4", n) - } + id, err := rd.ReadString() + if err != nil { + return nil, err + } - id, err := rd.ReadString() + consumer, err := rd.ReadString() + if err != nil && err != Nil { + return nil, err + } + + idle, err := rd.ReadIntReply() + if err != nil && err != Nil { + return nil, err + } + + retryCount, err := rd.ReadIntReply() + if err != nil && err != Nil { + return nil, err + } + + cmd.val = append(cmd.val, XPendingExt{ + ID: id, + Consumer: consumer, + Idle: time.Duration(idle) * time.Millisecond, + RetryCount: retryCount, + }) + return nil, nil + }) if err != nil { return nil, err } + } + return nil, nil + }) + return cmd.err +} - consumer, err := rd.ReadString() - if err != nil && err != Nil { - return nil, err +//------------------------------------------------------------------------------ + +type XInfoGroupsCmd struct { + baseCmd + val []XInfoGroups +} + +type XInfoGroups struct { + Name string + Consumers int64 + Pending int64 + LastDeliveredID string +} + +var _ Cmder = (*XInfoGroupsCmd)(nil) + +func NewXInfoGroupsCmd(stream string) *XInfoGroupsCmd { + return &XInfoGroupsCmd{ + baseCmd: baseCmd{args: []interface{}{"xinfo", "groups", stream}}, + } +} + +func (cmd *XInfoGroupsCmd) Val() []XInfoGroups { + return cmd.val +} + +func (cmd *XInfoGroupsCmd) Result() ([]XInfoGroups, error) { + return cmd.val, cmd.err +} + +func (cmd *XInfoGroupsCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *XInfoGroupsCmd) readReply(rd *proto.Reader) error { + _, cmd.err = rd.ReadArrayReply( + func(rd *proto.Reader, n int64) (interface{}, error) { + for i := int64(0); i < n; i++ { + v, err := rd.ReadReply(xGroupInfoParser) + if err != nil { + return nil, err + } + cmd.val = append(cmd.val, v.(XInfoGroups)) } - - idle, err := rd.ReadIntReply() - if err != nil && err != Nil { - return nil, err - } - - retryCount, err := rd.ReadIntReply() - if err != nil && err != Nil { - return nil, err - } - - ret = append(ret, XPendingExt{ - Id: id, - Consumer: consumer, - Idle: time.Duration(idle) * time.Millisecond, - RetryCount: retryCount, - }) return nil, nil }) + return nil +} + +func xGroupInfoParser(rd *proto.Reader, n int64) (interface{}, error) { + if n != 8 { + return nil, fmt.Errorf("redis: got %d elements in XINFO GROUPS reply,"+ + "wanted 8", n) + } + var ( + err error + grp XInfoGroups + key string + val string + ) + + for i := 0; i < 4; i++ { + key, err = rd.ReadString() + if err != nil { + return nil, err + } + val, err = rd.ReadString() + if err != nil { + return nil, err + } + switch key { + case "name": + grp.Name = val + case "consumers": + grp.Consumers, err = strconv.ParseInt(val, 0, 64) + case "pending": + grp.Pending, err = strconv.ParseInt(val, 0, 64) + case "last-delivered-id": + grp.LastDeliveredID = val + default: + return nil, fmt.Errorf("redis: unexpected content %s "+ + "in XINFO GROUPS reply", key) + } if err != nil { return nil, err } } - return ret, nil + return grp, err } //------------------------------------------------------------------------------ -//------------------------------------------------------------------------------ - type ZSliceCmd struct { baseCmd @@ -1288,7 +1403,7 @@ var _ Cmder = (*ZSliceCmd)(nil) func NewZSliceCmd(args ...interface{}) *ZSliceCmd { return &ZSliceCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -1305,34 +1420,84 @@ func (cmd *ZSliceCmd) String() string { } func (cmd *ZSliceCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(zSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]Z) - return nil + _, cmd.err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make([]Z, n/2) + for i := 0; i < len(cmd.val); i++ { + member, err := rd.ReadString() + if err != nil { + return nil, err + } + + score, err := rd.ReadFloatReply() + if err != nil { + return nil, err + } + + cmd.val[i] = Z{ + Member: member, + Score: score, + } + } + return nil, nil + }) + return cmd.err } -// Implements proto.MultiBulkParse -func zSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - zz := make([]Z, n/2) - for i := int64(0); i < n; i += 2 { +//------------------------------------------------------------------------------ + +type ZWithKeyCmd struct { + baseCmd + + val *ZWithKey +} + +var _ Cmder = (*ZWithKeyCmd)(nil) + +func NewZWithKeyCmd(args ...interface{}) *ZWithKeyCmd { + return &ZWithKeyCmd{ + baseCmd: baseCmd{args: args}, + } +} + +func (cmd *ZWithKeyCmd) Val() *ZWithKey { + return cmd.val +} + +func (cmd *ZWithKeyCmd) Result() (*ZWithKey, error) { + return cmd.Val(), cmd.Err() +} + +func (cmd *ZWithKeyCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *ZWithKeyCmd) readReply(rd *proto.Reader) error { + _, cmd.err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + if n != 3 { + return nil, fmt.Errorf("got %d elements, expected 3", n) + } + + cmd.val = &ZWithKey{} var err error - z := &zz[i/2] - - z.Member, err = rd.ReadString() + cmd.val.Key, err = rd.ReadString() if err != nil { return nil, err } - z.Score, err = rd.ReadFloatReply() + cmd.val.Member, err = rd.ReadString() if err != nil { return nil, err } - } - return zz, nil + + cmd.val.Score, err = rd.ReadFloatReply() + if err != nil { + return nil, err + } + + return nil, nil + }) + return cmd.err } //------------------------------------------------------------------------------ @@ -1350,7 +1515,7 @@ var _ Cmder = (*ScanCmd)(nil) func NewScanCmd(process func(cmd Cmder) error, args ...interface{}) *ScanCmd { return &ScanCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, process: process, } } @@ -1382,7 +1547,7 @@ func (cmd *ScanCmd) Iterator() *ScanIterator { //------------------------------------------------------------------------------ type ClusterNode struct { - Id string + ID string Addr string } @@ -1402,7 +1567,7 @@ var _ Cmder = (*ClusterSlotsCmd)(nil) func NewClusterSlotsCmd(args ...interface{}) *ClusterSlotsCmd { return &ClusterSlotsCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -1419,77 +1584,69 @@ func (cmd *ClusterSlotsCmd) String() string { } func (cmd *ClusterSlotsCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(clusterSlotsParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]ClusterSlot) - return nil -} - -// Implements proto.MultiBulkParse -func clusterSlotsParser(rd *proto.Reader, n int64) (interface{}, error) { - slots := make([]ClusterSlot, n) - for i := 0; i < len(slots); i++ { - n, err := rd.ReadArrayLen() - if err != nil { - return nil, err - } - if n < 2 { - err := fmt.Errorf("redis: got %d elements in cluster info, expected at least 2", n) - return nil, err - } - - start, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - - end, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - - nodes := make([]ClusterNode, n-2) - for j := 0; j < len(nodes); j++ { + _, cmd.err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make([]ClusterSlot, n) + for i := 0; i < len(cmd.val); i++ { n, err := rd.ReadArrayLen() if err != nil { return nil, err } - if n != 2 && n != 3 { - err := fmt.Errorf("got %d elements in cluster info address, expected 2 or 3", n) + if n < 2 { + err := fmt.Errorf("redis: got %d elements in cluster info, expected at least 2", n) return nil, err } - ip, err := rd.ReadString() + start, err := rd.ReadIntReply() if err != nil { return nil, err } - port, err := rd.ReadString() + end, err := rd.ReadIntReply() if err != nil { return nil, err } - nodes[j].Addr = net.JoinHostPort(ip, port) - - if n == 3 { - id, err := rd.ReadString() + nodes := make([]ClusterNode, n-2) + for j := 0; j < len(nodes); j++ { + n, err := rd.ReadArrayLen() if err != nil { return nil, err } - nodes[j].Id = id + if n != 2 && n != 3 { + err := fmt.Errorf("got %d elements in cluster info address, expected 2 or 3", n) + return nil, err + } + + ip, err := rd.ReadString() + if err != nil { + return nil, err + } + + port, err := rd.ReadString() + if err != nil { + return nil, err + } + + nodes[j].Addr = net.JoinHostPort(ip, port) + + if n == 3 { + id, err := rd.ReadString() + if err != nil { + return nil, err + } + nodes[j].ID = id + } + } + + cmd.val[i] = ClusterSlot{ + Start: int(start), + End: int(end), + Nodes: nodes, } } - - slots[i] = ClusterSlot{ - Start: int(start), - End: int(end), - Nodes: nodes, - } - } - return slots, nil + return nil, nil + }) + return cmd.err } //------------------------------------------------------------------------------ @@ -1526,6 +1683,13 @@ type GeoLocationCmd struct { var _ Cmder = (*GeoLocationCmd)(nil) func NewGeoLocationCmd(q *GeoRadiusQuery, args ...interface{}) *GeoLocationCmd { + return &GeoLocationCmd{ + baseCmd: baseCmd{args: geoLocationArgs(q, args...)}, + q: q, + } +} + +func geoLocationArgs(q *GeoRadiusQuery, args ...interface{}) []interface{} { args = append(args, q.Radius) if q.Unit != "" { args = append(args, q.Unit) @@ -1555,10 +1719,7 @@ func NewGeoLocationCmd(q *GeoRadiusQuery, args ...interface{}) *GeoLocationCmd { args = append(args, "storedist") args = append(args, q.StoreDist) } - return &GeoLocationCmd{ - baseCmd: baseCmd{_args: args}, - q: q, - } + return args } func (cmd *GeoLocationCmd) Val() []GeoLocation { @@ -1583,6 +1744,30 @@ func (cmd *GeoLocationCmd) readReply(rd *proto.Reader) error { return nil } +func newGeoLocationSliceParser(q *GeoRadiusQuery) proto.MultiBulkParse { + return func(rd *proto.Reader, n int64) (interface{}, error) { + locs := make([]GeoLocation, 0, n) + for i := int64(0); i < n; i++ { + v, err := rd.ReadReply(newGeoLocationParser(q)) + if err != nil { + return nil, err + } + switch vv := v.(type) { + case string: + locs = append(locs, GeoLocation{ + Name: vv, + }) + case *GeoLocation: + //TODO: avoid copying + locs = append(locs, *vv) + default: + return nil, fmt.Errorf("got %T, expected string or *GeoLocation", v) + } + } + return locs, nil + } +} + func newGeoLocationParser(q *GeoRadiusQuery) proto.MultiBulkParse { return func(rd *proto.Reader, n int64) (interface{}, error) { var loc GeoLocation @@ -1627,29 +1812,6 @@ func newGeoLocationParser(q *GeoRadiusQuery) proto.MultiBulkParse { } } -func newGeoLocationSliceParser(q *GeoRadiusQuery) proto.MultiBulkParse { - return func(rd *proto.Reader, n int64) (interface{}, error) { - locs := make([]GeoLocation, 0, n) - for i := int64(0); i < n; i++ { - v, err := rd.ReadReply(newGeoLocationParser(q)) - if err != nil { - return nil, err - } - switch vv := v.(type) { - case string: - locs = append(locs, GeoLocation{ - Name: vv, - }) - case *GeoLocation: - locs = append(locs, *vv) - default: - return nil, fmt.Errorf("got %T, expected string or *GeoLocation", v) - } - } - return locs, nil - } -} - //------------------------------------------------------------------------------ type GeoPos struct { @@ -1659,19 +1821,19 @@ type GeoPos struct { type GeoPosCmd struct { baseCmd - positions []*GeoPos + val []*GeoPos } var _ Cmder = (*GeoPosCmd)(nil) func NewGeoPosCmd(args ...interface{}) *GeoPosCmd { return &GeoPosCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } func (cmd *GeoPosCmd) Val() []*GeoPos { - return cmd.positions + return cmd.val } func (cmd *GeoPosCmd) Result() ([]*GeoPos, error) { @@ -1679,55 +1841,42 @@ func (cmd *GeoPosCmd) Result() ([]*GeoPos, error) { } func (cmd *GeoPosCmd) String() string { - return cmdString(cmd, cmd.positions) + return cmdString(cmd, cmd.val) } func (cmd *GeoPosCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(geoPosSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.positions = v.([]*GeoPos) - return nil -} + _, cmd.err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make([]*GeoPos, n) + for i := 0; i < len(cmd.val); i++ { + i := i + _, err := rd.ReadReply(func(rd *proto.Reader, n int64) (interface{}, error) { + longitude, err := rd.ReadFloatReply() + if err != nil { + return nil, err + } -func geoPosSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - positions := make([]*GeoPos, 0, n) - for i := int64(0); i < n; i++ { - v, err := rd.ReadReply(geoPosParser) - if err != nil { - if err == Nil { - positions = append(positions, nil) - continue + latitude, err := rd.ReadFloatReply() + if err != nil { + return nil, err + } + + cmd.val[i] = &GeoPos{ + Longitude: longitude, + Latitude: latitude, + } + return nil, nil + }) + if err != nil { + if err == Nil { + cmd.val[i] = nil + continue + } + return nil, err } - return nil, err } - switch v := v.(type) { - case *GeoPos: - positions = append(positions, v) - default: - return nil, fmt.Errorf("got %T, expected *GeoPos", v) - } - } - return positions, nil -} - -func geoPosParser(rd *proto.Reader, n int64) (interface{}, error) { - var pos GeoPos - var err error - - pos.Longitude, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - - pos.Latitude, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - - return &pos, nil + return nil, nil + }) + return cmd.err } //------------------------------------------------------------------------------ @@ -1752,7 +1901,7 @@ var _ Cmder = (*CommandsInfoCmd)(nil) func NewCommandsInfoCmd(args ...interface{}) *CommandsInfoCmd { return &CommandsInfoCmd{ - baseCmd: baseCmd{_args: args}, + baseCmd: baseCmd{args: args}, } } @@ -1769,38 +1918,29 @@ func (cmd *CommandsInfoCmd) String() string { } func (cmd *CommandsInfoCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(commandInfoSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.(map[string]*CommandInfo) - return nil -} - -// Implements proto.MultiBulkParse -func commandInfoSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - m := make(map[string]*CommandInfo, n) - for i := int64(0); i < n; i++ { - v, err := rd.ReadReply(commandInfoParser) - if err != nil { - return nil, err + _, cmd.err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make(map[string]*CommandInfo, n) + for i := int64(0); i < n; i++ { + v, err := rd.ReadReply(commandInfoParser) + if err != nil { + return nil, err + } + vv := v.(*CommandInfo) + cmd.val[vv.Name] = vv } - vv := v.(*CommandInfo) - m[vv.Name] = vv - - } - return m, nil + return nil, nil + }) + return cmd.err } func commandInfoParser(rd *proto.Reader, n int64) (interface{}, error) { - var cmd CommandInfo - var err error - if n != 6 { return nil, fmt.Errorf("redis: got %d elements in COMMAND reply, wanted 6", n) } + var cmd CommandInfo + var err error + cmd.Name, err = rd.ReadString() if err != nil { return nil, err @@ -1812,11 +1952,23 @@ func commandInfoParser(rd *proto.Reader, n int64) (interface{}, error) { } cmd.Arity = int8(arity) - flags, err := rd.ReadReply(stringSliceParser) + _, err = rd.ReadReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.Flags = make([]string, n) + for i := 0; i < len(cmd.Flags); i++ { + switch s, err := rd.ReadString(); { + case err == Nil: + cmd.Flags[i] = "" + case err != nil: + return nil, err + default: + cmd.Flags[i] = s + } + } + return nil, nil + }) if err != nil { return nil, err } - cmd.Flags = flags.([]string) firstKeyPos, err := rd.ReadIntReply() if err != nil { @@ -1867,6 +2019,15 @@ func (c *cmdsInfoCache) Get() (map[string]*CommandInfo, error) { if err != nil { return err } + + // Extensions have cmd names in upper case. Convert them to lower case. + for k, v := range cmds { + lower := internal.ToLower(k) + if lower != k { + cmds[lower] = v + } + } + c.cmds = cmds return nil }) diff --git a/vendor/github.com/go-redis/redis/commands.go b/vendor/github.com/go-redis/redis/v7/commands.go similarity index 60% rename from vendor/github.com/go-redis/redis/commands.go rename to vendor/github.com/go-redis/redis/v7/commands.go index b259e3a8c8..d4447c4d39 100644 --- a/vendor/github.com/go-redis/redis/commands.go +++ b/vendor/github.com/go-redis/redis/v7/commands.go @@ -5,23 +5,16 @@ import ( "io" "time" - "github.com/go-redis/redis/internal" + "github.com/go-redis/redis/v7/internal" ) -func readTimeout(timeout time.Duration) time.Duration { - if timeout == 0 { - return 0 - } - return timeout + 10*time.Second -} - func usePrecise(dur time.Duration) bool { return dur < time.Second || dur%time.Second != 0 } func formatMs(dur time.Duration) int64 { if dur > 0 && dur < time.Millisecond { - internal.Logf( + internal.Logger.Printf( "specified duration is %s, but minimal supported value is %s", dur, time.Millisecond, ) @@ -31,7 +24,7 @@ func formatMs(dur time.Duration) int64 { func formatSec(dur time.Duration) int64 { if dur > 0 && dur < time.Second { - internal.Logf( + internal.Logger.Printf( "specified duration is %s, but minimal supported value is %s", dur, time.Second, ) @@ -41,17 +34,21 @@ func formatSec(dur time.Duration) int64 { func appendArgs(dst, src []interface{}) []interface{} { if len(src) == 1 { - if ss, ok := src[0].([]string); ok { - for _, s := range ss { + switch v := src[0].(type) { + case []string: + for _, s := range v { dst = append(dst, s) } return dst + case map[string]interface{}: + for k, v := range v { + dst = append(dst, k, v) + } + return dst } } - for _, v := range src { - dst = append(dst, v) - } + dst = append(dst, src...) return dst } @@ -74,8 +71,8 @@ type Cmdable interface { Expire(key string, expiration time.Duration) *BoolCmd ExpireAt(key string, tm time.Time) *BoolCmd Keys(pattern string) *StringSliceCmd - Migrate(host, port, key string, db int64, timeout time.Duration) *StatusCmd - Move(key string, db int64) *BoolCmd + Migrate(host, port, key string, db int, timeout time.Duration) *StatusCmd + Move(key string, db int) *BoolCmd ObjectRefCount(key string) *IntCmd ObjectEncoding(key string) *StringCmd ObjectIdleTime(key string) *DurationCmd @@ -105,6 +102,7 @@ type Cmdable interface { BitOpXor(destKey string, keys ...string) *IntCmd BitOpNot(destKey string, key string) *IntCmd BitPos(key string, bit int64, pos ...int64) *IntCmd + BitField(key string, args ...interface{}) *IntSliceCmd Decr(key string) *IntCmd DecrBy(key string, decrement int64) *IntCmd Get(key string) *StringCmd @@ -115,8 +113,8 @@ type Cmdable interface { IncrBy(key string, value int64) *IntCmd IncrByFloat(key string, value float64) *FloatCmd MGet(keys ...string) *SliceCmd - MSet(pairs ...interface{}) *StatusCmd - MSetNX(pairs ...interface{}) *BoolCmd + MSet(values ...interface{}) *StatusCmd + MSetNX(values ...interface{}) *BoolCmd Set(key string, value interface{}, expiration time.Duration) *StatusCmd SetBit(key string, offset int64, value int) *IntCmd SetNX(key string, value interface{}, expiration time.Duration) *BoolCmd @@ -132,8 +130,8 @@ type Cmdable interface { HKeys(key string) *StringSliceCmd HLen(key string) *IntCmd HMGet(key string, fields ...string) *SliceCmd - HMSet(key string, fields map[string]interface{}) *StatusCmd - HSet(key, field string, value interface{}) *BoolCmd + HSet(key string, values ...interface{}) *IntCmd + HMSet(key string, values ...interface{}) *BoolCmd HSetNX(key, field string, value interface{}) *BoolCmd HVals(key string) *StringSliceCmd BLPop(timeout time.Duration, keys ...string) *StringSliceCmd @@ -146,7 +144,7 @@ type Cmdable interface { LLen(key string) *IntCmd LPop(key string) *StringCmd LPush(key string, values ...interface{}) *IntCmd - LPushX(key string, value interface{}) *IntCmd + LPushX(key string, values ...interface{}) *IntCmd LRange(key string, start, stop int64) *StringSliceCmd LRem(key string, count int64, value interface{}) *IntCmd LSet(key string, index int64, value interface{}) *StatusCmd @@ -154,7 +152,7 @@ type Cmdable interface { RPop(key string) *StringCmd RPopLPush(source, destination string) *StringCmd RPush(key string, values ...interface{}) *IntCmd - RPushX(key string, value interface{}) *IntCmd + RPushX(key string, values ...interface{}) *IntCmd SAdd(key string, members ...interface{}) *IntCmd SCard(key string) *IntCmd SDiff(keys ...string) *StringSliceCmd @@ -173,6 +171,7 @@ type Cmdable interface { SUnion(keys ...string) *StringSliceCmd SUnionStore(destination string, keys ...string) *IntCmd XAdd(a *XAddArgs) *StringCmd + XDel(stream string, ids ...string) *IntCmd XLen(stream string) *IntCmd XRange(stream, start, stop string) *XMessageSliceCmd XRangeN(stream, start, stop string, count int64) *XMessageSliceCmd @@ -181,6 +180,7 @@ type Cmdable interface { XRead(a *XReadArgs) *XStreamSliceCmd XReadStreams(streams ...string) *XStreamSliceCmd XGroupCreate(stream, group, start string) *StatusCmd + XGroupCreateMkStream(stream, group, start string) *StatusCmd XGroupSetID(stream, group, start string) *StatusCmd XGroupDestroy(stream, group string) *IntCmd XGroupDelConsumer(stream, group, consumer string) *IntCmd @@ -192,25 +192,30 @@ type Cmdable interface { XClaimJustID(a *XClaimArgs) *StringSliceCmd XTrim(key string, maxLen int64) *IntCmd XTrimApprox(key string, maxLen int64) *IntCmd - ZAdd(key string, members ...Z) *IntCmd - ZAddNX(key string, members ...Z) *IntCmd - ZAddXX(key string, members ...Z) *IntCmd - ZAddCh(key string, members ...Z) *IntCmd - ZAddNXCh(key string, members ...Z) *IntCmd - ZAddXXCh(key string, members ...Z) *IntCmd - ZIncr(key string, member Z) *FloatCmd - ZIncrNX(key string, member Z) *FloatCmd - ZIncrXX(key string, member Z) *FloatCmd + XInfoGroups(key string) *XInfoGroupsCmd + BZPopMax(timeout time.Duration, keys ...string) *ZWithKeyCmd + BZPopMin(timeout time.Duration, keys ...string) *ZWithKeyCmd + ZAdd(key string, members ...*Z) *IntCmd + ZAddNX(key string, members ...*Z) *IntCmd + ZAddXX(key string, members ...*Z) *IntCmd + ZAddCh(key string, members ...*Z) *IntCmd + ZAddNXCh(key string, members ...*Z) *IntCmd + ZAddXXCh(key string, members ...*Z) *IntCmd + ZIncr(key string, member *Z) *FloatCmd + ZIncrNX(key string, member *Z) *FloatCmd + ZIncrXX(key string, member *Z) *FloatCmd ZCard(key string) *IntCmd ZCount(key, min, max string) *IntCmd ZLexCount(key, min, max string) *IntCmd ZIncrBy(key string, increment float64, member string) *FloatCmd - ZInterStore(destination string, store ZStore, keys ...string) *IntCmd + ZInterStore(destination string, store *ZStore) *IntCmd + ZPopMax(key string, count ...int64) *ZSliceCmd + ZPopMin(key string, count ...int64) *ZSliceCmd ZRange(key string, start, stop int64) *StringSliceCmd ZRangeWithScores(key string, start, stop int64) *ZSliceCmd - ZRangeByScore(key string, opt ZRangeBy) *StringSliceCmd - ZRangeByLex(key string, opt ZRangeBy) *StringSliceCmd - ZRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd + ZRangeByScore(key string, opt *ZRangeBy) *StringSliceCmd + ZRangeByLex(key string, opt *ZRangeBy) *StringSliceCmd + ZRangeByScoreWithScores(key string, opt *ZRangeBy) *ZSliceCmd ZRank(key, member string) *IntCmd ZRem(key string, members ...interface{}) *IntCmd ZRemRangeByRank(key string, start, stop int64) *IntCmd @@ -218,12 +223,12 @@ type Cmdable interface { ZRemRangeByLex(key, min, max string) *IntCmd ZRevRange(key string, start, stop int64) *StringSliceCmd ZRevRangeWithScores(key string, start, stop int64) *ZSliceCmd - ZRevRangeByScore(key string, opt ZRangeBy) *StringSliceCmd - ZRevRangeByLex(key string, opt ZRangeBy) *StringSliceCmd - ZRevRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd + ZRevRangeByScore(key string, opt *ZRangeBy) *StringSliceCmd + ZRevRangeByLex(key string, opt *ZRangeBy) *StringSliceCmd + ZRevRangeByScoreWithScores(key string, opt *ZRangeBy) *ZSliceCmd ZRevRank(key, member string) *IntCmd ZScore(key, member string) *FloatCmd - ZUnionStore(dest string, store ZStore, keys ...string) *IntCmd + ZUnionStore(dest string, store *ZStore) *IntCmd PFAdd(key string, els ...interface{}) *IntCmd PFCount(keys ...string) *IntCmd PFMerge(dest string, keys ...string) *StatusCmd @@ -233,6 +238,7 @@ type Cmdable interface { ClientKillByFilter(keys ...string) *IntCmd ClientList() *StringCmd ClientPause(dur time.Duration) *BoolCmd + ClientID() *IntCmd ConfigGet(parameter string) *SliceCmd ConfigResetStat() *StatusCmd ConfigSet(parameter, value string) *StatusCmd @@ -270,6 +276,7 @@ type Cmdable interface { ClusterResetHard() *StatusCmd ClusterInfo() *StringCmd ClusterKeySlot(key string) *IntCmd + ClusterGetKeysInSlot(slot int, count int) *StringSliceCmd ClusterCountFailureReports(nodeID string) *IntCmd ClusterCountKeysInSlot(slot int) *IntCmd ClusterDelSlots(slots ...int) *StatusCmd @@ -282,9 +289,9 @@ type Cmdable interface { GeoAdd(key string, geoLocation ...*GeoLocation) *IntCmd GeoPos(key string, members ...string) *GeoPosCmd GeoRadius(key string, longitude, latitude float64, query *GeoRadiusQuery) *GeoLocationCmd - GeoRadiusRO(key string, longitude, latitude float64, query *GeoRadiusQuery) *GeoLocationCmd + GeoRadiusStore(key string, longitude, latitude float64, query *GeoRadiusQuery) *IntCmd GeoRadiusByMember(key, member string, query *GeoRadiusQuery) *GeoLocationCmd - GeoRadiusByMemberRO(key, member string, query *GeoRadiusQuery) *GeoLocationCmd + GeoRadiusByMemberStore(key, member string, query *GeoRadiusQuery) *IntCmd GeoDist(key string, member1, member2, unit string) *FloatCmd GeoHash(key string, members ...string) *StringSliceCmd ReadOnly() *StatusCmd @@ -305,132 +312,118 @@ var _ Cmdable = (*Tx)(nil) var _ Cmdable = (*Ring)(nil) var _ Cmdable = (*ClusterClient)(nil) -type cmdable struct { - process func(cmd Cmder) error -} +type cmdable func(cmd Cmder) error -func (c *cmdable) setProcessor(fn func(Cmder) error) { - c.process = fn -} - -type statefulCmdable struct { - cmdable - process func(cmd Cmder) error -} - -func (c *statefulCmdable) setProcessor(fn func(Cmder) error) { - c.process = fn - c.cmdable.setProcessor(fn) -} +type statefulCmdable func(cmd Cmder) error //------------------------------------------------------------------------------ -func (c *statefulCmdable) Auth(password string) *StatusCmd { +func (c statefulCmdable) Auth(password string) *StatusCmd { cmd := NewStatusCmd("auth", password) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Echo(message interface{}) *StringCmd { +func (c cmdable) Echo(message interface{}) *StringCmd { cmd := NewStringCmd("echo", message) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Ping() *StatusCmd { +func (c cmdable) Ping() *StatusCmd { cmd := NewStatusCmd("ping") - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Wait(numSlaves int, timeout time.Duration) *IntCmd { +func (c cmdable) Wait(numSlaves int, timeout time.Duration) *IntCmd { cmd := NewIntCmd("wait", numSlaves, int(timeout/time.Millisecond)) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Quit() *StatusCmd { +func (c cmdable) Quit() *StatusCmd { panic("not implemented") } -func (c *statefulCmdable) Select(index int) *StatusCmd { +func (c statefulCmdable) Select(index int) *StatusCmd { cmd := NewStatusCmd("select", index) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *statefulCmdable) SwapDB(index1, index2 int) *StatusCmd { +func (c statefulCmdable) SwapDB(index1, index2 int) *StatusCmd { cmd := NewStatusCmd("swapdb", index1, index2) - c.process(cmd) + _ = c(cmd) return cmd } //------------------------------------------------------------------------------ -func (c *cmdable) Command() *CommandsInfoCmd { +func (c cmdable) Command() *CommandsInfoCmd { cmd := NewCommandsInfoCmd("command") - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Del(keys ...string) *IntCmd { +func (c cmdable) Del(keys ...string) *IntCmd { args := make([]interface{}, 1+len(keys)) args[0] = "del" for i, key := range keys { args[1+i] = key } cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Unlink(keys ...string) *IntCmd { +func (c cmdable) Unlink(keys ...string) *IntCmd { args := make([]interface{}, 1+len(keys)) args[0] = "unlink" for i, key := range keys { args[1+i] = key } cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Dump(key string) *StringCmd { +func (c cmdable) Dump(key string) *StringCmd { cmd := NewStringCmd("dump", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Exists(keys ...string) *IntCmd { +func (c cmdable) Exists(keys ...string) *IntCmd { args := make([]interface{}, 1+len(keys)) args[0] = "exists" for i, key := range keys { args[1+i] = key } cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Expire(key string, expiration time.Duration) *BoolCmd { +func (c cmdable) Expire(key string, expiration time.Duration) *BoolCmd { cmd := NewBoolCmd("expire", key, formatSec(expiration)) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ExpireAt(key string, tm time.Time) *BoolCmd { +func (c cmdable) ExpireAt(key string, tm time.Time) *BoolCmd { cmd := NewBoolCmd("expireat", key, tm.Unix()) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Keys(pattern string) *StringSliceCmd { +func (c cmdable) Keys(pattern string) *StringSliceCmd { cmd := NewStringSliceCmd("keys", pattern) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Migrate(host, port, key string, db int64, timeout time.Duration) *StatusCmd { +func (c cmdable) Migrate(host, port, key string, db int, timeout time.Duration) *StatusCmd { cmd := NewStatusCmd( "migrate", host, @@ -440,92 +433,92 @@ func (c *cmdable) Migrate(host, port, key string, db int64, timeout time.Duratio formatMs(timeout), ) cmd.setReadTimeout(timeout) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Move(key string, db int64) *BoolCmd { +func (c cmdable) Move(key string, db int) *BoolCmd { cmd := NewBoolCmd("move", key, db) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ObjectRefCount(key string) *IntCmd { +func (c cmdable) ObjectRefCount(key string) *IntCmd { cmd := NewIntCmd("object", "refcount", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ObjectEncoding(key string) *StringCmd { +func (c cmdable) ObjectEncoding(key string) *StringCmd { cmd := NewStringCmd("object", "encoding", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ObjectIdleTime(key string) *DurationCmd { +func (c cmdable) ObjectIdleTime(key string) *DurationCmd { cmd := NewDurationCmd(time.Second, "object", "idletime", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Persist(key string) *BoolCmd { +func (c cmdable) Persist(key string) *BoolCmd { cmd := NewBoolCmd("persist", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) PExpire(key string, expiration time.Duration) *BoolCmd { +func (c cmdable) PExpire(key string, expiration time.Duration) *BoolCmd { cmd := NewBoolCmd("pexpire", key, formatMs(expiration)) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) PExpireAt(key string, tm time.Time) *BoolCmd { +func (c cmdable) PExpireAt(key string, tm time.Time) *BoolCmd { cmd := NewBoolCmd( "pexpireat", key, tm.UnixNano()/int64(time.Millisecond), ) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) PTTL(key string) *DurationCmd { +func (c cmdable) PTTL(key string) *DurationCmd { cmd := NewDurationCmd(time.Millisecond, "pttl", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) RandomKey() *StringCmd { +func (c cmdable) RandomKey() *StringCmd { cmd := NewStringCmd("randomkey") - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Rename(key, newkey string) *StatusCmd { +func (c cmdable) Rename(key, newkey string) *StatusCmd { cmd := NewStatusCmd("rename", key, newkey) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) RenameNX(key, newkey string) *BoolCmd { +func (c cmdable) RenameNX(key, newkey string) *BoolCmd { cmd := NewBoolCmd("renamenx", key, newkey) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Restore(key string, ttl time.Duration, value string) *StatusCmd { +func (c cmdable) Restore(key string, ttl time.Duration, value string) *StatusCmd { cmd := NewStatusCmd( "restore", key, formatMs(ttl), value, ) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) RestoreReplace(key string, ttl time.Duration, value string) *StatusCmd { +func (c cmdable) RestoreReplace(key string, ttl time.Duration, value string) *StatusCmd { cmd := NewStatusCmd( "restore", key, @@ -533,7 +526,7 @@ func (c *cmdable) RestoreReplace(key string, ttl time.Duration, value string) *S value, "replace", ) - c.process(cmd) + _ = c(cmd) return cmd } @@ -565,52 +558,52 @@ func (sort *Sort) args(key string) []interface{} { return args } -func (c *cmdable) Sort(key string, sort *Sort) *StringSliceCmd { +func (c cmdable) Sort(key string, sort *Sort) *StringSliceCmd { cmd := NewStringSliceCmd(sort.args(key)...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) SortStore(key, store string, sort *Sort) *IntCmd { +func (c cmdable) SortStore(key, store string, sort *Sort) *IntCmd { args := sort.args(key) if store != "" { args = append(args, "store", store) } cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) SortInterfaces(key string, sort *Sort) *SliceCmd { +func (c cmdable) SortInterfaces(key string, sort *Sort) *SliceCmd { cmd := NewSliceCmd(sort.args(key)...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Touch(keys ...string) *IntCmd { +func (c cmdable) Touch(keys ...string) *IntCmd { args := make([]interface{}, len(keys)+1) args[0] = "touch" for i, key := range keys { args[i+1] = key } cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) TTL(key string) *DurationCmd { +func (c cmdable) TTL(key string) *DurationCmd { cmd := NewDurationCmd(time.Second, "ttl", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Type(key string) *StatusCmd { +func (c cmdable) Type(key string) *StatusCmd { cmd := NewStatusCmd("type", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Scan(cursor uint64, match string, count int64) *ScanCmd { +func (c cmdable) Scan(cursor uint64, match string, count int64) *ScanCmd { args := []interface{}{"scan", cursor} if match != "" { args = append(args, "match", match) @@ -618,12 +611,12 @@ func (c *cmdable) Scan(cursor uint64, match string, count int64) *ScanCmd { if count > 0 { args = append(args, "count", count) } - cmd := NewScanCmd(c.process, args...) - c.process(cmd) + cmd := NewScanCmd(c, args...) + _ = c(cmd) return cmd } -func (c *cmdable) SScan(key string, cursor uint64, match string, count int64) *ScanCmd { +func (c cmdable) SScan(key string, cursor uint64, match string, count int64) *ScanCmd { args := []interface{}{"sscan", key, cursor} if match != "" { args = append(args, "match", match) @@ -631,12 +624,12 @@ func (c *cmdable) SScan(key string, cursor uint64, match string, count int64) *S if count > 0 { args = append(args, "count", count) } - cmd := NewScanCmd(c.process, args...) - c.process(cmd) + cmd := NewScanCmd(c, args...) + _ = c(cmd) return cmd } -func (c *cmdable) HScan(key string, cursor uint64, match string, count int64) *ScanCmd { +func (c cmdable) HScan(key string, cursor uint64, match string, count int64) *ScanCmd { args := []interface{}{"hscan", key, cursor} if match != "" { args = append(args, "match", match) @@ -644,12 +637,12 @@ func (c *cmdable) HScan(key string, cursor uint64, match string, count int64) *S if count > 0 { args = append(args, "count", count) } - cmd := NewScanCmd(c.process, args...) - c.process(cmd) + cmd := NewScanCmd(c, args...) + _ = c(cmd) return cmd } -func (c *cmdable) ZScan(key string, cursor uint64, match string, count int64) *ScanCmd { +func (c cmdable) ZScan(key string, cursor uint64, match string, count int64) *ScanCmd { args := []interface{}{"zscan", key, cursor} if match != "" { args = append(args, "match", match) @@ -657,16 +650,16 @@ func (c *cmdable) ZScan(key string, cursor uint64, match string, count int64) *S if count > 0 { args = append(args, "count", count) } - cmd := NewScanCmd(c.process, args...) - c.process(cmd) + cmd := NewScanCmd(c, args...) + _ = c(cmd) return cmd } //------------------------------------------------------------------------------ -func (c *cmdable) Append(key, value string) *IntCmd { +func (c cmdable) Append(key, value string) *IntCmd { cmd := NewIntCmd("append", key, value) - c.process(cmd) + _ = c(cmd) return cmd } @@ -674,7 +667,7 @@ type BitCount struct { Start, End int64 } -func (c *cmdable) BitCount(key string, bitCount *BitCount) *IntCmd { +func (c cmdable) BitCount(key string, bitCount *BitCount) *IntCmd { args := []interface{}{"bitcount", key} if bitCount != nil { args = append( @@ -684,11 +677,11 @@ func (c *cmdable) BitCount(key string, bitCount *BitCount) *IntCmd { ) } cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) bitOp(op, destKey string, keys ...string) *IntCmd { +func (c cmdable) bitOp(op, destKey string, keys ...string) *IntCmd { args := make([]interface{}, 3+len(keys)) args[0] = "bitop" args[1] = op @@ -697,27 +690,27 @@ func (c *cmdable) bitOp(op, destKey string, keys ...string) *IntCmd { args[3+i] = key } cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) BitOpAnd(destKey string, keys ...string) *IntCmd { +func (c cmdable) BitOpAnd(destKey string, keys ...string) *IntCmd { return c.bitOp("and", destKey, keys...) } -func (c *cmdable) BitOpOr(destKey string, keys ...string) *IntCmd { +func (c cmdable) BitOpOr(destKey string, keys ...string) *IntCmd { return c.bitOp("or", destKey, keys...) } -func (c *cmdable) BitOpXor(destKey string, keys ...string) *IntCmd { +func (c cmdable) BitOpXor(destKey string, keys ...string) *IntCmd { return c.bitOp("xor", destKey, keys...) } -func (c *cmdable) BitOpNot(destKey string, key string) *IntCmd { +func (c cmdable) BitOpNot(destKey string, key string) *IntCmd { return c.bitOp("not", destKey, key) } -func (c *cmdable) BitPos(key string, bit int64, pos ...int64) *IntCmd { +func (c cmdable) BitPos(key string, bit int64, pos ...int64) *IntCmd { args := make([]interface{}, 3+len(pos)) args[0] = "bitpos" args[1] = key @@ -733,91 +726,109 @@ func (c *cmdable) BitPos(key string, bit int64, pos ...int64) *IntCmd { panic("too many arguments") } cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Decr(key string) *IntCmd { +func (c cmdable) BitField(key string, args ...interface{}) *IntSliceCmd { + a := make([]interface{}, 0, 2+len(args)) + a = append(a, "bitfield") + a = append(a, key) + a = append(a, args...) + cmd := NewIntSliceCmd(a...) + _ = c(cmd) + return cmd +} + +func (c cmdable) Decr(key string) *IntCmd { cmd := NewIntCmd("decr", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) DecrBy(key string, decrement int64) *IntCmd { +func (c cmdable) DecrBy(key string, decrement int64) *IntCmd { cmd := NewIntCmd("decrby", key, decrement) - c.process(cmd) + _ = c(cmd) return cmd } // Redis `GET key` command. It returns redis.Nil error when key does not exist. -func (c *cmdable) Get(key string) *StringCmd { +func (c cmdable) Get(key string) *StringCmd { cmd := NewStringCmd("get", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) GetBit(key string, offset int64) *IntCmd { +func (c cmdable) GetBit(key string, offset int64) *IntCmd { cmd := NewIntCmd("getbit", key, offset) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) GetRange(key string, start, end int64) *StringCmd { +func (c cmdable) GetRange(key string, start, end int64) *StringCmd { cmd := NewStringCmd("getrange", key, start, end) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) GetSet(key string, value interface{}) *StringCmd { +func (c cmdable) GetSet(key string, value interface{}) *StringCmd { cmd := NewStringCmd("getset", key, value) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) Incr(key string) *IntCmd { +func (c cmdable) Incr(key string) *IntCmd { cmd := NewIntCmd("incr", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) IncrBy(key string, value int64) *IntCmd { +func (c cmdable) IncrBy(key string, value int64) *IntCmd { cmd := NewIntCmd("incrby", key, value) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) IncrByFloat(key string, value float64) *FloatCmd { +func (c cmdable) IncrByFloat(key string, value float64) *FloatCmd { cmd := NewFloatCmd("incrbyfloat", key, value) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) MGet(keys ...string) *SliceCmd { +func (c cmdable) MGet(keys ...string) *SliceCmd { args := make([]interface{}, 1+len(keys)) args[0] = "mget" for i, key := range keys { args[1+i] = key } cmd := NewSliceCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) MSet(pairs ...interface{}) *StatusCmd { - args := make([]interface{}, 1, 1+len(pairs)) +// MSet is like Set but accepts multiple values: +// - MSet("key1", "value1", "key2", "value2") +// - MSet([]string{"key1", "value1", "key2", "value2"}) +// - MSet(map[string]interface{}{"key1": "value1", "key2": "value2"}) +func (c cmdable) MSet(values ...interface{}) *StatusCmd { + args := make([]interface{}, 1, 1+len(values)) args[0] = "mset" - args = appendArgs(args, pairs) + args = appendArgs(args, values) cmd := NewStatusCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) MSetNX(pairs ...interface{}) *BoolCmd { - args := make([]interface{}, 1, 1+len(pairs)) +// MSetNX is like SetNX but accepts multiple values: +// - MSetNX("key1", "value1", "key2", "value2") +// - MSetNX([]string{"key1", "value1", "key2", "value2"}) +// - MSetNX(map[string]interface{}{"key1": "value1", "key2": "value2"}) +func (c cmdable) MSetNX(values ...interface{}) *BoolCmd { + args := make([]interface{}, 1, 1+len(values)) args[0] = "msetnx" - args = appendArgs(args, pairs) + args = appendArgs(args, values) cmd := NewBoolCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } @@ -825,8 +836,8 @@ func (c *cmdable) MSetNX(pairs ...interface{}) *BoolCmd { // // Use expiration for `SETEX`-like behavior. // Zero expiration means the key has no expiration time. -func (c *cmdable) Set(key string, value interface{}, expiration time.Duration) *StatusCmd { - args := make([]interface{}, 3, 4) +func (c cmdable) Set(key string, value interface{}, expiration time.Duration) *StatusCmd { + args := make([]interface{}, 3, 5) args[0] = "set" args[1] = key args[2] = value @@ -838,25 +849,25 @@ func (c *cmdable) Set(key string, value interface{}, expiration time.Duration) * } } cmd := NewStatusCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) SetBit(key string, offset int64, value int) *IntCmd { +func (c cmdable) SetBit(key string, offset int64, value int) *IntCmd { cmd := NewIntCmd( "setbit", key, offset, value, ) - c.process(cmd) + _ = c(cmd) return cmd } // Redis `SET key value [expiration] NX` command. // // Zero expiration means the key has no expiration time. -func (c *cmdable) SetNX(key string, value interface{}, expiration time.Duration) *BoolCmd { +func (c cmdable) SetNX(key string, value interface{}, expiration time.Duration) *BoolCmd { var cmd *BoolCmd if expiration == 0 { // Use old `SETNX` to support old Redis versions. @@ -868,14 +879,14 @@ func (c *cmdable) SetNX(key string, value interface{}, expiration time.Duration) cmd = NewBoolCmd("set", key, value, "ex", formatSec(expiration), "nx") } } - c.process(cmd) + _ = c(cmd) return cmd } // Redis `SET key value [expiration] XX` command. // // Zero expiration means the key has no expiration time. -func (c *cmdable) SetXX(key string, value interface{}, expiration time.Duration) *BoolCmd { +func (c cmdable) SetXX(key string, value interface{}, expiration time.Duration) *BoolCmd { var cmd *BoolCmd if expiration == 0 { cmd = NewBoolCmd("set", key, value, "xx") @@ -886,25 +897,25 @@ func (c *cmdable) SetXX(key string, value interface{}, expiration time.Duration) cmd = NewBoolCmd("set", key, value, "ex", formatSec(expiration), "xx") } } - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) SetRange(key string, offset int64, value string) *IntCmd { +func (c cmdable) SetRange(key string, offset int64, value string) *IntCmd { cmd := NewIntCmd("setrange", key, offset, value) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) StrLen(key string) *IntCmd { +func (c cmdable) StrLen(key string) *IntCmd { cmd := NewIntCmd("strlen", key) - c.process(cmd) + _ = c(cmd) return cmd } //------------------------------------------------------------------------------ -func (c *cmdable) HDel(key string, fields ...string) *IntCmd { +func (c cmdable) HDel(key string, fields ...string) *IntCmd { args := make([]interface{}, 2+len(fields)) args[0] = "hdel" args[1] = key @@ -912,53 +923,55 @@ func (c *cmdable) HDel(key string, fields ...string) *IntCmd { args[2+i] = field } cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) HExists(key, field string) *BoolCmd { +func (c cmdable) HExists(key, field string) *BoolCmd { cmd := NewBoolCmd("hexists", key, field) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) HGet(key, field string) *StringCmd { +func (c cmdable) HGet(key, field string) *StringCmd { cmd := NewStringCmd("hget", key, field) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) HGetAll(key string) *StringStringMapCmd { +func (c cmdable) HGetAll(key string) *StringStringMapCmd { cmd := NewStringStringMapCmd("hgetall", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) HIncrBy(key, field string, incr int64) *IntCmd { +func (c cmdable) HIncrBy(key, field string, incr int64) *IntCmd { cmd := NewIntCmd("hincrby", key, field, incr) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) HIncrByFloat(key, field string, incr float64) *FloatCmd { +func (c cmdable) HIncrByFloat(key, field string, incr float64) *FloatCmd { cmd := NewFloatCmd("hincrbyfloat", key, field, incr) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) HKeys(key string) *StringSliceCmd { +func (c cmdable) HKeys(key string) *StringSliceCmd { cmd := NewStringSliceCmd("hkeys", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) HLen(key string) *IntCmd { +func (c cmdable) HLen(key string) *IntCmd { cmd := NewIntCmd("hlen", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) HMGet(key string, fields ...string) *SliceCmd { +// HMGet returns the values for the specified fields in the hash stored at key. +// It returns an interface{} to distinguish between empty string and nil value. +func (c cmdable) HMGet(key string, fields ...string) *SliceCmd { args := make([]interface{}, 2+len(fields)) args[0] = "hmget" args[1] = key @@ -966,46 +979,52 @@ func (c *cmdable) HMGet(key string, fields ...string) *SliceCmd { args[2+i] = field } cmd := NewSliceCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) HMSet(key string, fields map[string]interface{}) *StatusCmd { - args := make([]interface{}, 2+len(fields)*2) +// HSet accepts values in following formats: +// - HMSet("myhash", "key1", "value1", "key2", "value2") +// - HMSet("myhash", []string{"key1", "value1", "key2", "value2"}) +// - HMSet("myhash", map[string]interface{}{"key1": "value1", "key2": "value2"}) +// +// Note that it requires Redis v4 for multiple field/value pairs support. +func (c cmdable) HSet(key string, values ...interface{}) *IntCmd { + args := make([]interface{}, 2, 2+len(values)) + args[0] = "hset" + args[1] = key + args = appendArgs(args, values) + cmd := NewIntCmd(args...) + _ = c(cmd) + return cmd +} + +// HMSet is a deprecated version of HSet left for compatibility with Redis 3. +func (c cmdable) HMSet(key string, values ...interface{}) *BoolCmd { + args := make([]interface{}, 2, 2+len(values)) args[0] = "hmset" args[1] = key - i := 2 - for k, v := range fields { - args[i] = k - args[i+1] = v - i += 2 - } - cmd := NewStatusCmd(args...) - c.process(cmd) + args = appendArgs(args, values) + cmd := NewBoolCmd(args...) + _ = c(cmd) return cmd } -func (c *cmdable) HSet(key, field string, value interface{}) *BoolCmd { - cmd := NewBoolCmd("hset", key, field, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) HSetNX(key, field string, value interface{}) *BoolCmd { +func (c cmdable) HSetNX(key, field string, value interface{}) *BoolCmd { cmd := NewBoolCmd("hsetnx", key, field, value) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) HVals(key string) *StringSliceCmd { +func (c cmdable) HVals(key string) *StringSliceCmd { cmd := NewStringSliceCmd("hvals", key) - c.process(cmd) + _ = c(cmd) return cmd } //------------------------------------------------------------------------------ -func (c *cmdable) BLPop(timeout time.Duration, keys ...string) *StringSliceCmd { +func (c cmdable) BLPop(timeout time.Duration, keys ...string) *StringSliceCmd { args := make([]interface{}, 1+len(keys)+1) args[0] = "blpop" for i, key := range keys { @@ -1014,11 +1033,11 @@ func (c *cmdable) BLPop(timeout time.Duration, keys ...string) *StringSliceCmd { args[len(args)-1] = formatSec(timeout) cmd := NewStringSliceCmd(args...) cmd.setReadTimeout(timeout) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) BRPop(timeout time.Duration, keys ...string) *StringSliceCmd { +func (c cmdable) BRPop(timeout time.Duration, keys ...string) *StringSliceCmd { args := make([]interface{}, 1+len(keys)+1) args[0] = "brpop" for i, key := range keys { @@ -1027,11 +1046,11 @@ func (c *cmdable) BRPop(timeout time.Duration, keys ...string) *StringSliceCmd { args[len(keys)+1] = formatSec(timeout) cmd := NewStringSliceCmd(args...) cmd.setReadTimeout(timeout) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) BRPopLPush(source, destination string, timeout time.Duration) *StringCmd { +func (c cmdable) BRPopLPush(source, destination string, timeout time.Duration) *StringCmd { cmd := NewStringCmd( "brpoplpush", source, @@ -1039,154 +1058,162 @@ func (c *cmdable) BRPopLPush(source, destination string, timeout time.Duration) formatSec(timeout), ) cmd.setReadTimeout(timeout) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) LIndex(key string, index int64) *StringCmd { +func (c cmdable) LIndex(key string, index int64) *StringCmd { cmd := NewStringCmd("lindex", key, index) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) LInsert(key, op string, pivot, value interface{}) *IntCmd { +func (c cmdable) LInsert(key, op string, pivot, value interface{}) *IntCmd { cmd := NewIntCmd("linsert", key, op, pivot, value) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) LInsertBefore(key string, pivot, value interface{}) *IntCmd { +func (c cmdable) LInsertBefore(key string, pivot, value interface{}) *IntCmd { cmd := NewIntCmd("linsert", key, "before", pivot, value) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) LInsertAfter(key string, pivot, value interface{}) *IntCmd { +func (c cmdable) LInsertAfter(key string, pivot, value interface{}) *IntCmd { cmd := NewIntCmd("linsert", key, "after", pivot, value) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) LLen(key string) *IntCmd { +func (c cmdable) LLen(key string) *IntCmd { cmd := NewIntCmd("llen", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) LPop(key string) *StringCmd { +func (c cmdable) LPop(key string) *StringCmd { cmd := NewStringCmd("lpop", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) LPush(key string, values ...interface{}) *IntCmd { +func (c cmdable) LPush(key string, values ...interface{}) *IntCmd { args := make([]interface{}, 2, 2+len(values)) args[0] = "lpush" args[1] = key args = appendArgs(args, values) cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) LPushX(key string, value interface{}) *IntCmd { - cmd := NewIntCmd("lpushx", key, value) - c.process(cmd) +func (c cmdable) LPushX(key string, values ...interface{}) *IntCmd { + args := make([]interface{}, 2, 2+len(values)) + args[0] = "lpushx" + args[1] = key + args = appendArgs(args, values) + cmd := NewIntCmd(args...) + _ = c(cmd) return cmd } -func (c *cmdable) LRange(key string, start, stop int64) *StringSliceCmd { +func (c cmdable) LRange(key string, start, stop int64) *StringSliceCmd { cmd := NewStringSliceCmd( "lrange", key, start, stop, ) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) LRem(key string, count int64, value interface{}) *IntCmd { +func (c cmdable) LRem(key string, count int64, value interface{}) *IntCmd { cmd := NewIntCmd("lrem", key, count, value) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) LSet(key string, index int64, value interface{}) *StatusCmd { +func (c cmdable) LSet(key string, index int64, value interface{}) *StatusCmd { cmd := NewStatusCmd("lset", key, index, value) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) LTrim(key string, start, stop int64) *StatusCmd { +func (c cmdable) LTrim(key string, start, stop int64) *StatusCmd { cmd := NewStatusCmd( "ltrim", key, start, stop, ) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) RPop(key string) *StringCmd { +func (c cmdable) RPop(key string) *StringCmd { cmd := NewStringCmd("rpop", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) RPopLPush(source, destination string) *StringCmd { +func (c cmdable) RPopLPush(source, destination string) *StringCmd { cmd := NewStringCmd("rpoplpush", source, destination) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) RPush(key string, values ...interface{}) *IntCmd { +func (c cmdable) RPush(key string, values ...interface{}) *IntCmd { args := make([]interface{}, 2, 2+len(values)) args[0] = "rpush" args[1] = key args = appendArgs(args, values) cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) RPushX(key string, value interface{}) *IntCmd { - cmd := NewIntCmd("rpushx", key, value) - c.process(cmd) +func (c cmdable) RPushX(key string, values ...interface{}) *IntCmd { + args := make([]interface{}, 2, 2+len(values)) + args[0] = "rpushx" + args[1] = key + args = appendArgs(args, values) + cmd := NewIntCmd(args...) + _ = c(cmd) return cmd } //------------------------------------------------------------------------------ -func (c *cmdable) SAdd(key string, members ...interface{}) *IntCmd { +func (c cmdable) SAdd(key string, members ...interface{}) *IntCmd { args := make([]interface{}, 2, 2+len(members)) args[0] = "sadd" args[1] = key args = appendArgs(args, members) cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) SCard(key string) *IntCmd { +func (c cmdable) SCard(key string) *IntCmd { cmd := NewIntCmd("scard", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) SDiff(keys ...string) *StringSliceCmd { +func (c cmdable) SDiff(keys ...string) *StringSliceCmd { args := make([]interface{}, 1+len(keys)) args[0] = "sdiff" for i, key := range keys { args[1+i] = key } cmd := NewStringSliceCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) SDiffStore(destination string, keys ...string) *IntCmd { +func (c cmdable) SDiffStore(destination string, keys ...string) *IntCmd { args := make([]interface{}, 2+len(keys)) args[0] = "sdiffstore" args[1] = destination @@ -1194,22 +1221,22 @@ func (c *cmdable) SDiffStore(destination string, keys ...string) *IntCmd { args[2+i] = key } cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) SInter(keys ...string) *StringSliceCmd { +func (c cmdable) SInter(keys ...string) *StringSliceCmd { args := make([]interface{}, 1+len(keys)) args[0] = "sinter" for i, key := range keys { args[1+i] = key } cmd := NewStringSliceCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) SInterStore(destination string, keys ...string) *IntCmd { +func (c cmdable) SInterStore(destination string, keys ...string) *IntCmd { args := make([]interface{}, 2+len(keys)) args[0] = "sinterstore" args[1] = destination @@ -1217,86 +1244,86 @@ func (c *cmdable) SInterStore(destination string, keys ...string) *IntCmd { args[2+i] = key } cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) SIsMember(key string, member interface{}) *BoolCmd { +func (c cmdable) SIsMember(key string, member interface{}) *BoolCmd { cmd := NewBoolCmd("sismember", key, member) - c.process(cmd) + _ = c(cmd) return cmd } // Redis `SMEMBERS key` command output as a slice -func (c *cmdable) SMembers(key string) *StringSliceCmd { +func (c cmdable) SMembers(key string) *StringSliceCmd { cmd := NewStringSliceCmd("smembers", key) - c.process(cmd) + _ = c(cmd) return cmd } // Redis `SMEMBERS key` command output as a map -func (c *cmdable) SMembersMap(key string) *StringStructMapCmd { +func (c cmdable) SMembersMap(key string) *StringStructMapCmd { cmd := NewStringStructMapCmd("smembers", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) SMove(source, destination string, member interface{}) *BoolCmd { +func (c cmdable) SMove(source, destination string, member interface{}) *BoolCmd { cmd := NewBoolCmd("smove", source, destination, member) - c.process(cmd) + _ = c(cmd) return cmd } // Redis `SPOP key` command. -func (c *cmdable) SPop(key string) *StringCmd { +func (c cmdable) SPop(key string) *StringCmd { cmd := NewStringCmd("spop", key) - c.process(cmd) + _ = c(cmd) return cmd } // Redis `SPOP key count` command. -func (c *cmdable) SPopN(key string, count int64) *StringSliceCmd { +func (c cmdable) SPopN(key string, count int64) *StringSliceCmd { cmd := NewStringSliceCmd("spop", key, count) - c.process(cmd) + _ = c(cmd) return cmd } // Redis `SRANDMEMBER key` command. -func (c *cmdable) SRandMember(key string) *StringCmd { +func (c cmdable) SRandMember(key string) *StringCmd { cmd := NewStringCmd("srandmember", key) - c.process(cmd) + _ = c(cmd) return cmd } // Redis `SRANDMEMBER key count` command. -func (c *cmdable) SRandMemberN(key string, count int64) *StringSliceCmd { +func (c cmdable) SRandMemberN(key string, count int64) *StringSliceCmd { cmd := NewStringSliceCmd("srandmember", key, count) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) SRem(key string, members ...interface{}) *IntCmd { +func (c cmdable) SRem(key string, members ...interface{}) *IntCmd { args := make([]interface{}, 2, 2+len(members)) args[0] = "srem" args[1] = key args = appendArgs(args, members) cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) SUnion(keys ...string) *StringSliceCmd { +func (c cmdable) SUnion(keys ...string) *StringSliceCmd { args := make([]interface{}, 1+len(keys)) args[0] = "sunion" for i, key := range keys { args[1+i] = key } cmd := NewStringSliceCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) SUnionStore(destination string, keys ...string) *IntCmd { +func (c cmdable) SUnionStore(destination string, keys ...string) *IntCmd { args := make([]interface{}, 2+len(keys)) args[0] = "sunionstore" args[1] = destination @@ -1304,7 +1331,7 @@ func (c *cmdable) SUnionStore(destination string, keys ...string) *IntCmd { args[2+i] = key } cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } @@ -1318,7 +1345,7 @@ type XAddArgs struct { Values map[string]interface{} } -func (c *cmdable) XAdd(a *XAddArgs) *StringCmd { +func (c cmdable) XAdd(a *XAddArgs) *StringCmd { args := make([]interface{}, 0, 6+len(a.Values)*2) args = append(args, "xadd") args = append(args, a.Stream) @@ -1338,47 +1365,57 @@ func (c *cmdable) XAdd(a *XAddArgs) *StringCmd { } cmd := NewStringCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) XLen(stream string) *IntCmd { +func (c cmdable) XDel(stream string, ids ...string) *IntCmd { + args := []interface{}{"xdel", stream} + for _, id := range ids { + args = append(args, id) + } + cmd := NewIntCmd(args...) + _ = c(cmd) + return cmd +} + +func (c cmdable) XLen(stream string) *IntCmd { cmd := NewIntCmd("xlen", stream) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) XRange(stream, start, stop string) *XMessageSliceCmd { +func (c cmdable) XRange(stream, start, stop string) *XMessageSliceCmd { cmd := NewXMessageSliceCmd("xrange", stream, start, stop) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) XRangeN(stream, start, stop string, count int64) *XMessageSliceCmd { +func (c cmdable) XRangeN(stream, start, stop string, count int64) *XMessageSliceCmd { cmd := NewXMessageSliceCmd("xrange", stream, start, stop, "count", count) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) XRevRange(stream, start, stop string) *XMessageSliceCmd { +func (c cmdable) XRevRange(stream, start, stop string) *XMessageSliceCmd { cmd := NewXMessageSliceCmd("xrevrange", stream, start, stop) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) XRevRangeN(stream, start, stop string, count int64) *XMessageSliceCmd { +func (c cmdable) XRevRangeN(stream, start, stop string, count int64) *XMessageSliceCmd { cmd := NewXMessageSliceCmd("xrevrange", stream, start, stop, "count", count) - c.process(cmd) + _ = c(cmd) return cmd } type XReadArgs struct { - Streams []string + Streams []string // list of streams and ids, e.g. stream1 stream2 id1 id2 Count int64 Block time.Duration } -func (c *cmdable) XRead(a *XReadArgs) *XStreamSliceCmd { +func (c cmdable) XRead(a *XReadArgs) *XStreamSliceCmd { args := make([]interface{}, 0, 5+len(a.Streams)) args = append(args, "xread") if a.Count > 0 { @@ -1389,56 +1426,67 @@ func (c *cmdable) XRead(a *XReadArgs) *XStreamSliceCmd { args = append(args, "block") args = append(args, int64(a.Block/time.Millisecond)) } + args = append(args, "streams") for _, s := range a.Streams { args = append(args, s) } cmd := NewXStreamSliceCmd(args...) - c.process(cmd) + if a.Block >= 0 { + cmd.setReadTimeout(a.Block) + } + _ = c(cmd) return cmd } -func (c *cmdable) XReadStreams(streams ...string) *XStreamSliceCmd { +func (c cmdable) XReadStreams(streams ...string) *XStreamSliceCmd { return c.XRead(&XReadArgs{ Streams: streams, Block: -1, }) } -func (c *cmdable) XGroupCreate(stream, group, start string) *StatusCmd { +func (c cmdable) XGroupCreate(stream, group, start string) *StatusCmd { cmd := NewStatusCmd("xgroup", "create", stream, group, start) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) XGroupSetID(stream, group, start string) *StatusCmd { +func (c cmdable) XGroupCreateMkStream(stream, group, start string) *StatusCmd { + cmd := NewStatusCmd("xgroup", "create", stream, group, start, "mkstream") + _ = c(cmd) + return cmd +} + +func (c cmdable) XGroupSetID(stream, group, start string) *StatusCmd { cmd := NewStatusCmd("xgroup", "setid", stream, group, start) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) XGroupDestroy(stream, group string) *IntCmd { +func (c cmdable) XGroupDestroy(stream, group string) *IntCmd { cmd := NewIntCmd("xgroup", "destroy", stream, group) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) XGroupDelConsumer(stream, group, consumer string) *IntCmd { +func (c cmdable) XGroupDelConsumer(stream, group, consumer string) *IntCmd { cmd := NewIntCmd("xgroup", "delconsumer", stream, group, consumer) - c.process(cmd) + _ = c(cmd) return cmd } type XReadGroupArgs struct { Group string Consumer string - Streams []string + Streams []string // list of streams and ids, e.g. stream1 stream2 id1 id2 Count int64 Block time.Duration + NoAck bool } -func (c *cmdable) XReadGroup(a *XReadGroupArgs) *XStreamSliceCmd { +func (c cmdable) XReadGroup(a *XReadGroupArgs) *XStreamSliceCmd { args := make([]interface{}, 0, 8+len(a.Streams)) args = append(args, "xreadgroup", "group", a.Group, a.Consumer) if a.Count > 0 { @@ -1447,29 +1495,35 @@ func (c *cmdable) XReadGroup(a *XReadGroupArgs) *XStreamSliceCmd { if a.Block >= 0 { args = append(args, "block", int64(a.Block/time.Millisecond)) } + if a.NoAck { + args = append(args, "noack") + } args = append(args, "streams") for _, s := range a.Streams { args = append(args, s) } cmd := NewXStreamSliceCmd(args...) - c.process(cmd) + if a.Block >= 0 { + cmd.setReadTimeout(a.Block) + } + _ = c(cmd) return cmd } -func (c *cmdable) XAck(stream, group string, ids ...string) *IntCmd { +func (c cmdable) XAck(stream, group string, ids ...string) *IntCmd { args := []interface{}{"xack", stream, group} for _, id := range ids { args = append(args, id) } cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) XPending(stream, group string) *XPendingCmd { +func (c cmdable) XPending(stream, group string) *XPendingCmd { cmd := NewXPendingCmd("xpending", stream, group) - c.process(cmd) + _ = c(cmd) return cmd } @@ -1482,14 +1536,14 @@ type XPendingExtArgs struct { Consumer string } -func (c *cmdable) XPendingExt(a *XPendingExtArgs) *XPendingExtCmd { +func (c cmdable) XPendingExt(a *XPendingExtArgs) *XPendingExtCmd { args := make([]interface{}, 0, 7) args = append(args, "xpending", a.Stream, a.Group, a.Start, a.End, a.Count) if a.Consumer != "" { args = append(args, a.Consumer) } cmd := NewXPendingExtCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } @@ -1501,18 +1555,18 @@ type XClaimArgs struct { Messages []string } -func (c *cmdable) XClaim(a *XClaimArgs) *XMessageSliceCmd { +func (c cmdable) XClaim(a *XClaimArgs) *XMessageSliceCmd { args := xClaimArgs(a) cmd := NewXMessageSliceCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) XClaimJustID(a *XClaimArgs) *StringSliceCmd { +func (c cmdable) XClaimJustID(a *XClaimArgs) *StringSliceCmd { args := xClaimArgs(a) args = append(args, "justid") cmd := NewStringSliceCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } @@ -1529,15 +1583,21 @@ func xClaimArgs(a *XClaimArgs) []interface{} { return args } -func (c *cmdable) XTrim(key string, maxLen int64) *IntCmd { +func (c cmdable) XTrim(key string, maxLen int64) *IntCmd { cmd := NewIntCmd("xtrim", key, "maxlen", maxLen) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) XTrimApprox(key string, maxLen int64) *IntCmd { +func (c cmdable) XTrimApprox(key string, maxLen int64) *IntCmd { cmd := NewIntCmd("xtrim", key, "maxlen", "~", maxLen) - c.process(cmd) + _ = c(cmd) + return cmd +} + +func (c cmdable) XInfoGroups(key string) *XInfoGroupsCmd { + cmd := NewXInfoGroupsCmd(key) + _ = c(cmd) return cmd } @@ -1549,25 +1609,60 @@ type Z struct { Member interface{} } +// ZWithKey represents sorted set member including the name of the key where it was popped. +type ZWithKey struct { + Z + Key string +} + // ZStore is used as an arg to ZInterStore and ZUnionStore. type ZStore struct { + Keys []string Weights []float64 // Can be SUM, MIN or MAX. Aggregate string } -func (c *cmdable) zAdd(a []interface{}, n int, members ...Z) *IntCmd { +// Redis `BZPOPMAX key [key ...] timeout` command. +func (c cmdable) BZPopMax(timeout time.Duration, keys ...string) *ZWithKeyCmd { + args := make([]interface{}, 1+len(keys)+1) + args[0] = "bzpopmax" + for i, key := range keys { + args[1+i] = key + } + args[len(args)-1] = formatSec(timeout) + cmd := NewZWithKeyCmd(args...) + cmd.setReadTimeout(timeout) + _ = c(cmd) + return cmd +} + +// Redis `BZPOPMIN key [key ...] timeout` command. +func (c cmdable) BZPopMin(timeout time.Duration, keys ...string) *ZWithKeyCmd { + args := make([]interface{}, 1+len(keys)+1) + args[0] = "bzpopmin" + for i, key := range keys { + args[1+i] = key + } + args[len(args)-1] = formatSec(timeout) + cmd := NewZWithKeyCmd(args...) + cmd.setReadTimeout(timeout) + _ = c(cmd) + return cmd +} + +func (c cmdable) zAdd(a []interface{}, n int, members ...*Z) *IntCmd { for i, m := range members { a[n+2*i] = m.Score a[n+2*i+1] = m.Member } cmd := NewIntCmd(a...) - c.process(cmd) + _ = c(cmd) return cmd } // Redis `ZADD key score member [score member ...]` command. -func (c *cmdable) ZAdd(key string, members ...Z) *IntCmd { +func (c cmdable) ZAdd(key string, members ...*Z) *IntCmd { const n = 2 a := make([]interface{}, n+2*len(members)) a[0], a[1] = "zadd", key @@ -1575,7 +1670,7 @@ func (c *cmdable) ZAdd(key string, members ...Z) *IntCmd { } // Redis `ZADD key NX score member [score member ...]` command. -func (c *cmdable) ZAddNX(key string, members ...Z) *IntCmd { +func (c cmdable) ZAddNX(key string, members ...*Z) *IntCmd { const n = 3 a := make([]interface{}, n+2*len(members)) a[0], a[1], a[2] = "zadd", key, "nx" @@ -1583,7 +1678,7 @@ func (c *cmdable) ZAddNX(key string, members ...Z) *IntCmd { } // Redis `ZADD key XX score member [score member ...]` command. -func (c *cmdable) ZAddXX(key string, members ...Z) *IntCmd { +func (c cmdable) ZAddXX(key string, members ...*Z) *IntCmd { const n = 3 a := make([]interface{}, n+2*len(members)) a[0], a[1], a[2] = "zadd", key, "xx" @@ -1591,7 +1686,7 @@ func (c *cmdable) ZAddXX(key string, members ...Z) *IntCmd { } // Redis `ZADD key CH score member [score member ...]` command. -func (c *cmdable) ZAddCh(key string, members ...Z) *IntCmd { +func (c cmdable) ZAddCh(key string, members ...*Z) *IntCmd { const n = 3 a := make([]interface{}, n+2*len(members)) a[0], a[1], a[2] = "zadd", key, "ch" @@ -1599,7 +1694,7 @@ func (c *cmdable) ZAddCh(key string, members ...Z) *IntCmd { } // Redis `ZADD key NX CH score member [score member ...]` command. -func (c *cmdable) ZAddNXCh(key string, members ...Z) *IntCmd { +func (c cmdable) ZAddNXCh(key string, members ...*Z) *IntCmd { const n = 4 a := make([]interface{}, n+2*len(members)) a[0], a[1], a[2], a[3] = "zadd", key, "nx", "ch" @@ -1607,25 +1702,25 @@ func (c *cmdable) ZAddNXCh(key string, members ...Z) *IntCmd { } // Redis `ZADD key XX CH score member [score member ...]` command. -func (c *cmdable) ZAddXXCh(key string, members ...Z) *IntCmd { +func (c cmdable) ZAddXXCh(key string, members ...*Z) *IntCmd { const n = 4 a := make([]interface{}, n+2*len(members)) a[0], a[1], a[2], a[3] = "zadd", key, "xx", "ch" return c.zAdd(a, n, members...) } -func (c *cmdable) zIncr(a []interface{}, n int, members ...Z) *FloatCmd { +func (c cmdable) zIncr(a []interface{}, n int, members ...*Z) *FloatCmd { for i, m := range members { a[n+2*i] = m.Score a[n+2*i+1] = m.Member } cmd := NewFloatCmd(a...) - c.process(cmd) + _ = c(cmd) return cmd } // Redis `ZADD key INCR score member` command. -func (c *cmdable) ZIncr(key string, member Z) *FloatCmd { +func (c cmdable) ZIncr(key string, member *Z) *FloatCmd { const n = 3 a := make([]interface{}, n+2) a[0], a[1], a[2] = "zadd", key, "incr" @@ -1633,7 +1728,7 @@ func (c *cmdable) ZIncr(key string, member Z) *FloatCmd { } // Redis `ZADD key NX INCR score member` command. -func (c *cmdable) ZIncrNX(key string, member Z) *FloatCmd { +func (c cmdable) ZIncrNX(key string, member *Z) *FloatCmd { const n = 4 a := make([]interface{}, n+2) a[0], a[1], a[2], a[3] = "zadd", key, "incr", "nx" @@ -1641,43 +1736,43 @@ func (c *cmdable) ZIncrNX(key string, member Z) *FloatCmd { } // Redis `ZADD key XX INCR score member` command. -func (c *cmdable) ZIncrXX(key string, member Z) *FloatCmd { +func (c cmdable) ZIncrXX(key string, member *Z) *FloatCmd { const n = 4 a := make([]interface{}, n+2) a[0], a[1], a[2], a[3] = "zadd", key, "incr", "xx" return c.zIncr(a, n, member) } -func (c *cmdable) ZCard(key string) *IntCmd { +func (c cmdable) ZCard(key string) *IntCmd { cmd := NewIntCmd("zcard", key) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZCount(key, min, max string) *IntCmd { +func (c cmdable) ZCount(key, min, max string) *IntCmd { cmd := NewIntCmd("zcount", key, min, max) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZLexCount(key, min, max string) *IntCmd { +func (c cmdable) ZLexCount(key, min, max string) *IntCmd { cmd := NewIntCmd("zlexcount", key, min, max) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZIncrBy(key string, increment float64, member string) *FloatCmd { +func (c cmdable) ZIncrBy(key string, increment float64, member string) *FloatCmd { cmd := NewFloatCmd("zincrby", key, increment, member) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZInterStore(destination string, store ZStore, keys ...string) *IntCmd { - args := make([]interface{}, 3+len(keys)) +func (c cmdable) ZInterStore(destination string, store *ZStore) *IntCmd { + args := make([]interface{}, 3+len(store.Keys)) args[0] = "zinterstore" args[1] = destination - args[2] = len(keys) - for i, key := range keys { + args[2] = len(store.Keys) + for i, key := range store.Keys { args[3+i] = key } if len(store.Weights) > 0 { @@ -1690,11 +1785,51 @@ func (c *cmdable) ZInterStore(destination string, store ZStore, keys ...string) args = append(args, "aggregate", store.Aggregate) } cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) zRange(key string, start, stop int64, withScores bool) *StringSliceCmd { +func (c cmdable) ZPopMax(key string, count ...int64) *ZSliceCmd { + args := []interface{}{ + "zpopmax", + key, + } + + switch len(count) { + case 0: + break + case 1: + args = append(args, count[0]) + default: + panic("too many arguments") + } + + cmd := NewZSliceCmd(args...) + _ = c(cmd) + return cmd +} + +func (c cmdable) ZPopMin(key string, count ...int64) *ZSliceCmd { + args := []interface{}{ + "zpopmin", + key, + } + + switch len(count) { + case 0: + break + case 1: + args = append(args, count[0]) + default: + panic("too many arguments") + } + + cmd := NewZSliceCmd(args...) + _ = c(cmd) + return cmd +} + +func (c cmdable) zRange(key string, start, stop int64, withScores bool) *StringSliceCmd { args := []interface{}{ "zrange", key, @@ -1705,17 +1840,17 @@ func (c *cmdable) zRange(key string, start, stop int64, withScores bool) *String args = append(args, "withscores") } cmd := NewStringSliceCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZRange(key string, start, stop int64) *StringSliceCmd { +func (c cmdable) ZRange(key string, start, stop int64) *StringSliceCmd { return c.zRange(key, start, stop, false) } -func (c *cmdable) ZRangeWithScores(key string, start, stop int64) *ZSliceCmd { +func (c cmdable) ZRangeWithScores(key string, start, stop int64) *ZSliceCmd { cmd := NewZSliceCmd("zrange", key, start, stop, "withscores") - c.process(cmd) + _ = c(cmd) return cmd } @@ -1724,7 +1859,7 @@ type ZRangeBy struct { Offset, Count int64 } -func (c *cmdable) zRangeBy(zcmd, key string, opt ZRangeBy, withScores bool) *StringSliceCmd { +func (c cmdable) zRangeBy(zcmd, key string, opt *ZRangeBy, withScores bool) *StringSliceCmd { args := []interface{}{zcmd, key, opt.Min, opt.Max} if withScores { args = append(args, "withscores") @@ -1738,19 +1873,19 @@ func (c *cmdable) zRangeBy(zcmd, key string, opt ZRangeBy, withScores bool) *Str ) } cmd := NewStringSliceCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZRangeByScore(key string, opt ZRangeBy) *StringSliceCmd { +func (c cmdable) ZRangeByScore(key string, opt *ZRangeBy) *StringSliceCmd { return c.zRangeBy("zrangebyscore", key, opt, false) } -func (c *cmdable) ZRangeByLex(key string, opt ZRangeBy) *StringSliceCmd { +func (c cmdable) ZRangeByLex(key string, opt *ZRangeBy) *StringSliceCmd { return c.zRangeBy("zrangebylex", key, opt, false) } -func (c *cmdable) ZRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd { +func (c cmdable) ZRangeByScoreWithScores(key string, opt *ZRangeBy) *ZSliceCmd { args := []interface{}{"zrangebyscore", key, opt.Min, opt.Max, "withscores"} if opt.Offset != 0 || opt.Count != 0 { args = append( @@ -1761,62 +1896,62 @@ func (c *cmdable) ZRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd { ) } cmd := NewZSliceCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZRank(key, member string) *IntCmd { +func (c cmdable) ZRank(key, member string) *IntCmd { cmd := NewIntCmd("zrank", key, member) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZRem(key string, members ...interface{}) *IntCmd { +func (c cmdable) ZRem(key string, members ...interface{}) *IntCmd { args := make([]interface{}, 2, 2+len(members)) args[0] = "zrem" args[1] = key args = appendArgs(args, members) cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZRemRangeByRank(key string, start, stop int64) *IntCmd { +func (c cmdable) ZRemRangeByRank(key string, start, stop int64) *IntCmd { cmd := NewIntCmd( "zremrangebyrank", key, start, stop, ) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZRemRangeByScore(key, min, max string) *IntCmd { +func (c cmdable) ZRemRangeByScore(key, min, max string) *IntCmd { cmd := NewIntCmd("zremrangebyscore", key, min, max) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZRemRangeByLex(key, min, max string) *IntCmd { +func (c cmdable) ZRemRangeByLex(key, min, max string) *IntCmd { cmd := NewIntCmd("zremrangebylex", key, min, max) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZRevRange(key string, start, stop int64) *StringSliceCmd { +func (c cmdable) ZRevRange(key string, start, stop int64) *StringSliceCmd { cmd := NewStringSliceCmd("zrevrange", key, start, stop) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZRevRangeWithScores(key string, start, stop int64) *ZSliceCmd { +func (c cmdable) ZRevRangeWithScores(key string, start, stop int64) *ZSliceCmd { cmd := NewZSliceCmd("zrevrange", key, start, stop, "withscores") - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) zRevRangeBy(zcmd, key string, opt ZRangeBy) *StringSliceCmd { +func (c cmdable) zRevRangeBy(zcmd, key string, opt *ZRangeBy) *StringSliceCmd { args := []interface{}{zcmd, key, opt.Max, opt.Min} if opt.Offset != 0 || opt.Count != 0 { args = append( @@ -1827,19 +1962,19 @@ func (c *cmdable) zRevRangeBy(zcmd, key string, opt ZRangeBy) *StringSliceCmd { ) } cmd := NewStringSliceCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZRevRangeByScore(key string, opt ZRangeBy) *StringSliceCmd { +func (c cmdable) ZRevRangeByScore(key string, opt *ZRangeBy) *StringSliceCmd { return c.zRevRangeBy("zrevrangebyscore", key, opt) } -func (c *cmdable) ZRevRangeByLex(key string, opt ZRangeBy) *StringSliceCmd { +func (c cmdable) ZRevRangeByLex(key string, opt *ZRangeBy) *StringSliceCmd { return c.zRevRangeBy("zrevrangebylex", key, opt) } -func (c *cmdable) ZRevRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd { +func (c cmdable) ZRevRangeByScoreWithScores(key string, opt *ZRangeBy) *ZSliceCmd { args := []interface{}{"zrevrangebyscore", key, opt.Max, opt.Min, "withscores"} if opt.Offset != 0 || opt.Count != 0 { args = append( @@ -1850,28 +1985,28 @@ func (c *cmdable) ZRevRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCm ) } cmd := NewZSliceCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZRevRank(key, member string) *IntCmd { +func (c cmdable) ZRevRank(key, member string) *IntCmd { cmd := NewIntCmd("zrevrank", key, member) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZScore(key, member string) *FloatCmd { +func (c cmdable) ZScore(key, member string) *FloatCmd { cmd := NewFloatCmd("zscore", key, member) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ZUnionStore(dest string, store ZStore, keys ...string) *IntCmd { - args := make([]interface{}, 3+len(keys)) +func (c cmdable) ZUnionStore(dest string, store *ZStore) *IntCmd { + args := make([]interface{}, 3+len(store.Keys)) args[0] = "zunionstore" args[1] = dest - args[2] = len(keys) - for i, key := range keys { + args[2] = len(store.Keys) + for i, key := range store.Keys { args[3+i] = key } if len(store.Weights) > 0 { @@ -1884,34 +2019,34 @@ func (c *cmdable) ZUnionStore(dest string, store ZStore, keys ...string) *IntCmd args = append(args, "aggregate", store.Aggregate) } cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } //------------------------------------------------------------------------------ -func (c *cmdable) PFAdd(key string, els ...interface{}) *IntCmd { +func (c cmdable) PFAdd(key string, els ...interface{}) *IntCmd { args := make([]interface{}, 2, 2+len(els)) args[0] = "pfadd" args[1] = key args = appendArgs(args, els) cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) PFCount(keys ...string) *IntCmd { +func (c cmdable) PFCount(keys ...string) *IntCmd { args := make([]interface{}, 1+len(keys)) args[0] = "pfcount" for i, key := range keys { args[1+i] = key } cmd := NewIntCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) PFMerge(dest string, keys ...string) *StatusCmd { +func (c cmdable) PFMerge(dest string, keys ...string) *StatusCmd { args := make([]interface{}, 2+len(keys)) args[0] = "pfmerge" args[1] = dest @@ -1919,33 +2054,33 @@ func (c *cmdable) PFMerge(dest string, keys ...string) *StatusCmd { args[2+i] = key } cmd := NewStatusCmd(args...) - c.process(cmd) + _ = c(cmd) return cmd } //------------------------------------------------------------------------------ -func (c *cmdable) BgRewriteAOF() *StatusCmd { +func (c cmdable) BgRewriteAOF() *StatusCmd { cmd := NewStatusCmd("bgrewriteaof") - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) BgSave() *StatusCmd { +func (c cmdable) BgSave() *StatusCmd { cmd := NewStatusCmd("bgsave") - c.process(cmd) + _ = c(cmd) return cmd } -func (c *cmdable) ClientKill(ipPort string) *StatusCmd { +func (c cmdable) ClientKill(ipPort string) *StatusCmd { cmd := NewStatusCmd("client", "kill", ipPort) - c.process(cmd) + _ = c(cmd) return cmd } // ClientKillByFilter is new style synx, while the ClientKill is old // CLIENT KILL