Procházet zdrojové kódy

Update dependencies

Andrea Fazzi před 7 roky
rodič
revize
2ecf7453b3
91 změnil soubory, kde provedl 4402 přidání a 681 odebrání
  1. 43 19
      Godeps/Godeps.json
  2. binární
      core
  3. 4 4
      main.go
  4. 3 3
      vendor/github.com/dgrijalva/jwt-go/README.md
  5. 6 0
      vendor/github.com/dgrijalva/jwt-go/VERSION_HISTORY.md
  6. 1 0
      vendor/github.com/go-sql-driver/mysql/.gitignore
  7. 84 5
      vendor/github.com/go-sql-driver/mysql/.travis.yml
  8. 23 0
      vendor/github.com/go-sql-driver/mysql/AUTHORS
  9. 58 25
      vendor/github.com/go-sql-driver/mysql/README.md
  10. 1 1
      vendor/github.com/go-sql-driver/mysql/appengine.go
  11. 116 32
      vendor/github.com/go-sql-driver/mysql/connection.go
  12. 202 0
      vendor/github.com/go-sql-driver/mysql/connection_go18.go
  13. 6 3
      vendor/github.com/go-sql-driver/mysql/const.go
  14. 12 2
      vendor/github.com/go-sql-driver/mysql/driver.go
  15. 79 41
      vendor/github.com/go-sql-driver/mysql/dsn.go
  16. 6 73
      vendor/github.com/go-sql-driver/mysql/errors.go
  17. 140 0
      vendor/github.com/go-sql-driver/mysql/fields.go
  18. 2 1
      vendor/github.com/go-sql-driver/mysql/infile.go
  19. 108 86
      vendor/github.com/go-sql-driver/mysql/packets.go
  20. 142 35
      vendor/github.com/go-sql-driver/mysql/rows.go
  21. 55 30
      vendor/github.com/go-sql-driver/mysql/statement.go
  22. 2 2
      vendor/github.com/go-sql-driver/mysql/transaction.go
  23. 83 1
      vendor/github.com/go-sql-driver/mysql/utils.go
  24. 40 0
      vendor/github.com/go-sql-driver/mysql/utils_go17.go
  25. 49 0
      vendor/github.com/go-sql-driver/mysql/utils_go18.go
  26. 11 1
      vendor/github.com/gorilla/handlers/cors.go
  27. 4 3
      vendor/github.com/gorilla/mux/.travis.yml
  28. 122 6
      vendor/github.com/gorilla/mux/README.md
  29. 12 5
      vendor/github.com/gorilla/mux/doc.go
  30. 63 34
      vendor/github.com/gorilla/mux/mux.go
  31. 14 4
      vendor/github.com/gorilla/mux/regexp.go
  32. 127 8
      vendor/github.com/gorilla/mux/route.go
  33. 1 2
      vendor/github.com/gorilla/schema/.travis.yml
  34. 26 2
      vendor/github.com/gorilla/schema/README.md
  35. 14 11
      vendor/github.com/gorilla/schema/cache.go
  36. 1 1
      vendor/github.com/gorilla/schema/converter.go
  37. 100 25
      vendor/github.com/gorilla/schema/decoder.go
  38. 2 2
      vendor/github.com/gorilla/schema/doc.go
  39. 161 0
      vendor/github.com/gorilla/schema/encoder.go
  40. 2 0
      vendor/github.com/gorilla/securecookie/README.md
  41. 14 3
      vendor/github.com/gorilla/sessions/README.md
  42. 1 2
      vendor/github.com/gorilla/sessions/doc.go
  43. 0 52
      vendor/github.com/jinzhu/gorm/CONTRIBUTING.md
  44. 3 1
      vendor/github.com/jinzhu/gorm/association.go
  45. 12 2
      vendor/github.com/jinzhu/gorm/callback_create.go
  46. 5 2
      vendor/github.com/jinzhu/gorm/callback_delete.go
  47. 4 2
      vendor/github.com/jinzhu/gorm/callback_query.go
  48. 35 1
      vendor/github.com/jinzhu/gorm/callback_query_preload.go
  49. 2 2
      vendor/github.com/jinzhu/gorm/dialect.go
  50. 10 6
      vendor/github.com/jinzhu/gorm/dialect_common.go
  51. 33 3
      vendor/github.com/jinzhu/gorm/dialect_mysql.go
  52. 3 7
      vendor/github.com/jinzhu/gorm/dialect_postgres.go
  53. 1 2
      vendor/github.com/jinzhu/gorm/dialect_sqlite3.go
  54. 2 1
      vendor/github.com/jinzhu/gorm/interface.go
  55. 2 0
      vendor/github.com/jinzhu/gorm/join_table_handler.go
  56. 4 2
      vendor/github.com/jinzhu/gorm/logger.go
  57. 80 64
      vendor/github.com/jinzhu/gorm/main.go
  58. 30 13
      vendor/github.com/jinzhu/gorm/model_struct.go
  59. 27 12
      vendor/github.com/jinzhu/gorm/scope.go
  60. 7 3
      vendor/github.com/jinzhu/gorm/search.go
  61. 1 1
      vendor/github.com/jinzhu/gorm/test_all.sh
  62. 19 1
      vendor/github.com/jinzhu/gorm/utils.go
  63. 56 0
      vendor/gogs.carducci-dante.gov.it/andrea.fazzi/karmen/config/config.go
  64. 42 0
      vendor/gogs.carducci-dante.gov.it/andrea.fazzi/karmen/datasource/edt/edt.go
  65. 11 0
      vendor/gogs.carducci-dante.gov.it/andrea.fazzi/karmen/datasource/importer.go
  66. 173 0
      vendor/gogs.carducci-dante.gov.it/andrea.fazzi/karmen/orm/activity.go
  67. 32 0
      vendor/gogs.carducci-dante.gov.it/andrea.fazzi/karmen/orm/class.go
  68. 32 0
      vendor/gogs.carducci-dante.gov.it/andrea.fazzi/karmen/orm/credential.go
  69. 16 0
      vendor/gogs.carducci-dante.gov.it/andrea.fazzi/karmen/orm/issue.go
  70. 298 0
      vendor/gogs.carducci-dante.gov.it/andrea.fazzi/karmen/orm/orm.go
  71. 10 0
      vendor/gogs.carducci-dante.gov.it/andrea.fazzi/karmen/orm/school.go
  72. 26 0
      vendor/gogs.carducci-dante.gov.it/andrea.fazzi/karmen/orm/subject.go
  73. 278 0
      vendor/gogs.carducci-dante.gov.it/andrea.fazzi/karmen/orm/teacher.go
  74. 104 0
      vendor/gogs.carducci-dante.gov.it/andrea.fazzi/karmen/renderer/renderer.go
  75. 173 0
      vendor/gogs.carducci-dante.gov.it/karmen/orm/activity.go
  76. 32 0
      vendor/gogs.carducci-dante.gov.it/karmen/orm/class.go
  77. 32 0
      vendor/gogs.carducci-dante.gov.it/karmen/orm/credential.go
  78. 16 0
      vendor/gogs.carducci-dante.gov.it/karmen/orm/issue.go
  79. 298 0
      vendor/gogs.carducci-dante.gov.it/karmen/orm/orm.go
  80. 10 0
      vendor/gogs.carducci-dante.gov.it/karmen/orm/school.go
  81. 26 0
      vendor/gogs.carducci-dante.gov.it/karmen/orm/subject.go
  82. 278 0
      vendor/gogs.carducci-dante.gov.it/karmen/orm/teacher.go
  83. 198 10
      vendor/gopkg.in/yaml.v2/LICENSE
  84. 3 1
      vendor/gopkg.in/yaml.v2/README.md
  85. 6 4
      vendor/gopkg.in/yaml.v2/decode.go
  86. 4 5
      vendor/gopkg.in/yaml.v2/emitterc.go
  87. 0 1
      vendor/gopkg.in/yaml.v2/parserc.go
  88. 8 3
      vendor/gopkg.in/yaml.v2/resolve.go
  89. 6 5
      vendor/gopkg.in/yaml.v2/scannerc.go
  90. 13 2
      vendor/gopkg.in/yaml.v2/yaml.go
  91. 1 1
      vendor/gopkg.in/yaml.v2/yamlh.go

+ 43 - 19
Godeps/Godeps.json

@@ -1,5 +1,5 @@
 {
-	"ImportPath": "gogs.carducci-dante.gov.it/andrea.fazzi/karmen",
+	"ImportPath": "gogs.carducci-dante.gov.it/karmen/core",
 	"GoVersion": "go1.9",
 	"GodepVersion": "v79",
 	"Deps": [
@@ -9,13 +9,13 @@
 		},
 		{
 			"ImportPath": "github.com/dgrijalva/jwt-go",
-			"Comment": "v3.0.0-17-g2268707",
-			"Rev": "2268707a8f0843315e2004ee4f1d021dc08baedf"
+			"Comment": "v3.1.0",
+			"Rev": "dbeaa9332f19a944acb5736b4456cfcc02140e29"
 		},
 		{
 			"ImportPath": "github.com/go-sql-driver/mysql",
-			"Comment": "v1.3-2-g2e00b5c",
-			"Rev": "2e00b5cd70399450106cec6431c2e2ce3cae5034"
+			"Comment": "v1.3-49-gcd4cb90",
+			"Rev": "cd4cb909ce1a31435164be29bf3682031f61539a"
 		},
 		{
 			"ImportPath": "github.com/gorilla/context",
@@ -24,45 +24,69 @@
 		},
 		{
 			"ImportPath": "github.com/gorilla/handlers",
-			"Comment": "v1.2.1-1-ga4d79d4",
-			"Rev": "a4d79d4487c2430a17d9dc8a1f74d1a6ed6908ca"
+			"Comment": "v1.3.0",
+			"Rev": "90663712d74cb411cbef281bc1e08c19d1a76145"
 		},
 		{
 			"ImportPath": "github.com/gorilla/mux",
-			"Comment": "v1.1-27-g757bef9",
-			"Rev": "757bef944d0f21880861c2dd9c871ca543023cba"
+			"Comment": "v1.6.0-1-g2d5fef0",
+			"Rev": "2d5fef06b891c971b14aa6f71ca5ab6c03a36e0e"
 		},
 		{
 			"ImportPath": "github.com/gorilla/schema",
-			"Rev": "0164a00ab4cd01d814d8cd5bf63fd9fcea30e23b"
+			"Rev": "e6c82218a8b3ed3cbeb5407429849c0b0b597d40"
 		},
 		{
 			"ImportPath": "github.com/gorilla/securecookie",
-			"Comment": "v1.1-5-gfa5329f",
-			"Rev": "fa5329f913702981df43dcb2a380bac429c810b5"
+			"Comment": "v1.1-6-ge59506c",
+			"Rev": "e59506cc896acb7f7bf732d4fdf5e25f7ccd8983"
 		},
 		{
 			"ImportPath": "github.com/gorilla/sessions",
-			"Comment": "v1.1",
-			"Rev": "ca9ada44574153444b00d3fd9c8559e4cc95f896"
+			"Comment": "v1.1-8-ga3acf13",
+			"Rev": "a3acf13e802c358d65f249324d14ed24aac11370"
 		},
 		{
 			"ImportPath": "github.com/jinzhu/gorm",
-			"Comment": "v1.0-114-g7fb9b62",
-			"Rev": "7fb9b62c17d90320e0582a4720db025c1652fd6a"
+			"Comment": "v1.0-168-gc3bb6aa",
+			"Rev": "c3bb6aaa828867eec72dd8571d111e442688f85f"
 		},
 		{
 			"ImportPath": "github.com/jinzhu/gorm/dialects/mysql",
-			"Comment": "v1.0-114-g7fb9b62",
-			"Rev": "7fb9b62c17d90320e0582a4720db025c1652fd6a"
+			"Comment": "v1.0-168-gc3bb6aa",
+			"Rev": "c3bb6aaa828867eec72dd8571d111e442688f85f"
 		},
 		{
 			"ImportPath": "github.com/jinzhu/inflection",
 			"Rev": "1c35d901db3da928c72a72d8458480cc9ade058f"
 		},
+		{
+			"ImportPath": "gogs.carducci-dante.gov.it/andrea.fazzi/karmen/config",
+			"Rev": "44a3a1310f86952c58f5ad4f91be3cafdbd7a31d"
+		},
+		{
+			"ImportPath": "gogs.carducci-dante.gov.it/andrea.fazzi/karmen/datasource",
+			"Rev": "44a3a1310f86952c58f5ad4f91be3cafdbd7a31d"
+		},
+		{
+			"ImportPath": "gogs.carducci-dante.gov.it/andrea.fazzi/karmen/datasource/edt",
+			"Rev": "44a3a1310f86952c58f5ad4f91be3cafdbd7a31d"
+		},
+		{
+			"ImportPath": "gogs.carducci-dante.gov.it/andrea.fazzi/karmen/orm",
+			"Rev": "44a3a1310f86952c58f5ad4f91be3cafdbd7a31d"
+		},
+		{
+			"ImportPath": "gogs.carducci-dante.gov.it/andrea.fazzi/karmen/renderer",
+			"Rev": "44a3a1310f86952c58f5ad4f91be3cafdbd7a31d"
+		},
+		{
+			"ImportPath": "gogs.carducci-dante.gov.it/karmen/orm",
+			"Rev": "e2b8f91c40e1ace0b1b1f6be315c70e03903abc1"
+		},
 		{
 			"ImportPath": "gopkg.in/yaml.v2",
-			"Rev": "e4d366fc3c7938e2958e662b4258c7a89e1f0e3e"
+			"Rev": "287cf08546ab5e7e37d55a84f7ed3fd1db036de5"
 		}
 	]
 }

binární
karmen → core


+ 4 - 4
main.go

@@ -9,10 +9,10 @@ import (
 	"github.com/gorilla/handlers"
 	"github.com/jinzhu/gorm"
 
-	"gogs.carducci-dante.gov.it/andrea.fazzi/karmen/api"
-	"gogs.carducci-dante.gov.it/andrea.fazzi/karmen/config"
-	"gogs.carducci-dante.gov.it/andrea.fazzi/karmen/orm"
-	"gogs.carducci-dante.gov.it/andrea.fazzi/karmen/renderer"
+	"gogs.carducci-dante.gov.it/karmen/core/api"
+	"gogs.carducci-dante.gov.it/karmen/core/config"
+	"gogs.carducci-dante.gov.it/karmen/core/renderer"
+	"gogs.carducci-dante.gov.it/karmen/orm"
 )
 
 func regenerateRelations(db *gorm.DB) error {

+ 3 - 3
vendor/github.com/dgrijalva/jwt-go/README.md

@@ -4,7 +4,7 @@ A [go](http://www.golang.org) (or 'golang' for search engine friendliness) imple
 
 **BREAKING CHANGES:*** Version 3.0.0 is here. It includes _a lot_ of changes including a few that break the API.  We've tried to break as few things as possible, so there should just be a few type signature changes.  A full list of breaking changes is available in `VERSION_HISTORY.md`.  See `MIGRATION_GUIDE.md` for more information on updating your code.
 
-**NOTICE:** A vulnerability in JWT was [recently published](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/).  As this library doesn't force users to validate the `alg` is what they expected, it's possible your usage is effected.  There will be an update soon to remedy this, and it will likey require backwards-incompatible changes to the API.  In the short term, please make sure your implementation verifies the `alg` is what you expect.
+**NOTICE:** It's important that you [validate the `alg` presented is what you expect](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/). This library attempts to make it easy to do the right thing by requiring key types match the expected alg, but you should take the extra step to verify it in your usage.  See the examples provided.
 
 
 ## What the heck is a JWT?
@@ -74,7 +74,7 @@ It's worth mentioning that OAuth and JWT are not the same thing. A JWT token is
 
 Without going too far down the rabbit hole, here's a description of the interaction of these technologies:
 
-* OAuth is a protocol for allowing an identity provider to be separate from the service a user is logging in to.  For example, whenever you use Facebook to log into a different service (Yelp, Spotify, etc), you are using OAuth.
+* OAuth is a protocol for allowing an identity provider to be separate from the service a user is logging in to. For example, whenever you use Facebook to log into a different service (Yelp, Spotify, etc), you are using OAuth.
 * OAuth defines several options for passing around authentication data. One popular method is called a "bearer token". A bearer token is simply a string that _should_ only be held by an authenticated user. Thus, simply presenting this token proves your identity. You can probably derive from here why a JWT might make a good bearer token.
 * Because bearer tokens are used for authentication, it's important they're kept secret. This is why transactions that use bearer tokens typically happen over SSL.
  
@@ -82,4 +82,4 @@ Without going too far down the rabbit hole, here's a description of the interact
 
 Documentation can be found [on godoc.org](http://godoc.org/github.com/dgrijalva/jwt-go).
 
-The command line utility included in this project (cmd/jwt) provides a straightforward example of token creation and parsing as well as a useful tool for debugging your own integration.  You'll also find several implementation examples in to documentation.
+The command line utility included in this project (cmd/jwt) provides a straightforward example of token creation and parsing as well as a useful tool for debugging your own integration. You'll also find several implementation examples in the documentation.

+ 6 - 0
vendor/github.com/dgrijalva/jwt-go/VERSION_HISTORY.md

@@ -1,5 +1,11 @@
 ## `jwt-go` Version History
 
+#### 3.1.0
+
+* Improvements to `jwt` command line tool
+* Added `SkipClaimsValidation` option to `Parser`
+* Documentation updates
+
 #### 3.0.0
 
 * **Compatibility Breaking Changes**: See MIGRATION_GUIDE.md for tips on updating your code

+ 1 - 0
vendor/github.com/go-sql-driver/mysql/.gitignore

@@ -6,3 +6,4 @@
 Icon?
 ehthumbs.db
 Thumbs.db
+.idea

+ 84 - 5
vendor/github.com/go-sql-driver/mysql/.travis.yml

@@ -1,13 +1,92 @@
 sudo: false
 language: go
 go:
-  - 1.2
-  - 1.3
-  - 1.4
-  - 1.5
-  - 1.6
   - 1.7
+  - 1.8
+  - 1.9
   - tip
 
+before_install:
+  - go get golang.org/x/tools/cmd/cover
+  - go get github.com/mattn/goveralls
+
 before_script:
+  - echo -e "[server]\ninnodb_log_file_size=256MB\ninnodb_buffer_pool_size=512MB\nmax_allowed_packet=16MB" | sudo tee -a /etc/mysql/my.cnf
+  - sudo service mysql restart
+  - .travis/wait_mysql.sh
   - mysql -e 'create database gotest;'
+
+matrix:
+  include:
+    - env: DB=MYSQL57
+      sudo: required
+      dist: trusty
+      go: 1.9
+      services:
+        - docker
+      before_install:
+        - go get golang.org/x/tools/cmd/cover
+        - go get github.com/mattn/goveralls
+        - docker pull mysql:5.7
+        - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
+          mysql:5.7 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB
+        - sleep 30
+        - cp .travis/docker.cnf ~/.my.cnf
+        - mysql --print-defaults
+        - .travis/wait_mysql.sh
+      before_script:
+        - export MYSQL_TEST_USER=gotest
+        - export MYSQL_TEST_PASS=secret
+        - export MYSQL_TEST_ADDR=127.0.0.1:3307
+        - export MYSQL_TEST_CONCURRENT=1
+
+    - env: DB=MARIA55
+      sudo: required
+      dist: trusty
+      go: 1.9
+      services:
+        - docker
+      before_install:
+        - go get golang.org/x/tools/cmd/cover
+        - go get github.com/mattn/goveralls
+        - docker pull mariadb:5.5
+        - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
+          mariadb:5.5 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB
+        - sleep 30
+        - cp .travis/docker.cnf ~/.my.cnf
+        - mysql --print-defaults
+        - .travis/wait_mysql.sh
+      before_script:
+        - export MYSQL_TEST_USER=gotest
+        - export MYSQL_TEST_PASS=secret
+        - export MYSQL_TEST_ADDR=127.0.0.1:3307
+        - export MYSQL_TEST_CONCURRENT=1
+
+    - env: DB=MARIA10_1
+      sudo: required
+      dist: trusty
+      go: 1.9
+      services:
+        - docker
+      before_install:
+        - go get golang.org/x/tools/cmd/cover
+        - go get github.com/mattn/goveralls
+        - docker pull mariadb:10.1
+        - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
+          mariadb:10.1 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB
+        - sleep 30
+        - cp .travis/docker.cnf ~/.my.cnf
+        - mysql --print-defaults
+        - .travis/wait_mysql.sh
+      before_script:
+        - export MYSQL_TEST_USER=gotest
+        - export MYSQL_TEST_PASS=secret
+        - export MYSQL_TEST_ADDR=127.0.0.1:3307
+        - export MYSQL_TEST_CONCURRENT=1
+
+script:
+  - go test -v -covermode=count -coverprofile=coverage.out
+  - go vet ./...
+  - test -z "$(gofmt -d -s . | tee /dev/stderr)"
+after_script:
+  - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci

+ 23 - 0
vendor/github.com/go-sql-driver/mysql/AUTHORS

@@ -12,36 +12,56 @@
 # Individual Persons
 
 Aaron Hopkins <go-sql-driver at die.net>
+Achille Roussel <achille.roussel at gmail.com>
 Arne Hormann <arnehormann at gmail.com>
+Asta Xie <xiemengjun at gmail.com>
+Bulat Gaifullin <gaifullinbf at gmail.com>
 Carlos Nieto <jose.carlos at menteslibres.net>
 Chris Moos <chris at tech9computers.com>
+Daniel Montoya <dsmontoyam at gmail.com>
 Daniel Nichter <nil at codenode.com>
 Daniël van Eeden <git at myname.nl>
+Dave Protasowski <dprotaso at gmail.com>
 DisposaBoy <disposaboy at dby.me>
 Egor Smolyakov <egorsmkv at gmail.com>
+Evan Shaw <evan at vendhq.com>
 Frederick Mayle <frederickmayle at gmail.com>
 Gustavo Kristic <gkristic at gmail.com>
 Hanno Braun <mail at hannobraun.com>
 Henri Yandell <flamefew at gmail.com>
 Hirotaka Yamamoto <ymmt2005 at gmail.com>
+ICHINOSE Shogo <shogo82148 at gmail.com>
 INADA Naoki <songofacandy at gmail.com>
+Jacek Szwec <szwec.jacek at gmail.com>
 James Harr <james.harr at gmail.com>
+Jeff Hodges <jeff at somethingsimilar.com>
+Jeffrey Charles <jeffreycharles at gmail.com>
 Jian Zhen <zhenjl at gmail.com>
 Joshua Prunier <joshua.prunier at gmail.com>
 Julien Lefevre <julien.lefevr at gmail.com>
 Julien Schmidt <go-sql-driver at julienschmidt.com>
+Justin Li <jli at j-li.net>
+Justin Nuß <nuss.justin at gmail.com>
 Kamil Dziedzic <kamil at klecza.pl>
 Kevin Malachowski <kevin at chowski.com>
 Lennart Rudolph <lrudolph at hmc.edu>
 Leonardo YongUk Kim <dalinaum at gmail.com>
+Linh Tran Tuan <linhduonggnu at gmail.com>
+Lion Yang <lion at aosc.xyz>
 Luca Looz <luca.looz92 at gmail.com>
 Lucas Liu <extrafliu at gmail.com>
 Luke Scott <luke at webconnex.com>
+Maciej Zimnoch <maciej.zimnoch@codilime.com>
 Michael Woolnough <michael.woolnough at gmail.com>
 Nicola Peduzzi <thenikso at gmail.com>
 Olivier Mengué <dolmen at cpan.org>
+oscarzhao <oscarzhaosl at gmail.com>
 Paul Bonser <misterpib at gmail.com>
+Peter Schultz <peter.schultz at classmarkets.com>
+Rebecca Chin <rchin at pivotal.io>
 Runrioter Wung <runrioter at gmail.com>
+Robert Russell <robert at rrbrussell.com>
+Shuode Li <elemount at qq.com>
 Soroush Pour <me at soroushjp.com>
 Stan Putrya <root.vagner at gmail.com>
 Stanley Gunawan <gunawan.stanley at gmail.com>
@@ -53,5 +73,8 @@ Zhenye Xie <xiezhenye at gmail.com>
 # Organizations
 
 Barracuda Networks, Inc.
+Counting Ltd.
 Google Inc.
+Keybase Inc.
+Pivotal Inc.
 Stripe Inc.

+ 58 - 25
vendor/github.com/go-sql-driver/mysql/README.md

@@ -15,6 +15,9 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
       * [Address](#address)
       * [Parameters](#parameters)
       * [Examples](#examples)
+    * [Connection pool and timeouts](#connection-pool-and-timeouts)
+    * [context.Context Support](#contextcontext-support)
+    * [ColumnType Support](#columntype-support)
     * [LOAD DATA LOCAL INFILE support](#load-data-local-infile-support)
     * [time.Time support](#timetime-support)
     * [Unicode support](#unicode-support)
@@ -37,7 +40,7 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
   * Optional placeholder interpolation
 
 ## Requirements
-  * Go 1.2 or higher
+  * Go 1.7 or higher. We aim to support the 3 latest versions of Go.
   * MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
 
 ---------------------------------------
@@ -45,7 +48,7 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
 ## Installation
 Simple install the package to your [$GOPATH](https://github.com/golang/go/wiki/GOPATH "GOPATH") with the [go tool](https://golang.org/cmd/go/ "go command") from shell:
 ```bash
-$ go get github.com/go-sql-driver/mysql
+$ go get -u github.com/go-sql-driver/mysql
 ```
 Make sure [Git is installed](https://git-scm.com/downloads) on your machine and in your system's `PATH`.
 
@@ -99,7 +102,8 @@ See [net.Dial](https://golang.org/pkg/net/#Dial) for more information which netw
 In general you should use an Unix domain socket if available and TCP otherwise for best performance.
 
 #### Address
-For TCP and UDP networks, addresses have the form `host:port`.
+For TCP and UDP networks, addresses have the form `host[:port]`.
+If `port` is omitted, the default port will be used.
 If `host` is a literal IPv6 address, it must be enclosed in square brackets.
 The functions [net.JoinHostPort](https://golang.org/pkg/net/#JoinHostPort) and [net.SplitHostPort](https://golang.org/pkg/net/#SplitHostPort) manipulate addresses in this form.
 
@@ -136,9 +140,9 @@ Default:        false
 ```
 Type:           bool
 Valid Values:   true, false
-Default:        false
+Default:        true
 ```
-`allowNativePasswords=true` allows the usage of the mysql native password method.
+`allowNativePasswords=false` disallows the usage of MySQL native password method.
 
 ##### `allowOldPasswords`
 
@@ -229,10 +233,10 @@ Please keep in mind, that param values must be [url.QueryEscape](https://golang.
 ##### `maxAllowedPacket`
 ```
 Type:          decimal number
-Default:       0
+Default:       4194304
 ```
 
-Max packet size allowed in bytes. Use `maxAllowedPacket=0` to automatically fetch the `max_allowed_packet` variable from server.
+Max packet size allowed in bytes. The default value is 4 MiB and should be adjusted to match the server settings. `maxAllowedPacket=0` can be used to automatically fetch the `max_allowed_packet` variable from server *on every connection*.
 
 ##### `multiStatements`
 
@@ -260,13 +264,13 @@ Default:        false
 ##### `readTimeout`
 
 ```
-Type:           decimal number
+Type:           duration
 Default:        0
 ```
 
-I/O read timeout. The value must be a decimal number with an unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*.
+I/O read timeout. The value must be a decimal number with a unit suffix (*"ms"*, *"s"*, *"m"*, *"h"*), such as *"30s"*, *"0.5m"* or *"1m30s"*.
 
-##### `strict`
+##### `rejectReadOnly`
 
 ```
 Type:           bool
@@ -274,20 +278,37 @@ Valid Values:   true, false
 Default:        false
 ```
 
-`strict=true` enables a driver-side strict mode in which MySQL warnings are treated as errors. This mode should not be used in production as it may lead to data corruption in certain situations.
 
-A server-side strict mode, which is safe for production use, can be set via the [`sql_mode`](https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html) system variable.
+`rejectReadOnly=true` causes the driver to reject read-only connections. This
+is for a possible race condition during an automatic failover, where the mysql
+client gets connected to a read-only replica after the failover.
+
+Note that this should be a fairly rare case, as an automatic failover normally
+happens when the primary is down, and the race condition shouldn't happen
+unless it comes back up online as soon as the failover is kicked off. On the
+other hand, when this happens, a MySQL application can get stuck on a
+read-only connection until restarted. It is however fairly easy to reproduce,
+for example, using a manual failover on AWS Aurora's MySQL-compatible cluster.
+
+If you are not relying on read-only transactions to reject writes that aren't
+supposed to happen, setting this on some MySQL providers (such as AWS Aurora)
+is safer for failovers.
+
+Note that ERROR 1290 can be returned for a `read-only` server and this option will
+cause a retry for that error. However the same error number is used for some
+other cases. You should ensure your application will never cause an ERROR 1290
+except for `read-only` mode when enabling this option.
 
-By default MySQL also treats notes as warnings. Use [`sql_notes=false`](http://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_sql_notes) to ignore notes.
 
 ##### `timeout`
 
 ```
-Type:           decimal number
+Type:           duration
 Default:        OS default
 ```
 
-*Driver* side connection timeout. The value must be a decimal number with an unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*. To set a server side timeout, use the parameter [`wait_timeout`](http://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_wait_timeout).
+Timeout for establishing connections, aka dial timeout. The value must be a decimal number with a unit suffix (*"ms"*, *"s"*, *"m"*, *"h"*), such as *"30s"*, *"0.5m"* or *"1m30s"*.
+
 
 ##### `tls`
 
@@ -299,14 +320,15 @@ Default:        false
 
 `tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side). Use a custom value registered with [`mysql.RegisterTLSConfig`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig).
 
+
 ##### `writeTimeout`
 
 ```
-Type:           decimal number
+Type:           duration
 Default:        0
 ```
 
-I/O write timeout. The value must be a decimal number with an unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*.
+I/O write timeout. The value must be a decimal number with a unit suffix (*"ms"*, *"s"*, *"m"*, *"h"*), such as *"30s"*, *"0.5m"* or *"1m30s"*.
 
 
 ##### System Variables
@@ -317,9 +339,9 @@ Any other parameters are interpreted as system variables:
   * `<string_var>=%27<value>%27`: `SET <string_var>='<value>'`
 
 Rules:
-* The values for string variables must be quoted with '
+* The values for string variables must be quoted with `'`.
 * The values must also be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed!
- (which implies values of string variables must be wrapped with `%27`)
+ (which implies values of string variables must be wrapped with `%27`).
 
 Examples:
   * `autocommit=1`: `SET autocommit=1`
@@ -380,6 +402,18 @@ No Database preselected:
 user:password@/
 ```
 
+
+### Connection pool and timeouts
+The connection pool is managed by Go's database/sql package. For details on how to configure the size of the pool and how long connections stay in the pool see `*DB.SetMaxOpenConns`, `*DB.SetMaxIdleConns`, and `*DB.SetConnMaxLifetime` in the [database/sql documentation](https://golang.org/pkg/database/sql/). The read, write, and dial timeouts for each individual connection are configured with the DSN parameters [`readTimeout`](#readtimeout), [`writeTimeout`](#writetimeout), and [`timeout`](#timeout), respectively.
+
+## `ColumnType` Support
+This driver supports the [`ColumnType` interface](https://golang.org/pkg/database/sql/#ColumnType) introduced in Go 1.8, with the exception of [`ColumnType.Length()`](https://golang.org/pkg/database/sql/#ColumnType.Length), which is currently not supported.
+
+## `context.Context` Support
+Go 1.8 added `database/sql` support for `context.Context`. This driver supports query timeouts and cancellation via contexts.
+See [context support in the database/sql package](https://golang.org/doc/go1.8#database_sql) for more details.
+
+
 ### `LOAD DATA LOCAL INFILE` support
 For this feature you need direct access to the package. Therefore you must change the import path (no `_`):
 ```go
@@ -394,7 +428,7 @@ See the [godoc of Go-MySQL-Driver](https://godoc.org/github.com/go-sql-driver/my
 
 
 ### `time.Time` support
-The default internal output type of MySQL `DATE` and `DATETIME` values is `[]byte` which allows you to scan the value into a `[]byte`, `string` or `sql.RawBytes` variable in your programm.
+The default internal output type of MySQL `DATE` and `DATETIME` values is `[]byte` which allows you to scan the value into a `[]byte`, `string` or `sql.RawBytes` variable in your program.
 
 However, many want to scan MySQL `DATE` and `DATETIME` values into `time.Time` variables, which is the logical opposite in Go to `DATE` and `DATETIME` in MySQL. You can do that by changing the internal output type from `[]byte` to `time.Time` with the DSN parameter `parseTime=true`. You can set the default [`time.Time` location](https://golang.org/pkg/time/#Location) with the `loc` DSN parameter.
 
@@ -412,7 +446,6 @@ Version 1.0 of the driver recommended adding `&charset=utf8` (alias for `SET NAM
 
 See http://dev.mysql.com/doc/refman/5.7/en/charset-unicode.html for more details on MySQL's Unicode support.
 
-
 ## Testing / Development
 To run the driver tests you may need to adjust the configuration. See the [Testing Wiki-Page](https://github.com/go-sql-driver/mysql/wiki/Testing "Testing") for details.
 
@@ -431,13 +464,13 @@ Mozilla summarizes the license scope as follows:
 
 
 That means:
-  * You can **use** the **unchanged** source code both in private and commercially
-  * When distributing, you **must publish** the source code of any **changed files** licensed under the MPL 2.0 under a) the MPL 2.0 itself or b) a compatible license (e.g. GPL 3.0 or Apache License 2.0)
-  * You **needn't publish** the source code of your library as long as the files licensed under the MPL 2.0 are **unchanged**
+  * You can **use** the **unchanged** source code both in private and commercially.
+  * When distributing, you **must publish** the source code of any **changed files** licensed under the MPL 2.0 under a) the MPL 2.0 itself or b) a compatible license (e.g. GPL 3.0 or Apache License 2.0).
+  * You **needn't publish** the source code of your library as long as the files licensed under the MPL 2.0 are **unchanged**.
 
 Please read the [MPL 2.0 FAQ](https://www.mozilla.org/en-US/MPL/2.0/FAQ/) if you have further questions regarding the license.
 
-You can read the full terms here: [LICENSE](https://raw.github.com/go-sql-driver/mysql/master/LICENSE)
+You can read the full terms here: [LICENSE](https://raw.github.com/go-sql-driver/mysql/master/LICENSE).
 
 ![Go Gopher and MySQL Dolphin](https://raw.github.com/wiki/go-sql-driver/mysql/go-mysql-driver_m.jpg "Golang Gopher transporting the MySQL Dolphin in a wheelbarrow")
 

+ 1 - 1
vendor/github.com/go-sql-driver/mysql/appengine.go

@@ -11,7 +11,7 @@
 package mysql
 
 import (
-	"appengine/cloudsql"
+	"google.golang.org/appengine/cloudsql"
 )
 
 func init() {

+ 116 - 32
vendor/github.com/go-sql-driver/mysql/connection.go

@@ -10,12 +10,23 @@ package mysql
 
 import (
 	"database/sql/driver"
+	"io"
 	"net"
 	"strconv"
 	"strings"
 	"time"
 )
 
+// a copy of context.Context for Go 1.7 and earlier
+type mysqlContext interface {
+	Done() <-chan struct{}
+	Err() error
+
+	// defined in context.Context, but not used in this driver:
+	// Deadline() (deadline time.Time, ok bool)
+	// Value(key interface{}) interface{}
+}
+
 type mysqlConn struct {
 	buf              buffer
 	netConn          net.Conn
@@ -29,7 +40,14 @@ type mysqlConn struct {
 	status           statusFlag
 	sequence         uint8
 	parseTime        bool
-	strict           bool
+
+	// for context support (Go 1.8+)
+	watching bool
+	watcher  chan<- mysqlContext
+	closech  chan struct{}
+	finished chan<- struct{}
+	canceled atomicError // set non-nil if conn is canceled
+	closed   atomicBool  // set when conn is closed, before closech is closed
 }
 
 // Handles parameters set in DSN after the connection is established
@@ -62,22 +80,41 @@ func (mc *mysqlConn) handleParams() (err error) {
 	return
 }
 
+func (mc *mysqlConn) markBadConn(err error) error {
+	if mc == nil {
+		return err
+	}
+	if err != errBadConnNoWrite {
+		return err
+	}
+	return driver.ErrBadConn
+}
+
 func (mc *mysqlConn) Begin() (driver.Tx, error) {
-	if mc.netConn == nil {
+	return mc.begin(false)
+}
+
+func (mc *mysqlConn) begin(readOnly bool) (driver.Tx, error) {
+	if mc.closed.IsSet() {
 		errLog.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
-	err := mc.exec("START TRANSACTION")
+	var q string
+	if readOnly {
+		q = "START TRANSACTION READ ONLY"
+	} else {
+		q = "START TRANSACTION"
+	}
+	err := mc.exec(q)
 	if err == nil {
 		return &mysqlTx{mc}, err
 	}
-
-	return nil, err
+	return nil, mc.markBadConn(err)
 }
 
 func (mc *mysqlConn) Close() (err error) {
 	// Makes Close idempotent
-	if mc.netConn != nil {
+	if !mc.closed.IsSet() {
 		err = mc.writeCommandPacket(comQuit)
 	}
 
@@ -91,26 +128,39 @@ func (mc *mysqlConn) Close() (err error) {
 // is called before auth or on auth failure because MySQL will have already
 // closed the network connection.
 func (mc *mysqlConn) cleanup() {
+	if !mc.closed.TrySet(true) {
+		return
+	}
+
 	// Makes cleanup idempotent
-	if mc.netConn != nil {
-		if err := mc.netConn.Close(); err != nil {
-			errLog.Print(err)
+	close(mc.closech)
+	if mc.netConn == nil {
+		return
+	}
+	if err := mc.netConn.Close(); err != nil {
+		errLog.Print(err)
+	}
+}
+
+func (mc *mysqlConn) error() error {
+	if mc.closed.IsSet() {
+		if err := mc.canceled.Value(); err != nil {
+			return err
 		}
-		mc.netConn = nil
+		return ErrInvalidConn
 	}
-	mc.cfg = nil
-	mc.buf.nc = nil
+	return nil
 }
 
 func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
-	if mc.netConn == nil {
+	if mc.closed.IsSet() {
 		errLog.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
 	// Send command
 	err := mc.writeCommandPacketStr(comStmtPrepare, query)
 	if err != nil {
-		return nil, err
+		return nil, mc.markBadConn(err)
 	}
 
 	stmt := &mysqlStmt{
@@ -144,7 +194,7 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
 	if buf == nil {
 		// can not take the buffer. Something must be wrong with the connection
 		errLog.Print(ErrBusyBuffer)
-		return "", driver.ErrBadConn
+		return "", ErrInvalidConn
 	}
 	buf = buf[:0]
 	argPos := 0
@@ -257,7 +307,7 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
 }
 
 func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
-	if mc.netConn == nil {
+	if mc.closed.IsSet() {
 		errLog.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
@@ -271,7 +321,6 @@ func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, err
 			return nil, err
 		}
 		query = prepared
-		args = nil
 	}
 	mc.affectedRows = 0
 	mc.insertId = 0
@@ -283,32 +332,43 @@ func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, err
 			insertId:     int64(mc.insertId),
 		}, err
 	}
-	return nil, err
+	return nil, mc.markBadConn(err)
 }
 
 // Internal function to execute commands
 func (mc *mysqlConn) exec(query string) error {
 	// Send command
-	err := mc.writeCommandPacketStr(comQuery, query)
-	if err != nil {
-		return err
+	if err := mc.writeCommandPacketStr(comQuery, query); err != nil {
+		return mc.markBadConn(err)
 	}
 
 	// Read Result
 	resLen, err := mc.readResultSetHeaderPacket()
-	if err == nil && resLen > 0 {
-		if err = mc.readUntilEOF(); err != nil {
+	if err != nil {
+		return err
+	}
+
+	if resLen > 0 {
+		// columns
+		if err := mc.readUntilEOF(); err != nil {
 			return err
 		}
 
-		err = mc.readUntilEOF()
+		// rows
+		if err := mc.readUntilEOF(); err != nil {
+			return err
+		}
 	}
 
-	return err
+	return mc.discardResults()
 }
 
 func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) {
-	if mc.netConn == nil {
+	return mc.query(query, args)
+}
+
+func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error) {
+	if mc.closed.IsSet() {
 		errLog.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
@@ -322,7 +382,6 @@ func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, erro
 			return nil, err
 		}
 		query = prepared
-		args = nil
 	}
 	// Send command
 	err := mc.writeCommandPacketStr(comQuery, query)
@@ -335,15 +394,22 @@ func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, erro
 			rows.mc = mc
 
 			if resLen == 0 {
-				// no columns, no more data
-				return emptyRows{}, nil
+				rows.rs.done = true
+
+				switch err := rows.NextResultSet(); err {
+				case nil, io.EOF:
+					return rows, nil
+				default:
+					return nil, err
+				}
 			}
+
 			// Columns
-			rows.columns, err = mc.readColumns(resLen)
+			rows.rs.columns, err = mc.readColumns(resLen)
 			return rows, err
 		}
 	}
-	return nil, err
+	return nil, mc.markBadConn(err)
 }
 
 // Gets the value of the given MySQL System Variable
@@ -359,7 +425,7 @@ func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {
 	if err == nil {
 		rows := new(textRows)
 		rows.mc = mc
-		rows.columns = []mysqlField{{fieldType: fieldTypeVarChar}}
+		rows.rs.columns = []mysqlField{{fieldType: fieldTypeVarChar}}
 
 		if resLen > 0 {
 			// Columns
@@ -375,3 +441,21 @@ func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {
 	}
 	return nil, err
 }
+
+// finish is called when the query has canceled.
+func (mc *mysqlConn) cancel(err error) {
+	mc.canceled.Set(err)
+	mc.cleanup()
+}
+
+// finish is called when the query has succeeded.
+func (mc *mysqlConn) finish() {
+	if !mc.watching || mc.finished == nil {
+		return
+	}
+	select {
+	case mc.finished <- struct{}{}:
+		mc.watching = false
+	case <-mc.closech:
+	}
+}

+ 202 - 0
vendor/github.com/go-sql-driver/mysql/connection_go18.go

@@ -0,0 +1,202 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// +build go1.8
+
+package mysql
+
+import (
+	"context"
+	"database/sql"
+	"database/sql/driver"
+)
+
+// Ping implements driver.Pinger interface
+func (mc *mysqlConn) Ping(ctx context.Context) error {
+	if mc.closed.IsSet() {
+		errLog.Print(ErrInvalidConn)
+		return driver.ErrBadConn
+	}
+
+	if err := mc.watchCancel(ctx); err != nil {
+		return err
+	}
+	defer mc.finish()
+
+	if err := mc.writeCommandPacket(comPing); err != nil {
+		return err
+	}
+	if _, err := mc.readResultOK(); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// BeginTx implements driver.ConnBeginTx interface
+func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
+	if err := mc.watchCancel(ctx); err != nil {
+		return nil, err
+	}
+	defer mc.finish()
+
+	if sql.IsolationLevel(opts.Isolation) != sql.LevelDefault {
+		level, err := mapIsolationLevel(opts.Isolation)
+		if err != nil {
+			return nil, err
+		}
+		err = mc.exec("SET TRANSACTION ISOLATION LEVEL " + level)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return mc.begin(opts.ReadOnly)
+}
+
+func (mc *mysqlConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
+	dargs, err := namedValueToValue(args)
+	if err != nil {
+		return nil, err
+	}
+
+	if err := mc.watchCancel(ctx); err != nil {
+		return nil, err
+	}
+
+	rows, err := mc.query(query, dargs)
+	if err != nil {
+		mc.finish()
+		return nil, err
+	}
+	rows.finish = mc.finish
+	return rows, err
+}
+
+func (mc *mysqlConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
+	dargs, err := namedValueToValue(args)
+	if err != nil {
+		return nil, err
+	}
+
+	if err := mc.watchCancel(ctx); err != nil {
+		return nil, err
+	}
+	defer mc.finish()
+
+	return mc.Exec(query, dargs)
+}
+
+func (mc *mysqlConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
+	if err := mc.watchCancel(ctx); err != nil {
+		return nil, err
+	}
+
+	stmt, err := mc.Prepare(query)
+	mc.finish()
+	if err != nil {
+		return nil, err
+	}
+
+	select {
+	default:
+	case <-ctx.Done():
+		stmt.Close()
+		return nil, ctx.Err()
+	}
+	return stmt, nil
+}
+
+func (stmt *mysqlStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
+	dargs, err := namedValueToValue(args)
+	if err != nil {
+		return nil, err
+	}
+
+	if err := stmt.mc.watchCancel(ctx); err != nil {
+		return nil, err
+	}
+
+	rows, err := stmt.query(dargs)
+	if err != nil {
+		stmt.mc.finish()
+		return nil, err
+	}
+	rows.finish = stmt.mc.finish
+	return rows, err
+}
+
+func (stmt *mysqlStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
+	dargs, err := namedValueToValue(args)
+	if err != nil {
+		return nil, err
+	}
+
+	if err := stmt.mc.watchCancel(ctx); err != nil {
+		return nil, err
+	}
+	defer stmt.mc.finish()
+
+	return stmt.Exec(dargs)
+}
+
+func (mc *mysqlConn) watchCancel(ctx context.Context) error {
+	if mc.watching {
+		// Reach here if canceled,
+		// so the connection is already invalid
+		mc.cleanup()
+		return nil
+	}
+	if ctx.Done() == nil {
+		return nil
+	}
+
+	mc.watching = true
+	select {
+	default:
+	case <-ctx.Done():
+		return ctx.Err()
+	}
+	if mc.watcher == nil {
+		return nil
+	}
+
+	mc.watcher <- ctx
+
+	return nil
+}
+
+func (mc *mysqlConn) startWatcher() {
+	watcher := make(chan mysqlContext, 1)
+	mc.watcher = watcher
+	finished := make(chan struct{})
+	mc.finished = finished
+	go func() {
+		for {
+			var ctx mysqlContext
+			select {
+			case ctx = <-watcher:
+			case <-mc.closech:
+				return
+			}
+
+			select {
+			case <-ctx.Done():
+				mc.cancel(ctx.Err())
+			case <-finished:
+			case <-mc.closech:
+				return
+			}
+		}
+	}()
+}
+
+func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) {
+	nv.Value, err = converter{}.ConvertValue(nv.Value)
+	return
+}

+ 6 - 3
vendor/github.com/go-sql-driver/mysql/const.go

@@ -9,7 +9,8 @@
 package mysql
 
 const (
-	minProtocolVersion byte = 10
+	defaultMaxAllowedPacket = 4 << 20 // 4 MiB
+	minProtocolVersion      = 10
 	maxPacketSize           = 1<<24 - 1
 	timeFormat              = "2006-01-02 15:04:05.999999"
 )
@@ -87,8 +88,10 @@ const (
 )
 
 // https://dev.mysql.com/doc/internals/en/com-query-response.html#packet-Protocol::ColumnType
+type fieldType byte
+
 const (
-	fieldTypeDecimal byte = iota
+	fieldTypeDecimal fieldType = iota
 	fieldTypeTiny
 	fieldTypeShort
 	fieldTypeLong
@@ -107,7 +110,7 @@ const (
 	fieldTypeBit
 )
 const (
-	fieldTypeJSON byte = iota + 0xf5
+	fieldTypeJSON fieldType = iota + 0xf5
 	fieldTypeNewDecimal
 	fieldTypeEnum
 	fieldTypeSet

+ 12 - 2
vendor/github.com/go-sql-driver/mysql/driver.go

@@ -4,7 +4,7 @@
 // License, v. 2.0. If a copy of the MPL was not distributed with this file,
 // You can obtain one at http://mozilla.org/MPL/2.0/.
 
-// Package mysql provides a MySQL driver for Go's database/sql package
+// Package mysql provides a MySQL driver for Go's database/sql package.
 //
 // The driver should be used via the database/sql package:
 //
@@ -22,6 +22,11 @@ import (
 	"net"
 )
 
+// watcher interface is used for context support (From Go 1.8)
+type watcher interface {
+	startWatcher()
+}
+
 // MySQLDriver is exported to make the driver directly accessible.
 // In general the driver is used via the database/sql package.
 type MySQLDriver struct{}
@@ -52,13 +57,13 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
 	mc := &mysqlConn{
 		maxAllowedPacket: maxPacketSize,
 		maxWriteSize:     maxPacketSize - 1,
+		closech:          make(chan struct{}),
 	}
 	mc.cfg, err = ParseDSN(dsn)
 	if err != nil {
 		return nil, err
 	}
 	mc.parseTime = mc.cfg.ParseTime
-	mc.strict = mc.cfg.Strict
 
 	// Connect to Server
 	if dial, ok := dials[mc.cfg.Net]; ok {
@@ -81,6 +86,11 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
 		}
 	}
 
+	// Call startWatcher for context support (From Go 1.8)
+	if s, ok := interface{}(mc).(watcher); ok {
+		s.startWatcher()
+	}
+
 	mc.buf = newBuffer(mc.netConn)
 
 	// Set I/O timeouts

+ 79 - 41
vendor/github.com/go-sql-driver/mysql/dsn.go

@@ -15,6 +15,7 @@ import (
 	"fmt"
 	"net"
 	"net/url"
+	"sort"
 	"strconv"
 	"strings"
 	"time"
@@ -27,7 +28,9 @@ var (
 	errInvalidDSNUnsafeCollation = errors.New("invalid DSN: interpolateParams can not be used with unsafe collations")
 )
 
-// Config is a configuration parsed from a DSN string
+// Config is a configuration parsed from a DSN string.
+// If a new Config is created instead of being parsed from a DSN string,
+// the NewConfig function should be used, which sets default values.
 type Config struct {
 	User             string            // Username
 	Passwd           string            // Password (requires User)
@@ -53,7 +56,45 @@ type Config struct {
 	InterpolateParams       bool // Interpolate placeholders into query string
 	MultiStatements         bool // Allow multiple statements in one query
 	ParseTime               bool // Parse time values to time.Time
-	Strict                  bool // Return warnings as errors
+	RejectReadOnly          bool // Reject read-only connections
+}
+
+// NewConfig creates a new Config and sets default values.
+func NewConfig() *Config {
+	return &Config{
+		Collation:            defaultCollation,
+		Loc:                  time.UTC,
+		MaxAllowedPacket:     defaultMaxAllowedPacket,
+		AllowNativePasswords: true,
+	}
+}
+
+func (cfg *Config) normalize() error {
+	if cfg.InterpolateParams && unsafeCollations[cfg.Collation] {
+		return errInvalidDSNUnsafeCollation
+	}
+
+	// Set default network if empty
+	if cfg.Net == "" {
+		cfg.Net = "tcp"
+	}
+
+	// Set default address if empty
+	if cfg.Addr == "" {
+		switch cfg.Net {
+		case "tcp":
+			cfg.Addr = "127.0.0.1:3306"
+		case "unix":
+			cfg.Addr = "/tmp/mysql.sock"
+		default:
+			return errors.New("default addr for network '" + cfg.Net + "' unknown")
+		}
+
+	} else if cfg.Net == "tcp" {
+		cfg.Addr = ensureHavePort(cfg.Addr)
+	}
+
+	return nil
 }
 
 // FormatDSN formats the given Config into a DSN string which can be passed to
@@ -102,12 +143,12 @@ func (cfg *Config) FormatDSN() string {
 		}
 	}
 
-	if cfg.AllowNativePasswords {
+	if !cfg.AllowNativePasswords {
 		if hasParam {
-			buf.WriteString("&allowNativePasswords=true")
+			buf.WriteString("&allowNativePasswords=false")
 		} else {
 			hasParam = true
-			buf.WriteString("?allowNativePasswords=true")
+			buf.WriteString("?allowNativePasswords=false")
 		}
 	}
 
@@ -195,12 +236,12 @@ func (cfg *Config) FormatDSN() string {
 		buf.WriteString(cfg.ReadTimeout.String())
 	}
 
-	if cfg.Strict {
+	if cfg.RejectReadOnly {
 		if hasParam {
-			buf.WriteString("&strict=true")
+			buf.WriteString("&rejectReadOnly=true")
 		} else {
 			hasParam = true
-			buf.WriteString("?strict=true")
+			buf.WriteString("?rejectReadOnly=true")
 		}
 	}
 
@@ -234,7 +275,7 @@ func (cfg *Config) FormatDSN() string {
 		buf.WriteString(cfg.WriteTimeout.String())
 	}
 
-	if cfg.MaxAllowedPacket > 0 {
+	if cfg.MaxAllowedPacket != defaultMaxAllowedPacket {
 		if hasParam {
 			buf.WriteString("&maxAllowedPacket=")
 		} else {
@@ -247,7 +288,12 @@ func (cfg *Config) FormatDSN() string {
 
 	// other params
 	if cfg.Params != nil {
-		for param, value := range cfg.Params {
+		var params []string
+		for param := range cfg.Params {
+			params = append(params, param)
+		}
+		sort.Strings(params)
+		for _, param := range params {
 			if hasParam {
 				buf.WriteByte('&')
 			} else {
@@ -257,7 +303,7 @@ func (cfg *Config) FormatDSN() string {
 
 			buf.WriteString(param)
 			buf.WriteByte('=')
-			buf.WriteString(url.QueryEscape(value))
+			buf.WriteString(url.QueryEscape(cfg.Params[param]))
 		}
 	}
 
@@ -267,10 +313,7 @@ func (cfg *Config) FormatDSN() string {
 // ParseDSN parses the DSN string to a Config
 func ParseDSN(dsn string) (cfg *Config, err error) {
 	// New config with some default values
-	cfg = &Config{
-		Loc:       time.UTC,
-		Collation: defaultCollation,
-	}
+	cfg = NewConfig()
 
 	// [user[:password]@][net[(addr)]]/dbname[?param1=value1&paramN=valueN]
 	// Find the last '/' (since the password or the net addr might contain a '/')
@@ -338,28 +381,9 @@ func ParseDSN(dsn string) (cfg *Config, err error) {
 		return nil, errInvalidDSNNoSlash
 	}
 
-	if cfg.InterpolateParams && unsafeCollations[cfg.Collation] {
-		return nil, errInvalidDSNUnsafeCollation
-	}
-
-	// Set default network if empty
-	if cfg.Net == "" {
-		cfg.Net = "tcp"
-	}
-
-	// Set default address if empty
-	if cfg.Addr == "" {
-		switch cfg.Net {
-		case "tcp":
-			cfg.Addr = "127.0.0.1:3306"
-		case "unix":
-			cfg.Addr = "/tmp/mysql.sock"
-		default:
-			return nil, errors.New("default addr for network '" + cfg.Net + "' unknown")
-		}
-
+	if err = cfg.normalize(); err != nil {
+		return nil, err
 	}
-
 	return
 }
 
@@ -374,7 +398,6 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 
 		// cfg params
 		switch value := param[1]; param[0] {
-
 		// Disable INFILE whitelist / enable all files
 		case "allowAllFiles":
 			var isBool bool
@@ -472,14 +495,18 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 				return
 			}
 
-		// Strict mode
-		case "strict":
+		// Reject read-only connections
+		case "rejectReadOnly":
 			var isBool bool
-			cfg.Strict, isBool = readBool(value)
+			cfg.RejectReadOnly, isBool = readBool(value)
 			if !isBool {
 				return errors.New("invalid bool value: " + value)
 			}
 
+		// Strict mode
+		case "strict":
+			panic("strict mode has been removed. See https://github.com/go-sql-driver/mysql/wiki/strict-mode")
+
 		// Dial Timeout
 		case "timeout":
 			cfg.Timeout, err = time.ParseDuration(value)
@@ -494,6 +521,10 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 				if boolValue {
 					cfg.TLSConfig = "true"
 					cfg.tls = &tls.Config{}
+					host, _, err := net.SplitHostPort(cfg.Addr)
+					if err == nil {
+						cfg.tls.ServerName = host
+					}
 				} else {
 					cfg.TLSConfig = "false"
 				}
@@ -506,7 +537,7 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 					return fmt.Errorf("invalid value for TLS config name: %v", err)
 				}
 
-				if tlsConfig, ok := tlsConfigRegister[name]; ok {
+				if tlsConfig := getTLSConfigClone(name); tlsConfig != nil {
 					if len(tlsConfig.ServerName) == 0 && !tlsConfig.InsecureSkipVerify {
 						host, _, err := net.SplitHostPort(cfg.Addr)
 						if err == nil {
@@ -546,3 +577,10 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 
 	return
 }
+
+func ensureHavePort(addr string) string {
+	if _, _, err := net.SplitHostPort(addr); err != nil {
+		return net.JoinHostPort(addr, "3306")
+	}
+	return addr
+}

+ 6 - 73
vendor/github.com/go-sql-driver/mysql/errors.go

@@ -9,10 +9,8 @@
 package mysql
 
 import (
-	"database/sql/driver"
 	"errors"
 	"fmt"
-	"io"
 	"log"
 	"os"
 )
@@ -31,6 +29,12 @@ var (
 	ErrPktSyncMul        = errors.New("commands out of sync. Did you run multiple statements at once?")
 	ErrPktTooLarge       = errors.New("packet for query is too large. Try adjusting the 'max_allowed_packet' variable on the server")
 	ErrBusyBuffer        = errors.New("busy buffer")
+
+	// errBadConnNoWrite is used for connection errors where nothing was sent to the database yet.
+	// If this happens first in a function starting a database interaction, it should be replaced by driver.ErrBadConn
+	// to trigger a resend.
+	// See https://github.com/go-sql-driver/mysql/pull/302
+	errBadConnNoWrite = errors.New("bad connection")
 )
 
 var errLog = Logger(log.New(os.Stderr, "[mysql] ", log.Ldate|log.Ltime|log.Lshortfile))
@@ -59,74 +63,3 @@ type MySQLError struct {
 func (me *MySQLError) Error() string {
 	return fmt.Sprintf("Error %d: %s", me.Number, me.Message)
 }
-
-// MySQLWarnings is an error type which represents a group of one or more MySQL
-// warnings
-type MySQLWarnings []MySQLWarning
-
-func (mws MySQLWarnings) Error() string {
-	var msg string
-	for i, warning := range mws {
-		if i > 0 {
-			msg += "\r\n"
-		}
-		msg += fmt.Sprintf(
-			"%s %s: %s",
-			warning.Level,
-			warning.Code,
-			warning.Message,
-		)
-	}
-	return msg
-}
-
-// MySQLWarning is an error type which represents a single MySQL warning.
-// Warnings are returned in groups only. See MySQLWarnings
-type MySQLWarning struct {
-	Level   string
-	Code    string
-	Message string
-}
-
-func (mc *mysqlConn) getWarnings() (err error) {
-	rows, err := mc.Query("SHOW WARNINGS", nil)
-	if err != nil {
-		return
-	}
-
-	var warnings = MySQLWarnings{}
-	var values = make([]driver.Value, 3)
-
-	for {
-		err = rows.Next(values)
-		switch err {
-		case nil:
-			warning := MySQLWarning{}
-
-			if raw, ok := values[0].([]byte); ok {
-				warning.Level = string(raw)
-			} else {
-				warning.Level = fmt.Sprintf("%s", values[0])
-			}
-			if raw, ok := values[1].([]byte); ok {
-				warning.Code = string(raw)
-			} else {
-				warning.Code = fmt.Sprintf("%s", values[1])
-			}
-			if raw, ok := values[2].([]byte); ok {
-				warning.Message = string(raw)
-			} else {
-				warning.Message = fmt.Sprintf("%s", values[0])
-			}
-
-			warnings = append(warnings, warning)
-
-		case io.EOF:
-			return warnings
-
-		default:
-			rows.Close()
-			return
-		}
-	}
-}

+ 140 - 0
vendor/github.com/go-sql-driver/mysql/fields.go

@@ -0,0 +1,140 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package mysql
+
+import (
+	"database/sql"
+	"reflect"
+)
+
+var typeDatabaseName = map[fieldType]string{
+	fieldTypeBit:        "BIT",
+	fieldTypeBLOB:       "BLOB",
+	fieldTypeDate:       "DATE",
+	fieldTypeDateTime:   "DATETIME",
+	fieldTypeDecimal:    "DECIMAL",
+	fieldTypeDouble:     "DOUBLE",
+	fieldTypeEnum:       "ENUM",
+	fieldTypeFloat:      "FLOAT",
+	fieldTypeGeometry:   "GEOMETRY",
+	fieldTypeInt24:      "MEDIUMINT",
+	fieldTypeJSON:       "JSON",
+	fieldTypeLong:       "INT",
+	fieldTypeLongBLOB:   "LONGBLOB",
+	fieldTypeLongLong:   "BIGINT",
+	fieldTypeMediumBLOB: "MEDIUMBLOB",
+	fieldTypeNewDate:    "DATE",
+	fieldTypeNewDecimal: "DECIMAL",
+	fieldTypeNULL:       "NULL",
+	fieldTypeSet:        "SET",
+	fieldTypeShort:      "SMALLINT",
+	fieldTypeString:     "CHAR",
+	fieldTypeTime:       "TIME",
+	fieldTypeTimestamp:  "TIMESTAMP",
+	fieldTypeTiny:       "TINYINT",
+	fieldTypeTinyBLOB:   "TINYBLOB",
+	fieldTypeVarChar:    "VARCHAR",
+	fieldTypeVarString:  "VARCHAR",
+	fieldTypeYear:       "YEAR",
+}
+
+var (
+	scanTypeFloat32   = reflect.TypeOf(float32(0))
+	scanTypeFloat64   = reflect.TypeOf(float64(0))
+	scanTypeInt8      = reflect.TypeOf(int8(0))
+	scanTypeInt16     = reflect.TypeOf(int16(0))
+	scanTypeInt32     = reflect.TypeOf(int32(0))
+	scanTypeInt64     = reflect.TypeOf(int64(0))
+	scanTypeNullFloat = reflect.TypeOf(sql.NullFloat64{})
+	scanTypeNullInt   = reflect.TypeOf(sql.NullInt64{})
+	scanTypeNullTime  = reflect.TypeOf(NullTime{})
+	scanTypeUint8     = reflect.TypeOf(uint8(0))
+	scanTypeUint16    = reflect.TypeOf(uint16(0))
+	scanTypeUint32    = reflect.TypeOf(uint32(0))
+	scanTypeUint64    = reflect.TypeOf(uint64(0))
+	scanTypeRawBytes  = reflect.TypeOf(sql.RawBytes{})
+	scanTypeUnknown   = reflect.TypeOf(new(interface{}))
+)
+
+type mysqlField struct {
+	tableName string
+	name      string
+	length    uint32
+	flags     fieldFlag
+	fieldType fieldType
+	decimals  byte
+}
+
+func (mf *mysqlField) scanType() reflect.Type {
+	switch mf.fieldType {
+	case fieldTypeTiny:
+		if mf.flags&flagNotNULL != 0 {
+			if mf.flags&flagUnsigned != 0 {
+				return scanTypeUint8
+			}
+			return scanTypeInt8
+		}
+		return scanTypeNullInt
+
+	case fieldTypeShort, fieldTypeYear:
+		if mf.flags&flagNotNULL != 0 {
+			if mf.flags&flagUnsigned != 0 {
+				return scanTypeUint16
+			}
+			return scanTypeInt16
+		}
+		return scanTypeNullInt
+
+	case fieldTypeInt24, fieldTypeLong:
+		if mf.flags&flagNotNULL != 0 {
+			if mf.flags&flagUnsigned != 0 {
+				return scanTypeUint32
+			}
+			return scanTypeInt32
+		}
+		return scanTypeNullInt
+
+	case fieldTypeLongLong:
+		if mf.flags&flagNotNULL != 0 {
+			if mf.flags&flagUnsigned != 0 {
+				return scanTypeUint64
+			}
+			return scanTypeInt64
+		}
+		return scanTypeNullInt
+
+	case fieldTypeFloat:
+		if mf.flags&flagNotNULL != 0 {
+			return scanTypeFloat32
+		}
+		return scanTypeNullFloat
+
+	case fieldTypeDouble:
+		if mf.flags&flagNotNULL != 0 {
+			return scanTypeFloat64
+		}
+		return scanTypeNullFloat
+
+	case fieldTypeDecimal, fieldTypeNewDecimal, fieldTypeVarChar,
+		fieldTypeBit, fieldTypeEnum, fieldTypeSet, fieldTypeTinyBLOB,
+		fieldTypeMediumBLOB, fieldTypeLongBLOB, fieldTypeBLOB,
+		fieldTypeVarString, fieldTypeString, fieldTypeGeometry, fieldTypeJSON,
+		fieldTypeTime:
+		return scanTypeRawBytes
+
+	case fieldTypeDate, fieldTypeNewDate,
+		fieldTypeTimestamp, fieldTypeDateTime:
+		// NullTime is always returned for more consistent behavior as it can
+		// handle both cases of parseTime regardless if the field is nullable.
+		return scanTypeNullTime
+
+	default:
+		return scanTypeUnknown
+	}
+}

+ 2 - 1
vendor/github.com/go-sql-driver/mysql/infile.go

@@ -147,7 +147,8 @@ func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
 	}
 
 	// send content packets
-	if err == nil {
+	// if packetSize == 0, the Reader contains no data
+	if err == nil && packetSize > 0 {
 		data := make([]byte, 4+packetSize)
 		var n int
 		for err == nil {

+ 108 - 86
vendor/github.com/go-sql-driver/mysql/packets.go

@@ -30,9 +30,12 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
 		// read packet header
 		data, err := mc.buf.readNext(4)
 		if err != nil {
+			if cerr := mc.canceled.Value(); cerr != nil {
+				return nil, cerr
+			}
 			errLog.Print(err)
 			mc.Close()
-			return nil, driver.ErrBadConn
+			return nil, ErrInvalidConn
 		}
 
 		// packet length [24 bit]
@@ -54,7 +57,7 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
 			if prevData == nil {
 				errLog.Print(ErrMalformPkt)
 				mc.Close()
-				return nil, driver.ErrBadConn
+				return nil, ErrInvalidConn
 			}
 
 			return prevData, nil
@@ -63,9 +66,12 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
 		// read packet body [pktLen bytes]
 		data, err = mc.buf.readNext(pktLen)
 		if err != nil {
+			if cerr := mc.canceled.Value(); cerr != nil {
+				return nil, cerr
+			}
 			errLog.Print(err)
 			mc.Close()
-			return nil, driver.ErrBadConn
+			return nil, ErrInvalidConn
 		}
 
 		// return data if this was the last packet
@@ -125,11 +131,20 @@ func (mc *mysqlConn) writePacket(data []byte) error {
 
 		// Handle error
 		if err == nil { // n != len(data)
+			mc.cleanup()
 			errLog.Print(ErrMalformPkt)
 		} else {
+			if cerr := mc.canceled.Value(); cerr != nil {
+				return cerr
+			}
+			if n == 0 && pktLen == len(data)-4 {
+				// only for the first loop iteration when nothing was written yet
+				return errBadConnNoWrite
+			}
+			mc.cleanup()
 			errLog.Print(err)
 		}
-		return driver.ErrBadConn
+		return ErrInvalidConn
 	}
 }
 
@@ -263,7 +278,7 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
 	if data == nil {
 		// can not take the buffer. Something must be wrong with the connection
 		errLog.Print(ErrBusyBuffer)
-		return driver.ErrBadConn
+		return errBadConnNoWrite
 	}
 
 	// ClientFlags [32 bit]
@@ -341,7 +356,9 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
 // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
 func (mc *mysqlConn) writeOldAuthPacket(cipher []byte) error {
 	// User password
-	scrambleBuff := scrambleOldPassword(cipher, []byte(mc.cfg.Passwd))
+	// https://dev.mysql.com/doc/internals/en/old-password-authentication.html
+	// Old password authentication only need and will need 8-byte challenge.
+	scrambleBuff := scrambleOldPassword(cipher[:8], []byte(mc.cfg.Passwd))
 
 	// Calculate the packet length and add a tailing 0
 	pktLen := len(scrambleBuff) + 1
@@ -349,7 +366,7 @@ func (mc *mysqlConn) writeOldAuthPacket(cipher []byte) error {
 	if data == nil {
 		// can not take the buffer. Something must be wrong with the connection
 		errLog.Print(ErrBusyBuffer)
-		return driver.ErrBadConn
+		return errBadConnNoWrite
 	}
 
 	// Add the scrambled password [null terminated string]
@@ -368,7 +385,7 @@ func (mc *mysqlConn) writeClearAuthPacket() error {
 	if data == nil {
 		// can not take the buffer. Something must be wrong with the connection
 		errLog.Print(ErrBusyBuffer)
-		return driver.ErrBadConn
+		return errBadConnNoWrite
 	}
 
 	// Add the clear password [null terminated string]
@@ -381,7 +398,9 @@ func (mc *mysqlConn) writeClearAuthPacket() error {
 //  Native password authentication method
 // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
 func (mc *mysqlConn) writeNativeAuthPacket(cipher []byte) error {
-	scrambleBuff := scramblePassword(cipher, []byte(mc.cfg.Passwd))
+	// https://dev.mysql.com/doc/internals/en/secure-password-authentication.html
+	// Native password authentication only need and will need 20-byte challenge.
+	scrambleBuff := scramblePassword(cipher[0:20], []byte(mc.cfg.Passwd))
 
 	// Calculate the packet length and add a tailing 0
 	pktLen := len(scrambleBuff)
@@ -389,7 +408,7 @@ func (mc *mysqlConn) writeNativeAuthPacket(cipher []byte) error {
 	if data == nil {
 		// can not take the buffer. Something must be wrong with the connection
 		errLog.Print(ErrBusyBuffer)
-		return driver.ErrBadConn
+		return errBadConnNoWrite
 	}
 
 	// Add the scramble
@@ -410,7 +429,7 @@ func (mc *mysqlConn) writeCommandPacket(command byte) error {
 	if data == nil {
 		// can not take the buffer. Something must be wrong with the connection
 		errLog.Print(ErrBusyBuffer)
-		return driver.ErrBadConn
+		return errBadConnNoWrite
 	}
 
 	// Add command byte
@@ -429,7 +448,7 @@ func (mc *mysqlConn) writeCommandPacketStr(command byte, arg string) error {
 	if data == nil {
 		// can not take the buffer. Something must be wrong with the connection
 		errLog.Print(ErrBusyBuffer)
-		return driver.ErrBadConn
+		return errBadConnNoWrite
 	}
 
 	// Add command byte
@@ -450,7 +469,7 @@ func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {
 	if data == nil {
 		// can not take the buffer. Something must be wrong with the connection
 		errLog.Print(ErrBusyBuffer)
-		return driver.ErrBadConn
+		return errBadConnNoWrite
 	}
 
 	// Add command byte
@@ -484,25 +503,26 @@ func (mc *mysqlConn) readResultOK() ([]byte, error) {
 			if len(data) > 1 {
 				pluginEndIndex := bytes.IndexByte(data, 0x00)
 				plugin := string(data[1:pluginEndIndex])
-				cipher := data[pluginEndIndex+1 : len(data)-1]
+				cipher := data[pluginEndIndex+1:]
 
-				if plugin == "mysql_old_password" {
+				switch plugin {
+				case "mysql_old_password":
 					// using old_passwords
 					return cipher, ErrOldPassword
-				} else if plugin == "mysql_clear_password" {
+				case "mysql_clear_password":
 					// using clear text password
 					return cipher, ErrCleartextPassword
-				} else if plugin == "mysql_native_password" {
+				case "mysql_native_password":
 					// using mysql default authentication method
 					return cipher, ErrNativePassword
-				} else {
+				default:
 					return cipher, ErrUnknownPlugin
 				}
-			} else {
-				// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::OldAuthSwitchRequest
-				return nil, ErrOldPassword
 			}
 
+			// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::OldAuthSwitchRequest
+			return nil, ErrOldPassword
+
 		default: // Error otherwise
 			return nil, mc.handleErrorPacket(data)
 		}
@@ -550,6 +570,22 @@ func (mc *mysqlConn) handleErrorPacket(data []byte) error {
 	// Error Number [16 bit uint]
 	errno := binary.LittleEndian.Uint16(data[1:3])
 
+	// 1792: ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION
+	// 1290: ER_OPTION_PREVENTS_STATEMENT (returned by Aurora during failover)
+	if (errno == 1792 || errno == 1290) && mc.cfg.RejectReadOnly {
+		// Oops; we are connected to a read-only connection, and won't be able
+		// to issue any write statements. Since RejectReadOnly is configured,
+		// we throw away this connection hoping this one would have write
+		// permission. This is specifically for a possible race condition
+		// during failover (e.g. on AWS Aurora). See README.md for more.
+		//
+		// We explicitly close the connection before returning
+		// driver.ErrBadConn to ensure that `database/sql` purges this
+		// connection and initiates a new one for next statement next time.
+		mc.Close()
+		return driver.ErrBadConn
+	}
+
 	pos := 3
 
 	// SQL State [optional: # + 5bytes string]
@@ -584,19 +620,12 @@ func (mc *mysqlConn) handleOkPacket(data []byte) error {
 
 	// server_status [2 bytes]
 	mc.status = readStatus(data[1+n+m : 1+n+m+2])
-	if err := mc.discardResults(); err != nil {
-		return err
+	if mc.status&statusMoreResultsExists != 0 {
+		return nil
 	}
 
 	// warning count [2 bytes]
-	if !mc.strict {
-		return nil
-	}
 
-	pos := 1 + n + m + 2
-	if binary.LittleEndian.Uint16(data[pos:pos+2]) > 0 {
-		return mc.getWarnings()
-	}
 	return nil
 }
 
@@ -671,11 +700,14 @@ func (mc *mysqlConn) readColumns(count int) ([]mysqlField, error) {
 
 		// Filler [uint8]
 		// Charset [charset, collation uint8]
+		pos += n + 1 + 2
+
 		// Length [uint32]
-		pos += n + 1 + 2 + 4
+		columns[i].length = binary.LittleEndian.Uint32(data[pos : pos+4])
+		pos += 4
 
 		// Field type [uint8]
-		columns[i].fieldType = data[pos]
+		columns[i].fieldType = fieldType(data[pos])
 		pos++
 
 		// Flags [uint16]
@@ -698,6 +730,10 @@ func (mc *mysqlConn) readColumns(count int) ([]mysqlField, error) {
 func (rows *textRows) readRow(dest []driver.Value) error {
 	mc := rows.mc
 
+	if rows.rs.done {
+		return io.EOF
+	}
+
 	data, err := mc.readPacket()
 	if err != nil {
 		return err
@@ -707,15 +743,11 @@ func (rows *textRows) readRow(dest []driver.Value) error {
 	if data[0] == iEOF && len(data) == 5 {
 		// server_status [2 bytes]
 		rows.mc.status = readStatus(data[3:])
-		err = rows.mc.discardResults()
-		if err == nil {
-			err = io.EOF
-		} else {
-			// connection unusable
-			rows.mc.Close()
+		rows.rs.done = true
+		if !rows.HasNextResultSet() {
+			rows.mc = nil
 		}
-		rows.mc = nil
-		return err
+		return io.EOF
 	}
 	if data[0] == iERR {
 		rows.mc = nil
@@ -736,7 +768,7 @@ func (rows *textRows) readRow(dest []driver.Value) error {
 				if !mc.parseTime {
 					continue
 				} else {
-					switch rows.columns[i].fieldType {
+					switch rows.rs.columns[i].fieldType {
 					case fieldTypeTimestamp, fieldTypeDateTime,
 						fieldTypeDate, fieldTypeNewDate:
 						dest[i], err = parseDateTime(
@@ -808,14 +840,7 @@ func (stmt *mysqlStmt) readPrepareResultPacket() (uint16, error) {
 		// Reserved [8 bit]
 
 		// Warning count [16 bit uint]
-		if !stmt.mc.strict {
-			return columnCount, nil
-		}
 
-		// Check for warnings count > 0, only available in MySQL > 4.1
-		if len(data) >= 12 && binary.LittleEndian.Uint16(data[10:12]) > 0 {
-			return columnCount, stmt.mc.getWarnings()
-		}
 		return columnCount, nil
 	}
 	return 0, err
@@ -900,7 +925,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 	if data == nil {
 		// can not take the buffer. Something must be wrong with the connection
 		errLog.Print(ErrBusyBuffer)
-		return driver.ErrBadConn
+		return errBadConnNoWrite
 	}
 
 	// command [1 byte]
@@ -959,7 +984,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 			// build NULL-bitmap
 			if arg == nil {
 				nullMask[i/8] |= 1 << (uint(i) & 7)
-				paramTypes[i+i] = fieldTypeNULL
+				paramTypes[i+i] = byte(fieldTypeNULL)
 				paramTypes[i+i+1] = 0x00
 				continue
 			}
@@ -967,7 +992,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 			// cache types and values
 			switch v := arg.(type) {
 			case int64:
-				paramTypes[i+i] = fieldTypeLongLong
+				paramTypes[i+i] = byte(fieldTypeLongLong)
 				paramTypes[i+i+1] = 0x00
 
 				if cap(paramValues)-len(paramValues)-8 >= 0 {
@@ -983,7 +1008,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 				}
 
 			case float64:
-				paramTypes[i+i] = fieldTypeDouble
+				paramTypes[i+i] = byte(fieldTypeDouble)
 				paramTypes[i+i+1] = 0x00
 
 				if cap(paramValues)-len(paramValues)-8 >= 0 {
@@ -999,7 +1024,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 				}
 
 			case bool:
-				paramTypes[i+i] = fieldTypeTiny
+				paramTypes[i+i] = byte(fieldTypeTiny)
 				paramTypes[i+i+1] = 0x00
 
 				if v {
@@ -1011,7 +1036,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 			case []byte:
 				// Common case (non-nil value) first
 				if v != nil {
-					paramTypes[i+i] = fieldTypeString
+					paramTypes[i+i] = byte(fieldTypeString)
 					paramTypes[i+i+1] = 0x00
 
 					if len(v) < mc.maxAllowedPacket-pos-len(paramValues)-(len(args)-(i+1))*64 {
@@ -1029,11 +1054,11 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 
 				// Handle []byte(nil) as a NULL value
 				nullMask[i/8] |= 1 << (uint(i) & 7)
-				paramTypes[i+i] = fieldTypeNULL
+				paramTypes[i+i] = byte(fieldTypeNULL)
 				paramTypes[i+i+1] = 0x00
 
 			case string:
-				paramTypes[i+i] = fieldTypeString
+				paramTypes[i+i] = byte(fieldTypeString)
 				paramTypes[i+i+1] = 0x00
 
 				if len(v) < mc.maxAllowedPacket-pos-len(paramValues)-(len(args)-(i+1))*64 {
@@ -1048,20 +1073,22 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
 				}
 
 			case time.Time:
-				paramTypes[i+i] = fieldTypeString
+				paramTypes[i+i] = byte(fieldTypeString)
 				paramTypes[i+i+1] = 0x00
 
-				var val []byte
+				var a [64]byte
+				var b = a[:0]
+
 				if v.IsZero() {
-					val = []byte("0000-00-00")
+					b = append(b, "0000-00-00"...)
 				} else {
-					val = []byte(v.In(mc.cfg.Loc).Format(timeFormat))
+					b = v.In(mc.cfg.Loc).AppendFormat(b, timeFormat)
 				}
 
 				paramValues = appendLengthEncodedInteger(paramValues,
-					uint64(len(val)),
+					uint64(len(b)),
 				)
-				paramValues = append(paramValues, val...)
+				paramValues = append(paramValues, b...)
 
 			default:
 				return fmt.Errorf("can not convert type: %T", arg)
@@ -1097,8 +1124,6 @@ func (mc *mysqlConn) discardResults() error {
 			if err := mc.readUntilEOF(); err != nil {
 				return err
 			}
-		} else {
-			mc.status &^= statusMoreResultsExists
 		}
 	}
 	return nil
@@ -1116,20 +1141,17 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
 		// EOF Packet
 		if data[0] == iEOF && len(data) == 5 {
 			rows.mc.status = readStatus(data[3:])
-			err = rows.mc.discardResults()
-			if err == nil {
-				err = io.EOF
-			} else {
-				// connection unusable
-				rows.mc.Close()
+			rows.rs.done = true
+			if !rows.HasNextResultSet() {
+				rows.mc = nil
 			}
-			rows.mc = nil
-			return err
+			return io.EOF
 		}
+		mc := rows.mc
 		rows.mc = nil
 
 		// Error otherwise
-		return rows.mc.handleErrorPacket(data)
+		return mc.handleErrorPacket(data)
 	}
 
 	// NULL-bitmap,  [(column-count + 7 + 2) / 8 bytes]
@@ -1145,14 +1167,14 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
 		}
 
 		// Convert to byte-coded string
-		switch rows.columns[i].fieldType {
+		switch rows.rs.columns[i].fieldType {
 		case fieldTypeNULL:
 			dest[i] = nil
 			continue
 
 		// Numeric Types
 		case fieldTypeTiny:
-			if rows.columns[i].flags&flagUnsigned != 0 {
+			if rows.rs.columns[i].flags&flagUnsigned != 0 {
 				dest[i] = int64(data[pos])
 			} else {
 				dest[i] = int64(int8(data[pos]))
@@ -1161,7 +1183,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
 			continue
 
 		case fieldTypeShort, fieldTypeYear:
-			if rows.columns[i].flags&flagUnsigned != 0 {
+			if rows.rs.columns[i].flags&flagUnsigned != 0 {
 				dest[i] = int64(binary.LittleEndian.Uint16(data[pos : pos+2]))
 			} else {
 				dest[i] = int64(int16(binary.LittleEndian.Uint16(data[pos : pos+2])))
@@ -1170,7 +1192,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
 			continue
 
 		case fieldTypeInt24, fieldTypeLong:
-			if rows.columns[i].flags&flagUnsigned != 0 {
+			if rows.rs.columns[i].flags&flagUnsigned != 0 {
 				dest[i] = int64(binary.LittleEndian.Uint32(data[pos : pos+4]))
 			} else {
 				dest[i] = int64(int32(binary.LittleEndian.Uint32(data[pos : pos+4])))
@@ -1179,7 +1201,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
 			continue
 
 		case fieldTypeLongLong:
-			if rows.columns[i].flags&flagUnsigned != 0 {
+			if rows.rs.columns[i].flags&flagUnsigned != 0 {
 				val := binary.LittleEndian.Uint64(data[pos : pos+8])
 				if val > math.MaxInt64 {
 					dest[i] = uint64ToString(val)
@@ -1193,7 +1215,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
 			continue
 
 		case fieldTypeFloat:
-			dest[i] = float32(math.Float32frombits(binary.LittleEndian.Uint32(data[pos : pos+4])))
+			dest[i] = math.Float32frombits(binary.LittleEndian.Uint32(data[pos : pos+4]))
 			pos += 4
 			continue
 
@@ -1233,10 +1255,10 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
 			case isNull:
 				dest[i] = nil
 				continue
-			case rows.columns[i].fieldType == fieldTypeTime:
+			case rows.rs.columns[i].fieldType == fieldTypeTime:
 				// database/sql does not support an equivalent to TIME, return a string
 				var dstlen uint8
-				switch decimals := rows.columns[i].decimals; decimals {
+				switch decimals := rows.rs.columns[i].decimals; decimals {
 				case 0x00, 0x1f:
 					dstlen = 8
 				case 1, 2, 3, 4, 5, 6:
@@ -1244,7 +1266,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
 				default:
 					return fmt.Errorf(
 						"protocol error, illegal decimals value %d",
-						rows.columns[i].decimals,
+						rows.rs.columns[i].decimals,
 					)
 				}
 				dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen, true)
@@ -1252,10 +1274,10 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
 				dest[i], err = parseBinaryDateTime(num, data[pos:], rows.mc.cfg.Loc)
 			default:
 				var dstlen uint8
-				if rows.columns[i].fieldType == fieldTypeDate {
+				if rows.rs.columns[i].fieldType == fieldTypeDate {
 					dstlen = 10
 				} else {
-					switch decimals := rows.columns[i].decimals; decimals {
+					switch decimals := rows.rs.columns[i].decimals; decimals {
 					case 0x00, 0x1f:
 						dstlen = 19
 					case 1, 2, 3, 4, 5, 6:
@@ -1263,7 +1285,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
 					default:
 						return fmt.Errorf(
 							"protocol error, illegal decimals value %d",
-							rows.columns[i].decimals,
+							rows.rs.columns[i].decimals,
 						)
 					}
 				}
@@ -1279,7 +1301,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
 
 		// Please report if this happens!
 		default:
-			return fmt.Errorf("unknown field type %d", rows.columns[i].fieldType)
+			return fmt.Errorf("unknown field type %d", rows.rs.columns[i].fieldType)
 		}
 	}
 

+ 142 - 35
vendor/github.com/go-sql-driver/mysql/rows.go

@@ -11,19 +11,20 @@ package mysql
 import (
 	"database/sql/driver"
 	"io"
+	"math"
+	"reflect"
 )
 
-type mysqlField struct {
-	tableName string
-	name      string
-	flags     fieldFlag
-	fieldType byte
-	decimals  byte
+type resultSet struct {
+	columns     []mysqlField
+	columnNames []string
+	done        bool
 }
 
 type mysqlRows struct {
-	mc      *mysqlConn
-	columns []mysqlField
+	mc     *mysqlConn
+	rs     resultSet
+	finish func()
 }
 
 type binaryRows struct {
@@ -34,37 +35,89 @@ type textRows struct {
 	mysqlRows
 }
 
-type emptyRows struct{}
-
 func (rows *mysqlRows) Columns() []string {
-	columns := make([]string, len(rows.columns))
+	if rows.rs.columnNames != nil {
+		return rows.rs.columnNames
+	}
+
+	columns := make([]string, len(rows.rs.columns))
 	if rows.mc != nil && rows.mc.cfg.ColumnsWithAlias {
 		for i := range columns {
-			if tableName := rows.columns[i].tableName; len(tableName) > 0 {
-				columns[i] = tableName + "." + rows.columns[i].name
+			if tableName := rows.rs.columns[i].tableName; len(tableName) > 0 {
+				columns[i] = tableName + "." + rows.rs.columns[i].name
 			} else {
-				columns[i] = rows.columns[i].name
+				columns[i] = rows.rs.columns[i].name
 			}
 		}
 	} else {
 		for i := range columns {
-			columns[i] = rows.columns[i].name
+			columns[i] = rows.rs.columns[i].name
 		}
 	}
+
+	rows.rs.columnNames = columns
 	return columns
 }
 
-func (rows *mysqlRows) Close() error {
+func (rows *mysqlRows) ColumnTypeDatabaseTypeName(i int) string {
+	if name, ok := typeDatabaseName[rows.rs.columns[i].fieldType]; ok {
+		return name
+	}
+	return ""
+}
+
+// func (rows *mysqlRows) ColumnTypeLength(i int) (length int64, ok bool) {
+// 	return int64(rows.rs.columns[i].length), true
+// }
+
+func (rows *mysqlRows) ColumnTypeNullable(i int) (nullable, ok bool) {
+	return rows.rs.columns[i].flags&flagNotNULL == 0, true
+}
+
+func (rows *mysqlRows) ColumnTypePrecisionScale(i int) (int64, int64, bool) {
+	column := rows.rs.columns[i]
+	decimals := int64(column.decimals)
+
+	switch column.fieldType {
+	case fieldTypeDecimal, fieldTypeNewDecimal:
+		if decimals > 0 {
+			return int64(column.length) - 2, decimals, true
+		}
+		return int64(column.length) - 1, decimals, true
+	case fieldTypeTimestamp, fieldTypeDateTime, fieldTypeTime:
+		return decimals, decimals, true
+	case fieldTypeFloat, fieldTypeDouble:
+		if decimals == 0x1f {
+			return math.MaxInt64, math.MaxInt64, true
+		}
+		return math.MaxInt64, decimals, true
+	}
+
+	return 0, 0, false
+}
+
+func (rows *mysqlRows) ColumnTypeScanType(i int) reflect.Type {
+	return rows.rs.columns[i].scanType()
+}
+
+func (rows *mysqlRows) Close() (err error) {
+	if f := rows.finish; f != nil {
+		f()
+		rows.finish = nil
+	}
+
 	mc := rows.mc
 	if mc == nil {
 		return nil
 	}
-	if mc.netConn == nil {
-		return ErrInvalidConn
+	if err := mc.error(); err != nil {
+		return err
 	}
 
 	// Remove unread packets from stream
-	err := mc.readUntilEOF()
+	if !rows.rs.done {
+		err = mc.readUntilEOF()
+	}
 	if err == nil {
 		if err = mc.discardResults(); err != nil {
 			return err
@@ -75,22 +128,66 @@ func (rows *mysqlRows) Close() error {
 	return err
 }
 
-func (rows *binaryRows) Next(dest []driver.Value) error {
-	if mc := rows.mc; mc != nil {
-		if mc.netConn == nil {
-			return ErrInvalidConn
+func (rows *mysqlRows) HasNextResultSet() (b bool) {
+	if rows.mc == nil {
+		return false
+	}
+	return rows.mc.status&statusMoreResultsExists != 0
+}
+
+func (rows *mysqlRows) nextResultSet() (int, error) {
+	if rows.mc == nil {
+		return 0, io.EOF
+	}
+	if err := rows.mc.error(); err != nil {
+		return 0, err
+	}
+
+	// Remove unread packets from stream
+	if !rows.rs.done {
+		if err := rows.mc.readUntilEOF(); err != nil {
+			return 0, err
 		}
+		rows.rs.done = true
+	}
 
-		// Fetch next row from stream
-		return rows.readRow(dest)
+	if !rows.HasNextResultSet() {
+		rows.mc = nil
+		return 0, io.EOF
 	}
-	return io.EOF
+	rows.rs = resultSet{}
+	return rows.mc.readResultSetHeaderPacket()
 }
 
-func (rows *textRows) Next(dest []driver.Value) error {
+func (rows *mysqlRows) nextNotEmptyResultSet() (int, error) {
+	for {
+		resLen, err := rows.nextResultSet()
+		if err != nil {
+			return 0, err
+		}
+
+		if resLen > 0 {
+			return resLen, nil
+		}
+
+		rows.rs.done = true
+	}
+}
+
+func (rows *binaryRows) NextResultSet() error {
+	resLen, err := rows.nextNotEmptyResultSet()
+	if err != nil {
+		return err
+	}
+
+	rows.rs.columns, err = rows.mc.readColumns(resLen)
+	return err
+}
+
+func (rows *binaryRows) Next(dest []driver.Value) error {
 	if mc := rows.mc; mc != nil {
-		if mc.netConn == nil {
-			return ErrInvalidConn
+		if err := mc.error(); err != nil {
+			return err
 		}
 
 		// Fetch next row from stream
@@ -99,14 +196,24 @@ func (rows *textRows) Next(dest []driver.Value) error {
 	return io.EOF
 }
 
-func (rows emptyRows) Columns() []string {
-	return nil
-}
+func (rows *textRows) NextResultSet() (err error) {
+	resLen, err := rows.nextNotEmptyResultSet()
+	if err != nil {
+		return err
+	}
 
-func (rows emptyRows) Close() error {
-	return nil
+	rows.rs.columns, err = rows.mc.readColumns(resLen)
+	return err
 }
 
-func (rows emptyRows) Next(dest []driver.Value) error {
+func (rows *textRows) Next(dest []driver.Value) error {
+	if mc := rows.mc; mc != nil {
+		if err := mc.error(); err != nil {
+			return err
+		}
+
+		// Fetch next row from stream
+		return rows.readRow(dest)
+	}
 	return io.EOF
 }

+ 55 - 30
vendor/github.com/go-sql-driver/mysql/statement.go

@@ -11,6 +11,7 @@ package mysql
 import (
 	"database/sql/driver"
 	"fmt"
+	"io"
 	"reflect"
 	"strconv"
 )
@@ -19,11 +20,10 @@ type mysqlStmt struct {
 	mc         *mysqlConn
 	id         uint32
 	paramCount int
-	columns    []mysqlField // cached from the first query
 }
 
 func (stmt *mysqlStmt) Close() error {
-	if stmt.mc == nil || stmt.mc.netConn == nil {
+	if stmt.mc == nil || stmt.mc.closed.IsSet() {
 		// driver.Stmt.Close can be called more than once, thus this function
 		// has to be idempotent.
 		// See also Issue #450 and golang/go#16019.
@@ -45,14 +45,14 @@ func (stmt *mysqlStmt) ColumnConverter(idx int) driver.ValueConverter {
 }
 
 func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
-	if stmt.mc.netConn == nil {
+	if stmt.mc.closed.IsSet() {
 		errLog.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
 	// Send command
 	err := stmt.writeExecutePacket(args)
 	if err != nil {
-		return nil, err
+		return nil, stmt.mc.markBadConn(err)
 	}
 
 	mc := stmt.mc
@@ -62,37 +62,45 @@ func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
 
 	// Read Result
 	resLen, err := mc.readResultSetHeaderPacket()
-	if err == nil {
-		if resLen > 0 {
-			// Columns
-			err = mc.readUntilEOF()
-			if err != nil {
-				return nil, err
-			}
-
-			// Rows
-			err = mc.readUntilEOF()
+	if err != nil {
+		return nil, err
+	}
+
+	if resLen > 0 {
+		// Columns
+		if err = mc.readUntilEOF(); err != nil {
+			return nil, err
 		}
-		if err == nil {
-			return &mysqlResult{
-				affectedRows: int64(mc.affectedRows),
-				insertId:     int64(mc.insertId),
-			}, nil
+
+		// Rows
+		if err := mc.readUntilEOF(); err != nil {
+			return nil, err
 		}
 	}
 
-	return nil, err
+	if err := mc.discardResults(); err != nil {
+		return nil, err
+	}
+
+	return &mysqlResult{
+		affectedRows: int64(mc.affectedRows),
+		insertId:     int64(mc.insertId),
+	}, nil
 }
 
 func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
-	if stmt.mc.netConn == nil {
+	return stmt.query(args)
+}
+
+func (stmt *mysqlStmt) query(args []driver.Value) (*binaryRows, error) {
+	if stmt.mc.closed.IsSet() {
 		errLog.Print(ErrInvalidConn)
 		return nil, driver.ErrBadConn
 	}
 	// Send command
 	err := stmt.writeExecutePacket(args)
 	if err != nil {
-		return nil, err
+		return nil, stmt.mc.markBadConn(err)
 	}
 
 	mc := stmt.mc
@@ -107,14 +115,15 @@ func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
 
 	if resLen > 0 {
 		rows.mc = mc
-		// Columns
-		// If not cached, read them and cache them
-		if stmt.columns == nil {
-			rows.columns, err = mc.readColumns(resLen)
-			stmt.columns = rows.columns
-		} else {
-			rows.columns = stmt.columns
-			err = mc.readUntilEOF()
+		rows.rs.columns, err = mc.readColumns(resLen)
+	} else {
+		rows.rs.done = true
+
+		switch err := rows.NextResultSet(); err {
+		case nil, io.EOF:
+			return rows, nil
+		default:
+			return nil, err
 		}
 	}
 
@@ -128,6 +137,12 @@ func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
 		return v, nil
 	}
 
+	if v != nil {
+		if valuer, ok := v.(driver.Valuer); ok {
+			return valuer.Value()
+		}
+	}
+
 	rv := reflect.ValueOf(v)
 	switch rv.Kind() {
 	case reflect.Ptr:
@@ -148,6 +163,16 @@ func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
 		return int64(u64), nil
 	case reflect.Float32, reflect.Float64:
 		return rv.Float(), nil
+	case reflect.Bool:
+		return rv.Bool(), nil
+	case reflect.Slice:
+		ek := rv.Type().Elem().Kind()
+		if ek == reflect.Uint8 {
+			return rv.Bytes(), nil
+		}
+		return nil, fmt.Errorf("unsupported type %T, a slice of %s", v, ek)
+	case reflect.String:
+		return rv.String(), nil
 	}
 	return nil, fmt.Errorf("unsupported type %T, a %s", v, rv.Kind())
 }

+ 2 - 2
vendor/github.com/go-sql-driver/mysql/transaction.go

@@ -13,7 +13,7 @@ type mysqlTx struct {
 }
 
 func (tx *mysqlTx) Commit() (err error) {
-	if tx.mc == nil || tx.mc.netConn == nil {
+	if tx.mc == nil || tx.mc.closed.IsSet() {
 		return ErrInvalidConn
 	}
 	err = tx.mc.exec("COMMIT")
@@ -22,7 +22,7 @@ func (tx *mysqlTx) Commit() (err error) {
 }
 
 func (tx *mysqlTx) Rollback() (err error) {
-	if tx.mc == nil || tx.mc.netConn == nil {
+	if tx.mc == nil || tx.mc.closed.IsSet() {
 		return ErrInvalidConn
 	}
 	err = tx.mc.exec("ROLLBACK")

+ 83 - 1
vendor/github.com/go-sql-driver/mysql/utils.go

@@ -16,16 +16,21 @@ import (
 	"fmt"
 	"io"
 	"strings"
+	"sync"
+	"sync/atomic"
 	"time"
 )
 
 var (
+	tlsConfigLock     sync.RWMutex
 	tlsConfigRegister map[string]*tls.Config // Register for custom tls.Configs
 )
 
 // RegisterTLSConfig registers a custom tls.Config to be used with sql.Open.
 // Use the key as a value in the DSN where tls=value.
 //
+// Note: The tls.Config provided to needs to be exclusively owned by the driver after registering.
+//
 //  rootCertPool := x509.NewCertPool()
 //  pem, err := ioutil.ReadFile("/path/ca-cert.pem")
 //  if err != nil {
@@ -51,19 +56,32 @@ func RegisterTLSConfig(key string, config *tls.Config) error {
 		return fmt.Errorf("key '%s' is reserved", key)
 	}
 
+	tlsConfigLock.Lock()
 	if tlsConfigRegister == nil {
 		tlsConfigRegister = make(map[string]*tls.Config)
 	}
 
 	tlsConfigRegister[key] = config
+	tlsConfigLock.Unlock()
 	return nil
 }
 
 // DeregisterTLSConfig removes the tls.Config associated with key.
 func DeregisterTLSConfig(key string) {
+	tlsConfigLock.Lock()
 	if tlsConfigRegister != nil {
 		delete(tlsConfigRegister, key)
 	}
+	tlsConfigLock.Unlock()
+}
+
+func getTLSConfigClone(key string) (config *tls.Config) {
+	tlsConfigLock.RLock()
+	if v, ok := tlsConfigRegister[key]; ok {
+		config = cloneTLSConfig(v)
+	}
+	tlsConfigLock.RUnlock()
+	return
 }
 
 // Returns the bool value of the input.
@@ -548,8 +566,8 @@ func readLengthEncodedInteger(b []byte) (uint64, bool, int) {
 	if len(b) == 0 {
 		return 0, true, 1
 	}
-	switch b[0] {
 
+	switch b[0] {
 	// 251: NULL
 	case 0xfb:
 		return 0, true, 1
@@ -738,3 +756,67 @@ func escapeStringQuotes(buf []byte, v string) []byte {
 
 	return buf[:pos]
 }
+
+/******************************************************************************
+*                               Sync utils                                    *
+******************************************************************************/
+
+// noCopy may be embedded into structs which must not be copied
+// after the first use.
+//
+// See https://github.com/golang/go/issues/8005#issuecomment-190753527
+// for details.
+type noCopy struct{}
+
+// Lock is a no-op used by -copylocks checker from `go vet`.
+func (*noCopy) Lock() {}
+
+// atomicBool is a wrapper around uint32 for usage as a boolean value with
+// atomic access.
+type atomicBool struct {
+	_noCopy noCopy
+	value   uint32
+}
+
+// IsSet returns wether the current boolean value is true
+func (ab *atomicBool) IsSet() bool {
+	return atomic.LoadUint32(&ab.value) > 0
+}
+
+// Set sets the value of the bool regardless of the previous value
+func (ab *atomicBool) Set(value bool) {
+	if value {
+		atomic.StoreUint32(&ab.value, 1)
+	} else {
+		atomic.StoreUint32(&ab.value, 0)
+	}
+}
+
+// TrySet sets the value of the bool and returns wether the value changed
+func (ab *atomicBool) TrySet(value bool) bool {
+	if value {
+		return atomic.SwapUint32(&ab.value, 1) == 0
+	}
+	return atomic.SwapUint32(&ab.value, 0) > 0
+}
+
+// atomicBool is a wrapper for atomically accessed error values
+type atomicError struct {
+	_noCopy noCopy
+	value   atomic.Value
+}
+
+// Set sets the error value regardless of the previous value.
+// The value must not be nil
+func (ae *atomicError) Set(value error) {
+	ae.value.Store(value)
+}
+
+// Value returns the current error value
+func (ae *atomicError) Value() error {
+	if v := ae.value.Load(); v != nil {
+		// this will panic if the value doesn't implement the error interface
+		return v.(error)
+	}
+	return nil
+}

+ 40 - 0
vendor/github.com/go-sql-driver/mysql/utils_go17.go

@@ -0,0 +1,40 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// +build go1.7
+// +build !go1.8
+
+package mysql
+
+import "crypto/tls"
+
+func cloneTLSConfig(c *tls.Config) *tls.Config {
+	return &tls.Config{
+		Rand:                        c.Rand,
+		Time:                        c.Time,
+		Certificates:                c.Certificates,
+		NameToCertificate:           c.NameToCertificate,
+		GetCertificate:              c.GetCertificate,
+		RootCAs:                     c.RootCAs,
+		NextProtos:                  c.NextProtos,
+		ServerName:                  c.ServerName,
+		ClientAuth:                  c.ClientAuth,
+		ClientCAs:                   c.ClientCAs,
+		InsecureSkipVerify:          c.InsecureSkipVerify,
+		CipherSuites:                c.CipherSuites,
+		PreferServerCipherSuites:    c.PreferServerCipherSuites,
+		SessionTicketsDisabled:      c.SessionTicketsDisabled,
+		SessionTicketKey:            c.SessionTicketKey,
+		ClientSessionCache:          c.ClientSessionCache,
+		MinVersion:                  c.MinVersion,
+		MaxVersion:                  c.MaxVersion,
+		CurvePreferences:            c.CurvePreferences,
+		DynamicRecordSizingDisabled: c.DynamicRecordSizingDisabled,
+		Renegotiation:               c.Renegotiation,
+	}
+}

+ 49 - 0
vendor/github.com/go-sql-driver/mysql/utils_go18.go

@@ -0,0 +1,49 @@
+// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
+//
+// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// +build go1.8
+
+package mysql
+
+import (
+	"crypto/tls"
+	"database/sql"
+	"database/sql/driver"
+	"errors"
+)
+
+func cloneTLSConfig(c *tls.Config) *tls.Config {
+	return c.Clone()
+}
+
+func namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) {
+	dargs := make([]driver.Value, len(named))
+	for n, param := range named {
+		if len(param.Name) > 0 {
+			// TODO: support the use of Named Parameters #561
+			return nil, errors.New("mysql: driver does not support the use of Named Parameters")
+		}
+		dargs[n] = param.Value
+	}
+	return dargs, nil
+}
+
+func mapIsolationLevel(level driver.IsolationLevel) (string, error) {
+	switch sql.IsolationLevel(level) {
+	case sql.LevelRepeatableRead:
+		return "REPEATABLE READ", nil
+	case sql.LevelReadCommitted:
+		return "READ COMMITTED", nil
+	case sql.LevelReadUncommitted:
+		return "READ UNCOMMITTED", nil
+	case sql.LevelSerializable:
+		return "SERIALIZABLE", nil
+	default:
+		return "", errors.New("mysql: unsupported isolation level: " + string(level))
+	}
+}

+ 11 - 1
vendor/github.com/gorilla/handlers/cors.go

@@ -110,7 +110,17 @@ func (ch *cors) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		w.Header().Set(corsVaryHeader, corsOriginHeader)
 	}
 
-	w.Header().Set(corsAllowOriginHeader, origin)
+	returnOrigin := origin
+	for _, o := range ch.allowedOrigins {
+		// A configuration of * is different than explicitly setting an allowed
+		// origin. Returning arbitrary origin headers an an access control allow
+		// origin header is unsafe and is not required by any use case.
+		if o == corsOriginMatchAll {
+			returnOrigin = "*"
+			break
+		}
+	}
+	w.Header().Set(corsAllowOriginHeader, returnOrigin)
 
 	if r.Method == corsOptionMethod {
 		return

+ 4 - 3
vendor/github.com/gorilla/mux/.travis.yml

@@ -3,12 +3,13 @@ sudo: false
 
 matrix:
   include:
-    - go: 1.2
-    - go: 1.3
-    - go: 1.4
     - go: 1.5
     - go: 1.6
     - go: 1.7
+    - go: 1.8
+    - go: 1.9
+    - go: tip
+  allow_failures:
     - go: tip
 
 install:

+ 122 - 6
vendor/github.com/gorilla/mux/README.md

@@ -2,6 +2,7 @@ gorilla/mux
 ===
 [![GoDoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux)
 [![Build Status](https://travis-ci.org/gorilla/mux.svg?branch=master)](https://travis-ci.org/gorilla/mux)
+[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge)
 
 ![Gorilla Logo](http://www.gorillatoolkit.org/static/images/gorilla-icon-64.png)
 
@@ -14,7 +15,7 @@ The name mux stands for "HTTP request multiplexer". Like the standard `http.Serv
 
 * It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`.
 * Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers.
-* URL hosts and paths can have variables with an optional regular expression.
+* URL hosts, paths and query values can have variables with an optional regular expression.
 * Registered URLs can be built, or "reversed", which helps maintaining references to resources.
 * Routes can be used as subrouters: nested routes are only tested if the parent route matches. This is useful to define groups of routes that share common conditions like a host, a path prefix or other repeated attributes. As a bonus, this optimizes request matching.
 
@@ -25,6 +26,7 @@ The name mux stands for "HTTP request multiplexer". Like the standard `http.Serv
 * [Matching Routes](#matching-routes)
 * [Static Files](#static-files)
 * [Registered URLs](#registered-urls)
+* [Walking Routes](#walking-routes)
 * [Full Example](#full-example)
 
 ---
@@ -65,8 +67,11 @@ r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
 The names are used to create a map of route variables which can be retrieved calling `mux.Vars()`:
 
 ```go
-vars := mux.Vars(request)
-category := vars["category"]
+func ArticlesCategoryHandler(w http.ResponseWriter, r *http.Request) {
+	vars := mux.Vars(r)
+	w.WriteHeader(http.StatusOK)
+	fmt.Fprintf(w, "Category: %v\n", vars["category"])
+}
 ```
 
 And this is all you need to know about the basic usage. More advanced options are explained below.
@@ -130,6 +135,14 @@ r.HandleFunc("/products", ProductsHandler).
   Schemes("http")
 ```
 
+Routes are tested in the order they were added to the router. If two routes match, the first one wins:
+
+```go
+r := mux.NewRouter()
+r.HandleFunc("/specific", specificHandler)
+r.PathPrefix("/").Handler(catchAllHandler)
+```
+
 Setting the same matching conditions again and again can be boring, so we have a way to group several routes that share the same requirements. We call it "subrouting".
 
 For example, let's say we have several URLs that should only match when the host is `www.example.com`. Create a route for that host and get a "subrouter" from it:
@@ -163,6 +176,64 @@ s.HandleFunc("/{key}/", ProductHandler)
 // "/products/{key}/details"
 s.HandleFunc("/{key}/details", ProductDetailsHandler)
 ```
+### Listing Routes
+
+Routes on a mux can be listed using the Router.Walk method—useful for generating documentation:
+
+```go
+package main
+
+import (
+    "fmt"
+    "net/http"
+    "strings"
+
+    "github.com/gorilla/mux"
+)
+
+func handler(w http.ResponseWriter, r *http.Request) {
+    return
+}
+
+func main() {
+    r := mux.NewRouter()
+    r.HandleFunc("/", handler)
+    r.HandleFunc("/products", handler).Methods("POST")
+    r.HandleFunc("/articles", handler).Methods("GET")
+    r.HandleFunc("/articles/{id}", handler).Methods("GET", "PUT")
+    r.HandleFunc("/authors", handler).Queries("surname", "{surname}")
+    r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
+        t, err := route.GetPathTemplate()
+        if err != nil {
+            return err
+        }
+        qt, err := route.GetQueriesTemplates()
+        if err != nil {
+            return err
+        }
+        // p will contain regular expression is compatible with regular expression in Perl, Python, and other languages.
+        // for instance the regular expression for path '/articles/{id}' will be '^/articles/(?P<v0>[^/]+)$'
+        p, err := route.GetPathRegexp()
+        if err != nil {
+            return err
+        }
+        // qr will contain a list of regular expressions with the same semantics as GetPathRegexp,
+        // just applied to the Queries pairs instead, e.g., 'Queries("surname", "{surname}") will return
+        // {"^surname=(?P<v0>.*)$}. Where each combined query pair will have an entry in the list.
+        qr, err := route.GetQueriesRegexp()
+        if err != nil {
+            return err
+        }
+        m, err := route.GetMethods()
+        if err != nil {
+            return err
+        }
+        fmt.Println(strings.Join(m, ","), strings.Join(qt, ","), strings.Join(qr, ","), t, p)
+        return nil
+    })
+    http.Handle("/", r)
+}
+```
 
 ### Static Files
 
@@ -217,19 +288,21 @@ url, err := r.Get("article").URL("category", "technology", "id", "42")
 "/articles/technology/42"
 ```
 
-This also works for host variables:
+This also works for host and query value variables:
 
 ```go
 r := mux.NewRouter()
 r.Host("{subdomain}.domain.com").
   Path("/articles/{category}/{id:[0-9]+}").
+  Queries("filter", "{filter}").
   HandlerFunc(ArticleHandler).
   Name("article")
 
-// url.String() will be "http://news.domain.com/articles/technology/42"
+// url.String() will be "http://news.domain.com/articles/technology/42?filter=gorilla"
 url, err := r.Get("article").URL("subdomain", "news",
                                  "category", "technology",
-                                 "id", "42")
+                                 "id", "42",
+                                 "filter", "gorilla")
 ```
 
 All variables defined in the route are required, and their values must conform to the corresponding patterns. These requirements guarantee that a generated URL will always match a registered route -- the only exception is for explicitly defined "build-only" routes which never match.
@@ -267,6 +340,49 @@ url, err := r.Get("article").URL("subdomain", "news",
                                  "id", "42")
 ```
 
+### Walking Routes
+
+The `Walk` function on `mux.Router` can be used to visit all of the routes that are registered on a router. For example,
+the following prints all of the registered routes:
+
+```go
+r := mux.NewRouter()
+r.HandleFunc("/", handler)
+r.HandleFunc("/products", handler).Methods("POST")
+r.HandleFunc("/articles", handler).Methods("GET")
+r.HandleFunc("/articles/{id}", handler).Methods("GET", "PUT")
+r.HandleFunc("/authors", handler).Queries("surname", "{surname}")
+r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
+    t, err := route.GetPathTemplate()
+    if err != nil {
+        return err
+    }
+    qt, err := route.GetQueriesTemplates()
+    if err != nil {
+        return err
+    }
+    // p will contain a regular expression that is compatible with regular expressions in Perl, Python, and other languages.
+    // For example, the regular expression for path '/articles/{id}' will be '^/articles/(?P<v0>[^/]+)$'.
+    p, err := route.GetPathRegexp()
+    if err != nil {
+        return err
+    }
+    // qr will contain a list of regular expressions with the same semantics as GetPathRegexp,
+    // just applied to the Queries pairs instead, e.g., 'Queries("surname", "{surname}") will return
+    // {"^surname=(?P<v0>.*)$}. Where each combined query pair will have an entry in the list.
+    qr, err := route.GetQueriesRegexp()
+    if err != nil {
+        return err
+    }
+    m, err := route.GetMethods()
+    if err != nil {
+        return err
+    }
+    fmt.Println(strings.Join(m, ","), strings.Join(qt, ","), strings.Join(qr, ","), t, p)
+    return nil
+})
+```
+
 ## Full Example
 
 Here's a complete, runnable example of a small `mux` based server:

+ 12 - 5
vendor/github.com/gorilla/mux/doc.go

@@ -12,8 +12,8 @@ or other conditions. The main features are:
 
 	* Requests can be matched based on URL host, path, path prefix, schemes,
 	  header and query values, HTTP methods or using custom matchers.
-	* URL hosts and paths can have variables with an optional regular
-	  expression.
+	* URL hosts, paths and query values can have variables with an optional
+	  regular expression.
 	* Registered URLs can be built, or "reversed", which helps maintaining
 	  references to resources.
 	* Routes can be used as subrouters: nested routes are only tested if the
@@ -57,6 +57,11 @@ calling mux.Vars():
 	vars := mux.Vars(request)
 	category := vars["category"]
 
+Note that if any capturing groups are present, mux will panic() during parsing. To prevent
+this, convert any capturing groups to non-capturing, e.g. change "/{sort:(asc|desc)}" to
+"/{sort:(?:asc|desc)}". This is a change from prior versions which behaved unpredictably
+when capturing groups were present.
+
 And this is all you need to know about the basic usage. More advanced options
 are explained below.
 
@@ -183,18 +188,20 @@ key/value pairs for the route variables. For the previous route, we would do:
 
 	"/articles/technology/42"
 
-This also works for host variables:
+This also works for host and query value variables:
 
 	r := mux.NewRouter()
 	r.Host("{subdomain}.domain.com").
 	  Path("/articles/{category}/{id:[0-9]+}").
+	  Queries("filter", "{filter}").
 	  HandlerFunc(ArticleHandler).
 	  Name("article")
 
-	// url.String() will be "http://news.domain.com/articles/technology/42"
+	// url.String() will be "http://news.domain.com/articles/technology/42?filter=gorilla"
 	url, err := r.Get("article").URL("subdomain", "news",
 	                                 "category", "technology",
-	                                 "id", "42")
+	                                 "id", "42",
+	                                 "filter", "gorilla")
 
 All variables defined in the route are required, and their values must
 conform to the corresponding patterns. These requirements guarantee that a

+ 63 - 34
vendor/github.com/gorilla/mux/mux.go

@@ -10,7 +10,11 @@ import (
 	"net/http"
 	"path"
 	"regexp"
-	"strings"
+)
+
+var (
+	ErrMethodMismatch = errors.New("method is not allowed")
+	ErrNotFound       = errors.New("no matching route was found")
 )
 
 // NewRouter returns a new router instance.
@@ -39,6 +43,10 @@ func NewRouter() *Router {
 type Router struct {
 	// Configurable Handler to be used when no route matches.
 	NotFoundHandler http.Handler
+
+	// Configurable Handler to be used when the request method does not match the route.
+	MethodNotAllowedHandler http.Handler
+
 	// Parent route, if this is a subrouter.
 	parent parentRoute
 	// Routes to be matched, in order.
@@ -57,7 +65,17 @@ type Router struct {
 	useEncodedPath bool
 }
 
-// Match matches registered routes against the request.
+// Match attempts to match the given request against the router's registered routes.
+//
+// If the request matches a route of this router or one of its subrouters the Route,
+// Handler, and Vars fields of the the match argument are filled and this function
+// returns true.
+//
+// If the request does not match any of this router's or its subrouters' routes
+// then this function returns false. If available, a reason for the match failure
+// will be filled in the match argument's MatchErr field. If the match failure type
+// (eg: not found) has a registered handler, the handler is assigned to the Handler
+// field of the match argument.
 func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
 	for _, route := range r.routes {
 		if route.Match(req, match) {
@@ -65,11 +83,23 @@ func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
 		}
 	}
 
+	if match.MatchErr == ErrMethodMismatch {
+		if r.MethodNotAllowedHandler != nil {
+			match.Handler = r.MethodNotAllowedHandler
+			return true
+		} else {
+			return false
+		}
+	}
+
 	// Closest match for a router (includes sub-routers)
 	if r.NotFoundHandler != nil {
 		match.Handler = r.NotFoundHandler
+		match.MatchErr = ErrNotFound
 		return true
 	}
+
+	match.MatchErr = ErrNotFound
 	return false
 }
 
@@ -81,7 +111,7 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 	if !r.skipClean {
 		path := req.URL.Path
 		if r.useEncodedPath {
-			path = getPath(req)
+			path = req.URL.EscapedPath()
 		}
 		// Clean path to canonical form and redirect.
 		if p := cleanPath(path); p != path {
@@ -105,9 +135,15 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 		req = setVars(req, match.Vars)
 		req = setCurrentRoute(req, match.Route)
 	}
+
+	if handler == nil && match.MatchErr == ErrMethodMismatch {
+		handler = methodNotAllowedHandler()
+	}
+
 	if handler == nil {
 		handler = http.NotFoundHandler()
 	}
+
 	if !r.KeepContext {
 		defer contextClear(req)
 	}
@@ -160,10 +196,6 @@ func (r *Router) SkipClean(value bool) *Router {
 // UseEncodedPath tells the router to match the encoded original path
 // to the routes.
 // For eg. "/path/foo%2Fbar/to" will match the path "/path/{var}/to".
-// This behavior has the drawback of needing to match routes against
-// r.RequestURI instead of r.URL.Path. Any modifications (such as http.StripPrefix)
-// to r.URL.Path will not affect routing when this flag is on and thus may
-// induce unintended behavior.
 //
 // If not called, the router will match the unencoded path to the routes.
 // For eg. "/path/foo%2Fbar/to" will match the path "/path/foo/bar/to"
@@ -176,6 +208,13 @@ func (r *Router) UseEncodedPath() *Router {
 // parentRoute
 // ----------------------------------------------------------------------------
 
+func (r *Router) getBuildScheme() string {
+	if r.parent != nil {
+		return r.parent.getBuildScheme()
+	}
+	return ""
+}
+
 // getNamedRoutes returns the map where named routes are registered.
 func (r *Router) getNamedRoutes() map[string]*Route {
 	if r.namedRoutes == nil {
@@ -299,10 +338,6 @@ type WalkFunc func(route *Route, router *Router, ancestors []*Route) error
 
 func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error {
 	for _, t := range r.routes {
-		if t.regexp == nil || t.regexp.path == nil || t.regexp.path.template == "" {
-			continue
-		}
-
 		err := walkFn(t, r, ancestors)
 		if err == SkipRouter {
 			continue
@@ -312,10 +347,12 @@ func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error {
 		}
 		for _, sr := range t.matchers {
 			if h, ok := sr.(*Router); ok {
+				ancestors = append(ancestors, t)
 				err := h.walk(walkFn, ancestors)
 				if err != nil {
 					return err
 				}
+				ancestors = ancestors[:len(ancestors)-1]
 			}
 		}
 		if h, ok := t.handler.(*Router); ok {
@@ -339,6 +376,11 @@ type RouteMatch struct {
 	Route   *Route
 	Handler http.Handler
 	Vars    map[string]string
+
+	// MatchErr is set to appropriate matching error
+	// It is set to ErrMethodMismatch if there is a mismatch in
+	// the request method and route method
+	MatchErr error
 }
 
 type contextKey int
@@ -380,28 +422,6 @@ func setCurrentRoute(r *http.Request, val interface{}) *http.Request {
 // Helpers
 // ----------------------------------------------------------------------------
 
-// getPath returns the escaped path if possible; doing what URL.EscapedPath()
-// which was added in go1.5 does
-func getPath(req *http.Request) string {
-	if req.RequestURI != "" {
-		// Extract the path from RequestURI (which is escaped unlike URL.Path)
-		// as detailed here as detailed in https://golang.org/pkg/net/url/#URL
-		// for < 1.5 server side workaround
-		// http://localhost/path/here?v=1 -> /path/here
-		path := req.RequestURI
-		path = strings.TrimPrefix(path, req.URL.Scheme+`://`)
-		path = strings.TrimPrefix(path, req.URL.Host)
-		if i := strings.LastIndex(path, "?"); i > -1 {
-			path = path[:i]
-		}
-		if i := strings.LastIndex(path, "#"); i > -1 {
-			path = path[:i]
-		}
-		return path
-	}
-	return req.URL.Path
-}
-
 // cleanPath returns the canonical path for p, eliminating . and .. elements.
 // Borrowed from the net/http package.
 func cleanPath(p string) string {
@@ -458,7 +478,7 @@ func mapFromPairsToString(pairs ...string) (map[string]string, error) {
 	return m, nil
 }
 
-// mapFromPairsToRegex converts variadic string paramers to a
+// mapFromPairsToRegex converts variadic string parameters to a
 // string to regex map.
 func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) {
 	length, err := checkPairs(pairs...)
@@ -540,3 +560,12 @@ func matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[string][]s
 	}
 	return true
 }
+
+// methodNotAllowed replies to the request with an HTTP status code 405.
+func methodNotAllowed(w http.ResponseWriter, r *http.Request) {
+	w.WriteHeader(http.StatusMethodNotAllowed)
+}
+
+// methodNotAllowedHandler returns a simple request handler
+// that replies to each request with a status code 405.
+func methodNotAllowedHandler() http.Handler { return http.HandlerFunc(methodNotAllowed) }

+ 14 - 4
vendor/github.com/gorilla/mux/regexp.go

@@ -35,7 +35,7 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash,
 	// Now let's parse it.
 	defaultPattern := "[^/]+"
 	if matchQuery {
-		defaultPattern = "[^?&]*"
+		defaultPattern = ".*"
 	} else if matchHost {
 		defaultPattern = "[^.]+"
 		matchPrefix = false
@@ -109,6 +109,13 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash,
 	if errCompile != nil {
 		return nil, errCompile
 	}
+
+	// Check for capturing groups which used to work in older versions
+	if reg.NumSubexp() != len(idxs)/2 {
+		panic(fmt.Sprintf("route %s contains capture groups in its regexp. ", template) +
+			"Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)")
+	}
+
 	// Done!
 	return &routeRegexp{
 		template:       template,
@@ -134,7 +141,7 @@ type routeRegexp struct {
 	matchQuery bool
 	// The strictSlash value defined on the route, but disabled if PathPrefix was used.
 	strictSlash bool
-	// Determines whether to use encoded path from getPath function or unencoded
+	// Determines whether to use encoded req.URL.EnscapedPath() or unencoded
 	// req.URL.Path for path matching
 	useEncodedPath bool
 	// Expanded regexp.
@@ -155,7 +162,7 @@ func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
 		}
 		path := req.URL.Path
 		if r.useEncodedPath {
-			path = getPath(req)
+			path = req.URL.EscapedPath()
 		}
 		return r.regexp.MatchString(path)
 	}
@@ -171,6 +178,9 @@ func (r *routeRegexp) url(values map[string]string) (string, error) {
 		if !ok {
 			return "", fmt.Errorf("mux: missing route variable %q", v)
 		}
+		if r.matchQuery {
+			value = url.QueryEscape(value)
+		}
 		urlValues[k] = value
 	}
 	rv := fmt.Sprintf(r.reverse, urlValues...)
@@ -262,7 +272,7 @@ func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route)
 	}
 	path := req.URL.Path
 	if r.useEncodedPath {
-		path = getPath(req)
+		path = req.URL.EscapedPath()
 	}
 	// Store path variables.
 	if v.path != nil {

+ 127 - 8
vendor/github.com/gorilla/mux/route.go

@@ -31,6 +31,8 @@ type Route struct {
 	skipClean bool
 	// If true, "/path/foo%2Fbar/to" will match the path "/path/{var}/to"
 	useEncodedPath bool
+	// The scheme used when building URLs.
+	buildScheme string
 	// If true, this route never matches: it is only used to build URLs.
 	buildOnly bool
 	// The name used to build URLs.
@@ -50,12 +52,31 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
 	if r.buildOnly || r.err != nil {
 		return false
 	}
+
+	var matchErr error
+
 	// Match everything.
 	for _, m := range r.matchers {
 		if matched := m.Match(req, match); !matched {
+			if _, ok := m.(methodMatcher); ok {
+				matchErr = ErrMethodMismatch
+				continue
+			}
+			matchErr = nil
 			return false
 		}
 	}
+
+	if matchErr != nil {
+		match.MatchErr = matchErr
+		return false
+	}
+
+	if match.MatchErr == ErrMethodMismatch {
+		// We found a route which matches request method, clear MatchErr
+		match.MatchErr = nil
+	}
+
 	// Yay, we have a match. Let's collect some info about it.
 	if match.Route == nil {
 		match.Route = r
@@ -66,6 +87,7 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
 	if match.Vars == nil {
 		match.Vars = make(map[string]string)
 	}
+
 	// Set variables.
 	if r.regexp != nil {
 		r.regexp.setMatch(req, match, r)
@@ -153,7 +175,7 @@ func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery
 	}
 	r.regexp = r.getRegexpGroup()
 	if !matchHost && !matchQuery {
-		if len(tpl) == 0 || tpl[0] != '/' {
+		if len(tpl) > 0 && tpl[0] != '/' {
 			return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
 		}
 		if r.regexp.path != nil {
@@ -394,6 +416,9 @@ func (r *Route) Schemes(schemes ...string) *Route {
 	for k, v := range schemes {
 		schemes[k] = strings.ToLower(v)
 	}
+	if r.buildScheme == "" && len(schemes) > 0 {
+		r.buildScheme = schemes[0]
+	}
 	return r.addMatcher(schemeMatcher(schemes))
 }
 
@@ -477,22 +502,33 @@ func (r *Route) URL(pairs ...string) (*url.URL, error) {
 		return nil, err
 	}
 	var scheme, host, path string
+	queries := make([]string, 0, len(r.regexp.queries))
 	if r.regexp.host != nil {
-		// Set a default scheme.
-		scheme = "http"
 		if host, err = r.regexp.host.url(values); err != nil {
 			return nil, err
 		}
+		scheme = "http"
+		if s := r.getBuildScheme(); s != "" {
+			scheme = s
+		}
 	}
 	if r.regexp.path != nil {
 		if path, err = r.regexp.path.url(values); err != nil {
 			return nil, err
 		}
 	}
+	for _, q := range r.regexp.queries {
+		var query string
+		if query, err = q.url(values); err != nil {
+			return nil, err
+		}
+		queries = append(queries, query)
+	}
 	return &url.URL{
-		Scheme: scheme,
-		Host:   host,
-		Path:   path,
+		Scheme:   scheme,
+		Host:     host,
+		Path:     path,
+		RawQuery: strings.Join(queries, "&"),
 	}, nil
 }
 
@@ -514,10 +550,14 @@ func (r *Route) URLHost(pairs ...string) (*url.URL, error) {
 	if err != nil {
 		return nil, err
 	}
-	return &url.URL{
+	u := &url.URL{
 		Scheme: "http",
 		Host:   host,
-	}, nil
+	}
+	if s := r.getBuildScheme(); s != "" {
+		u.Scheme = s
+	}
+	return u, nil
 }
 
 // URLPath builds the path part of the URL for a route. See Route.URL().
@@ -558,6 +598,74 @@ func (r *Route) GetPathTemplate() (string, error) {
 	return r.regexp.path.template, nil
 }
 
+// GetPathRegexp returns the expanded regular expression used to match route path.
+// This is useful for building simple REST API documentation and for instrumentation
+// against third-party services.
+// An error will be returned if the route does not define a path.
+func (r *Route) GetPathRegexp() (string, error) {
+	if r.err != nil {
+		return "", r.err
+	}
+	if r.regexp == nil || r.regexp.path == nil {
+		return "", errors.New("mux: route does not have a path")
+	}
+	return r.regexp.path.regexp.String(), nil
+}
+
+// GetQueriesRegexp returns the expanded regular expressions used to match the
+// route queries.
+// This is useful for building simple REST API documentation and for instrumentation
+// against third-party services.
+// An empty list will be returned if the route does not have queries.
+func (r *Route) GetQueriesRegexp() ([]string, error) {
+	if r.err != nil {
+		return nil, r.err
+	}
+	if r.regexp == nil || r.regexp.queries == nil {
+		return nil, errors.New("mux: route doesn't have queries")
+	}
+	var queries []string
+	for _, query := range r.regexp.queries {
+		queries = append(queries, query.regexp.String())
+	}
+	return queries, nil
+}
+
+// GetQueriesTemplates returns the templates used to build the
+// query matching.
+// This is useful for building simple REST API documentation and for instrumentation
+// against third-party services.
+// An empty list will be returned if the route does not define queries.
+func (r *Route) GetQueriesTemplates() ([]string, error) {
+	if r.err != nil {
+		return nil, r.err
+	}
+	if r.regexp == nil || r.regexp.queries == nil {
+		return nil, errors.New("mux: route doesn't have queries")
+	}
+	var queries []string
+	for _, query := range r.regexp.queries {
+		queries = append(queries, query.template)
+	}
+	return queries, nil
+}
+
+// GetMethods returns the methods the route matches against
+// This is useful for building simple REST API documentation and for instrumentation
+// against third-party services.
+// An empty list will be returned if route does not have methods.
+func (r *Route) GetMethods() ([]string, error) {
+	if r.err != nil {
+		return nil, r.err
+	}
+	for _, m := range r.matchers {
+		if methods, ok := m.(methodMatcher); ok {
+			return []string(methods), nil
+		}
+	}
+	return nil, nil
+}
+
 // GetHostTemplate returns the template used to build the
 // route match.
 // This is useful for building simple REST API documentation and for instrumentation
@@ -599,11 +707,22 @@ func (r *Route) buildVars(m map[string]string) map[string]string {
 
 // parentRoute allows routes to know about parent host and path definitions.
 type parentRoute interface {
+	getBuildScheme() string
 	getNamedRoutes() map[string]*Route
 	getRegexpGroup() *routeRegexpGroup
 	buildVars(map[string]string) map[string]string
 }
 
+func (r *Route) getBuildScheme() string {
+	if r.buildScheme != "" {
+		return r.buildScheme
+	}
+	if r.parent != nil {
+		return r.parent.getBuildScheme()
+	}
+	return ""
+}
+
 // getNamedRoutes returns the map where named routes are registered.
 func (r *Route) getNamedRoutes() map[string]*Route {
 	if r.parent == nil {

+ 1 - 2
vendor/github.com/gorilla/schema/.travis.yml

@@ -3,11 +3,10 @@ sudo: false
 
 matrix:
   include:
-    - go: 1.3
-    - go: 1.4
     - go: 1.5
     - go: 1.6
     - go: 1.7
+    - go: 1.8
     - go: tip
   allow_failures:
     - go: tip

+ 26 - 2
vendor/github.com/gorilla/schema/README.md

@@ -1,8 +1,10 @@
 schema
 ======
 [![GoDoc](https://godoc.org/github.com/gorilla/schema?status.svg)](https://godoc.org/github.com/gorilla/schema) [![Build Status](https://travis-ci.org/gorilla/schema.png?branch=master)](https://travis-ci.org/gorilla/schema)
+[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/schema/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/schema?badge)
 
-Package gorilla/schema fills a struct with form values.
+
+Package gorilla/schema converts structs to and from form values.
 
 ## Example
 
@@ -36,6 +38,28 @@ func MyHandler(w http.ResponseWriter, r *http.Request) {
 }
 ```
 
+Conversely, contents of a struct can be encoded into form values. Here's a variant of the previous example using the Encoder:
+
+```go
+var encoder = schema.NewEncoder()
+
+func MyHttpRequest() {
+    person := Person{"Jane Doe", "555-5555"}
+    form := url.Values{}
+
+    err := encoder.Encode(person, form)
+
+    if err != nil {
+        // Handle error
+    }
+
+    // Use form values, for example, with an http client
+    client := new(http.Client)
+    res, err := client.PostForm("http://my-api.test", form)
+}
+
+```
+
 To define custom names for fields, use a struct tag "schema". To not populate certain fields, use a dash for the name and it will be ignored:
 
 ```go
@@ -46,7 +70,7 @@ type Person struct {
 }
 ```
 
-The supported field types in the destination struct are:
+The supported field types in the struct are:
 
 * bool
 * float variants (float32, float64)

+ 14 - 11
vendor/github.com/gorilla/schema/cache.go

@@ -18,13 +18,9 @@ var invalidPath = errors.New("schema: invalid path")
 func newCache() *cache {
 	c := cache{
 		m:       make(map[reflect.Type]*structInfo),
-		conv:    make(map[reflect.Kind]Converter),
 		regconv: make(map[reflect.Type]Converter),
 		tag:     "schema",
 	}
-	for k, v := range converters {
-		c.conv[k] = v
-	}
 	return &c
 }
 
@@ -32,11 +28,15 @@ func newCache() *cache {
 type cache struct {
 	l       sync.RWMutex
 	m       map[reflect.Type]*structInfo
-	conv    map[reflect.Kind]Converter
 	regconv map[reflect.Type]Converter
 	tag     string
 }
 
+// registerConverter registers a converter function for a custom type.
+func (c *cache) registerConverter(value interface{}, converterFunc Converter) {
+	c.regconv[reflect.TypeOf(value)] = converterFunc
+}
+
 // parsePath parses a path in dotted notation verifying that it is a valid
 // path to a struct field.
 //
@@ -138,7 +138,12 @@ func (c *cache) create(t reflect.Type, info *structInfo) *structInfo {
 				ft = ft.Elem()
 			}
 			if ft.Kind() == reflect.Struct {
+				bef := len(info.fields)
 				c.create(ft, info)
+				for _, fi := range info.fields[bef:len(info.fields)] {
+					// exclude required check because duplicated to embedded field
+					fi.required = false
+				}
 			}
 		}
 		c.createField(field, info)
@@ -173,7 +178,7 @@ func (c *cache) createField(field reflect.StructField, info *structInfo) {
 		}
 	}
 	if isStruct = ft.Kind() == reflect.Struct; !isStruct {
-		if conv := c.conv[ft.Kind()]; conv == nil {
+		if c.converter(ft) == nil && builtinConverters[ft.Kind()] == nil {
 			// Type is not supported.
 			return
 		}
@@ -184,17 +189,14 @@ func (c *cache) createField(field reflect.StructField, info *structInfo) {
 		name:     field.Name,
 		ss:       isSlice && isStruct,
 		alias:    alias,
+		anon:     field.Anonymous,
 		required: options.Contains("required"),
 	})
 }
 
 // converter returns the converter for a type.
 func (c *cache) converter(t reflect.Type) Converter {
-	conv := c.regconv[t]
-	if conv == nil {
-		conv = c.conv[t.Kind()]
-	}
-	return conv
+	return c.regconv[t]
 }
 
 // ----------------------------------------------------------------------------
@@ -217,6 +219,7 @@ type fieldInfo struct {
 	name     string // field name in the struct.
 	ss       bool   // true if this is a slice of structs.
 	alias    string
+	anon     bool // is an embedded field
 	required bool // tag option
 }
 

+ 1 - 1
vendor/github.com/gorilla/schema/converter.go

@@ -30,7 +30,7 @@ var (
 )
 
 // Default converters for basic types.
-var converters = map[reflect.Kind]Converter{
+var builtinConverters = map[reflect.Kind]Converter{
 	boolType:    convertBool,
 	float32Type: convertFloat32,
 	float64Type: convertFloat64,

+ 100 - 25
vendor/github.com/gorilla/schema/decoder.go

@@ -56,7 +56,7 @@ func (d *Decoder) IgnoreUnknownKeys(i bool) {
 
 // RegisterConverter registers a converter function for a custom type.
 func (d *Decoder) RegisterConverter(value interface{}, converterFunc Converter) {
-	d.cache.regconv[reflect.TypeOf(value)] = converterFunc
+	d.cache.registerConverter(value, converterFunc)
 }
 
 // Decode decodes a map[string][]string to a struct.
@@ -90,7 +90,7 @@ func (d *Decoder) Decode(dst interface{}, src map[string][]string) error {
 	return d.checkRequired(t, src, "")
 }
 
-// checkRequired checks whether requred field empty
+// checkRequired checks whether required fields are empty
 //
 // check type t recursively if t has struct fields, and prefix is same as parsePath: in dotted notation
 //
@@ -106,7 +106,14 @@ func (d *Decoder) checkRequired(t reflect.Type, src map[string][]string, prefix
 		if f.typ.Kind() == reflect.Struct {
 			err := d.checkRequired(f.typ, src, prefix+f.alias+".")
 			if err != nil {
-				return err
+				if !f.anon {
+					return err
+				}
+				// check embedded parent field.
+				err2 := d.checkRequired(f.typ, src, prefix)
+				if err2 != nil {
+					return err
+				}
 			}
 		}
 		if f.required {
@@ -136,6 +143,7 @@ func isEmpty(t reflect.Type, value []string) bool {
 
 // decode fills a struct field using a parsed path.
 func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values []string) error {
+
 	// Get the field walking the struct fields by index.
 	for _, name := range parts[0].path {
 		if v.Type().Kind() == reflect.Ptr {
@@ -146,7 +154,6 @@ func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values
 		}
 		v = v.FieldByName(name)
 	}
-
 	// Don't even bother for unexported fields.
 	if !v.CanSet() {
 		return nil
@@ -189,9 +196,12 @@ func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values
 		// Try to get a converter for the element type.
 		conv := d.cache.converter(elemT)
 		if conv == nil {
-			// As we are not dealing with slice of structs here, we don't need to check if the type
-			// implements TextUnmarshaler interface
-			return fmt.Errorf("schema: converter not found for %v", elemT)
+			conv = builtinConverters[elemT.Kind()]
+			if conv == nil {
+				// As we are not dealing with slice of structs here, we don't need to check if the type
+				// implements TextUnmarshaler interface
+				return fmt.Errorf("schema: converter not found for %v", elemT)
+			}
 		}
 
 		for key, value := range values {
@@ -199,6 +209,26 @@ func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values
 				if d.zeroEmpty {
 					items = append(items, reflect.Zero(elemT))
 				}
+			} else if m := isTextUnmarshaler(v); m.IsValid {
+				u := reflect.New(elemT)
+				if m.IsPtr {
+					u = reflect.New(reflect.PtrTo(elemT).Elem())
+				}
+				if err := u.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(value)); err != nil {
+					return ConversionError{
+						Key:   path,
+						Type:  t,
+						Index: key,
+						Err:   err,
+					}
+				}
+				if m.IsPtr {
+					items = append(items, u.Elem().Addr())
+				} else if u.Kind() == reflect.Ptr {
+					items = append(items, u.Elem())
+				} else {
+					items = append(items, u)
+				}
 			} else if item := conv(value); item.IsValid() {
 				if isPtrElem {
 					ptr := reflect.New(elemT)
@@ -267,32 +297,77 @@ func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values
 					Index: -1,
 				}
 			}
-		} else {
-			// When there's no registered conversion for the custom type, we will check if the type
-			// implements the TextUnmarshaler interface. As the UnmarshalText function should be applied
-			// to the pointer of the type, we convert the value to pointer.
-			if v.CanAddr() {
-				v = v.Addr()
-			}
-
-			if u, ok := v.Interface().(encoding.TextUnmarshaler); ok {
-				if err := u.UnmarshalText([]byte(val)); err != nil {
-					return ConversionError{
-						Key:   path,
-						Type:  t,
-						Index: -1,
-						Err:   err,
-					}
+		} else if m := isTextUnmarshaler(v); m.IsValid {
+			// If the value implements the encoding.TextUnmarshaler interface
+			// apply UnmarshalText as the converter
+			if err := m.Unmarshaler.UnmarshalText([]byte(val)); err != nil {
+				return ConversionError{
+					Key:   path,
+					Type:  t,
+					Index: -1,
+					Err:   err,
 				}
-
+			}
+		} else if conv := builtinConverters[t.Kind()]; conv != nil {
+			if value := conv(val); value.IsValid() {
+				v.Set(value.Convert(t))
 			} else {
-				return fmt.Errorf("schema: converter not found for %v", t)
+				return ConversionError{
+					Key:   path,
+					Type:  t,
+					Index: -1,
+				}
 			}
+		} else {
+			return fmt.Errorf("schema: converter not found for %v", t)
 		}
 	}
 	return nil
 }
 
+func isTextUnmarshaler(v reflect.Value) unmarshaler {
+
+	// Create a new unmarshaller instance
+	m := unmarshaler{}
+
+	// As the UnmarshalText function should be applied
+	// to the pointer of the type, we convert the value to pointer.
+	if v.CanAddr() {
+		v = v.Addr()
+	}
+	if m.Unmarshaler, m.IsValid = v.Interface().(encoding.TextUnmarshaler); m.IsValid {
+		return m
+	}
+
+	// if v is []T or *[]T create new T
+	t := v.Type()
+	if t.Kind() == reflect.Ptr {
+		t = t.Elem()
+	}
+	if t.Kind() == reflect.Slice {
+		// if t is a pointer slice, check if it implements encoding.TextUnmarshaler
+		if t = t.Elem(); t.Kind() == reflect.Ptr {
+			t = reflect.PtrTo(t.Elem())
+			v = reflect.Zero(t)
+			m.IsPtr = true
+			m.Unmarshaler, m.IsValid = v.Interface().(encoding.TextUnmarshaler)
+			return m
+		}
+	}
+
+	v = reflect.New(t)
+	m.Unmarshaler, m.IsValid = v.Interface().(encoding.TextUnmarshaler)
+	return m
+}
+
+// TextUnmarshaler helpers ----------------------------------------------------
+// unmarshaller contains information about a TextUnmarshaler type
+type unmarshaler struct {
+	Unmarshaler encoding.TextUnmarshaler
+	IsPtr       bool
+	IsValid     bool
+}
+
 // Errors ---------------------------------------------------------------------
 
 // ConversionError stores information about a failed conversion.

+ 2 - 2
vendor/github.com/gorilla/schema/doc.go

@@ -24,7 +24,7 @@ The basic usage is really simple. Given this struct:
 
 This is just a simple example and it doesn't make a lot of sense to create
 the map manually. Typically it will come from a http.Request object and
-will be of type url.Values: http.Request.Form or http.Request.MultipartForm:
+will be of type url.Values, http.Request.Form, or http.Request.MultipartForm:
 
 	func MyHandler(w http.ResponseWriter, r *http.Request) {
 		err := r.ParseForm()
@@ -45,7 +45,7 @@ will be of type url.Values: http.Request.Form or http.Request.MultipartForm:
 	}
 
 Note: it is a good idea to set a Decoder instance as a package global,
-because it caches meta-data about structs, and a instance can be shared safely:
+because it caches meta-data about structs, and an instance can be shared safely:
 
 	var decoder = schema.NewDecoder()
 

+ 161 - 0
vendor/github.com/gorilla/schema/encoder.go

@@ -0,0 +1,161 @@
+package schema
+
+import (
+	"errors"
+	"fmt"
+	"reflect"
+	"strconv"
+)
+
+type encoderFunc func(reflect.Value) string
+
+// Encoder encodes values from a struct into url.Values.
+type Encoder struct {
+	cache  *cache
+	regenc map[reflect.Type]encoderFunc
+}
+
+// NewEncoder returns a new Encoder with defaults.
+func NewEncoder() *Encoder {
+	return &Encoder{cache: newCache(), regenc: make(map[reflect.Type]encoderFunc)}
+}
+
+// Encode encodes a struct into map[string][]string.
+//
+// Intended for use with url.Values.
+func (e *Encoder) Encode(src interface{}, dst map[string][]string) error {
+	v := reflect.ValueOf(src)
+
+	return e.encode(v, dst)
+}
+
+// RegisterEncoder registers a converter for encoding a custom type.
+func (e *Encoder) RegisterEncoder(value interface{}, encoder func(reflect.Value) string) {
+	e.regenc[reflect.TypeOf(value)] = encoder
+}
+
+// SetAliasTag changes the tag used to locate custom field aliases.
+// The default tag is "schema".
+func (e *Encoder) SetAliasTag(tag string) {
+	e.cache.tag = tag
+}
+
+func (e *Encoder) encode(v reflect.Value, dst map[string][]string) error {
+	if v.Kind() == reflect.Ptr {
+		v = v.Elem()
+	}
+	if v.Kind() != reflect.Struct {
+		return errors.New("schema: interface must be a struct")
+	}
+	t := v.Type()
+
+	errors := MultiError{}
+
+	for i := 0; i < v.NumField(); i++ {
+		name, opts := fieldAlias(t.Field(i), e.cache.tag)
+		if name == "-" {
+			continue
+		}
+
+		if v.Field(i).Type().Kind() == reflect.Struct {
+			e.encode(v.Field(i), dst)
+			continue
+		}
+
+		encFunc := typeEncoder(v.Field(i).Type(), e.regenc)
+
+		// Encode non-slice types and custom implementations immediately.
+		if encFunc != nil {
+			value := encFunc(v.Field(i))
+			if opts.Contains("omitempty") && v.Field(i).Interface() == reflect.Zero(reflect.TypeOf(v.Field(i).Interface())).Interface() {
+				continue
+			}
+
+			dst[name] = append(dst[name], value)
+			continue
+		}
+
+		if v.Field(i).Type().Kind() == reflect.Slice {
+			encFunc = typeEncoder(v.Field(i).Type().Elem(), e.regenc)
+		}
+
+		if encFunc == nil {
+			errors[v.Field(i).Type().String()] = fmt.Errorf("schema: encoder not found for %v", v.Field(i))
+			continue
+		}
+
+		// Encode a slice.
+		if v.Field(i).Len() == 0 && opts.Contains("omitempty") {
+			continue
+		}
+
+		dst[name] = []string{}
+		for j := 0; j < v.Field(i).Len(); j++ {
+			dst[name] = append(dst[name], encFunc(v.Field(i).Index(j)))
+		}
+	}
+
+	if len(errors) > 0 {
+		return errors
+	}
+	return nil
+}
+
+func typeEncoder(t reflect.Type, reg map[reflect.Type]encoderFunc) encoderFunc {
+	if f, ok := reg[t]; ok {
+		return f
+	}
+
+	switch t.Kind() {
+	case reflect.Bool:
+		return encodeBool
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return encodeInt
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+		return encodeUint
+	case reflect.Float32:
+		return encodeFloat32
+	case reflect.Float64:
+		return encodeFloat64
+	case reflect.Ptr:
+		f := typeEncoder(t.Elem(), reg)
+		return func(v reflect.Value) string {
+			if v.IsNil() {
+				return "null"
+			}
+			return f(v.Elem())
+		}
+	case reflect.String:
+		return encodeString
+	default:
+		return nil
+	}
+}
+
+func encodeBool(v reflect.Value) string {
+	return strconv.FormatBool(v.Bool())
+}
+
+func encodeInt(v reflect.Value) string {
+	return strconv.FormatInt(int64(v.Int()), 10)
+}
+
+func encodeUint(v reflect.Value) string {
+	return strconv.FormatUint(uint64(v.Uint()), 10)
+}
+
+func encodeFloat(v reflect.Value, bits int) string {
+	return strconv.FormatFloat(v.Float(), 'f', 6, bits)
+}
+
+func encodeFloat32(v reflect.Value) string {
+	return encodeFloat(v, 32)
+}
+
+func encodeFloat64(v reflect.Value) string {
+	return encodeFloat(v, 64)
+}
+
+func encodeString(v reflect.Value) string {
+	return v.String()
+}

+ 2 - 0
vendor/github.com/gorilla/securecookie/README.md

@@ -1,6 +1,8 @@
 securecookie
 ============
 [![GoDoc](https://godoc.org/github.com/gorilla/securecookie?status.svg)](https://godoc.org/github.com/gorilla/securecookie) [![Build Status](https://travis-ci.org/gorilla/securecookie.png?branch=master)](https://travis-ci.org/gorilla/securecookie)
+[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/securecookie/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/securecookie?badge)
+
 
 securecookie encodes and decodes authenticated and optionally encrypted 
 cookie values.

+ 14 - 3
vendor/github.com/gorilla/sessions/README.md

@@ -1,6 +1,8 @@
 sessions
 ========
 [![GoDoc](https://godoc.org/github.com/gorilla/sessions?status.svg)](https://godoc.org/github.com/gorilla/sessions) [![Build Status](https://travis-ci.org/gorilla/sessions.png?branch=master)](https://travis-ci.org/gorilla/sessions)
+[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/sessions/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/sessions?badge)
+
 
 gorilla/sessions provides cookie and filesystem sessions and infrastructure for
 custom session backends.
@@ -42,16 +44,22 @@ Let's start with an example that shows the sessions API in a nutshell:
 
 First we initialize a session store calling `NewCookieStore()` and passing a
 secret key used to authenticate the session. Inside the handler, we call
-`store.Get()` to retrieve an existing session or a new one. Then we set some
-session values in session.Values, which is a `map[interface{}]interface{}`.
+`store.Get()` to retrieve an existing session or create a new one. Then we set
+some session values in session.Values, which is a `map[interface{}]interface{}`.
 And finally we call `session.Save()` to save the session in the response.
 
 Important Note: If you aren't using gorilla/mux, you need to wrap your handlers
 with
 [`context.ClearHandler`](http://www.gorillatoolkit.org/pkg/context#ClearHandler)
-as or else you will leak memory! An easy way to do this is to wrap the top-level
+or else you will leak memory! An easy way to do this is to wrap the top-level
 mux when calling http.ListenAndServe:
 
+```go
+	http.ListenAndServe(":8080", context.ClearHandler(http.DefaultServeMux))
+```
+
+The ClearHandler function is provided by the gorilla/context package.
+
 More examples are available [on the Gorilla
 website](http://www.gorillatoolkit.org/pkg/sessions).
 
@@ -63,16 +71,19 @@ Other implementations of the `sessions.Store` interface:
 * [github.com/yosssi/boltstore](https://github.com/yosssi/boltstore) - Bolt
 * [github.com/srinathgs/couchbasestore](https://github.com/srinathgs/couchbasestore) - Couchbase
 * [github.com/denizeren/dynamostore](https://github.com/denizeren/dynamostore) - Dynamodb on AWS
+* [github.com/savaki/dynastore](https://github.com/savaki/dynastore) - DynamoDB on AWS (Official AWS library)
 * [github.com/bradleypeabody/gorilla-sessions-memcache](https://github.com/bradleypeabody/gorilla-sessions-memcache) - Memcache
 * [github.com/dsoprea/go-appengine-sessioncascade](https://github.com/dsoprea/go-appengine-sessioncascade) - Memcache/Datastore/Context in AppEngine
 * [github.com/kidstuff/mongostore](https://github.com/kidstuff/mongostore) - MongoDB
 * [github.com/srinathgs/mysqlstore](https://github.com/srinathgs/mysqlstore) - MySQL
+* [github.com/EnumApps/clustersqlstore](https://github.com/EnumApps/clustersqlstore) - MySQL Cluster
 * [github.com/antonlindstrom/pgstore](https://github.com/antonlindstrom/pgstore) - PostgreSQL
 * [github.com/boj/redistore](https://github.com/boj/redistore) - Redis
 * [github.com/boj/rethinkstore](https://github.com/boj/rethinkstore) - RethinkDB
 * [github.com/boj/riakstore](https://github.com/boj/riakstore) - Riak
 * [github.com/michaeljs1990/sqlitestore](https://github.com/michaeljs1990/sqlitestore) - SQLite
 * [github.com/wader/gormstore](https://github.com/wader/gormstore) - GORM (MySQL, PostgreSQL, SQLite)
+* [github.com/gernest/qlstore](https://github.com/gernest/qlstore) - ql
 
 ## License
 

+ 1 - 2
vendor/github.com/gorilla/sessions/doc.go

@@ -29,8 +29,7 @@ Let's start with an example that shows the sessions API in a nutshell:
 	var store = sessions.NewCookieStore([]byte("something-very-secret"))
 
 	func MyHandler(w http.ResponseWriter, r *http.Request) {
-		// Get a session. We're ignoring the error resulted from decoding an
-		// existing session: Get() always returns a session, even if empty.
+		// Get a session. Get() always returns a session, even if empty.
 		session, err := store.Get(r, "session-name")
 		if err != nil {
 			http.Error(w, err.Error(), http.StatusInternalServerError)

+ 0 - 52
vendor/github.com/jinzhu/gorm/CONTRIBUTING.md

@@ -1,52 +0,0 @@
-# How to Contribute
-
-## Bug Report
-
-- Do a search on GitHub under Issues in case it has already been reported
-- Submit __executable script__ or failing test pull request that could demonstrates the issue is *MUST HAVE*
-
-## Feature Request
-
-- Feature request with pull request is welcome
-- Or it won't be implemented until I (other developers) find it is helpful for my (their) daily work
-
-## Pull Request
-
-- Prefer single commit pull request, that make the git history can be a bit easier to follow.
-- New features need to be covered with tests to make sure your code works as expected, and won't be broken by others in future
-
-## Contributing to Documentation
-
-- You are welcome ;)
-- You can help improve the README by making them more coherent, consistent or readable, and add more godoc documents to make people easier to follow.
-- Blogs & Usage Guides & PPT also welcome, please add them to https://github.com/jinzhu/gorm/wiki/Guides
-
-### Executable script template
-
-```go
-package main
-
-import (
-	_ "github.com/go-sql-driver/mysql"
-	"github.com/jinzhu/gorm"
-	_ "github.com/lib/pq"
-	_ "github.com/mattn/go-sqlite3"
-)
-
-var db *gorm.DB
-
-func init() {
-	var err error
-	db, err = gorm.Open("sqlite3", "test.db")
-	// db, err = gorm.Open("postgres", "user=username dbname=password sslmode=disable")
-	// db, err = gorm.Open("mysql", "user:password@/dbname?charset=utf8&parseTime=True")
-	if err != nil {
-		panic(err)
-	}
-	db.LogMode(true)
-}
-
-func main() {
-	// Your code
-}
-```

+ 3 - 1
vendor/github.com/jinzhu/gorm/association.go

@@ -290,7 +290,9 @@ func (association *Association) Count() int {
 		)
 	}
 
-	query.Model(fieldValue).Count(&count)
+	if err := query.Model(fieldValue).Count(&count).Error; err != nil {
+		association.Error = err
+	}
 	return count
 }
 

+ 12 - 2
vendor/github.com/jinzhu/gorm/callback_create.go

@@ -32,8 +32,18 @@ func beforeCreateCallback(scope *Scope) {
 func updateTimeStampForCreateCallback(scope *Scope) {
 	if !scope.HasError() {
 		now := NowFunc()
-		scope.SetColumn("CreatedAt", now)
-		scope.SetColumn("UpdatedAt", now)
+
+		if createdAtField, ok := scope.FieldByName("CreatedAt"); ok {
+			if createdAtField.IsBlank {
+				createdAtField.Set(now)
+			}
+		}
+
+		if updatedAtField, ok := scope.FieldByName("UpdatedAt"); ok {
+			if updatedAtField.IsBlank {
+				updatedAtField.Set(now)
+			}
+		}
 	}
 }
 

+ 5 - 2
vendor/github.com/jinzhu/gorm/callback_delete.go

@@ -33,10 +33,13 @@ func deleteCallback(scope *Scope) {
 			extraOption = fmt.Sprint(str)
 		}
 
-		if !scope.Search.Unscoped && scope.HasColumn("DeletedAt") {
+		deletedAtField, hasDeletedAtField := scope.FieldByName("DeletedAt")
+
+		if !scope.Search.Unscoped && hasDeletedAtField {
 			scope.Raw(fmt.Sprintf(
-				"UPDATE %v SET deleted_at=%v%v%v",
+				"UPDATE %v SET %v=%v%v%v",
 				scope.QuotedTableName(),
+				scope.Quote(deletedAtField.DBName),
 				scope.AddToVars(NowFunc()),
 				addExtraSpaceIfExist(scope.CombinedConditionSql()),
 				addExtraSpaceIfExist(extraOption),

+ 4 - 2
vendor/github.com/jinzhu/gorm/callback_query.go

@@ -30,7 +30,7 @@ func queryCallback(scope *Scope) {
 	}
 
 	if value, ok := scope.Get("gorm:query_destination"); ok {
-		results = reflect.Indirect(reflect.ValueOf(value))
+		results = indirect(reflect.ValueOf(value))
 	}
 
 	if kind := results.Kind(); kind == reflect.Slice {
@@ -78,7 +78,9 @@ func queryCallback(scope *Scope) {
 				}
 			}
 
-			if scope.db.RowsAffected == 0 && !isSlice {
+			if err := rows.Err(); err != nil {
+				scope.Err(err)
+			} else if scope.db.RowsAffected == 0 && !isSlice {
 				scope.Err(ErrRecordNotFound)
 			}
 		}

+ 35 - 1
vendor/github.com/jinzhu/gorm/callback_query_preload.go

@@ -4,11 +4,17 @@ import (
 	"errors"
 	"fmt"
 	"reflect"
+	"strconv"
 	"strings"
 )
 
 // preloadCallback used to preload associations
 func preloadCallback(scope *Scope) {
+
+	if _, ok := scope.Get("gorm:auto_preload"); ok {
+		autoPreload(scope)
+	}
+
 	if scope.Search.preload == nil || scope.HasError() {
 		return
 	}
@@ -79,6 +85,25 @@ func preloadCallback(scope *Scope) {
 	}
 }
 
+func autoPreload(scope *Scope) {
+	for _, field := range scope.Fields() {
+		if field.Relationship == nil {
+			continue
+		}
+
+		if val, ok := field.TagSettings["PRELOAD"]; ok {
+			if preload, err := strconv.ParseBool(val); err != nil {
+				scope.Err(errors.New("invalid preload option"))
+				return
+			} else if !preload {
+				continue
+			}
+		}
+
+		scope.Search.Preload(field.Name)
+	}
+}
+
 func (scope *Scope) generatePreloadDBWithConditions(conditions []interface{}) (*DB, []interface{}) {
 	var (
 		preloadDB         = scope.NewDB()
@@ -264,7 +289,12 @@ func (scope *Scope) handleManyToManyPreload(field *Field, conditions []interface
 
 	// generate query with join table
 	newScope := scope.New(reflect.New(fieldType).Interface())
-	preloadDB = preloadDB.Table(newScope.TableName()).Model(newScope.Value).Select("*")
+	preloadDB = preloadDB.Table(newScope.TableName()).Model(newScope.Value)
+
+	if len(preloadDB.search.selects) == 0 {
+		preloadDB = preloadDB.Select("*")
+	}
+
 	preloadDB = joinTableHandler.JoinWith(joinTableHandler, preloadDB, scope.Value)
 
 	// preload inline conditions
@@ -310,6 +340,10 @@ func (scope *Scope) handleManyToManyPreload(field *Field, conditions []interface
 		}
 	}
 
+	if err := rows.Err(); err != nil {
+		scope.Err(err)
+	}
+
 	// assign find results
 	var (
 		indirectScopeValue = scope.IndirectValue()

+ 2 - 2
vendor/github.com/jinzhu/gorm/dialect.go

@@ -14,7 +14,7 @@ type Dialect interface {
 	GetName() string
 
 	// SetDB set db for dialect
-	SetDB(db *sql.DB)
+	SetDB(db SQLCommon)
 
 	// BindVar return the placeholder for actual values in SQL statements, in many dbs it is "?", Postgres using $1
 	BindVar(i int) string
@@ -50,7 +50,7 @@ type Dialect interface {
 
 var dialectsMap = map[string]Dialect{}
 
-func newDialect(name string, db *sql.DB) Dialect {
+func newDialect(name string, db SQLCommon) Dialect {
 	if value, ok := dialectsMap[name]; ok {
 		dialect := reflect.New(reflect.TypeOf(value).Elem()).Interface().(Dialect)
 		dialect.SetDB(db)

+ 10 - 6
vendor/github.com/jinzhu/gorm/dialect_common.go

@@ -1,7 +1,6 @@
 package gorm
 
 import (
-	"database/sql"
 	"fmt"
 	"reflect"
 	"regexp"
@@ -15,7 +14,7 @@ type DefaultForeignKeyNamer struct {
 }
 
 type commonDialect struct {
-	db *sql.DB
+	db SQLCommon
 	DefaultForeignKeyNamer
 }
 
@@ -27,12 +26,12 @@ func (commonDialect) GetName() string {
 	return "common"
 }
 
-func (s *commonDialect) SetDB(db *sql.DB) {
+func (s *commonDialect) SetDB(db SQLCommon) {
 	s.db = db
 }
 
 func (commonDialect) BindVar(i int) string {
-	return "$$" // ?
+	return "$$$" // ?
 }
 
 func (commonDialect) Quote(key string) string {
@@ -125,12 +124,12 @@ func (s commonDialect) CurrentDatabase() (name string) {
 
 func (commonDialect) LimitAndOffsetSQL(limit, offset interface{}) (sql string) {
 	if limit != nil {
-		if parsedLimit, err := strconv.ParseInt(fmt.Sprint(limit), 0, 0); err == nil && parsedLimit > 0 {
+		if parsedLimit, err := strconv.ParseInt(fmt.Sprint(limit), 0, 0); err == nil && parsedLimit >= 0 {
 			sql += fmt.Sprintf(" LIMIT %d", parsedLimit)
 		}
 	}
 	if offset != nil {
-		if parsedOffset, err := strconv.ParseInt(fmt.Sprint(offset), 0, 0); err == nil && parsedOffset > 0 {
+		if parsedOffset, err := strconv.ParseInt(fmt.Sprint(offset), 0, 0); err == nil && parsedOffset >= 0 {
 			sql += fmt.Sprintf(" OFFSET %d", parsedOffset)
 		}
 	}
@@ -150,3 +149,8 @@ func (DefaultForeignKeyNamer) BuildForeignKeyName(tableName, field, dest string)
 	keyName = regexp.MustCompile("(_*[^a-zA-Z]+_*|_+)").ReplaceAllString(keyName, "_")
 	return keyName
 }
+
+// IsByteArrayOrSlice returns true of the reflected value is an array or slice
+func IsByteArrayOrSlice(value reflect.Value) bool {
+	return (value.Kind() == reflect.Array || value.Kind() == reflect.Slice) && value.Type().Elem() == reflect.TypeOf(uint8(0))
+}

+ 33 - 3
vendor/github.com/jinzhu/gorm/dialect_mysql.go

@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"reflect"
 	"regexp"
+	"strconv"
 	"strings"
 	"time"
 	"unicode/utf8"
@@ -42,14 +43,28 @@ func (s *mysql) DataTypeOf(field *StructField) string {
 		switch dataValue.Kind() {
 		case reflect.Bool:
 			sqlType = "boolean"
-		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
+		case reflect.Int8:
+			if _, ok := field.TagSettings["AUTO_INCREMENT"]; ok || field.IsPrimaryKey {
+				field.TagSettings["AUTO_INCREMENT"] = "AUTO_INCREMENT"
+				sqlType = "tinyint AUTO_INCREMENT"
+			} else {
+				sqlType = "tinyint"
+			}
+		case reflect.Int, reflect.Int16, reflect.Int32:
 			if _, ok := field.TagSettings["AUTO_INCREMENT"]; ok || field.IsPrimaryKey {
 				field.TagSettings["AUTO_INCREMENT"] = "AUTO_INCREMENT"
 				sqlType = "int AUTO_INCREMENT"
 			} else {
 				sqlType = "int"
 			}
-		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr:
+		case reflect.Uint8:
+			if _, ok := field.TagSettings["AUTO_INCREMENT"]; ok || field.IsPrimaryKey {
+				field.TagSettings["AUTO_INCREMENT"] = "AUTO_INCREMENT"
+				sqlType = "tinyint unsigned AUTO_INCREMENT"
+			} else {
+				sqlType = "tinyint unsigned"
+			}
+		case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uintptr:
 			if _, ok := field.TagSettings["AUTO_INCREMENT"]; ok || field.IsPrimaryKey {
 				field.TagSettings["AUTO_INCREMENT"] = "AUTO_INCREMENT"
 				sqlType = "int unsigned AUTO_INCREMENT"
@@ -87,7 +102,7 @@ func (s *mysql) DataTypeOf(field *StructField) string {
 				}
 			}
 		default:
-			if _, ok := dataValue.Interface().([]byte); ok {
+			if IsByteArrayOrSlice(dataValue) {
 				if size > 0 && size < 65532 {
 					sqlType = fmt.Sprintf("varbinary(%d)", size)
 				} else {
@@ -112,6 +127,21 @@ func (s mysql) RemoveIndex(tableName string, indexName string) error {
 	return err
 }
 
+func (s mysql) LimitAndOffsetSQL(limit, offset interface{}) (sql string) {
+	if limit != nil {
+		if parsedLimit, err := strconv.ParseInt(fmt.Sprint(limit), 0, 0); err == nil && parsedLimit >= 0 {
+			sql += fmt.Sprintf(" LIMIT %d", parsedLimit)
+
+			if offset != nil {
+				if parsedOffset, err := strconv.ParseInt(fmt.Sprint(offset), 0, 0); err == nil && parsedOffset >= 0 {
+					sql += fmt.Sprintf(" OFFSET %d", parsedOffset)
+				}
+			}
+		}
+	}
+	return
+}
+
 func (s mysql) HasForeignKey(tableName string, foreignKeyName string) bool {
 	var count int
 	s.db.QueryRow("SELECT count(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_SCHEMA=? AND TABLE_NAME=? AND CONSTRAINT_NAME=? AND CONSTRAINT_TYPE='FOREIGN KEY'", s.CurrentDatabase(), tableName, foreignKeyName).Scan(&count)

+ 3 - 7
vendor/github.com/jinzhu/gorm/dialect_postgres.go

@@ -30,14 +30,14 @@ func (s *postgres) DataTypeOf(field *StructField) string {
 		switch dataValue.Kind() {
 		case reflect.Bool:
 			sqlType = "boolean"
-		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr:
+		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uintptr:
 			if _, ok := field.TagSettings["AUTO_INCREMENT"]; ok || field.IsPrimaryKey {
 				field.TagSettings["AUTO_INCREMENT"] = "AUTO_INCREMENT"
 				sqlType = "serial"
 			} else {
 				sqlType = "integer"
 			}
-		case reflect.Int64, reflect.Uint64:
+		case reflect.Int64, reflect.Uint32, reflect.Uint64:
 			if _, ok := field.TagSettings["AUTO_INCREMENT"]; ok || field.IsPrimaryKey {
 				field.TagSettings["AUTO_INCREMENT"] = "AUTO_INCREMENT"
 				sqlType = "bigserial"
@@ -65,7 +65,7 @@ func (s *postgres) DataTypeOf(field *StructField) string {
 				sqlType = "hstore"
 			}
 		default:
-			if isByteArrayOrSlice(dataValue) {
+			if IsByteArrayOrSlice(dataValue) {
 				sqlType = "bytea"
 			} else if isUUID(dataValue) {
 				sqlType = "uuid"
@@ -120,10 +120,6 @@ func (postgres) SupportLastInsertID() bool {
 	return false
 }
 
-func isByteArrayOrSlice(value reflect.Value) bool {
-	return (value.Kind() == reflect.Array || value.Kind() == reflect.Slice) && value.Type().Elem() == reflect.TypeOf(uint8(0))
-}
-
 func isUUID(value reflect.Value) bool {
 	if value.Kind() != reflect.Array || value.Type().Len() != 16 {
 		return false

+ 1 - 2
vendor/github.com/jinzhu/gorm/dialect_sqlite3.go

@@ -12,7 +12,6 @@ type sqlite3 struct {
 }
 
 func init() {
-	RegisterDialect("sqlite", &sqlite3{})
 	RegisterDialect("sqlite3", &sqlite3{})
 }
 
@@ -55,7 +54,7 @@ func (s *sqlite3) DataTypeOf(field *StructField) string {
 				sqlType = "datetime"
 			}
 		default:
-			if _, ok := dataValue.Interface().([]byte); ok {
+			if IsByteArrayOrSlice(dataValue) {
 				sqlType = "blob"
 			}
 		}

+ 2 - 1
vendor/github.com/jinzhu/gorm/interface.go

@@ -2,7 +2,8 @@ package gorm
 
 import "database/sql"
 
-type sqlCommon interface {
+// SQLCommon is the minimal database connection functionality gorm requires.  Implemented by *sql.DB.
+type SQLCommon interface {
 	Exec(query string, args ...interface{}) (sql.Result, error)
 	Prepare(query string) (*sql.Stmt, error)
 	Query(query string, args ...interface{}) (*sql.Rows, error)

+ 2 - 0
vendor/github.com/jinzhu/gorm/join_table_handler.go

@@ -59,6 +59,7 @@ func (s *JoinTableHandler) Setup(relationship *Relationship, tableName string, s
 	s.TableName = tableName
 
 	s.Source = JoinTableSource{ModelType: source}
+	s.Source.ForeignKeys = []JoinTableForeignKey{}
 	for idx, dbName := range relationship.ForeignFieldNames {
 		s.Source.ForeignKeys = append(s.Source.ForeignKeys, JoinTableForeignKey{
 			DBName:            relationship.ForeignDBNames[idx],
@@ -67,6 +68,7 @@ func (s *JoinTableHandler) Setup(relationship *Relationship, tableName string, s
 	}
 
 	s.Destination = JoinTableSource{ModelType: destination}
+	s.Destination.ForeignKeys = []JoinTableForeignKey{}
 	for idx, dbName := range relationship.AssociationForeignFieldNames {
 		s.Destination.ForeignKeys = append(s.Destination.ForeignKeys, JoinTableForeignKey{
 			DBName:            relationship.AssociationForeignDBNames[idx],

+ 4 - 2
vendor/github.com/jinzhu/gorm/logger.go

@@ -7,6 +7,7 @@ import (
 	"os"
 	"reflect"
 	"regexp"
+	"strconv"
 	"time"
 	"unicode"
 )
@@ -73,8 +74,8 @@ var LogFormatter = func(values ...interface{}) (messages []interface{}) {
 			if numericPlaceHolderRegexp.MatchString(values[3].(string)) {
 				sql = values[3].(string)
 				for index, value := range formattedValues {
-					placeholder := fmt.Sprintf(`\$%d`, index+1)
-					sql = regexp.MustCompile(placeholder).ReplaceAllString(sql, value)
+					placeholder := fmt.Sprintf(`\$%d([^\d]|$)`, index+1)
+					sql = regexp.MustCompile(placeholder).ReplaceAllString(sql, value+"$1")
 				}
 			} else {
 				formattedValuesLength := len(formattedValues)
@@ -87,6 +88,7 @@ var LogFormatter = func(values ...interface{}) (messages []interface{}) {
 			}
 
 			messages = append(messages, sql)
+			messages = append(messages, fmt.Sprintf(" \n\033[36;31m[%v]\033[0m ", strconv.FormatInt(values[5].(int64), 10)+" rows affected or returned "))
 		} else {
 			messages = append(messages, "\033[31;1m")
 			messages = append(messages, values[2:]...)

+ 80 - 64
vendor/github.com/jinzhu/gorm/main.go

@@ -11,21 +11,23 @@ import (
 
 // DB contains information for current db connection
 type DB struct {
-	Value             interface{}
-	Error             error
-	RowsAffected      int64
-	callbacks         *Callback
-	db                sqlCommon
-	parent            *DB
-	search            *search
+	Value        interface{}
+	Error        error
+	RowsAffected int64
+
+	// single db
+	db                SQLCommon
+	blockGlobalUpdate bool
 	logMode           int
 	logger            logger
-	dialect           Dialect
-	singularTable     bool
-	source            string
+	search            *search
 	values            map[string]interface{}
-	joinTableHandlers map[string]JoinTableHandler
-	blockGlobalUpdate bool
+
+	// global db
+	parent        *DB
+	callbacks     *Callback
+	dialect       Dialect
+	singularTable bool
 }
 
 // Open initialize a new db connection, need to import driver first, e.g:
@@ -39,16 +41,13 @@ type DB struct {
 //    // import _ "github.com/jinzhu/gorm/dialects/postgres"
 //    // import _ "github.com/jinzhu/gorm/dialects/sqlite"
 //    // import _ "github.com/jinzhu/gorm/dialects/mssql"
-func Open(dialect string, args ...interface{}) (*DB, error) {
-	var db DB
-	var err error
-
+func Open(dialect string, args ...interface{}) (db *DB, err error) {
 	if len(args) == 0 {
 		err = errors.New("invalid database source")
 		return nil, err
 	}
 	var source string
-	var dbSQL sqlCommon
+	var dbSQL SQLCommon
 
 	switch value := args[0].(type) {
 	case string:
@@ -60,44 +59,28 @@ func Open(dialect string, args ...interface{}) (*DB, error) {
 			source = args[1].(string)
 		}
 		dbSQL, err = sql.Open(driver, source)
-	case sqlCommon:
-		source = reflect.Indirect(reflect.ValueOf(value)).FieldByName("dsn").String()
+	case SQLCommon:
 		dbSQL = value
 	}
 
-	db = DB{
-		dialect:   newDialect(dialect, dbSQL.(*sql.DB)),
+	db = &DB{
+		db:        dbSQL,
 		logger:    defaultLogger,
-		callbacks: DefaultCallback,
-		source:    source,
 		values:    map[string]interface{}{},
-		db:        dbSQL,
+		callbacks: DefaultCallback,
+		dialect:   newDialect(dialect, dbSQL),
 	}
-	db.parent = &db
-
-	if err == nil {
-		err = db.DB().Ping() // Send a ping to make sure the database connection is alive.
-		if err != nil {
-			db.DB().Close()
+	db.parent = db
+	if err != nil {
+		return
+	}
+	// Send a ping to make sure the database connection is alive.
+	if d, ok := dbSQL.(*sql.DB); ok {
+		if err = d.Ping(); err != nil {
+			d.Close()
 		}
 	}
-
-	return &db, err
-}
-
-// Close close current db connection
-func (s *DB) Close() error {
-	return s.parent.db.(*sql.DB).Close()
-}
-
-// DB get `*sql.DB` from current connection
-func (s *DB) DB() *sql.DB {
-	return s.db.(*sql.DB)
-}
-
-// Dialect get dialect
-func (s *DB) Dialect() Dialect {
-	return s.parent.dialect
+	return
 }
 
 // New clone a new db connection without search conditions
@@ -108,18 +91,35 @@ func (s *DB) New() *DB {
 	return clone
 }
 
-// NewScope create a scope for current operation
-func (s *DB) NewScope(value interface{}) *Scope {
-	dbClone := s.clone()
-	dbClone.Value = value
-	return &Scope{db: dbClone, Search: dbClone.search.clone(), Value: value}
+type closer interface {
+	Close() error
+}
+
+// Close close current db connection.  If database connection is not an io.Closer, returns an error.
+func (s *DB) Close() error {
+	if db, ok := s.parent.db.(closer); ok {
+		return db.Close()
+	}
+	return errors.New("can't close current db")
+}
+
+// DB get `*sql.DB` from current connection
+// If the underlying database connection is not a *sql.DB, returns nil
+func (s *DB) DB() *sql.DB {
+	db, _ := s.db.(*sql.DB)
+	return db
 }
 
 // CommonDB return the underlying `*sql.DB` or `*sql.Tx` instance, mainly intended to allow coexistence with legacy non-GORM code.
-func (s *DB) CommonDB() sqlCommon {
+func (s *DB) CommonDB() SQLCommon {
 	return s.db
 }
 
+// Dialect get dialect
+func (s *DB) Dialect() Dialect {
+	return s.parent.dialect
+}
+
 // Callback return `Callbacks` container, you could add/change/delete callbacks with it
 //     db.Callback().Create().Register("update_created_at", updateCreated)
 // Refer https://jinzhu.github.io/gorm/development.html#callbacks
@@ -161,6 +161,22 @@ func (s *DB) SingularTable(enable bool) {
 	s.parent.singularTable = enable
 }
 
+// NewScope create a scope for current operation
+func (s *DB) NewScope(value interface{}) *Scope {
+	dbClone := s.clone()
+	dbClone.Value = value
+	return &Scope{db: dbClone, Search: dbClone.search.clone(), Value: value}
+}
+
+// QueryExpr returns the query as expr object
+func (s *DB) QueryExpr() *expr {
+	scope := s.NewScope(s.Value)
+	scope.InstanceSet("skip_bindvar", true)
+	scope.prepareQuerySQL()
+
+	return Expr(scope.SQL, scope.SQLVars...)
+}
+
 // Where return a new relation, filter records with given conditions, accepts `map`, `struct` or `string` as conditions, refer http://jinzhu.github.io/gorm/crud.html#query
 func (s *DB) Where(query interface{}, args ...interface{}) *DB {
 	return s.clone().search.Where(query, args...).db
@@ -211,7 +227,7 @@ func (s *DB) Group(query string) *DB {
 }
 
 // Having specify HAVING conditions for GROUP BY
-func (s *DB) Having(query string, values ...interface{}) *DB {
+func (s *DB) Having(query interface{}, values ...interface{}) *DB {
 	return s.clone().search.Having(query, values...).db
 }
 
@@ -447,9 +463,9 @@ func (s *DB) Debug() *DB {
 // Begin begin a transaction
 func (s *DB) Begin() *DB {
 	c := s.clone()
-	if db, ok := c.db.(sqlDb); ok {
+	if db, ok := c.db.(sqlDb); ok && db != nil {
 		tx, err := db.Begin()
-		c.db = interface{}(tx).(sqlCommon)
+		c.db = interface{}(tx).(SQLCommon)
 		c.AddError(err)
 	} else {
 		c.AddError(ErrCantStartTransaction)
@@ -459,7 +475,7 @@ func (s *DB) Begin() *DB {
 
 // Commit commit a transaction
 func (s *DB) Commit() *DB {
-	if db, ok := s.db.(sqlTx); ok {
+	if db, ok := s.db.(sqlTx); ok && db != nil {
 		s.AddError(db.Commit())
 	} else {
 		s.AddError(ErrInvalidTransaction)
@@ -469,7 +485,7 @@ func (s *DB) Commit() *DB {
 
 // Rollback rollback a transaction
 func (s *DB) Rollback() *DB {
-	if db, ok := s.db.(sqlTx); ok {
+	if db, ok := s.db.(sqlTx); ok && db != nil {
 		s.AddError(db.Rollback())
 	} else {
 		s.AddError(ErrInvalidTransaction)
@@ -691,11 +707,11 @@ func (s *DB) GetErrors() []error {
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-// Private Methods For *gorm.DB
+// Private Methods For DB
 ////////////////////////////////////////////////////////////////////////////////
 
 func (s *DB) clone() *DB {
-	db := DB{
+	db := &DB{
 		db:                s.db,
 		parent:            s.parent,
 		logger:            s.logger,
@@ -716,12 +732,12 @@ func (s *DB) clone() *DB {
 		db.search = s.search.clone()
 	}
 
-	db.search.db = &db
-	return &db
+	db.search.db = db
+	return db
 }
 
 func (s *DB) print(v ...interface{}) {
-	s.logger.(logger).Print(v...)
+	s.logger.Print(v...)
 }
 
 func (s *DB) log(v ...interface{}) {
@@ -732,6 +748,6 @@ func (s *DB) log(v ...interface{}) {
 
 func (s *DB) slog(sql string, t time.Time, vars ...interface{}) {
 	if s.logMode == 2 {
-		s.print("sql", fileWithLineNum(), NowFunc().Sub(t), sql, vars)
+		s.print("sql", fileWithLineNum(), NowFunc().Sub(t), sql, vars, s.RowsAffected)
 	}
 }

+ 30 - 13
vendor/github.com/jinzhu/gorm/model_struct.go

@@ -50,6 +50,19 @@ type ModelStruct struct {
 
 // TableName get model's table name
 func (s *ModelStruct) TableName(db *DB) string {
+	if s.defaultTableName == "" && db != nil && s.ModelType != nil {
+		// Set default table name
+		if tabler, ok := reflect.New(s.ModelType).Interface().(tabler); ok {
+			s.defaultTableName = tabler.TableName()
+		} else {
+			tableName := ToDBName(s.ModelType.Name())
+			if db == nil || !db.parent.singularTable {
+				tableName = inflection.Plural(tableName)
+			}
+			s.defaultTableName = tableName
+		}
+	}
+
 	return DefaultTableNameHandler(db, s.defaultTableName)
 }
 
@@ -84,7 +97,11 @@ func (structField *StructField) clone() *StructField {
 		TagSettings:     map[string]string{},
 		Struct:          structField.Struct,
 		IsForeignKey:    structField.IsForeignKey,
-		Relationship:    structField.Relationship,
+	}
+
+	if structField.Relationship != nil {
+		relationship := *structField.Relationship
+		clone.Relationship = &relationship
 	}
 
 	for key, value := range structField.TagSettings {
@@ -141,17 +158,6 @@ func (scope *Scope) GetModelStruct() *ModelStruct {
 
 	modelStruct.ModelType = reflectType
 
-	// Set default table name
-	if tabler, ok := reflect.New(reflectType).Interface().(tabler); ok {
-		modelStruct.defaultTableName = tabler.TableName()
-	} else {
-		tableName := ToDBName(reflectType.Name())
-		if scope.db == nil || !scope.db.parent.singularTable {
-			tableName = inflection.Plural(tableName)
-		}
-		modelStruct.defaultTableName = tableName
-	}
-
 	// Get all fields
 	for i := 0; i < reflectType.NumField(); i++ {
 		if fieldStruct := reflectType.Field(i); ast.IsExported(fieldStruct.Name) {
@@ -192,7 +198,9 @@ func (scope *Scope) GetModelStruct() *ModelStruct {
 					if indirectType.Kind() == reflect.Struct {
 						for i := 0; i < indirectType.NumField(); i++ {
 							for key, value := range parseTagSetting(indirectType.Field(i).Tag) {
-								field.TagSettings[key] = value
+								if _, ok := field.TagSettings[key]; !ok {
+									field.TagSettings[key] = value
+								}
 							}
 						}
 					}
@@ -215,6 +223,15 @@ func (scope *Scope) GetModelStruct() *ModelStruct {
 								subField.IsPrimaryKey = false
 							}
 						}
+
+						if subField.Relationship != nil && subField.Relationship.JoinTableHandler != nil {
+							if joinTableHandler, ok := subField.Relationship.JoinTableHandler.(*JoinTableHandler); ok {
+								newJoinTableHandler := &JoinTableHandler{}
+								newJoinTableHandler.Setup(subField.Relationship, joinTableHandler.TableName, reflectType, joinTableHandler.Destination.ModelType)
+								subField.Relationship.JoinTableHandler = newJoinTableHandler
+							}
+						}
+
 						modelStruct.StructFields = append(modelStruct.StructFields, subField)
 					}
 					continue

+ 27 - 12
vendor/github.com/jinzhu/gorm/scope.go

@@ -58,7 +58,7 @@ func (scope *Scope) NewDB() *DB {
 }
 
 // SQLDB return *sql.DB
-func (scope *Scope) SQLDB() sqlCommon {
+func (scope *Scope) SQLDB() SQLCommon {
 	return scope.db.db
 }
 
@@ -253,15 +253,25 @@ func (scope *Scope) CallMethod(methodName string) {
 
 // AddToVars add value as sql's vars, used to prevent SQL injection
 func (scope *Scope) AddToVars(value interface{}) string {
+	_, skipBindVar := scope.InstanceGet("skip_bindvar")
+
 	if expr, ok := value.(*expr); ok {
 		exp := expr.expr
 		for _, arg := range expr.args {
-			exp = strings.Replace(exp, "?", scope.AddToVars(arg), 1)
+			if skipBindVar {
+				scope.AddToVars(arg)
+			} else {
+				exp = strings.Replace(exp, "?", scope.AddToVars(arg), 1)
+			}
 		}
 		return exp
 	}
 
 	scope.SQLVars = append(scope.SQLVars, value)
+
+	if skipBindVar {
+		return "?"
+	}
 	return scope.Dialect().BindVar(len(scope.SQLVars))
 }
 
@@ -329,18 +339,18 @@ func (scope *Scope) QuotedTableName() (name string) {
 
 // CombinedConditionSql return combined condition sql
 func (scope *Scope) CombinedConditionSql() string {
-	joinSql := scope.joinsSQL()
-	whereSql := scope.whereSQL()
+	joinSQL := scope.joinsSQL()
+	whereSQL := scope.whereSQL()
 	if scope.Search.raw {
-		whereSql = strings.TrimSuffix(strings.TrimPrefix(whereSql, "WHERE ("), ")")
+		whereSQL = strings.TrimSuffix(strings.TrimPrefix(whereSQL, "WHERE ("), ")")
 	}
-	return joinSql + whereSql + scope.groupSQL() +
+	return joinSQL + whereSQL + scope.groupSQL() +
 		scope.havingSQL() + scope.orderSQL() + scope.limitAndOffsetSQL()
 }
 
 // Raw set raw sql
 func (scope *Scope) Raw(sql string) *Scope {
-	scope.SQL = strings.Replace(sql, "$$", "?", -1)
+	scope.SQL = strings.Replace(sql, "$$$", "?", -1)
 	return scope
 }
 
@@ -391,7 +401,7 @@ func (scope *Scope) InstanceGet(name string) (interface{}, bool) {
 func (scope *Scope) Begin() *Scope {
 	if db, ok := scope.SQLDB().(sqlDb); ok {
 		if tx, err := db.Begin(); err == nil {
-			scope.db.db = interface{}(tx).(sqlCommon)
+			scope.db.db = interface{}(tx).(SQLCommon)
 			scope.InstanceSet("gorm:started_transaction", true)
 		}
 	}
@@ -448,8 +458,8 @@ func (scope *Scope) callMethod(methodName string, reflectValue reflect.Value) {
 }
 
 var (
-	columnRegexp        = regexp.MustCompile("^[a-zA-Z]+(\\.[a-zA-Z]+)*$") // only match string like `name`, `users.name`
-	isNumberRegexp      = regexp.MustCompile("^\\s*\\d+\\s*$")             // match if string is number
+	columnRegexp        = regexp.MustCompile("^[a-zA-Z\\d]+(\\.[a-zA-Z\\d]+)*$") // only match string like `name`, `users.name`
+	isNumberRegexp      = regexp.MustCompile("^\\s*\\d+\\s*$")                   // match if string is number
 	comparisonRegexp    = regexp.MustCompile("(?i) (=|<>|>|<|LIKE|IS|IN) ")
 	countingQueryRegexp = regexp.MustCompile("(?i)^count(.+)$")
 )
@@ -673,11 +683,12 @@ func (scope *Scope) buildSelectQuery(clause map[string]interface{}) (str string)
 func (scope *Scope) whereSQL() (sql string) {
 	var (
 		quotedTableName                                = scope.QuotedTableName()
+		deletedAtField, hasDeletedAtField              = scope.FieldByName("DeletedAt")
 		primaryConditions, andConditions, orConditions []string
 	)
 
-	if !scope.Search.Unscoped && scope.HasColumn("deleted_at") {
-		sql := fmt.Sprintf("%v.deleted_at IS NULL", quotedTableName)
+	if !scope.Search.Unscoped && hasDeletedAtField {
+		sql := fmt.Sprintf("%v.%v IS NULL", quotedTableName, scope.Quote(deletedAtField.DBName))
 		primaryConditions = append(primaryConditions, sql)
 	}
 
@@ -929,6 +940,10 @@ func (scope *Scope) pluck(column string, value interface{}) *Scope {
 			scope.Err(rows.Scan(elem))
 			dest.Set(reflect.Append(dest, reflect.ValueOf(elem).Elem()))
 		}
+
+		if err := rows.Err(); err != nil {
+			scope.Err(err)
+		}
 	}
 	return scope
 }

+ 7 - 3
vendor/github.com/jinzhu/gorm/search.go

@@ -67,7 +67,7 @@ func (s *search) Order(value interface{}, reorder ...bool) *search {
 		s.orders = []interface{}{}
 	}
 
-	if value != nil {
+	if value != nil && value != "" {
 		s.orders = append(s.orders, value)
 	}
 	return s
@@ -104,8 +104,12 @@ func (s *search) Group(query string) *search {
 	return s
 }
 
-func (s *search) Having(query string, values ...interface{}) *search {
-	s.havingConditions = append(s.havingConditions, map[string]interface{}{"query": query, "args": values})
+func (s *search) Having(query interface{}, values ...interface{}) *search {
+	if val, ok := query.(*expr); ok {
+		s.havingConditions = append(s.havingConditions, map[string]interface{}{"query": val.expr, "args": val.args})
+	} else {
+		s.havingConditions = append(s.havingConditions, map[string]interface{}{"query": query, "args": values})
+	}
 	return s
 }
 

+ 1 - 1
vendor/github.com/jinzhu/gorm/test_all.sh

@@ -1,4 +1,4 @@
-dialects=("postgres" "mysql" "sqlite")
+dialects=("postgres" "mysql" "mssql" "sqlite")
 
 for dialect in "${dialects[@]}" ; do
     GORM_DIALECT=${dialect} go test

+ 19 - 1
vendor/github.com/jinzhu/gorm/utils.go

@@ -97,6 +97,9 @@ func ToDBName(name string) string {
 				}
 			} else {
 				buf.WriteRune(v)
+				if i == len(value)-2 && nextCase == upper {
+					buf.WriteRune('_')
+				}
 			}
 		} else {
 			currCase = upper
@@ -137,7 +140,7 @@ func toQueryMarks(primaryValues [][]interface{}) string {
 
 	for _, primaryValue := range primaryValues {
 		var marks []string
-		for _, _ = range primaryValue {
+		for range primaryValue {
 			marks = append(marks, "?")
 		}
 
@@ -182,6 +185,21 @@ func fileWithLineNum() string {
 }
 
 func isBlank(value reflect.Value) bool {
+	switch value.Kind() {
+	case reflect.String:
+		return value.Len() == 0
+	case reflect.Bool:
+		return !value.Bool()
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return value.Int() == 0
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		return value.Uint() == 0
+	case reflect.Float32, reflect.Float64:
+		return value.Float() == 0
+	case reflect.Interface, reflect.Ptr:
+		return value.IsNil()
+	}
+
 	return reflect.DeepEqual(value.Interface(), reflect.Zero(value.Type()).Interface())
 }
 

+ 56 - 0
vendor/gogs.carducci-dante.gov.it/andrea.fazzi/karmen/config/config.go

@@ -0,0 +1,56 @@
+package config
+
+const (
+	LOG_LEVEL_OFF = iota
+	LOG_LEVEL_INFO
+	LOG_LEVEL_DEBUG
+)
+
+var (
+	// Database
+
+	DBConnection      = "root:password@tcp(localhost:3306)/karmen_dev"
+	DBConnectionOpts  = "charset=utf8&parseTime=True&loc=Local"
+	ResetOnBoot       = false
+	AutoMigrateOnBoot = true
+	RegenerateOnBoot  = false
+
+	AdminUsername = "admin"
+	AdminPassword = "admin"
+
+	Domain = "foo.org"
+
+	// LDAP
+
+	LDAPHostAddress  = "localhost"
+	LDAPHostPort     = 389
+	LDAPBindPassword = "admin"
+	LDAPBindUser     = "cn=admin,dc=foo,dc=org"
+	LDAPRootDN       = "dc=foo,dc=org"
+
+	LDAPPeopleOU        = "Persone"
+	LDAPTeacherOU       = "Docenti"
+	LDAPGroupsOU        = "Gruppi"
+	LDAPTeachersGroupOU = "Docenti"
+
+	// LimeSurvey Remote Control URL
+
+	LSAPIRemoteControlUrl = "http://localhost:8081/index.php/admin/remotecontrol"
+	LSAPIUsername         = "admin"
+	LSAPIPassword         = "password"
+
+	// WebDAV
+
+	WebDAVUrl      = "http://localhost:8082/remote.php/webdav/"
+	WebDavUsername = "admin"
+	WebDAVPassword = "password"
+
+	// Document generator
+
+	DocumentRootDir  = "documents/"
+	DocumentConfigFn = "config.yaml"
+
+	// Logging
+
+	LogLevel = LOG_LEVEL_DEBUG
+)

+ 42 - 0
vendor/gogs.carducci-dante.gov.it/andrea.fazzi/karmen/datasource/edt/edt.go

@@ -0,0 +1,42 @@
+package edt
+
+import (
+	"encoding/csv"
+	"strings"
+)
+
+type EDT struct {
+	records []map[string]interface{}
+}
+
+func NewImporter(data string, fields map[string]string) (*EDT, error) {
+	reader := csv.NewReader(strings.NewReader(string(data)))
+	csvData, err := reader.ReadAll()
+	if err != nil {
+		return nil, err
+	}
+
+	csvFields := csvData[0][:]
+
+	edt := &EDT{}
+	edt.records = make([]map[string]interface{}, 0)
+
+	n := len(csvData)
+	for i := 1; i < n; i++ {
+		record := make(map[string]interface{}, 0)
+		for pos, value := range csvData[i] {
+			if field := fields[csvFields[pos]]; field != "" {
+				record[field] = value
+			} else {
+				field := csvFields[pos]
+				record[field] = value
+			}
+		}
+		edt.records = append(edt.records, record)
+	}
+	return edt, nil
+}
+
+func (edt *EDT) Records() []map[string]interface{} {
+	return edt.records
+}

+ 11 - 0
vendor/gogs.carducci-dante.gov.it/andrea.fazzi/karmen/datasource/importer.go

@@ -0,0 +1,11 @@
+package datasource
+
+import "github.com/jinzhu/gorm"
+
+type Importer interface {
+	Records() []map[string]interface{}
+}
+
+type Importable interface {
+	Create(*gorm.DB, map[string]interface{}) error
+}

+ 173 - 0
vendor/gogs.carducci-dante.gov.it/andrea.fazzi/karmen/orm/activity.go

@@ -0,0 +1,173 @@
+package orm
+
+import "github.com/jinzhu/gorm"
+
+type Activity struct {
+	gorm.Model
+	Name      string
+	Class     Class
+	Teacher   Teacher
+	Subject   Subject
+	ClassID   uint `schema:"class_id"`
+	TeacherID uint `schema:"teacher_id"`
+	SubjectID uint `schema:"subject_id"`
+	Hours     int
+
+	prevTeacherID uint
+}
+
+func (a *Activity) updateRelatedTeacher(tx *gorm.DB, teacherID uint) {
+	var (
+		teacher Teacher
+		class   Class
+		subject Subject
+	)
+	if err := tx.Find(&teacher, teacherID).Error; err != nil {
+		panic(err)
+	}
+
+	// Calculate teacher hours
+
+	teacher.CalcHours(tx)
+
+	// Teacher -> Classes association
+
+	if err := tx.First(&class, a.ClassID).Error; err != nil {
+		panic(err)
+	}
+
+	if err := tx.Model(&teacher).Association("Classes").Append(class).Error; err != nil {
+		panic(err)
+	}
+
+	// Teacher -> Subjects association
+
+	if err := tx.First(&subject, a.SubjectID).Error; err != nil {
+		panic(err)
+	}
+
+	if err := tx.Model(&teacher).Association("Subjects").Append(subject).Error; err != nil {
+		panic(err)
+	}
+
+}
+
+func (a *Activity) deleteRelatedTeacherAssociations(tx *gorm.DB, teacherID uint) {
+	var (
+		teacher Teacher
+		class   Class
+		subject Subject
+	)
+	if err := tx.Find(&teacher, teacherID).Error; err != nil {
+		panic(err)
+	}
+
+	// Calculate teacher hours
+
+	teacher.CalcHours(tx)
+
+	// Teacher -> Classes association
+
+	if err := tx.First(&class, a.ClassID).Error; err != nil {
+		panic(err)
+	}
+
+	if err := tx.Preload("Classes").Find(&teacher, teacher.ID).Error; err != nil {
+		panic(err)
+	}
+
+	if err := tx.Model(&teacher).Association("Classes").Delete(class).Error; err != nil {
+		panic(err)
+	}
+
+	if err := tx.Preload("Classes").Find(&teacher, teacher.ID).Error; err != nil {
+		panic(err)
+	}
+
+	// Teacher -> Subjects association
+
+	if err := tx.First(&subject, a.SubjectID).Error; err != nil {
+		panic(err)
+	}
+
+	if err := tx.Model(&teacher).Association("Subjects").Delete(subject).Error; err != nil {
+		panic(err)
+	}
+
+}
+
+func (a *Activity) createUpdateActivity(tx *gorm.DB) {
+	if (a.TeacherID != a.prevTeacherID) && (a.prevTeacherID != 0) {
+		a.deleteRelatedTeacherAssociations(tx, a.prevTeacherID)
+	}
+	if a.TeacherID != 0 {
+		a.updateRelatedTeacher(tx, a.TeacherID)
+	}
+}
+
+func (a *Activity) deleteActivity(tx *gorm.DB) {
+	if a.TeacherID != 0 {
+		var (
+			teacher Teacher
+			class   Class
+			subject Subject
+		)
+		if err := tx.Find(&teacher, a.TeacherID).Error; err != nil {
+			panic(err)
+		}
+
+		// Calculate teacher hours
+
+		teacher.CalcHours(tx)
+
+		// Teacher -> Classes association
+
+		if err := tx.First(&class, a.ClassID).Error; err != nil {
+			panic(err)
+		}
+
+		if err := tx.Model(&teacher).Association("Classes").Delete(class).Error; err != nil {
+			panic(err)
+		}
+
+		// Teacher -> Subjects association
+
+		if err := tx.First(&subject, a.SubjectID).Error; err != nil {
+			panic(err)
+		}
+
+		if err := tx.Model(&teacher).Association("Subjects").Delete(subject).Error; err != nil {
+			panic(err)
+		}
+	}
+
+}
+
+func (a *Activity) AfterCreate(tx *gorm.DB) error {
+	a.createUpdateActivity(tx)
+	return nil
+}
+
+func (a *Activity) BeforeUpdate(tx *gorm.DB) error {
+	var currActivity Activity
+	if err := tx.First(&currActivity, a.ID).Error; err != nil {
+		panic(err)
+	}
+	a.prevTeacherID = currActivity.TeacherID
+	return nil
+}
+
+func (a *Activity) AfterSave(tx *gorm.DB) error {
+	a.createUpdateActivity(tx)
+	return nil
+}
+
+// func (a *Activity) AfterUpdate(tx *gorm.DB) error {
+// 	a.createUpdateActivity(tx)
+// 	return nil
+// }
+
+func (a *Activity) AfterDelete(tx *gorm.DB) error {
+	a.deleteActivity(tx)
+	return nil
+}

+ 32 - 0
vendor/gogs.carducci-dante.gov.it/andrea.fazzi/karmen/orm/class.go

@@ -0,0 +1,32 @@
+package orm
+
+import (
+	"errors"
+
+	"github.com/jinzhu/gorm"
+)
+
+type Class struct {
+	gorm.Model
+	Name          string
+	CoordinatorID uint `schema:"coordinator_id"`
+	MinuterID     uint `schema:"minuter_id"`
+	Coordinator   Teacher
+	Minuter       Teacher
+	Teachers      []Teacher `gorm:"many2many:teacher_classes;"`
+	Students      []Student
+}
+
+func (t *Class) Create(db *gorm.DB, record map[string]interface{}) error {
+	result := new(Class)
+
+	if name := record["name"]; name == nil {
+		return errors.New("Error in creating class: field name is empty")
+	} else {
+		result.Name = name.(string)
+	}
+
+	db.Create(result)
+
+	return nil
+}

+ 32 - 0
vendor/gogs.carducci-dante.gov.it/andrea.fazzi/karmen/orm/credential.go

@@ -0,0 +1,32 @@
+package orm
+
+import (
+	"crypto/sha1"
+	"encoding/base64"
+	"fmt"
+	"strings"
+)
+
+type Credential struct {
+	Name            string
+	Surname         string
+	Username        string
+	Password        string
+	Email           string
+	TelephoneNumber string
+}
+
+func (c *Credential) sanitize(s string) string {
+	r := strings.NewReplacer("'", "", "-", "", " ", "")
+	return strings.ToLower(r.Replace(s))
+}
+
+func (c *Credential) GenerateUsername() string {
+	return strings.Join([]string{c.sanitize(c.Name), c.sanitize(c.Surname)}, ".")
+}
+
+func (c *Credential) GenerateSaltedPassword(password string) string {
+	bs := sha1.Sum([]byte(password + "salt"))
+	str := base64.StdEncoding.EncodeToString(append(bs[:], []byte("salt")...))
+	return fmt.Sprintf("%s", str)
+}

+ 16 - 0
vendor/gogs.carducci-dante.gov.it/andrea.fazzi/karmen/orm/issue.go

@@ -0,0 +1,16 @@
+package orm
+
+import "github.com/jinzhu/gorm"
+
+type Issue struct {
+	gorm.Model
+	Description string
+	Type        uint
+	Reported    bool
+	TeacherID   uint
+	Teacher     Teacher
+}
+
+func (i *Issue) newTeacherMaxHoursIssue(t *Teacher) *Issue {
+	return nil
+}

+ 298 - 0
vendor/gogs.carducci-dante.gov.it/andrea.fazzi/karmen/orm/orm.go

@@ -0,0 +1,298 @@
+package orm
+
+import (
+	"errors"
+	"fmt"
+	"strings"
+
+	"github.com/jinzhu/gorm"
+	_ "github.com/jinzhu/gorm/dialects/mysql"
+	"gogs.carducci-dante.gov.it/andrea.fazzi/karmen/datasource"
+)
+
+const (
+	ADMINISTRATIVE_ROLE_PERSONAL = iota
+	ADMINISTRATIVE_ROLE_TEACHING
+	ADMINISTRATIVE_ROLE_DSGA
+	ADMINISTRATIVE_ROLE_SCHOOL_CARETAKER
+	ADMINISTRATIVE_ROLE_TECHNICAL_ASSISTANT
+	ADMINISTRATIVE_ROLE_LIBRARIAN
+	ADMINISTRATIVE_ROLE_UNDEFINED
+
+	IssueTeacherHours = iota
+)
+
+var currDB *gorm.DB
+
+type Timetable [][]Activity
+
+type Person interface {
+	GetCredential() *Credential
+}
+
+type TheBoss struct {
+	gorm.Model
+	Credential
+	Teachers []Teacher
+}
+
+type Address struct {
+	City    string
+	Street  string
+	ZipCode string
+	Country string
+}
+
+type Location struct {
+	Id      string
+	Name    string
+	Address *Address
+}
+
+type Room struct {
+	Id       string
+	Name     string
+	Capacity int
+	Location *Location
+}
+
+type Desk struct {
+	Id       string
+	Name     string
+	Students []*Student
+	Teacher  *Teacher
+}
+
+type Student struct {
+	gorm.Model
+	Credential
+	Class *Class
+}
+
+type Director struct {
+	gorm.Model
+	Credential
+}
+
+type Administrative struct {
+	gorm.Model
+	Credential
+	Role int
+}
+
+type Office struct {
+	gorm.Model
+	Credential
+	Persons []Person
+}
+
+type Department struct {
+	gorm.Model
+	Name     string
+	Subjects []Subject
+	Teachers []Teacher `gorm:"many2many:department_teachers;"`
+}
+
+func (b *TheBoss) Create(db *gorm.DB, record map[string]interface{}) error {
+	if record["role"] == "boss" {
+		result := new(TheBoss)
+		result.Name = record["name"].(string)
+		result.Surname = record["surname"].(string)
+		result.Username = result.GenerateUsername()
+		result.Password = result.GenerateSaltedPassword(result.Username)
+		db.Create(result)
+	} else {
+		boss := TheBoss{}
+		teacher := Teacher{}
+		db.Model(TheBoss{}).First(&boss)
+		db.Where("username=?", record["username"]).First(&teacher)
+		db.Model(&boss).Association("Teachers").Append(&teacher)
+	}
+
+	return nil
+}
+
+func (a *Administrative) Create(db *gorm.DB, record map[string]interface{}) error {
+	result := Administrative{}
+	result.Name = record["name"].(string)
+	result.Surname = record["surname"].(string)
+	result.Username = result.GenerateUsername()
+	result.Password = result.GenerateSaltedPassword(result.Username)
+
+	switch record["role"] {
+	case "personal_administrator":
+		result.Role = ADMINISTRATIVE_ROLE_PERSONAL
+	case "teaching_administrator":
+		result.Role = ADMINISTRATIVE_ROLE_TEACHING
+	case "school_caretaker":
+		result.Role = ADMINISTRATIVE_ROLE_SCHOOL_CARETAKER
+	case "technical_assistant":
+		result.Role = ADMINISTRATIVE_ROLE_TECHNICAL_ASSISTANT
+	case "librarian":
+		result.Role = ADMINISTRATIVE_ROLE_LIBRARIAN
+	case "dsga":
+		result.Role = ADMINISTRATIVE_ROLE_DSGA
+	default:
+		result.Role = ADMINISTRATIVE_ROLE_UNDEFINED
+	}
+
+	db.Create(&result)
+	return nil
+}
+
+func (d *Director) Create(db *gorm.DB, record map[string]interface{}) error {
+	result := new(Director)
+
+	if name := record["name"]; name == nil {
+		return errors.New("Error in creating director: field name is empty")
+	} else {
+		result.Name = name.(string)
+	}
+	if surname := record["surname"]; surname == nil {
+		return errors.New("Error in creating director: field surname is empty")
+	} else {
+		result.Surname = surname.(string)
+	}
+
+	// Generate username and initial password
+
+	result.Username = result.GenerateUsername()
+	result.Password = result.GenerateSaltedPassword(result.Username)
+
+	db.Set("gorm:save_associations", false).Create(result)
+
+	return nil
+}
+
+func (t *Student) Create(db *gorm.DB, record map[string]interface{}) error {
+	result := new(Student)
+
+	if name := record["name"]; name == nil {
+		return errors.New("Error in creating student: field name is empty")
+	} else {
+		result.Name = name.(string)
+	}
+	if surname := record["surname"]; surname == nil {
+		return errors.New("Error in creating student: field surname is empty")
+	} else {
+		result.Surname = surname.(string)
+	}
+
+	// Generate username and initial password
+
+	result.Username = result.GenerateUsername()
+	result.Password = result.GenerateSaltedPassword(result.Username)
+
+	db.Set("gorm:save_associations", false).Create(result)
+
+	return nil
+}
+
+func (d *Department) Create(db *gorm.DB, record map[string]interface{}) error {
+	result := new(Department)
+
+	if name := record["name"]; name == nil {
+		return errors.New("Error in updating subject: field name is empty")
+	} else {
+		result.Name = name.(string)
+	}
+
+	db.Set("gorm:save_associations", false).Create(result)
+
+	// Handle department-subjects relationship
+
+	for _, s := range record["subjects"].([]interface{}) {
+		subject := Subject{}
+		db.Where("name=?", strings.TrimSpace(s.(string))).Find(&subject)
+		db.Model(result).Association("Subjects").Append(subject)
+	}
+
+	// Handle department-teachers relationship
+
+	allTeachers := make([]Teacher, 0)
+	db.Find(&allTeachers)
+
+	for _, t := range allTeachers {
+		db.Model(&t).Related(&t.Subjects, "Subjects")
+		for _, s := range t.Subjects {
+			for _, depS := range result.Subjects {
+				if s.Name == depS.Name {
+					db.Model(result).Association("Teachers").Append(t)
+				}
+			}
+		}
+	}
+
+	return nil
+}
+
+func Import(importable datasource.Importable, importer datasource.Importer) error {
+	for _, record := range importer.Records() {
+		err := importable.Create(currDB, record)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func New(connection string) (*gorm.DB, error) {
+	db, err := gorm.Open("mysql", connection)
+	if err != nil {
+		return nil, err
+	}
+	return db, nil
+}
+
+func Use(db *gorm.DB) {
+	currDB = db
+}
+
+func DB() *gorm.DB {
+	return currDB
+}
+
+func Reset() {
+	currDB.DropTableIfExists(
+		&School{},
+		&Subject{},
+		&Teacher{},
+		&Class{},
+		&Activity{},
+		&Student{},
+		&Department{},
+		&TheBoss{},
+		&Administrative{},
+		&Director{},
+		&Issue{},
+	)
+
+	currDB.Exec("TRUNCATE teacher_subjects;")
+	currDB.Exec("TRUNCATE teacher_classes;")
+	currDB.Exec("TRUNCATE department_teachers;")
+}
+
+func AutoMigrate() {
+	if err := currDB.AutoMigrate(
+		&School{},
+		&Subject{},
+		&Teacher{},
+		&Class{},
+		&Activity{},
+		&Department{},
+		&TheBoss{},
+		&Administrative{},
+		&Student{},
+		&Director{},
+		&Issue{},
+	).Error; err != nil {
+		panic(err)
+	}
+
+}
+
+func GetTeacher(username string) (*Teacher, error) {
+	teacher := Teacher{}
+	currDB.Where(fmt.Sprintf("username='%s'", username)).Find(&teacher)
+	return &teacher, nil
+}

+ 10 - 0
vendor/gogs.carducci-dante.gov.it/andrea.fazzi/karmen/orm/school.go

@@ -0,0 +1,10 @@
+package orm
+
+import "github.com/jinzhu/gorm"
+
+type School struct {
+	gorm.Model
+	Name    string
+	Address string
+	Domain  string
+}

+ 26 - 0
vendor/gogs.carducci-dante.gov.it/andrea.fazzi/karmen/orm/subject.go

@@ -0,0 +1,26 @@
+package orm
+
+import (
+	"errors"
+
+	"github.com/jinzhu/gorm"
+)
+
+type Subject struct {
+	gorm.Model
+	Name         string
+	DepartmentId uint
+	TeacherID    uint
+	Teachers     []Teacher `gorm:"many2many:teacher_subjects;"`
+}
+
+func (s *Subject) Create(db *gorm.DB, record map[string]interface{}) error {
+	result := new(Subject)
+	if name := record["name"]; name == nil {
+		return errors.New("Error in updating subject: field name is empty")
+	} else {
+		result.Name = name.(string)
+	}
+	db.Create(result)
+	return nil
+}

+ 278 - 0
vendor/gogs.carducci-dante.gov.it/andrea.fazzi/karmen/orm/teacher.go

@@ -0,0 +1,278 @@
+package orm
+
+import (
+	"errors"
+	"fmt"
+
+	"github.com/jinzhu/gorm"
+	"gogs.carducci-dante.gov.it/andrea.fazzi/karmen/config"
+)
+
+type Teacher struct {
+	gorm.Model
+	Credential
+	Classes     []Class   `gorm:"many2many:teacher_classes;"`
+	Subjects    []Subject `gorm:"many2many:teacher_subjects;"`
+	Activities  []Activity
+	Departments []Department
+	Issues      []Issue
+	Hours       int
+	CurrHours   int
+	TheBossId   uint
+}
+
+func (t *Teacher) Create(db *gorm.DB, record map[string]interface{}) error {
+	result := new(Teacher)
+
+	if name := record["name"]; name == nil {
+		return errors.New("Error in creating teacher: field name is empty")
+	} else {
+		result.Name = name.(string)
+	}
+	if surname := record["surname"]; surname == nil {
+		return errors.New("Error in creating teacher: field surname is empty")
+	} else {
+		result.Surname = surname.(string)
+	}
+
+	db.Create(result)
+
+	return nil
+}
+
+func (t *Teacher) CalcHours(tx *gorm.DB) {
+	var (
+		activities []Activity
+		sum        int
+	)
+
+	if err := tx.Model(t).Related(&activities).Error; err != nil {
+		panic(err)
+	}
+
+	for _, a := range activities {
+		sum += a.Hours
+	}
+
+	if err := tx.Model(t).Update("curr_hours", sum).Error; err != nil {
+		panic(err)
+	}
+}
+
+func (t Teacher) GetSubjectsByClassID(classID uint) []Subject {
+	var (
+		activities []Activity
+		subjects   []Subject
+	)
+
+	if err := DB().Model(t).Preload("Subject").Related(&activities).Error; err != nil {
+		panic(err)
+	}
+
+	for _, a := range activities {
+		if a.ClassID == classID {
+			subjects = append(subjects, a.Subject)
+		}
+	}
+
+	return subjects
+
+}
+
+func (t Teacher) IsCoordinator() []Class {
+	var (
+		classes []Class
+		output  []Class
+	)
+
+	if err := DB().Model(t).Related(&classes, "Classes").Error; err != nil {
+		panic(err)
+	}
+
+	for _, class := range classes {
+		if class.CoordinatorID == t.ID {
+			output = append(output, class)
+		}
+	}
+
+	return output
+
+}
+
+func (t Teacher) IsCoordinatorInClass(className string) bool {
+	var classes []Class
+
+	if err := DB().Model(t).Where("name = ?", className).Related(&classes, "Classes").Error; err != nil {
+		panic(err)
+	}
+
+	for _, class := range classes {
+		if class.CoordinatorID == t.ID {
+			return true
+		}
+	}
+
+	return false
+
+}
+
+func (t Teacher) HasClass(name string) bool {
+	var classes []Class
+
+	if err := DB().Model(t).Related(&classes, "Classes").Error; err != nil {
+		panic(err)
+	}
+
+	for _, class := range classes {
+		if class.Name == name {
+			return true
+		}
+	}
+
+	return false
+
+}
+
+func (t Teacher) HasSubject(name string) bool {
+	var subjects []Subject
+
+	if err := DB().Model(t).Related(&subjects, "Subjects").Error; err != nil {
+		panic(err)
+	}
+
+	for _, subject := range subjects {
+		if subject.Name == name {
+			return true
+		}
+	}
+
+	return false
+
+}
+
+func (t Teacher) HasSubjectInClass(subjectName string, className string) bool {
+	var classes []Class
+
+	if err := DB().Where("name = ?", className).Find(&classes).Error; err != nil {
+		panic(err)
+	}
+
+	subjects := t.GetSubjectsByClassID(classes[0].ID)
+
+	for _, s := range subjects {
+		if s.Name == subjectName {
+			return true
+		}
+	}
+
+	return false
+}
+
+func (t Teacher) IsMinuter() []Class {
+	var (
+		classes []Class
+		output  []Class
+	)
+
+	if err := DB().Model(t).Related(&classes, "Classes").Error; err != nil {
+		panic(err)
+	}
+
+	for _, class := range classes {
+		if class.MinuterID == t.ID {
+			output = append(output, class)
+		}
+	}
+
+	return output
+
+}
+
+// func (t *Teacher) CheckForIssues(tx *gorm.DB) {
+// 	if t.CurrHours != t.Hours {
+// 		var issues []Issue
+// 		if err := tx.Where("type = ? and reported = ?", IssueTeacherHours, 1).Model(t).Related(&issues).Error; err != nil {
+// 			panic(err)
+// 		}
+// 		if len(issues) == 0 {
+// 			desc := fmt.Sprintf(
+// 				"Le ore relative alla cattedra del docente %s %s non coincidono con la somma delle ore delle sue attività (%d/%d)",
+// 				t.Surname, t.Name,
+// 				t.CurrHours, t.Hours)
+// 			issue := Issue{
+// 				Type:        IssueTeacherHours,
+// 				Description: desc,
+// 				Reported:    true,
+// 				TeacherID:   t.ID,
+// 			}
+// 			if err := tx.Save(&issue).Error; err != nil {
+// 				panic(err)
+// 			}
+// 		} else {
+// 			issues[0].Description = fmt.Sprintf(
+// 				"Le ore relative alla cattedra del docente %s %s non coincidono con la somma delle ore delle sue attività (%d/%d)",
+// 				t.Surname, t.Name,
+// 				t.CurrHours, t.Hours)
+// 			if err := tx.Save(&issues[0]).Error; err != nil {
+// 				panic(err)
+// 			}
+// 		}
+// 	} else {
+// 		var issues []Issue
+// 		if err := tx.Where("type = ? and reported = ?", IssueTeacherHours, 1).Model(t).Related(&issues).Error; err != nil {
+// 			panic(err)
+// 		}
+// 		if len(issues) > 0 {
+// 			if err := tx.Delete(&issues[0]).Error; err != nil {
+// 				panic(err)
+// 			}
+// 		}
+
+// 	}
+// }
+
+func (t *Teacher) generateCredential() error {
+	t.Username = t.GenerateUsername()
+	t.Password = t.GenerateSaltedPassword(t.Username)
+	t.Email = fmt.Sprintf("%s@%s", t.Username, config.Domain)
+	return nil
+}
+
+func (t *Teacher) CheckForIssues(tx *gorm.DB) {
+	// issue := newTeacherMaxHoursIssue(t)
+	// if t.CurrHours != t.Hours {
+	// 	issue.createIfNotExist(t)
+	// } else {
+	// 	issue.resetIfExist(t)
+	// }
+}
+
+func (t *Teacher) BeforeCreate(tx *gorm.DB) error {
+	// FIXME: should re-generate only if related columns are modified
+	t.generateCredential()
+	return nil
+}
+
+func (t *Teacher) BeforeUpdate(tx *gorm.DB) error {
+	// FIXME: should re-generate only if related columns are modified
+	t.generateCredential()
+	return nil
+}
+
+// func (t *Teacher) AfterUpdate(tx *gorm.DB) error {
+// 	// FIXME: should re-generate only if related columns are modified
+// 	t.generateCredential()
+// 	// t.CheckForIssues(tx)
+// 	return nil
+// }
+
+func (t *Teacher) AfterDelete(tx *gorm.DB) error {
+	var issues []Issue
+	if err := tx.Where("type = ?", IssueTeacherHours).Model(t).Related(&issues).Error; err != nil {
+		panic(err)
+	}
+	if err := tx.Delete(&issues).Error; err != nil {
+		panic(err)
+	}
+	return nil
+}

+ 104 - 0
vendor/gogs.carducci-dante.gov.it/andrea.fazzi/karmen/renderer/renderer.go

@@ -0,0 +1,104 @@
+package renderer
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"html/template"
+	"log"
+	"net/http"
+	"path"
+	"path/filepath"
+	"strings"
+
+	"gogs.carducci-dante.gov.it/andrea.fazzi/karmen/config"
+)
+
+var currRenderer Renderer
+
+type Renderer interface {
+	Render(http.ResponseWriter, string, string, interface{}) error
+}
+
+type JSONRenderer struct{}
+
+func NewJSONRenderer() *JSONRenderer {
+	return &JSONRenderer{}
+}
+
+func (r *JSONRenderer) Render(w http.ResponseWriter, layout string, name string, data interface{}) error {
+	w.Header().Set("Content-Type", "application/json; charset=utf-8")
+	j, err := json.Marshal(data)
+	if err != nil {
+		return err
+	}
+	w.Write(j)
+	return nil
+}
+
+type HTMLRenderer struct {
+	TemplatePath string
+
+	templates map[string]*template.Template
+}
+
+func NewHTMLRenderer(templatePath string) (*HTMLRenderer, error) {
+	r := &HTMLRenderer{
+		TemplatePath: templatePath,
+		templates:    make(map[string]*template.Template),
+	}
+	fns, err := filepath.Glob(filepath.Join(templatePath, "*.tpl"))
+	if err != nil {
+		return nil, err
+	}
+	lfns, err := filepath.Glob(filepath.Join(templatePath, "layout", "*.tpl"))
+	if err != nil {
+		return nil, err
+	}
+
+	for _, fn := range fns {
+		if config.LogLevel > config.LOG_LEVEL_OFF {
+			log.Printf("Load %s template", fn)
+		}
+
+		tplName := filepath.Base(fn)
+		tplName = strings.TrimSuffix(tplName, path.Ext(tplName))
+		tplName = strings.TrimSuffix(tplName, path.Ext(tplName))
+
+		files := append(lfns, fn)
+		if err != nil {
+			return nil, err
+		}
+		r.templates[tplName] = template.Must(r.templates[tplName].ParseFiles(files...))
+		if err != nil {
+			return nil, err
+		}
+
+	}
+
+	return r, nil
+}
+
+func Use(r Renderer) {
+	currRenderer = r
+}
+
+func (r *HTMLRenderer) Render(w http.ResponseWriter, layout string, name string, data interface{}) error {
+	t, ok := r.templates[name]
+	if !ok {
+		return fmt.Errorf("Template %s not found", name)
+	}
+	w.Header().Set("Content-Type", "text/html; charset=utf-8")
+	err := t.ExecuteTemplate(w, layout, data)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func Render(w http.ResponseWriter, layout string, name string, data interface{}) error {
+	if currRenderer != nil {
+		return currRenderer.Render(w, layout, name, data)
+	}
+	return errors.New("You should set a renderer with Use() first if you want to use Renderer() package function.")
+}

+ 173 - 0
vendor/gogs.carducci-dante.gov.it/karmen/orm/activity.go

@@ -0,0 +1,173 @@
+package orm
+
+import "github.com/jinzhu/gorm"
+
+type Activity struct {
+	gorm.Model
+	Name      string
+	Class     Class
+	Teacher   Teacher
+	Subject   Subject
+	ClassID   uint `schema:"class_id"`
+	TeacherID uint `schema:"teacher_id"`
+	SubjectID uint `schema:"subject_id"`
+	Hours     int
+
+	prevTeacherID uint
+}
+
+func (a *Activity) updateRelatedTeacher(tx *gorm.DB, teacherID uint) {
+	var (
+		teacher Teacher
+		class   Class
+		subject Subject
+	)
+	if err := tx.Find(&teacher, teacherID).Error; err != nil {
+		panic(err)
+	}
+
+	// Calculate teacher hours
+
+	teacher.CalcHours(tx)
+
+	// Teacher -> Classes association
+
+	if err := tx.First(&class, a.ClassID).Error; err != nil {
+		panic(err)
+	}
+
+	if err := tx.Model(&teacher).Association("Classes").Append(class).Error; err != nil {
+		panic(err)
+	}
+
+	// Teacher -> Subjects association
+
+	if err := tx.First(&subject, a.SubjectID).Error; err != nil {
+		panic(err)
+	}
+
+	if err := tx.Model(&teacher).Association("Subjects").Append(subject).Error; err != nil {
+		panic(err)
+	}
+
+}
+
+func (a *Activity) deleteRelatedTeacherAssociations(tx *gorm.DB, teacherID uint) {
+	var (
+		teacher Teacher
+		class   Class
+		subject Subject
+	)
+	if err := tx.Find(&teacher, teacherID).Error; err != nil {
+		panic(err)
+	}
+
+	// Calculate teacher hours
+
+	teacher.CalcHours(tx)
+
+	// Teacher -> Classes association
+
+	if err := tx.First(&class, a.ClassID).Error; err != nil {
+		panic(err)
+	}
+
+	if err := tx.Preload("Classes").Find(&teacher, teacher.ID).Error; err != nil {
+		panic(err)
+	}
+
+	if err := tx.Model(&teacher).Association("Classes").Delete(class).Error; err != nil {
+		panic(err)
+	}
+
+	if err := tx.Preload("Classes").Find(&teacher, teacher.ID).Error; err != nil {
+		panic(err)
+	}
+
+	// Teacher -> Subjects association
+
+	if err := tx.First(&subject, a.SubjectID).Error; err != nil {
+		panic(err)
+	}
+
+	if err := tx.Model(&teacher).Association("Subjects").Delete(subject).Error; err != nil {
+		panic(err)
+	}
+
+}
+
+func (a *Activity) createUpdateActivity(tx *gorm.DB) {
+	if (a.TeacherID != a.prevTeacherID) && (a.prevTeacherID != 0) {
+		a.deleteRelatedTeacherAssociations(tx, a.prevTeacherID)
+	}
+	if a.TeacherID != 0 {
+		a.updateRelatedTeacher(tx, a.TeacherID)
+	}
+}
+
+func (a *Activity) deleteActivity(tx *gorm.DB) {
+	if a.TeacherID != 0 {
+		var (
+			teacher Teacher
+			class   Class
+			subject Subject
+		)
+		if err := tx.Find(&teacher, a.TeacherID).Error; err != nil {
+			panic(err)
+		}
+
+		// Calculate teacher hours
+
+		teacher.CalcHours(tx)
+
+		// Teacher -> Classes association
+
+		if err := tx.First(&class, a.ClassID).Error; err != nil {
+			panic(err)
+		}
+
+		if err := tx.Model(&teacher).Association("Classes").Delete(class).Error; err != nil {
+			panic(err)
+		}
+
+		// Teacher -> Subjects association
+
+		if err := tx.First(&subject, a.SubjectID).Error; err != nil {
+			panic(err)
+		}
+
+		if err := tx.Model(&teacher).Association("Subjects").Delete(subject).Error; err != nil {
+			panic(err)
+		}
+	}
+
+}
+
+func (a *Activity) AfterCreate(tx *gorm.DB) error {
+	a.createUpdateActivity(tx)
+	return nil
+}
+
+func (a *Activity) BeforeUpdate(tx *gorm.DB) error {
+	var currActivity Activity
+	if err := tx.First(&currActivity, a.ID).Error; err != nil {
+		panic(err)
+	}
+	a.prevTeacherID = currActivity.TeacherID
+	return nil
+}
+
+func (a *Activity) AfterSave(tx *gorm.DB) error {
+	a.createUpdateActivity(tx)
+	return nil
+}
+
+// func (a *Activity) AfterUpdate(tx *gorm.DB) error {
+// 	a.createUpdateActivity(tx)
+// 	return nil
+// }
+
+func (a *Activity) AfterDelete(tx *gorm.DB) error {
+	a.deleteActivity(tx)
+	return nil
+}

+ 32 - 0
vendor/gogs.carducci-dante.gov.it/karmen/orm/class.go

@@ -0,0 +1,32 @@
+package orm
+
+import (
+	"errors"
+
+	"github.com/jinzhu/gorm"
+)
+
+type Class struct {
+	gorm.Model
+	Name          string
+	CoordinatorID uint `schema:"coordinator_id"`
+	MinuterID     uint `schema:"minuter_id"`
+	Coordinator   Teacher
+	Minuter       Teacher
+	Teachers      []Teacher `gorm:"many2many:teacher_classes;"`
+	Students      []Student
+}
+
+func (t *Class) Create(db *gorm.DB, record map[string]interface{}) error {
+	result := new(Class)
+
+	if name := record["name"]; name == nil {
+		return errors.New("Error in creating class: field name is empty")
+	} else {
+		result.Name = name.(string)
+	}
+
+	db.Create(result)
+
+	return nil
+}

+ 32 - 0
vendor/gogs.carducci-dante.gov.it/karmen/orm/credential.go

@@ -0,0 +1,32 @@
+package orm
+
+import (
+	"crypto/sha1"
+	"encoding/base64"
+	"fmt"
+	"strings"
+)
+
+type Credential struct {
+	Name            string
+	Surname         string
+	Username        string
+	Password        string
+	Email           string
+	TelephoneNumber string
+}
+
+func (c *Credential) sanitize(s string) string {
+	r := strings.NewReplacer("'", "", "-", "", " ", "")
+	return strings.ToLower(r.Replace(s))
+}
+
+func (c *Credential) GenerateUsername() string {
+	return strings.Join([]string{c.sanitize(c.Name), c.sanitize(c.Surname)}, ".")
+}
+
+func (c *Credential) GenerateSaltedPassword(password string) string {
+	bs := sha1.Sum([]byte(password + "salt"))
+	str := base64.StdEncoding.EncodeToString(append(bs[:], []byte("salt")...))
+	return fmt.Sprintf("%s", str)
+}

+ 16 - 0
vendor/gogs.carducci-dante.gov.it/karmen/orm/issue.go

@@ -0,0 +1,16 @@
+package orm
+
+import "github.com/jinzhu/gorm"
+
+type Issue struct {
+	gorm.Model
+	Description string
+	Type        uint
+	Reported    bool
+	TeacherID   uint
+	Teacher     Teacher
+}
+
+func (i *Issue) newTeacherMaxHoursIssue(t *Teacher) *Issue {
+	return nil
+}

+ 298 - 0
vendor/gogs.carducci-dante.gov.it/karmen/orm/orm.go

@@ -0,0 +1,298 @@
+package orm
+
+import (
+	"errors"
+	"fmt"
+	"strings"
+
+	"github.com/jinzhu/gorm"
+	_ "github.com/jinzhu/gorm/dialects/mysql"
+	"gogs.carducci-dante.gov.it/andrea.fazzi/karmen/datasource"
+)
+
+const (
+	ADMINISTRATIVE_ROLE_PERSONAL = iota
+	ADMINISTRATIVE_ROLE_TEACHING
+	ADMINISTRATIVE_ROLE_DSGA
+	ADMINISTRATIVE_ROLE_SCHOOL_CARETAKER
+	ADMINISTRATIVE_ROLE_TECHNICAL_ASSISTANT
+	ADMINISTRATIVE_ROLE_LIBRARIAN
+	ADMINISTRATIVE_ROLE_UNDEFINED
+
+	IssueTeacherHours = iota
+)
+
+var currDB *gorm.DB
+
+type Timetable [][]Activity
+
+type Person interface {
+	GetCredential() *Credential
+}
+
+type TheBoss struct {
+	gorm.Model
+	Credential
+	Teachers []Teacher
+}
+
+type Address struct {
+	City    string
+	Street  string
+	ZipCode string
+	Country string
+}
+
+type Location struct {
+	Id      string
+	Name    string
+	Address *Address
+}
+
+type Room struct {
+	Id       string
+	Name     string
+	Capacity int
+	Location *Location
+}
+
+type Desk struct {
+	Id       string
+	Name     string
+	Students []*Student
+	Teacher  *Teacher
+}
+
+type Student struct {
+	gorm.Model
+	Credential
+	Class *Class
+}
+
+type Director struct {
+	gorm.Model
+	Credential
+}
+
+type Administrative struct {
+	gorm.Model
+	Credential
+	Role int
+}
+
+type Office struct {
+	gorm.Model
+	Credential
+	Persons []Person
+}
+
+type Department struct {
+	gorm.Model
+	Name     string
+	Subjects []Subject
+	Teachers []Teacher `gorm:"many2many:department_teachers;"`
+}
+
+func (b *TheBoss) Create(db *gorm.DB, record map[string]interface{}) error {
+	if record["role"] == "boss" {
+		result := new(TheBoss)
+		result.Name = record["name"].(string)
+		result.Surname = record["surname"].(string)
+		result.Username = result.GenerateUsername()
+		result.Password = result.GenerateSaltedPassword(result.Username)
+		db.Create(result)
+	} else {
+		boss := TheBoss{}
+		teacher := Teacher{}
+		db.Model(TheBoss{}).First(&boss)
+		db.Where("username=?", record["username"]).First(&teacher)
+		db.Model(&boss).Association("Teachers").Append(&teacher)
+	}
+
+	return nil
+}
+
+func (a *Administrative) Create(db *gorm.DB, record map[string]interface{}) error {
+	result := Administrative{}
+	result.Name = record["name"].(string)
+	result.Surname = record["surname"].(string)
+	result.Username = result.GenerateUsername()
+	result.Password = result.GenerateSaltedPassword(result.Username)
+
+	switch record["role"] {
+	case "personal_administrator":
+		result.Role = ADMINISTRATIVE_ROLE_PERSONAL
+	case "teaching_administrator":
+		result.Role = ADMINISTRATIVE_ROLE_TEACHING
+	case "school_caretaker":
+		result.Role = ADMINISTRATIVE_ROLE_SCHOOL_CARETAKER
+	case "technical_assistant":
+		result.Role = ADMINISTRATIVE_ROLE_TECHNICAL_ASSISTANT
+	case "librarian":
+		result.Role = ADMINISTRATIVE_ROLE_LIBRARIAN
+	case "dsga":
+		result.Role = ADMINISTRATIVE_ROLE_DSGA
+	default:
+		result.Role = ADMINISTRATIVE_ROLE_UNDEFINED
+	}
+
+	db.Create(&result)
+	return nil
+}
+
+func (d *Director) Create(db *gorm.DB, record map[string]interface{}) error {
+	result := new(Director)
+
+	if name := record["name"]; name == nil {
+		return errors.New("Error in creating director: field name is empty")
+	} else {
+		result.Name = name.(string)
+	}
+	if surname := record["surname"]; surname == nil {
+		return errors.New("Error in creating director: field surname is empty")
+	} else {
+		result.Surname = surname.(string)
+	}
+
+	// Generate username and initial password
+
+	result.Username = result.GenerateUsername()
+	result.Password = result.GenerateSaltedPassword(result.Username)
+
+	db.Set("gorm:save_associations", false).Create(result)
+
+	return nil
+}
+
+func (t *Student) Create(db *gorm.DB, record map[string]interface{}) error {
+	result := new(Student)
+
+	if name := record["name"]; name == nil {
+		return errors.New("Error in creating student: field name is empty")
+	} else {
+		result.Name = name.(string)
+	}
+	if surname := record["surname"]; surname == nil {
+		return errors.New("Error in creating student: field surname is empty")
+	} else {
+		result.Surname = surname.(string)
+	}
+
+	// Generate username and initial password
+
+	result.Username = result.GenerateUsername()
+	result.Password = result.GenerateSaltedPassword(result.Username)
+
+	db.Set("gorm:save_associations", false).Create(result)
+
+	return nil
+}
+
+func (d *Department) Create(db *gorm.DB, record map[string]interface{}) error {
+	result := new(Department)
+
+	if name := record["name"]; name == nil {
+		return errors.New("Error in updating subject: field name is empty")
+	} else {
+		result.Name = name.(string)
+	}
+
+	db.Set("gorm:save_associations", false).Create(result)
+
+	// Handle department-subjects relationship
+
+	for _, s := range record["subjects"].([]interface{}) {
+		subject := Subject{}
+		db.Where("name=?", strings.TrimSpace(s.(string))).Find(&subject)
+		db.Model(result).Association("Subjects").Append(subject)
+	}
+
+	// Handle department-teachers relationship
+
+	allTeachers := make([]Teacher, 0)
+	db.Find(&allTeachers)
+
+	for _, t := range allTeachers {
+		db.Model(&t).Related(&t.Subjects, "Subjects")
+		for _, s := range t.Subjects {
+			for _, depS := range result.Subjects {
+				if s.Name == depS.Name {
+					db.Model(result).Association("Teachers").Append(t)
+				}
+			}
+		}
+	}
+
+	return nil
+}
+
+func Import(importable datasource.Importable, importer datasource.Importer) error {
+	for _, record := range importer.Records() {
+		err := importable.Create(currDB, record)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func New(connection string) (*gorm.DB, error) {
+	db, err := gorm.Open("mysql", connection)
+	if err != nil {
+		return nil, err
+	}
+	return db, nil
+}
+
+func Use(db *gorm.DB) {
+	currDB = db
+}
+
+func DB() *gorm.DB {
+	return currDB
+}
+
+func Reset() {
+	currDB.DropTableIfExists(
+		&School{},
+		&Subject{},
+		&Teacher{},
+		&Class{},
+		&Activity{},
+		&Student{},
+		&Department{},
+		&TheBoss{},
+		&Administrative{},
+		&Director{},
+		&Issue{},
+	)
+
+	currDB.Exec("TRUNCATE teacher_subjects;")
+	currDB.Exec("TRUNCATE teacher_classes;")
+	currDB.Exec("TRUNCATE department_teachers;")
+}
+
+func AutoMigrate() {
+	if err := currDB.AutoMigrate(
+		&School{},
+		&Subject{},
+		&Teacher{},
+		&Class{},
+		&Activity{},
+		&Department{},
+		&TheBoss{},
+		&Administrative{},
+		&Student{},
+		&Director{},
+		&Issue{},
+	).Error; err != nil {
+		panic(err)
+	}
+
+}
+
+func GetTeacher(username string) (*Teacher, error) {
+	teacher := Teacher{}
+	currDB.Where(fmt.Sprintf("username='%s'", username)).Find(&teacher)
+	return &teacher, nil
+}

+ 10 - 0
vendor/gogs.carducci-dante.gov.it/karmen/orm/school.go

@@ -0,0 +1,10 @@
+package orm
+
+import "github.com/jinzhu/gorm"
+
+type School struct {
+	gorm.Model
+	Name    string
+	Address string
+	Domain  string
+}

+ 26 - 0
vendor/gogs.carducci-dante.gov.it/karmen/orm/subject.go

@@ -0,0 +1,26 @@
+package orm
+
+import (
+	"errors"
+
+	"github.com/jinzhu/gorm"
+)
+
+type Subject struct {
+	gorm.Model
+	Name         string
+	DepartmentId uint
+	TeacherID    uint
+	Teachers     []Teacher `gorm:"many2many:teacher_subjects;"`
+}
+
+func (s *Subject) Create(db *gorm.DB, record map[string]interface{}) error {
+	result := new(Subject)
+	if name := record["name"]; name == nil {
+		return errors.New("Error in updating subject: field name is empty")
+	} else {
+		result.Name = name.(string)
+	}
+	db.Create(result)
+	return nil
+}

+ 278 - 0
vendor/gogs.carducci-dante.gov.it/karmen/orm/teacher.go

@@ -0,0 +1,278 @@
+package orm
+
+import (
+	"errors"
+	"fmt"
+
+	"github.com/jinzhu/gorm"
+	"gogs.carducci-dante.gov.it/andrea.fazzi/karmen/config"
+)
+
+type Teacher struct {
+	gorm.Model
+	Credential
+	Classes     []Class   `gorm:"many2many:teacher_classes;"`
+	Subjects    []Subject `gorm:"many2many:teacher_subjects;"`
+	Activities  []Activity
+	Departments []Department
+	Issues      []Issue
+	Hours       int
+	CurrHours   int
+	TheBossId   uint
+}
+
+func (t *Teacher) Create(db *gorm.DB, record map[string]interface{}) error {
+	result := new(Teacher)
+
+	if name := record["name"]; name == nil {
+		return errors.New("Error in creating teacher: field name is empty")
+	} else {
+		result.Name = name.(string)
+	}
+	if surname := record["surname"]; surname == nil {
+		return errors.New("Error in creating teacher: field surname is empty")
+	} else {
+		result.Surname = surname.(string)
+	}
+
+	db.Create(result)
+
+	return nil
+}
+
+func (t *Teacher) CalcHours(tx *gorm.DB) {
+	var (
+		activities []Activity
+		sum        int
+	)
+
+	if err := tx.Model(t).Related(&activities).Error; err != nil {
+		panic(err)
+	}
+
+	for _, a := range activities {
+		sum += a.Hours
+	}
+
+	if err := tx.Model(t).Update("curr_hours", sum).Error; err != nil {
+		panic(err)
+	}
+}
+
+func (t Teacher) GetSubjectsByClassID(classID uint) []Subject {
+	var (
+		activities []Activity
+		subjects   []Subject
+	)
+
+	if err := DB().Model(t).Preload("Subject").Related(&activities).Error; err != nil {
+		panic(err)
+	}
+
+	for _, a := range activities {
+		if a.ClassID == classID {
+			subjects = append(subjects, a.Subject)
+		}
+	}
+
+	return subjects
+
+}
+
+func (t Teacher) IsCoordinator() []Class {
+	var (
+		classes []Class
+		output  []Class
+	)
+
+	if err := DB().Model(t).Related(&classes, "Classes").Error; err != nil {
+		panic(err)
+	}
+
+	for _, class := range classes {
+		if class.CoordinatorID == t.ID {
+			output = append(output, class)
+		}
+	}
+
+	return output
+
+}
+
+func (t Teacher) IsCoordinatorInClass(className string) bool {
+	var classes []Class
+
+	if err := DB().Model(t).Where("name = ?", className).Related(&classes, "Classes").Error; err != nil {
+		panic(err)
+	}
+
+	for _, class := range classes {
+		if class.CoordinatorID == t.ID {
+			return true
+		}
+	}
+
+	return false
+
+}
+
+func (t Teacher) HasClass(name string) bool {
+	var classes []Class
+
+	if err := DB().Model(t).Related(&classes, "Classes").Error; err != nil {
+		panic(err)
+	}
+
+	for _, class := range classes {
+		if class.Name == name {
+			return true
+		}
+	}
+
+	return false
+
+}
+
+func (t Teacher) HasSubject(name string) bool {
+	var subjects []Subject
+
+	if err := DB().Model(t).Related(&subjects, "Subjects").Error; err != nil {
+		panic(err)
+	}
+
+	for _, subject := range subjects {
+		if subject.Name == name {
+			return true
+		}
+	}
+
+	return false
+
+}
+
+func (t Teacher) HasSubjectInClass(subjectName string, className string) bool {
+	var classes []Class
+
+	if err := DB().Where("name = ?", className).Find(&classes).Error; err != nil {
+		panic(err)
+	}
+
+	subjects := t.GetSubjectsByClassID(classes[0].ID)
+
+	for _, s := range subjects {
+		if s.Name == subjectName {
+			return true
+		}
+	}
+
+	return false
+}
+
+func (t Teacher) IsMinuter() []Class {
+	var (
+		classes []Class
+		output  []Class
+	)
+
+	if err := DB().Model(t).Related(&classes, "Classes").Error; err != nil {
+		panic(err)
+	}
+
+	for _, class := range classes {
+		if class.MinuterID == t.ID {
+			output = append(output, class)
+		}
+	}
+
+	return output
+
+}
+
+// func (t *Teacher) CheckForIssues(tx *gorm.DB) {
+// 	if t.CurrHours != t.Hours {
+// 		var issues []Issue
+// 		if err := tx.Where("type = ? and reported = ?", IssueTeacherHours, 1).Model(t).Related(&issues).Error; err != nil {
+// 			panic(err)
+// 		}
+// 		if len(issues) == 0 {
+// 			desc := fmt.Sprintf(
+// 				"Le ore relative alla cattedra del docente %s %s non coincidono con la somma delle ore delle sue attività (%d/%d)",
+// 				t.Surname, t.Name,
+// 				t.CurrHours, t.Hours)
+// 			issue := Issue{
+// 				Type:        IssueTeacherHours,
+// 				Description: desc,
+// 				Reported:    true,
+// 				TeacherID:   t.ID,
+// 			}
+// 			if err := tx.Save(&issue).Error; err != nil {
+// 				panic(err)
+// 			}
+// 		} else {
+// 			issues[0].Description = fmt.Sprintf(
+// 				"Le ore relative alla cattedra del docente %s %s non coincidono con la somma delle ore delle sue attività (%d/%d)",
+// 				t.Surname, t.Name,
+// 				t.CurrHours, t.Hours)
+// 			if err := tx.Save(&issues[0]).Error; err != nil {
+// 				panic(err)
+// 			}
+// 		}
+// 	} else {
+// 		var issues []Issue
+// 		if err := tx.Where("type = ? and reported = ?", IssueTeacherHours, 1).Model(t).Related(&issues).Error; err != nil {
+// 			panic(err)
+// 		}
+// 		if len(issues) > 0 {
+// 			if err := tx.Delete(&issues[0]).Error; err != nil {
+// 				panic(err)
+// 			}
+// 		}
+
+// 	}
+// }
+
+func (t *Teacher) generateCredential() error {
+	t.Username = t.GenerateUsername()
+	t.Password = t.GenerateSaltedPassword(t.Username)
+	t.Email = fmt.Sprintf("%s@%s", t.Username, config.Domain)
+	return nil
+}
+
+func (t *Teacher) CheckForIssues(tx *gorm.DB) {
+	// issue := newTeacherMaxHoursIssue(t)
+	// if t.CurrHours != t.Hours {
+	// 	issue.createIfNotExist(t)
+	// } else {
+	// 	issue.resetIfExist(t)
+	// }
+}
+
+func (t *Teacher) BeforeCreate(tx *gorm.DB) error {
+	// FIXME: should re-generate only if related columns are modified
+	t.generateCredential()
+	return nil
+}
+
+func (t *Teacher) BeforeUpdate(tx *gorm.DB) error {
+	// FIXME: should re-generate only if related columns are modified
+	t.generateCredential()
+	return nil
+}
+
+// func (t *Teacher) AfterUpdate(tx *gorm.DB) error {
+// 	// FIXME: should re-generate only if related columns are modified
+// 	t.generateCredential()
+// 	// t.CheckForIssues(tx)
+// 	return nil
+// }
+
+func (t *Teacher) AfterDelete(tx *gorm.DB) error {
+	var issues []Issue
+	if err := tx.Where("type = ?", IssueTeacherHours).Model(t).Related(&issues).Error; err != nil {
+		panic(err)
+	}
+	if err := tx.Delete(&issues).Error; err != nil {
+		panic(err)
+	}
+	return nil
+}

+ 198 - 10
vendor/gopkg.in/yaml.v2/LICENSE

@@ -1,13 +1,201 @@
-Copyright 2011-2016 Canonical Ltd.
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
 
-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
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
 
-    http://www.apache.org/licenses/LICENSE-2.0
+   1. Definitions.
 
-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.
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright {yyyy} {name of copyright owner}
+
+   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.

+ 3 - 1
vendor/gopkg.in/yaml.v2/README.md

@@ -42,12 +42,14 @@ The package API for yaml v2 will remain stable as described in [gopkg.in](https:
 License
 -------
 
-The yaml package is licensed under the LGPL with an exception that allows it to be linked statically. Please see the LICENSE file for details.
+The yaml package is licensed under the Apache License 2.0. Please see the LICENSE file for details.
 
 
 Example
 -------
 
+Some more examples can be found in the "examples" folder.
+
 ```Go
 package main
 

+ 6 - 4
vendor/gopkg.in/yaml.v2/decode.go

@@ -120,7 +120,6 @@ func (p *parser) parse() *node {
 	default:
 		panic("attempted to parse unknown event: " + strconv.Itoa(int(p.event.typ)))
 	}
-	panic("unreachable")
 }
 
 func (p *parser) node(kind int) *node {
@@ -191,6 +190,7 @@ type decoder struct {
 	aliases map[string]bool
 	mapType reflect.Type
 	terrors []string
+	strict  bool
 }
 
 var (
@@ -200,8 +200,8 @@ var (
 	ifaceType      = defaultMapType.Elem()
 )
 
-func newDecoder() *decoder {
-	d := &decoder{mapType: defaultMapType}
+func newDecoder(strict bool) *decoder {
+	d := &decoder{mapType: defaultMapType, strict: strict}
 	d.aliases = make(map[string]bool)
 	return d
 }
@@ -251,7 +251,7 @@ func (d *decoder) callUnmarshaler(n *node, u Unmarshaler) (good bool) {
 //
 // If n holds a null value, prepare returns before doing anything.
 func (d *decoder) prepare(n *node, out reflect.Value) (newout reflect.Value, unmarshaled, good bool) {
-	if n.tag == yaml_NULL_TAG || n.kind == scalarNode && n.tag == "" && (n.value == "null" || n.value == "") {
+	if n.tag == yaml_NULL_TAG || n.kind == scalarNode && n.tag == "" && (n.value == "null" || n.value == "~" || n.value == "" && n.implicit) {
 		return out, false, false
 	}
 	again := true
@@ -640,6 +640,8 @@ func (d *decoder) mappingStruct(n *node, out reflect.Value) (good bool) {
 			value := reflect.New(elemType).Elem()
 			d.unmarshal(n.children[i+1], value)
 			inlineMap.SetMapIndex(name, value)
+		} else if d.strict {
+			d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s not found in struct %s", n.line+1, name.String(), out.Type()))
 		}
 	}
 	return true

+ 4 - 5
vendor/gopkg.in/yaml.v2/emitterc.go

@@ -666,7 +666,6 @@ func yaml_emitter_emit_node(emitter *yaml_emitter_t, event *yaml_event_t,
 		return yaml_emitter_set_emitter_error(emitter,
 			"expected SCALAR, SEQUENCE-START, MAPPING-START, or ALIAS")
 	}
-	return false
 }
 
 // Expect ALIAS.
@@ -995,7 +994,7 @@ func yaml_emitter_analyze_scalar(emitter *yaml_emitter_t, value []byte) bool {
 		break_space    = false
 		space_break    = false
 
-		preceeded_by_whitespace = false
+		preceded_by_whitespace = false
 		followed_by_whitespace  = false
 		previous_space          = false
 		previous_break          = false
@@ -1017,7 +1016,7 @@ func yaml_emitter_analyze_scalar(emitter *yaml_emitter_t, value []byte) bool {
 		flow_indicators = true
 	}
 
-	preceeded_by_whitespace = true
+	preceded_by_whitespace = true
 	for i, w := 0, 0; i < len(value); i += w {
 		w = width(value[i])
 		followed_by_whitespace = i+w >= len(value) || is_blank(value, i+w)
@@ -1048,7 +1047,7 @@ func yaml_emitter_analyze_scalar(emitter *yaml_emitter_t, value []byte) bool {
 					block_indicators = true
 				}
 			case '#':
-				if preceeded_by_whitespace {
+				if preceded_by_whitespace {
 					flow_indicators = true
 					block_indicators = true
 				}
@@ -1089,7 +1088,7 @@ func yaml_emitter_analyze_scalar(emitter *yaml_emitter_t, value []byte) bool {
 		}
 
 		// [Go]: Why 'z'? Couldn't be the end of the string as that's the loop condition.
-		preceeded_by_whitespace = is_blankz(value, i)
+		preceded_by_whitespace = is_blankz(value, i)
 	}
 
 	emitter.scalar_data.multiline = line_breaks

+ 0 - 1
vendor/gopkg.in/yaml.v2/parserc.go

@@ -166,7 +166,6 @@ func yaml_parser_state_machine(parser *yaml_parser_t, event *yaml_event_t) bool
 	default:
 		panic("invalid parser state")
 	}
-	return false
 }
 
 // Parse the production:

+ 8 - 3
vendor/gopkg.in/yaml.v2/resolve.go

@@ -3,6 +3,7 @@ package yaml
 import (
 	"encoding/base64"
 	"math"
+	"regexp"
 	"strconv"
 	"strings"
 	"unicode/utf8"
@@ -80,6 +81,8 @@ func resolvableTag(tag string) bool {
 	return false
 }
 
+var yamlStyleFloat = regexp.MustCompile(`^[-+]?[0-9]*\.?[0-9]+([eE][-+][0-9]+)?$`)
+
 func resolve(tag string, in string) (rtag string, out interface{}) {
 	if !resolvableTag(tag) {
 		return tag, in
@@ -135,9 +138,11 @@ func resolve(tag string, in string) (rtag string, out interface{}) {
 			if err == nil {
 				return yaml_INT_TAG, uintv
 			}
-			floatv, err := strconv.ParseFloat(plain, 64)
-			if err == nil {
-				return yaml_FLOAT_TAG, floatv
+			if yamlStyleFloat.MatchString(plain) {
+				floatv, err := strconv.ParseFloat(plain, 64)
+				if err == nil {
+					return yaml_FLOAT_TAG, floatv
+				}
 			}
 			if strings.HasPrefix(plain, "0b") {
 				intv, err := strconv.ParseInt(plain[2:], 2, 64)

+ 6 - 5
vendor/gopkg.in/yaml.v2/scannerc.go

@@ -9,7 +9,7 @@ import (
 // ************
 //
 // The following notes assume that you are familiar with the YAML specification
-// (http://yaml.org/spec/cvs/current.html).  We mostly follow it, although in
+// (http://yaml.org/spec/1.2/spec.html).  We mostly follow it, although in
 // some cases we are less restrictive that it requires.
 //
 // The process of transforming a YAML stream into a sequence of events is
@@ -611,7 +611,7 @@ func yaml_parser_set_scanner_tag_error(parser *yaml_parser_t, directive bool, co
 	if directive {
 		context = "while parsing a %TAG directive"
 	}
-	return yaml_parser_set_scanner_error(parser, context, context_mark, "did not find URI escaped octet")
+	return yaml_parser_set_scanner_error(parser, context, context_mark, problem)
 }
 
 func trace(args ...interface{}) func() {
@@ -1944,7 +1944,7 @@ func yaml_parser_scan_tag_handle(parser *yaml_parser_t, directive bool, start_ma
 	} else {
 		// It's either the '!' tag or not really a tag handle.  If it's a %TAG
 		// directive, it's an error.  If it's a tag token, it must be a part of URI.
-		if directive && !(s[0] == '!' && s[1] == 0) {
+		if directive && string(s) != "!" {
 			yaml_parser_set_scanner_tag_error(parser, directive,
 				start_mark, "did not find expected '!'")
 			return false
@@ -1959,6 +1959,7 @@ func yaml_parser_scan_tag_handle(parser *yaml_parser_t, directive bool, start_ma
 func yaml_parser_scan_tag_uri(parser *yaml_parser_t, directive bool, head []byte, start_mark yaml_mark_t, uri *[]byte) bool {
 	//size_t length = head ? strlen((char *)head) : 0
 	var s []byte
+	hasTag := len(head) > 0
 
 	// Copy the head if needed.
 	//
@@ -2000,10 +2001,10 @@ func yaml_parser_scan_tag_uri(parser *yaml_parser_t, directive bool, head []byte
 		if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) {
 			return false
 		}
+		hasTag = true
 	}
 
-	// Check if the tag is non-empty.
-	if len(s) == 0 {
+	if !hasTag {
 		yaml_parser_set_scanner_tag_error(parser, directive,
 			start_mark, "did not find expected tag URI")
 		return false

+ 13 - 2
vendor/gopkg.in/yaml.v2/yaml.go

@@ -77,8 +77,19 @@ type Marshaler interface {
 // supported tag options.
 //
 func Unmarshal(in []byte, out interface{}) (err error) {
+	return unmarshal(in, out, false)
+}
+
+// UnmarshalStrict is like Unmarshal except that any fields that are found
+// in the data that do not have corresponding struct members will result in
+// an error.
+func UnmarshalStrict(in []byte, out interface{}) (err error) {
+	return unmarshal(in, out, true)
+}
+
+func unmarshal(in []byte, out interface{}, strict bool) (err error) {
 	defer handleErr(&err)
-	d := newDecoder()
+	d := newDecoder(strict)
 	p := newParser(in)
 	defer p.destroy()
 	node := p.parse()
@@ -129,7 +140,7 @@ func Unmarshal(in []byte, out interface{}) (err error) {
 // For example:
 //
 //     type T struct {
-//         F int "a,omitempty"
+//         F int `yaml:"a,omitempty"`
 //         B int
 //     }
 //     yaml.Marshal(&T{B: 2}) // Returns "b: 2\n"

+ 1 - 1
vendor/gopkg.in/yaml.v2/yamlh.go

@@ -508,7 +508,7 @@ type yaml_parser_t struct {
 
 	problem string // Error description.
 
-	// The byte about which the problem occured.
+	// The byte about which the problem occurred.
 	problem_offset int
 	problem_value  int
 	problem_mark   yaml_mark_t