upgrade to use testfixtures v3 (#11904)

* upgrade to use testfixtures v3

* simplify logic

* make vendor

* update per @lunny

* Update templates/repo/empty.tmpl

* Update templates/repo/empty.tmpl

Co-authored-by: Lauris BH <lauris@nix.lv>
tokarchuk/v1.17
techknowlogick 4 years ago committed by GitHub
parent 1645d4a5d8
commit 9e6a79bea9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      contrib/pr/checkout.go
  2. 8
      go.mod
  3. 15
      go.sum
  4. 16
      integrations/integration_test.go
  5. 40
      models/test_fixtures.go
  6. 3
      models/unit_tests.go
  7. 0
      vendor/github.com/go-testfixtures/testfixtures/v3/.editorconfig
  8. 0
      vendor/github.com/go-testfixtures/testfixtures/v3/.gitattributes
  9. 4
      vendor/github.com/go-testfixtures/testfixtures/v3/.gitignore
  10. 41
      vendor/github.com/go-testfixtures/testfixtures/v3/.goreleaser.yml
  11. 1
      vendor/github.com/go-testfixtures/testfixtures/v3/.sample.env
  12. 93
      vendor/github.com/go-testfixtures/testfixtures/v3/CHANGELOG.md
  13. 9
      vendor/github.com/go-testfixtures/testfixtures/v3/Dockerfile
  14. 0
      vendor/github.com/go-testfixtures/testfixtures/v3/LICENSE
  15. 483
      vendor/github.com/go-testfixtures/testfixtures/v3/README.md
  16. 59
      vendor/github.com/go-testfixtures/testfixtures/v3/Taskfile.yml
  17. 37
      vendor/github.com/go-testfixtures/testfixtures/v3/docker-compose.yml
  18. 165
      vendor/github.com/go-testfixtures/testfixtures/v3/dump.go
  19. 14
      vendor/github.com/go-testfixtures/testfixtures/v3/go.mod
  20. 26
      vendor/github.com/go-testfixtures/testfixtures/v3/go.sum
  21. 14
      vendor/github.com/go-testfixtures/testfixtures/v3/helper.go
  22. 0
      vendor/github.com/go-testfixtures/testfixtures/v3/json.go
  23. 35
      vendor/github.com/go-testfixtures/testfixtures/v3/mysql.go
  24. 63
      vendor/github.com/go-testfixtures/testfixtures/v3/postgresql.go
  25. 11
      vendor/github.com/go-testfixtures/testfixtures/v3/sqlite.go
  26. 64
      vendor/github.com/go-testfixtures/testfixtures/v3/sqlserver.go
  27. 599
      vendor/github.com/go-testfixtures/testfixtures/v3/testfixtures.go
  28. 43
      vendor/github.com/go-testfixtures/testfixtures/v3/time.go
  29. 6
      vendor/github.com/mattn/go-sqlite3/.travis.yml
  30. 57
      vendor/github.com/mattn/go-sqlite3/README.md
  31. 8
      vendor/github.com/mattn/go-sqlite3/backup.go
  32. 26
      vendor/github.com/mattn/go-sqlite3/callback.go
  33. 299
      vendor/github.com/mattn/go-sqlite3/convert.go
  34. 21
      vendor/github.com/mattn/go-sqlite3/error.go
  35. 9191
      vendor/github.com/mattn/go-sqlite3/sqlite3-binding.c
  36. 81
      vendor/github.com/mattn/go-sqlite3/sqlite3-binding.h
  37. 154
      vendor/github.com/mattn/go-sqlite3/sqlite3.go
  38. 2
      vendor/github.com/mattn/go-sqlite3/sqlite3_context.go
  39. 2
      vendor/github.com/mattn/go-sqlite3/sqlite3_go18.go
  40. 3
      vendor/github.com/mattn/go-sqlite3/sqlite3_libsqlite3.go
  41. 2
      vendor/github.com/mattn/go-sqlite3/sqlite3_load_extension.go
  42. 2
      vendor/github.com/mattn/go-sqlite3/sqlite3_load_extension_omit.go
  43. 2
      vendor/github.com/mattn/go-sqlite3/sqlite3_opt_allow_uri_authority.go
  44. 4
      vendor/github.com/mattn/go-sqlite3/sqlite3_opt_app_armor.go
  45. 2
      vendor/github.com/mattn/go-sqlite3/sqlite3_opt_foreign_keys.go
  46. 2
      vendor/github.com/mattn/go-sqlite3/sqlite3_opt_fts5.go
  47. 2
      vendor/github.com/mattn/go-sqlite3/sqlite3_opt_icu.go
  48. 2
      vendor/github.com/mattn/go-sqlite3/sqlite3_opt_introspect.go
  49. 2
      vendor/github.com/mattn/go-sqlite3/sqlite3_opt_json1.go
  50. 20
      vendor/github.com/mattn/go-sqlite3/sqlite3_opt_preupdate.go
  51. 112
      vendor/github.com/mattn/go-sqlite3/sqlite3_opt_preupdate_hook.go
  52. 21
      vendor/github.com/mattn/go-sqlite3/sqlite3_opt_preupdate_omit.go
  53. 2
      vendor/github.com/mattn/go-sqlite3/sqlite3_opt_secure_delete.go
  54. 2
      vendor/github.com/mattn/go-sqlite3/sqlite3_opt_secure_delete_fast.go
  55. 2
      vendor/github.com/mattn/go-sqlite3/sqlite3_opt_stat4.go
  56. 2
      vendor/github.com/mattn/go-sqlite3/sqlite3_opt_unlock_notify.go
  57. 2
      vendor/github.com/mattn/go-sqlite3/sqlite3_opt_vacuum_full.go
  58. 2
      vendor/github.com/mattn/go-sqlite3/sqlite3_opt_vacuum_incr.go
  59. 2
      vendor/github.com/mattn/go-sqlite3/sqlite3_opt_vtable.go
  60. 2
      vendor/github.com/mattn/go-sqlite3/sqlite3_other.go
  61. 2
      vendor/github.com/mattn/go-sqlite3/sqlite3_solaris.go
  62. 3
      vendor/github.com/mattn/go-sqlite3/sqlite3_trace.go
  63. 5
      vendor/github.com/mattn/go-sqlite3/sqlite3_type.go
  64. 2
      vendor/github.com/mattn/go-sqlite3/sqlite3_windows.go
  65. 4
      vendor/github.com/mattn/go-sqlite3/sqlite3ext.h
  66. 5
      vendor/github.com/mattn/go-sqlite3/static_mock.go
  67. 7
      vendor/github.com/spf13/pflag/.travis.yml
  68. 4
      vendor/github.com/spf13/pflag/README.md
  69. 38
      vendor/github.com/spf13/pflag/bool_slice.go
  70. 4
      vendor/github.com/spf13/pflag/count.go
  71. 38
      vendor/github.com/spf13/pflag/duration_slice.go
  72. 16
      vendor/github.com/spf13/pflag/flag.go
  73. 174
      vendor/github.com/spf13/pflag/float32_slice.go
  74. 166
      vendor/github.com/spf13/pflag/float64_slice.go
  75. 3
      vendor/github.com/spf13/pflag/go.mod
  76. 0
      vendor/github.com/spf13/pflag/go.sum
  77. 174
      vendor/github.com/spf13/pflag/int32_slice.go
  78. 166
      vendor/github.com/spf13/pflag/int64_slice.go
  79. 30
      vendor/github.com/spf13/pflag/int_slice.go
  80. 40
      vendor/github.com/spf13/pflag/ip_slice.go
  81. 26
      vendor/github.com/spf13/pflag/string_array.go
  82. 22
      vendor/github.com/spf13/pflag/string_slice.go
  83. 149
      vendor/github.com/spf13/pflag/string_to_int64.go
  84. 42
      vendor/github.com/spf13/pflag/uint_slice.go
  85. 26
      vendor/gopkg.in/testfixtures.v2/.travis.yml
  86. 358
      vendor/gopkg.in/testfixtures.v2/README.md
  87. 64
      vendor/gopkg.in/testfixtures.v2/Taskfile.yml
  88. 51
      vendor/gopkg.in/testfixtures.v2/appveyor.yml
  89. 75
      vendor/gopkg.in/testfixtures.v2/deprecated.go
  90. 41
      vendor/gopkg.in/testfixtures.v2/errors.go
  91. 110
      vendor/gopkg.in/testfixtures.v2/generate.go
  92. 19
      vendor/gopkg.in/testfixtures.v2/options.go
  93. 171
      vendor/gopkg.in/testfixtures.v2/oracle.go
  94. 305
      vendor/gopkg.in/testfixtures.v2/testfixtures.go
  95. 34
      vendor/gopkg.in/testfixtures.v2/time.go
  96. 1
      vendor/gopkg.in/yaml.v2/apic.go
  97. 16
      vendor/modules.txt

@ -37,7 +37,6 @@ import (
"github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing"
context2 "github.com/gorilla/context" context2 "github.com/gorilla/context"
"github.com/unknwon/com" "github.com/unknwon/com"
"gopkg.in/testfixtures.v2"
"xorm.io/xorm" "xorm.io/xorm"
) )
@ -96,14 +95,12 @@ func runPR() {
setting.Database.LogSQL = true setting.Database.LogSQL = true
//x, err = xorm.NewEngine("sqlite3", "file::memory:?cache=shared") //x, err = xorm.NewEngine("sqlite3", "file::memory:?cache=shared")
var helper testfixtures.Helper = &testfixtures.SQLite{}
models.NewEngine(context.Background(), func(_ *xorm.Engine) error { models.NewEngine(context.Background(), func(_ *xorm.Engine) error {
return nil return nil
}) })
models.HasEngine = true models.HasEngine = true
//x.ShowSQL(true) //x.ShowSQL(true)
err = models.InitFixtures( err = models.InitFixtures(
helper,
path.Join(curDir, "models/fixtures/"), path.Join(curDir, "models/fixtures/"),
) )
if err != nil { if err != nil {

@ -44,6 +44,7 @@ require (
github.com/go-redis/redis v6.15.2+incompatible github.com/go-redis/redis v6.15.2+incompatible
github.com/go-sql-driver/mysql v1.4.1 github.com/go-sql-driver/mysql v1.4.1
github.com/go-swagger/go-swagger v0.21.0 github.com/go-swagger/go-swagger v0.21.0
github.com/go-testfixtures/testfixtures/v3 v3.2.0
github.com/gobwas/glob v0.2.3 github.com/gobwas/glob v0.2.3
github.com/gogs/chardet v0.0.0-20191104214054-4b6791f73a28 github.com/gogs/chardet v0.0.0-20191104214054-4b6791f73a28
github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14 github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14
@ -56,7 +57,6 @@ require (
github.com/issue9/identicon v1.0.1 github.com/issue9/identicon v1.0.1
github.com/jaytaylor/html2text v0.0.0-20160923191438-8fb95d837f7d github.com/jaytaylor/html2text v0.0.0-20160923191438-8fb95d837f7d
github.com/jmhodges/levigo v1.0.0 // indirect github.com/jmhodges/levigo v1.0.0 // indirect
github.com/joho/godotenv v1.3.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20170619183022-cd60e84ee657 github.com/kballard/go-shellquote v0.0.0-20170619183022-cd60e84ee657
github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4 github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4
github.com/klauspost/compress v1.10.2 github.com/klauspost/compress v1.10.2
@ -66,8 +66,7 @@ require (
github.com/mailru/easyjson v0.7.0 // indirect github.com/mailru/easyjson v0.7.0 // indirect
github.com/markbates/goth v1.61.2 github.com/markbates/goth v1.61.2
github.com/mattn/go-isatty v0.0.11 github.com/mattn/go-isatty v0.0.11
github.com/mattn/go-oci8 v0.0.0-20190320171441-14ba190cf52d // indirect github.com/mattn/go-sqlite3 v2.0.2+incompatible
github.com/mattn/go-sqlite3 v1.11.0
github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75 github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75
github.com/mgechev/dots v0.0.0-20190921121421-c36f7dcfbb81 github.com/mgechev/dots v0.0.0-20190921121421-c36f7dcfbb81
github.com/mgechev/revive v1.0.2 github.com/mgechev/revive v1.0.2
@ -115,8 +114,7 @@ require (
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/ini.v1 v1.52.0 gopkg.in/ini.v1 v1.52.0
gopkg.in/ldap.v3 v3.0.2 gopkg.in/ldap.v3 v3.0.2
gopkg.in/testfixtures.v2 v2.5.0 gopkg.in/yaml.v2 v2.3.0
gopkg.in/yaml.v2 v2.2.8
mvdan.cc/xurls/v2 v2.1.0 mvdan.cc/xurls/v2 v2.1.0
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251 strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251
xorm.io/builder v0.3.7 xorm.io/builder v0.3.7

@ -149,6 +149,7 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
github.com/denisenkom/go-mssqldb v0.0.0-20190924004331-208c0a498538 h1:bpWCJ5MddHsv4Xtl3azkK89mZzd/vvut32mvAnKbyUA= github.com/denisenkom/go-mssqldb v0.0.0-20190924004331-208c0a498538 h1:bpWCJ5MddHsv4Xtl3azkK89mZzd/vvut32mvAnKbyUA=
github.com/denisenkom/go-mssqldb v0.0.0-20190924004331-208c0a498538/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denisenkom/go-mssqldb v0.0.0-20190924004331-208c0a498538/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc h1:VRRKCwnzqk8QCaRC4os14xoKDdbHqqlJtJA0oc1ZAjg= github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc h1:VRRKCwnzqk8QCaRC4os14xoKDdbHqqlJtJA0oc1ZAjg=
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
@ -278,6 +279,8 @@ github.com/go-swagger/go-swagger v0.21.0 h1:AX9mdfzp6eJtUe92nFrWmbK7ocRgkCDPJs0F
github.com/go-swagger/go-swagger v0.21.0/go.mod h1:tDb8PdDVFcaE8EPXkMOsuxpL3UEPiwu1UDZar9Z/1RY= github.com/go-swagger/go-swagger v0.21.0/go.mod h1:tDb8PdDVFcaE8EPXkMOsuxpL3UEPiwu1UDZar9Z/1RY=
github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013 h1:l9rI6sNaZgNC0LnF3MiE+qTmyBA/tZAg1rtyrGbUMK0= github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013 h1:l9rI6sNaZgNC0LnF3MiE+qTmyBA/tZAg1rtyrGbUMK0=
github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013/go.mod h1:b65mBPzqzZWxOZGxSWrqs4GInLIn+u99Q9q7p+GKni0= github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013/go.mod h1:b65mBPzqzZWxOZGxSWrqs4GInLIn+u99Q9q7p+GKni0=
github.com/go-testfixtures/testfixtures/v3 v3.2.0 h1:FGAW3z5UzmrZGjR/dZp1u3Tbld0SDmirLO4RrR5++7Q=
github.com/go-testfixtures/testfixtures/v3 v3.2.0/go.mod h1:RZctY24ixituGC73XlAV1gkCwYMVwiSwPm26MNlQIhE=
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y= github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y=
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM= github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
@ -437,6 +440,7 @@ github.com/lestrrat-go/jwx v0.9.0/go.mod h1:iEoxlYfZjvoGpuWwxUz+eR5e6KTJGsaRcy/Y
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY= github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY=
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 h1:uNwtsDp7ci48vBTTxDuwcoTXz4lwtDTe7TjCQ0noaWY= github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 h1:uNwtsDp7ci48vBTTxDuwcoTXz4lwtDTe7TjCQ0noaWY=
@ -462,13 +466,13 @@ github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-oci8 v0.0.0-20190320171441-14ba190cf52d h1:m+dSK37rFf2fqppZhg15yI2IwC9BtucBiRwSDm9VL8g=
github.com/mattn/go-oci8 v0.0.0-20190320171441-14ba190cf52d/go.mod h1:/M9VLO+lUPmxvoOK2PfWRZ8mTtB4q1Hy9lEGijv9Nr8=
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v2.0.2+incompatible h1:qzw9c2GNT8UFrgWNDhCTqRqYUSmu/Dav/9Z58LGpk7U=
github.com/mattn/go-sqlite3 v2.0.2+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75 h1:Pijfgr7ZuvX7QIQiEwLdRVr3RoMG+i0SbBO1Qu+7yVk= github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75 h1:Pijfgr7ZuvX7QIQiEwLdRVr3RoMG+i0SbBO1Qu+7yVk=
@ -610,6 +614,8 @@ github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmq
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
@ -890,8 +896,6 @@ gopkg.in/ldap.v3 v3.0.2 h1:R6RBtabK6e1GO0eQKtkyOFbAHO73QesLzI2w2DZ6b9w=
gopkg.in/ldap.v3 v3.0.2/go.mod h1:oxD7NyBuxchC+SgJDE1Q5Od05eGt29SDQVBmV+HYbzw= gopkg.in/ldap.v3 v3.0.2/go.mod h1:oxD7NyBuxchC+SgJDE1Q5Od05eGt29SDQVBmV+HYbzw=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/testfixtures.v2 v2.5.0 h1:N08B7l2GzFQenyYbzqthDnKAA+cmb17iAZhhFxr7JHw=
gopkg.in/testfixtures.v2 v2.5.0/go.mod h1:vyAq+MYCgNpR29qitQdLZhdbLFf4mR/2MFJRFoQZZ2M=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
@ -902,8 +906,11 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

@ -36,7 +36,6 @@ import (
"github.com/PuerkitoBio/goquery" "github.com/PuerkitoBio/goquery"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/unknwon/com" "github.com/unknwon/com"
"gopkg.in/testfixtures.v2"
) )
var mac *macaron.Macaron var mac *macaron.Macaron
@ -88,22 +87,7 @@ func TestMain(m *testing.M) {
} }
} }
var helper testfixtures.Helper
if setting.Database.UseMySQL {
helper = &testfixtures.MySQL{}
} else if setting.Database.UsePostgreSQL {
helper = &testfixtures.PostgreSQL{}
} else if setting.Database.UseSQLite3 {
helper = &testfixtures.SQLite{}
} else if setting.Database.UseMSSQL {
helper = &testfixtures.SQLServer{}
} else {
fmt.Println("Unsupported RDBMS for integration tests")
os.Exit(1)
}
err := models.InitFixtures( err := models.InitFixtures(
helper,
path.Join(filepath.Dir(setting.AppPath), "models/fixtures/"), path.Join(filepath.Dir(setting.AppPath), "models/fixtures/"),
) )
if err != nil { if err != nil {

@ -6,18 +6,48 @@ package models
import ( import (
"fmt" "fmt"
"os"
"time" "time"
"gopkg.in/testfixtures.v2" "github.com/go-testfixtures/testfixtures/v3"
"xorm.io/xorm/schemas" "xorm.io/xorm/schemas"
) )
var fixtures *testfixtures.Context var fixtures *testfixtures.Loader
// InitFixtures initialize test fixtures for a test database // InitFixtures initialize test fixtures for a test database
func InitFixtures(helper testfixtures.Helper, dir string) (err error) { func InitFixtures(dir string) (err error) {
testfixtures.SkipDatabaseNameCheck(true) testfiles := testfixtures.Directory(dir)
fixtures, err = testfixtures.NewFolder(x.DB().DB, helper, dir) dialect := "unknown"
switch x.Dialect().URI().DBType {
case schemas.POSTGRES:
dialect = "postgres"
case schemas.MYSQL:
dialect = "mysql"
case schemas.MSSQL:
dialect = "mssql"
case schemas.SQLITE:
dialect = "sqlite3"
default:
fmt.Println("Unsupported RDBMS for integration tests")
os.Exit(1)
}
loaderOptions := []func(loader *testfixtures.Loader) error{
testfixtures.Database(x.DB().DB),
testfixtures.Dialect(dialect),
testfixtures.DangerousSkipTestDatabaseCheck(),
testfiles,
}
if x.Dialect().URI().DBType == schemas.POSTGRES {
loaderOptions = append(loaderOptions, testfixtures.SkipResetSequences())
}
fixtures, err = testfixtures.New(loaderOptions...)
if err != nil {
return err
}
return err return err
} }

@ -19,7 +19,6 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/unknwon/com" "github.com/unknwon/com"
"gopkg.in/testfixtures.v2"
"xorm.io/xorm" "xorm.io/xorm"
"xorm.io/xorm/names" "xorm.io/xorm/names"
) )
@ -101,7 +100,7 @@ func CreateTestEngine(fixturesDir string) error {
x.ShowSQL(true) x.ShowSQL(true)
} }
return InitFixtures(&testfixtures.SQLite{}, fixturesDir) return InitFixtures(fixturesDir)
} }
func removeAllWithRetry(dir string) error { func removeAllWithRetry(dir string) error {

@ -23,7 +23,7 @@ _testmain.go
*.test *.test
*.prof *.prof
# SQLite databases
*.sqlite3 *.sqlite3
.env .env
/testfixtures
/dist

@ -0,0 +1,41 @@
build:
binary: testfixtures
main: ./cmd/testfixtures
goos:
- windows
- darwin
- linux
goarch:
- 386
- amd64
ignore:
- goos: darwin
goarch: 386
flags:
- -tags=sqlite
archives:
- name_template: "{{.Binary}}_{{.Os}}_{{.Arch}}"
format_overrides:
- goos: windows
format: zip
release:
draft: true
snapshot:
name_template: "{{.Tag}}"
checksum:
name_template: "testfixtures_checksums.txt"
nfpms:
- vendor: testfixtures
homepage: https://github.com/go-testfixtures/testfixtures
maintainer: Andrey Nering <andrey.nering@gmail.com>
description: Ruby on Rails like test fixtures for Go.
license: MIT
formats:
- deb
- rpm
file_name_template: "{{.ProjectName}}_{{.Os}}_{{.Arch}}"

@ -2,4 +2,3 @@ PG_CONN_STRING="user=postgres dbname=testfixtures_test sslmode=disable"
MYSQL_CONN_STRING="root:@/testfixtures_test?multiStatements=true" MYSQL_CONN_STRING="root:@/testfixtures_test?multiStatements=true"
SQLITE_CONN_STRING="testdb.sqlite3" SQLITE_CONN_STRING="testdb.sqlite3"
SQLSERVER_CONN_STRING="server=localhost\SQLExpress;database=testfixtures_test;user id=sa;password=sqlserver;encrypt=disable" SQLSERVER_CONN_STRING="server=localhost\SQLExpress;database=testfixtures_test;user id=sa;password=sqlserver;encrypt=disable"
ORACLE_CONN_STRING="testfixtures/testfixtures@localhost/XE"

@ -0,0 +1,93 @@
# Changelog
## v3.2.0 - 2020-05-10
- Add support for loading multiple files and directories
([#65](https://github.com/go-testfixtures/testfixtures/pull/65)).
## v3.1.2 - 2020-04-26
- Dump: Fix column order in generated YAML files
([#62](https://github.com/go-testfixtures/testfixtures/pull/62)).
## v3.1.1 - 2020-01-11
- testfixtures now work with both `mssql` and `sqlserver` drivers.
Note that [the `mssql` one is deprecated](https://github.com/denisenkom/go-mssqldb#deprecated),
though. So try to migrate to `sqlserver` once possible.
## v3.1.0 - 2020-01-09
- Using `sqlserver` driver instead of the deprecated `mssql`
([#58](https://github.com/go-testfixtures/testfixtures/pull/58)).
## v3.0.0 - 2019-12-26
### Breaking changes
- The import path changed from `gopkg.in/testfixtures.v2` to
`github.com/go-testfixtures/testfixtures/v3`.
- This package no longer support Oracle databases. This decision was
taken because too few people actually used this package with Oracle and it
was the most difficult to test (we didn't run on CI due the lack of an
official Docker image, etc).
- The public API was totally rewritten to be more flexible and ideomatic.
It now uses functional options. It differs from v2, but should be easy
enough to upgrade.
- Some deprecated APIs from v2 were removed as well.
- This now requires Go >= 1.13.
### New features
- We now have a CLI so you can easily use testfixtures to load a sample
database from fixtures if you want.
- Templating via [text/template](https://golang.org/pkg/text/template/)
is now available. This allows some fancier use cases like generating data
or specific columns dynamically.
- It's now possible to choose which time zone to use when parsing timestamps
from fixtures. The default is the same as before, whatever is set on
`time.Local`.
- Errors now use the new `%w` verb only available on Go >= 1.13.
### MISC
- Travis and AppVeyor are gone. We're using GitHub Actions exclusively now.
The whole suite is ran inside Docker (with help of Docker Compose), so it's
easy to run tests locally as well.
Check the new README for some examples!
## v2.6.0 - 2019-10-24
- Add support for TimescaleDB
([#53](https://github.com/go-testfixtures/testfixtures/pull/53)).
## v2.5.3 - 2018-12-15
- Fixes related to use of foreign key pragmas on MySQL (#43).
## v2.5.2 - 2018-11-25
- This library now supports [Go Modules](https://github.com/golang/go/wiki/Modules);
- Also allow `.yaml` (as an alternative to `.yml`) as the file extension (#42).
## v2.5.1 - 2018-11-04
- Allowing disabling reset of PostgreSQL sequences (#38).
## v2.5.0 - 2018-09-07
- Add public function DetectTestDatabase (#35, #36).
## v2.4.5 - 2018-07-07
- Fix for MySQL/MariaDB: ignoring views on operations that should be run only on tables (#33).
## v2.4.4 - 2018-07-02
- Fix for multiple schemas on Microsoft SQL Server (#29 and #30);
- Configuring AppVeyor CI to also test for Microsoft SQL Server.
---
Sorry, we don't have changelog for older releases 😢.

@ -0,0 +1,9 @@
FROM golang:1.14-alpine
RUN apk update
RUN apk add alpine-sdk
WORKDIR /testfixtures
COPY . .
RUN go mod download

@ -0,0 +1,483 @@
# testfixtures
[![GoDoc](https://godoc.org/github.com/go-testfixtures/testfixtures?status.svg)][doc]
> ***Warning***: this package will wipe the database data before loading the
fixtures! It is supposed to be used on a test database. Please, double check
if you are running it against the correct database.
> **TIP**: There are options not described in this README page. It's
> recommended that you also check [the documentation][doc].
Writing tests is hard, even more when you have to deal with an SQL database.
This package aims to make writing functional tests for web apps written in
Go easier.
Basically this package mimics the ["Ruby on Rails' way"][railstests] of writing tests
for database applications, where sample data is kept in fixtures files. Before
the execution of every test, the test database is cleaned and the fixture data
is loaded into the database.
The idea is running tests against a real database, instead of relying in mocks,
which is boring to setup and may lead to production bugs not being caught in
the tests.
## Installation
First, import it like this:
```go
import (
"github.com/go-testfixtures/testfixtures/v3"
)
```
## Usage
Create a folder for the fixture files. Each file should contain data for a
single table and have the name `<table_name>.yml`:
```
myapp/
myapp.go
myapp_test.go
...
fixtures/
posts.yml
comments.yml
tags.yml
posts_tags.yml
...
```
The file would look like this (it can have as many record you want):
```yml
# comments.yml
- id: 1
post_id: 1
content: A comment...
author_name: John Doe
author_email: john@doe.com
created_at: 2020-12-31 23:59:59
updated_at: 2020-12-31 23:59:59
- id: 2
post_id: 2
content: Another comment...
author_name: John Doe
author_email: john@doe.com
created_at: 2020-12-31 23:59:59
updated_at: 2020-12-31 23:59:59
# ...
```
An YAML object or array will be converted to JSON. It will be stored on a native
JSON type like JSONB on PostgreSQL or as a TEXT or VARCHAR column on other
databases.
```yml
- id: 1
post_attributes:
author: John Due
author_email: john@due.com
title: "..."
tags:
- programming
- go
- testing
post: "..."
```
If you need to write raw SQL, probably to call a function, prefix the value
of the column with `RAW=`:
```yml
- id: 1
uuid_column: RAW=uuid_generate_v4()
postgis_type_column: RAW=ST_GeomFromText('params...')
created_at: RAW=NOW()
updated_at: RAW=NOW()
```
Your tests would look like this:
```go
package myapp
import (
"database/sql"
_ "github.com/lib/pq"
"github.com/go-testfixtures/testfixtures/v3"
)
var (
db *sql.DB
fixtures *testfixtures.Loader
)
func TestMain(m *testing.M) {
var err error
// Open connection to the test database.
// Do NOT import fixtures in a production database!
// Existing data would be deleted.
db, err = sql.Open("postgres", "dbname=myapp_test")
if err != nil {
...
}
fixtures, err := testfixtures.New(
testfixtures.Database(db), // You database connection
testfixtures.Dialect("postgres"), // Available: "postgresql", "timescaledb", "mysql", "mariadb", "sqlite" and "sqlserver"
testfixtures.Directory("testdata/fixtures"), // the directory containing the YAML files
)
if err != nil {
...
}
os.Exit(m.Run())
}
func prepareTestDatabase() {
if err := fixtures.Load(); err != nil {
...
}
}
func TestX(t *testing.T) {
prepareTestDatabase()
// Your test here ...
}
func TestY(t *testing.T) {
prepareTestDatabase()
// Your test here ...
}
func TestZ(t *testing.T) {
prepareTestDatabase()
// Your test here ...
}
```
Alternatively, you can use the `Files` option, to specify which
files you want to load into the database:
```go
fixtures, err := testfixtures.New(
testfixtures.Database(db),
testfixtures.Dialect("postgres"),
testfixtures.Files(
"fixtures/orders.yml",
"fixtures/customers.yml",
),
)
if err != nil {
...
}
fixtures, err := testfixtures.NewFiles(db, &testfixtures.PostgreSQL{},
"fixtures/orders.yml",
"fixtures/customers.yml",
// add as many files you want
)
if err != nil {
...
}
```
With `Paths` option, you can specify the paths that fixtures will load
from. Path can be directory or file. If directory, we will search YAML files
in it.
```go
fixtures, err := testfixtures.New(
testfixtures.Database(db),
testfixtures.Dialect("postgres"),
testfixtures.Paths(
"fixtures/orders.yml",
"fixtures/customers.yml",
"common_fixtures/users"
),
)
if err != nil {
...
}
```
## Security check
In order to prevent you from accidentally wiping the wrong database, this
package will refuse to load fixtures if the database name (or database
filename for SQLite) doesn't contains "test". If you want to disable this
check, use:
```go
testfixtures.New(
...
testfixtures.DangerousSkipTestDatabaseCheck(),
)
```
## Sequences
For PostgreSQL, this package also resets all sequences to a high
number to prevent duplicated primary keys while running the tests.
The default is 10000, but you can change that with:
```go
testfixtures.New(
...
testfixtures.ResetSequencesTo(10000),
)
```
Or, if you want to skip the reset of sequences entirely:
```go
testfixtures.New(
...
testfixtures.SkipResetSequences(),
)
```
## Compatible databases
### PostgreSQL / TimescaleDB
This package has two approaches to disable foreign keys while importing fixtures
for PostgreSQL databases:
#### With `DISABLE TRIGGER`
This is the default approach. For that use:
```go
testfixtures.New(
...
testfixtures.Dialect("postgres"), // or "timescaledb"
)
```
With the above snippet this package will use `DISABLE TRIGGER` to temporarily
disabling foreign key constraints while loading fixtures. This work with any
version of PostgreSQL, but it is **required** to be connected in the database
as a SUPERUSER. You can make a PostgreSQL user a SUPERUSER with:
```sql
ALTER USER your_user SUPERUSER;
```
#### With `ALTER CONSTRAINT`
This approach don't require to be connected as a SUPERUSER, but only work with
PostgreSQL versions >= 9.4. Try this if you are getting foreign key violation
errors with the previous approach. It is as simple as using:
```go
testfixtures.New(
...
testfixtures.Dialect("postgres"),
testfixtures.UseAlterConstraint(),
)
```
Tested using the [github.com/lib/pq](https://github.com/lib/pq) driver.
### MySQL / MariaDB
Just make sure the connection string have
[the multistatement parameter](https://github.com/go-sql-driver/mysql#multistatements)
set to true, and use:
```go
testfixtures.New(
...
testfixtures.Dialect("mysql"), // or "mariadb"
)
```
Tested using the [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) driver.
### SQLite
SQLite is also supported. It is recommended to create foreign keys as
`DEFERRABLE` (the default) to prevent problems. See more
[on the SQLite documentation](https://www.sqlite.org/foreignkeys.html#fk_deferred).
(Foreign key constraints are no-op by default on SQLite, but enabling it is
recommended).
```go
testfixtures.New(
...
testfixtures.Dialect("sqlite"),
)
```
Tested using the [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) driver.
### Microsoft SQL Server
SQL Server support requires SQL Server >= 2008. Inserting on `IDENTITY` columns
are handled as well. Just make sure you are logged in with a user with
`ALTER TABLE` permission.
```go
testfixtures.New(
...
testfixtures.Dialect("sqlserver"),
)
```
Tested using the `mssql` and `sqlserver` drivers from the
[github.com/denisenkom/go-mssqldb](https://github.com/denisenkom/go-mssqldb) lib.
## Templating
Testfixtures supports templating, but it's disabled by default. Most people
won't need it, but it may be useful to dynamically generate data.
Enable it by doing:
```go
testfixtures.New(
...
testfixtures.Template(),
// the above options are optional
TemplateFuncs(...),
TemplateDelims("{{", "}}"),
TemplateOptions("missingkey=zero"),
TemplateData(...),
)
```
The YAML file could look like this:
```yaml
# It's possible generate values...
- id: {{sha256 "my-awesome-post}}
title: My Awesome Post
text: {{randomText}}
# ... or records
{{range $post := $.Posts}}
- id: {{$post.Id}}
title: {{$post.Title}}
text: {{$post.Text}}
{{end}}
```
## Generating fixtures for a existing database
The following code will generate a YAML file for each table of the database
into a given folder. It may be useful to boostrap a test scenario from a sample
database of your app.
```go
dumper, err := testfixtures.NewDumper(
testfixtures.DumpDatabase(db),
testfixtures.DumpDialect("postgres"), // or your database of choice
testfixtures.DumpDirectory("tmp/fixtures"),
textfixtures.DumpTables( // optional, will dump all table if not given
"posts",
"comments",
"tags",
)
)
if err != nil {
...
}
if err := dumper.Dump(); err != nil {
...
}
```
> This was intended to run in small sample databases. It will likely break
if run in a production/big database.
## Gotchas
### Parallel testing
This library doesn't yet support running tests in parallel! Running tests
in parallel can result in random data being present in the database, which
will likely cause tests to randomly/intermittently fail.
This is specially tricky since it's not immediately clear that `go test ./...`
run tests for each package in parallel. If more than one package use this
library, you can face this issue. Please, use `go test -p 1 ./...` or run tests
for each package in separated commands to fix this issue.
If you're looking into being able to run tests in parallel you can try using
testfixtures together with the [txdb][gotxdb] package, which allows wrapping
each test run in a transaction.
## CLI
We also have a CLI to load fixtures in a given database.
Grab it from the [releases page](https://github.com/go-testfixtures/testfixtures/releases)
and use it like:
```bash
testfixtures -d postgres -c "postgres://user:password@localhost/database" -D testdata/fixtures
```
The connection string changes for each database driver.
Use `--help` for all flags.
## Contributing
We recommend you to [install Task](https://taskfile.dev/#/installation) and
Docker before contributing to this package, since some stuff is automated
using these tools.
It's recommended to use Docker Compose to run tests, since it runs tests for
all supported databases once. To do that you just need to run:
```bash
task docker
```
But if you want to run tests locally, copy the `.sample.env` file as `.env`
and edit it according to your database setup. You'll need to create a database
(likely names `testfixtures_test`) before continuing. Then run the command
for the database you want to run tests against:
```bash
task test:pg # PostgreSQL
task test:mysql # MySQL
task test:sqlite # SQLite
task test:sqlserver # Microsoft SQL Server
```
GitHub Actions (CI) runs the same Docker setup available locally.
## Alternatives
If you don't think using fixtures is a good idea, you can try one of these
packages instead:
- [factory-go][factorygo]: Factory for Go. Inspired by Python's Factory Boy
and Ruby's Factory Girl
- [go-txdb (Single transaction SQL driver for Go)][gotxdb]: Use a single
database transaction for each functional test, so you can rollback to
previous state between tests to have the same database state in all tests
- [go-sqlmock][gosqlmock]: A mock for the sql.DB interface. This allow you to
unit test database code without having to connect to a real database
- [dbcleaner][dbcleaner] - Clean database for testing, inspired by
database_cleaner for Ruby
[doc]: https://pkg.go.dev/github.com/go-testfixtures/testfixtures/v3?tab=doc
[railstests]: http://guides.rubyonrails.org/testing.html#the-test-database
[gotxdb]: https://github.com/DATA-DOG/go-txdb
[gosqlmock]: https://github.com/DATA-DOG/go-sqlmock
[factorygo]: https://github.com/bluele/factory-go
[dbcleaner]: https://github.com/khaiql/dbcleaner

@ -0,0 +1,59 @@
# https://taskfile.org
version: '2'
tasks:
build:
cmds:
- go build -v -tags sqlite -o ./testfixtures{{exeExt}} ./cmd/testfixtures
test-cli:
cmds:
- ./testfixtures -d sqlite -c testdb.sqlite3 -D testdata/fixtures
test:pg:
desc: Test PostgreSQL
cmds:
- task: test-db
vars: {DATABASE: postgresql}
test:mysql:
desc: Test MySQL
cmds:
- task: test:db
vars: {DATABASE: mysql}
test:sqlite:
desc: Test SQLite
cmds:
- task: test-db
vars: {DATABASE: sqlite}
test:sqlserver:
desc: Test SQLServer
cmds:
- task: test-db
vars: {DATABASE: sqlserver}
test-db:
cmds:
- go test -v -tags {{.DATABASE}}
goreleaser:test:
desc: Tests release process without publishing
cmds:
- goreleaser --snapshot --rm-dist
docker:
cmds:
- task: docker:build
- task: docker:test
docker:build:
cmds:
- docker build -t testfixtures .
docker:test:
cmds:
- docker-compose down -v
- docker-compose run testfixtures go test -v -tags 'postgresql sqlite mysql sqlserver'

@ -0,0 +1,37 @@
version: '3'
services:
testfixtures:
image: testfixtures
depends_on:
- postgresql
- mysql
- sqlserver
environment:
PGPASSWORD: postgres
PG_CONN_STRING: host=postgresql user=postgres dbname=testfixtures_test port=5432 sslmode=disable
MYSQL_CONN_STRING: root:mysql@tcp(mysql)/testfixtures_test?multiStatements=true
SQLITE_CONN_STRING: testfixtures_test.sqlite3
SQLSERVER_CONN_STRING: server=sqlserver;database=master;user id=sa;password=SQL@1server;encrypt=disable
postgresql:
image: postgres:12.1-alpine
environment:
POSTGRES_DB: testfixtures_test
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
mysql:
image: mysql:8.0
environment:
MYSQL_DATABASE: testfixtures_test
MYSQL_ROOT_PASSWORD: mysql
sqlserver:
image: mcr.microsoft.com/mssql/server:2019-latest
environment:
ACCEPT_EULA: 'Y'
SA_PASSWORD: SQL@1server

@ -0,0 +1,165 @@
package testfixtures
import (
"database/sql"
"fmt"
"os"
"path/filepath"
"unicode/utf8"
"gopkg.in/yaml.v2"
)
// Dumper is resposible for dumping fixtures from the database into a
// directory.
type Dumper struct {
db *sql.DB
helper helper
dir string
tables []string
}
// NewDumper creates a new dumper with the given options.
//
// The "DumpDatabase", "DumpDialect" and "DumpDirectory" options are required.
func NewDumper(options ...func(*Dumper) error) (*Dumper, error) {
d := &Dumper{}
for _, option := range options {
if err := option(d); err != nil {
return nil, err
}
}
return d, nil
}
// DumpDatabase sets the database to be dumped.
func DumpDatabase(db *sql.DB) func(*Dumper) error {
return func(d *Dumper) error {
d.db = db
return nil
}
}
// DumpDialect informs Loader about which database dialect you're using.
//
// Possible options are "postgresql", "timescaledb", "mysql", "mariadb",
// "sqlite" and "sqlserver".
func DumpDialect(dialect string) func(*Dumper) error {
return func(d *Dumper) error {
h, err := helperForDialect(dialect)
if err != nil {
return err
}
d.helper = h
return nil
}
}
// DumpDirectory sets the directory where the fixtures files will be created.
func DumpDirectory(dir string) func(*Dumper) error {
return func(d *Dumper) error {
d.dir = dir
return nil
}
}
// DumpTables allows you to choose which tables you want to dump.
//
// If not informed, Dumper will dump all tables by default.
func DumpTables(tables ...string) func(*Dumper) error {
return func(d *Dumper) error {
d.tables = tables
return nil
}
}
// Dump dumps the databases as YAML fixtures.
func (d *Dumper) Dump() error {
tables := d.tables
if len(tables) == 0 {
var err error
tables, err = d.helper.tableNames(d.db)
if err != nil {
return err
}
}
for _, table := range tables {
if err := d.dumpTable(table); err != nil {
return err
}
}
return nil
}
func (d *Dumper) dumpTable(table string) error {
query := fmt.Sprintf("SELECT * FROM %s", d.helper.quoteKeyword(table))
stmt, err := d.db.Prepare(query)
if err != nil {
return err
}
defer stmt.Close()
rows, err := stmt.Query()
if err != nil {
return err
}
defer rows.Close()
columns, err := rows.Columns()
if err != nil {
return err
}
fixtures := make([]yaml.MapSlice, 0, 10)
for rows.Next() {
entries := make([]interface{}, len(columns))
entryPtrs := make([]interface{}, len(entries))
for i := range entries {
entryPtrs[i] = &entries[i]
}
if err := rows.Scan(entryPtrs...); err != nil {
return err
}
entryMap := make([]yaml.MapItem, len(entries))
for i, column := range columns {
entryMap[i] = yaml.MapItem{
Key: column,
Value: convertValue(entries[i]),
}
}
fixtures = append(fixtures, entryMap)
}
if err = rows.Err(); err != nil {
return err
}
filePath := filepath.Join(d.dir, table+".yml")
f, err := os.Create(filePath)
if err != nil {
return err
}
defer f.Close()
data, err := yaml.Marshal(fixtures)
if err != nil {
return err
}
_, err = f.Write(data)
return err
}
func convertValue(value interface{}) interface{} {
switch v := value.(type) {
case []byte:
if utf8.Valid(v) {
return string(v)
}
}
return value
}

@ -0,0 +1,14 @@
module github.com/go-testfixtures/testfixtures/v3
require (
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73
github.com/go-sql-driver/mysql v1.4.1
github.com/joho/godotenv v1.3.0
github.com/lib/pq v1.3.0
github.com/mattn/go-sqlite3 v2.0.2+incompatible
github.com/spf13/pflag v1.0.5
google.golang.org/appengine v1.3.0 // indirect
gopkg.in/yaml.v2 v2.2.7
)
go 1.13

@ -0,0 +1,26 @@
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73 h1:OGNva6WhsKst5OZf7eZOklDztV3hwtTHovdrLHV+MsA=
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-sqlite3 v2.0.2+incompatible h1:qzw9c2GNT8UFrgWNDhCTqRqYUSmu/Dav/9Z58LGpk7U=
github.com/mattn/go-sqlite3 v2.0.2+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

@ -8,13 +8,12 @@ import (
const ( const (
paramTypeDollar = iota + 1 paramTypeDollar = iota + 1
paramTypeQuestion paramTypeQuestion
paramTypeColon paramTypeAtSign
) )
type loadFunction func(tx *sql.Tx) error type loadFunction func(tx *sql.Tx) error
// Helper is the generic interface for the database helper type helper interface {
type Helper interface {
init(*sql.DB) error init(*sql.DB) error
disableReferentialIntegrity(*sql.DB, loadFunction) error disableReferentialIntegrity(*sql.DB, loadFunction) error
paramType() int paramType() int
@ -43,11 +42,10 @@ type batchSplitter interface {
} }
var ( var (
_ Helper = &MySQL{} _ helper = &mySQL{}
_ Helper = &PostgreSQL{} _ helper = &postgreSQL{}
_ Helper = &SQLite{} _ helper = &sqlite{}
_ Helper = &Oracle{} _ helper = &sqlserver{}
_ Helper = &SQLServer{}
) )
type baseHelper struct{} type baseHelper struct{}

@ -5,14 +5,13 @@ import (
"fmt" "fmt"
) )
// MySQL is the MySQL helper for this package type mySQL struct {
type MySQL struct {
baseHelper baseHelper
tables []string tables []string
tablesChecksum map[string]int64 tablesChecksum map[string]int64
} }
func (h *MySQL) init(db *sql.DB) error { func (h *mySQL) init(db *sql.DB) error {
var err error var err error
h.tables, err = h.tableNames(db) h.tables, err = h.tableNames(db)
if err != nil { if err != nil {
@ -22,21 +21,21 @@ func (h *MySQL) init(db *sql.DB) error {
return nil return nil
} }
func (*MySQL) paramType() int { func (*mySQL) paramType() int {
return paramTypeQuestion return paramTypeQuestion
} }
func (*MySQL) quoteKeyword(str string) string { func (*mySQL) quoteKeyword(str string) string {
return fmt.Sprintf("`%s`", str) return fmt.Sprintf("`%s`", str)
} }
func (*MySQL) databaseName(q queryable) (string, error) { func (*mySQL) databaseName(q queryable) (string, error) {
var dbName string var dbName string
err := q.QueryRow("SELECT DATABASE()").Scan(&dbName) err := q.QueryRow("SELECT DATABASE()").Scan(&dbName)
return dbName, err return dbName, err
} }
func (h *MySQL) tableNames(q queryable) ([]string, error) { func (h *mySQL) tableNames(q queryable) ([]string, error) {
query := ` query := `
SELECT table_name SELECT table_name
FROM information_schema.tables FROM information_schema.tables
@ -69,14 +68,7 @@ func (h *MySQL) tableNames(q queryable) ([]string, error) {
} }
func (h *MySQL) disableReferentialIntegrity(db *sql.DB, loadFn loadFunction) (err error) { func (h *mySQL) disableReferentialIntegrity(db *sql.DB, loadFn loadFunction) (err error) {
// re-enable after load
defer func() {
if _, err2 := db.Exec("SET FOREIGN_KEY_CHECKS = 1"); err2 != nil && err == nil {
err = err2
}
}()
tx, err := db.Begin() tx, err := db.Begin()
if err != nil { if err != nil {
return err return err
@ -87,14 +79,19 @@ func (h *MySQL) disableReferentialIntegrity(db *sql.DB, loadFn loadFunction) (er
return err return err
} }
if err = loadFn(tx); err != nil { err = loadFn(tx)
_, err2 := tx.Exec("SET FOREIGN_KEY_CHECKS = 1")
if err != nil {
return err return err
} }
if err2 != nil {
return err2
}
return tx.Commit() return tx.Commit()
} }
func (h *MySQL) isTableModified(q queryable, tableName string) (bool, error) { func (h *mySQL) isTableModified(q queryable, tableName string) (bool, error) {
checksum, err := h.getChecksum(q, tableName) checksum, err := h.getChecksum(q, tableName)
if err != nil { if err != nil {
return true, err return true, err
@ -105,7 +102,7 @@ func (h *MySQL) isTableModified(q queryable, tableName string) (bool, error) {
return oldChecksum == 0 || checksum != oldChecksum, nil return oldChecksum == 0 || checksum != oldChecksum, nil
} }
func (h *MySQL) afterLoad(q queryable) error { func (h *mySQL) afterLoad(q queryable) error {
if h.tablesChecksum != nil { if h.tablesChecksum != nil {
return nil return nil
} }
@ -121,7 +118,7 @@ func (h *MySQL) afterLoad(q queryable) error {
return nil return nil
} }
func (h *MySQL) getChecksum(q queryable, tableName string) (int64, error) { func (h *mySQL) getChecksum(q queryable, tableName string) (int64, error) {
sql := fmt.Sprintf("CHECKSUM TABLE %s", h.quoteKeyword(tableName)) sql := fmt.Sprintf("CHECKSUM TABLE %s", h.quoteKeyword(tableName))
var ( var (
table string table string

@ -6,15 +6,12 @@ import (
"strings" "strings"
) )
// PostgreSQL is the PG helper for this package type postgreSQL struct {
type PostgreSQL struct {
baseHelper baseHelper
// UseAlterConstraint If true, the contraint disabling will do useAlterConstraint bool
// using ALTER CONTRAINT sintax, only allowed in PG >= 9.4. skipResetSequences bool
// If false, the constraint disabling will use DISABLE TRIGGER ALL, resetSequencesTo int64
// which requires SUPERUSER privileges.
UseAlterConstraint bool
tables []string tables []string
sequences []string sequences []string
@ -27,7 +24,7 @@ type pgConstraint struct {
constraintName string constraintName string
} }
func (h *PostgreSQL) init(db *sql.DB) error { func (h *postgreSQL) init(db *sql.DB) error {
var err error var err error
h.tables, err = h.tableNames(db) h.tables, err = h.tableNames(db)
@ -48,17 +45,17 @@ func (h *PostgreSQL) init(db *sql.DB) error {
return nil return nil
} }
func (*PostgreSQL) paramType() int { func (*postgreSQL) paramType() int {
return paramTypeDollar return paramTypeDollar
} }
func (*PostgreSQL) databaseName(q queryable) (string, error) { func (*postgreSQL) databaseName(q queryable) (string, error) {
var dbName string var dbName string
err := q.QueryRow("SELECT current_database()").Scan(&dbName) err := q.QueryRow("SELECT current_database()").Scan(&dbName)
return dbName, err return dbName, err
} }
func (h *PostgreSQL) tableNames(q queryable) ([]string, error) { func (h *postgreSQL) tableNames(q queryable) ([]string, error) {
var tables []string var tables []string
sql := ` sql := `
@ -67,7 +64,8 @@ func (h *PostgreSQL) tableNames(q queryable) ([]string, error) {
INNER JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace INNER JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace
WHERE pg_class.relkind = 'r' WHERE pg_class.relkind = 'r'
AND pg_namespace.nspname NOT IN ('pg_catalog', 'information_schema') AND pg_namespace.nspname NOT IN ('pg_catalog', 'information_schema')
AND pg_namespace.nspname NOT LIKE 'pg_toast%'; AND pg_namespace.nspname NOT LIKE 'pg_toast%'
AND pg_namespace.nspname NOT LIKE '\_timescaledb%';
` `
rows, err := q.Query(sql) rows, err := q.Query(sql)
if err != nil { if err != nil {
@ -88,12 +86,13 @@ func (h *PostgreSQL) tableNames(q queryable) ([]string, error) {
return tables, nil return tables, nil
} }
func (h *PostgreSQL) getSequences(q queryable) ([]string, error) { func (h *postgreSQL) getSequences(q queryable) ([]string, error) {
const sql = ` const sql = `
SELECT pg_namespace.nspname || '.' || pg_class.relname AS sequence_name SELECT pg_namespace.nspname || '.' || pg_class.relname AS sequence_name
FROM pg_class FROM pg_class
INNER JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace INNER JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace
WHERE pg_class.relkind = 'S' WHERE pg_class.relkind = 'S'
AND pg_namespace.nspname NOT LIKE '\_timescaledb%'
` `
rows, err := q.Query(sql) rows, err := q.Query(sql)
@ -116,7 +115,7 @@ func (h *PostgreSQL) getSequences(q queryable) ([]string, error) {
return sequences, nil return sequences, nil
} }
func (*PostgreSQL) getNonDeferrableConstraints(q queryable) ([]pgConstraint, error) { func (*postgreSQL) getNonDeferrableConstraints(q queryable) ([]pgConstraint, error) {
var constraints []pgConstraint var constraints []pgConstraint
sql := ` sql := `
@ -124,6 +123,7 @@ func (*PostgreSQL) getNonDeferrableConstraints(q queryable) ([]pgConstraint, err
FROM information_schema.table_constraints FROM information_schema.table_constraints
WHERE constraint_type = 'FOREIGN KEY' WHERE constraint_type = 'FOREIGN KEY'
AND is_deferrable = 'NO' AND is_deferrable = 'NO'
AND table_schema NOT LIKE '\_timescaledb%'
` `
rows, err := q.Query(sql) rows, err := q.Query(sql)
if err != nil { if err != nil {
@ -144,7 +144,7 @@ func (*PostgreSQL) getNonDeferrableConstraints(q queryable) ([]pgConstraint, err
return constraints, nil return constraints, nil
} }
func (h *PostgreSQL) disableTriggers(db *sql.DB, loadFn loadFunction) (err error) { func (h *postgreSQL) disableTriggers(db *sql.DB, loadFn loadFunction) (err error) {
defer func() { defer func() {
// re-enable triggers after load // re-enable triggers after load
var sql string var sql string
@ -177,7 +177,7 @@ func (h *PostgreSQL) disableTriggers(db *sql.DB, loadFn loadFunction) (err error
return tx.Commit() return tx.Commit()
} }
func (h *PostgreSQL) makeConstraintsDeferrable(db *sql.DB, loadFn loadFunction) (err error) { func (h *postgreSQL) makeConstraintsDeferrable(db *sql.DB, loadFn loadFunction) (err error) {
defer func() { defer func() {
// ensure constraint being not deferrable again after load // ensure constraint being not deferrable again after load
var sql string var sql string
@ -214,21 +214,28 @@ func (h *PostgreSQL) makeConstraintsDeferrable(db *sql.DB, loadFn loadFunction)
return tx.Commit() return tx.Commit()
} }
func (h *PostgreSQL) disableReferentialIntegrity(db *sql.DB, loadFn loadFunction) (err error) { func (h *postgreSQL) disableReferentialIntegrity(db *sql.DB, loadFn loadFunction) (err error) {
// ensure sequences being reset after load // ensure sequences being reset after load
defer func() { if !h.skipResetSequences {
if err2 := h.resetSequences(db); err2 != nil && err == nil { defer func() {
err = err2 if err2 := h.resetSequences(db); err2 != nil && err == nil {
} err = err2
}() }
}()
}
if h.UseAlterConstraint { if h.useAlterConstraint {
return h.makeConstraintsDeferrable(db, loadFn) return h.makeConstraintsDeferrable(db, loadFn)
} }
return h.disableTriggers(db, loadFn) return h.disableTriggers(db, loadFn)
} }
func (h *PostgreSQL) resetSequences(db *sql.DB) error { func (h *postgreSQL) resetSequences(db *sql.DB) error {
resetSequencesTo := h.resetSequencesTo
if resetSequencesTo == 0 {
resetSequencesTo = 10000
}
for _, sequence := range h.sequences { for _, sequence := range h.sequences {
_, err := db.Exec(fmt.Sprintf("SELECT SETVAL('%s', %d)", sequence, resetSequencesTo)) _, err := db.Exec(fmt.Sprintf("SELECT SETVAL('%s', %d)", sequence, resetSequencesTo))
if err != nil { if err != nil {
@ -238,7 +245,7 @@ func (h *PostgreSQL) resetSequences(db *sql.DB) error {
return nil return nil
} }
func (h *PostgreSQL) isTableModified(q queryable, tableName string) (bool, error) { func (h *postgreSQL) isTableModified(q queryable, tableName string) (bool, error) {
checksum, err := h.getChecksum(q, tableName) checksum, err := h.getChecksum(q, tableName)
if err != nil { if err != nil {
return false, err return false, err
@ -249,7 +256,7 @@ func (h *PostgreSQL) isTableModified(q queryable, tableName string) (bool, error
return oldChecksum == "" || checksum != oldChecksum, nil return oldChecksum == "" || checksum != oldChecksum, nil
} }
func (h *PostgreSQL) afterLoad(q queryable) error { func (h *postgreSQL) afterLoad(q queryable) error {
if h.tablesChecksum != nil { if h.tablesChecksum != nil {
return nil return nil
} }
@ -265,7 +272,7 @@ func (h *PostgreSQL) afterLoad(q queryable) error {
return nil return nil
} }
func (h *PostgreSQL) getChecksum(q queryable, tableName string) (string, error) { func (h *postgreSQL) getChecksum(q queryable, tableName string) (string, error) {
sqlStr := fmt.Sprintf(` sqlStr := fmt.Sprintf(`
SELECT md5(CAST((array_agg(t.*)) AS TEXT)) SELECT md5(CAST((array_agg(t.*)) AS TEXT))
FROM %s AS t FROM %s AS t
@ -280,7 +287,7 @@ func (h *PostgreSQL) getChecksum(q queryable, tableName string) (string, error)
return checksum.String, nil return checksum.String, nil
} }
func (*PostgreSQL) quoteKeyword(s string) string { func (*postgreSQL) quoteKeyword(s string) string {
parts := strings.Split(s, ".") parts := strings.Split(s, ".")
for i, p := range parts { for i, p := range parts {
parts[i] = fmt.Sprintf(`"%s"`, p) parts[i] = fmt.Sprintf(`"%s"`, p)

@ -5,16 +5,15 @@ import (
"path/filepath" "path/filepath"
) )
// SQLite is the SQLite Helper for this package type sqlite struct {
type SQLite struct {
baseHelper baseHelper
} }
func (*SQLite) paramType() int { func (*sqlite) paramType() int {
return paramTypeQuestion return paramTypeQuestion
} }
func (*SQLite) databaseName(q queryable) (string, error) { func (*sqlite) databaseName(q queryable) (string, error) {
var seq int var seq int
var main, dbName string var main, dbName string
err := q.QueryRow("PRAGMA database_list").Scan(&seq, &main, &dbName) err := q.QueryRow("PRAGMA database_list").Scan(&seq, &main, &dbName)
@ -25,7 +24,7 @@ func (*SQLite) databaseName(q queryable) (string, error) {
return dbName, nil return dbName, nil
} }
func (*SQLite) tableNames(q queryable) ([]string, error) { func (*sqlite) tableNames(q queryable) ([]string, error) {
query := ` query := `
SELECT name SELECT name
FROM sqlite_master FROM sqlite_master
@ -51,7 +50,7 @@ func (*SQLite) tableNames(q queryable) ([]string, error) {
return tables, nil return tables, nil
} }
func (*SQLite) disableReferentialIntegrity(db *sql.DB, loadFn loadFunction) (err error) { func (*sqlite) disableReferentialIntegrity(db *sql.DB, loadFn loadFunction) (err error) {
defer func() { defer func() {
if _, err2 := db.Exec("PRAGMA defer_foreign_keys = OFF"); err2 != nil && err == nil { if _, err2 := db.Exec("PRAGMA defer_foreign_keys = OFF"); err2 != nil && err == nil {
err = err2 err = err2

@ -6,17 +6,29 @@ import (
"strings" "strings"
) )
// SQLServer is the helper for SQL Server for this package. type sqlserver struct {
// SQL Server >= 2008 is required.
type SQLServer struct {
baseHelper baseHelper
tables []string paramTypeCache int
tables []string
} }
func (h *SQLServer) init(db *sql.DB) error { func (h *sqlserver) init(db *sql.DB) error {
var err error var err error
// NOTE(@andreynering): The SQL Server lib (github.com/denisenkom/go-mssqldb)
// supports both the "?" style (when using the deprecated "mssql" driver)
// and the "@p1" style (when using the new "sqlserver" driver).
//
// Since we don't have a way to know which driver it's been used,
// this is a small hack to detect the allowed param style.
var v int
if err := db.QueryRow("SELECT ?", 1).Scan(&v); err == nil && v == 1 {
h.paramTypeCache = paramTypeQuestion
} else {
h.paramTypeCache = paramTypeAtSign
}
h.tables, err = h.tableNames(db) h.tables, err = h.tableNames(db)
if err != nil { if err != nil {
return err return err
@ -25,11 +37,11 @@ func (h *SQLServer) init(db *sql.DB) error {
return nil return nil
} }
func (*SQLServer) paramType() int { func (h *sqlserver) paramType() int {
return paramTypeQuestion return h.paramTypeCache
} }
func (*SQLServer) quoteKeyword(s string) string { func (*sqlserver) quoteKeyword(s string) string {
parts := strings.Split(s, ".") parts := strings.Split(s, ".")
for i, p := range parts { for i, p := range parts {
parts[i] = fmt.Sprintf(`[%s]`, p) parts[i] = fmt.Sprintf(`[%s]`, p)
@ -37,14 +49,14 @@ func (*SQLServer) quoteKeyword(s string) string {
return strings.Join(parts, ".") return strings.Join(parts, ".")
} }
func (*SQLServer) databaseName(q queryable) (string, error) { func (*sqlserver) databaseName(q queryable) (string, error) {
var dbName string var dbName string
err := q.QueryRow("SELECT DB_NAME()").Scan(&dbName) err := q.QueryRow("SELECT DB_NAME()").Scan(&dbName)
return dbName, err return dbName, err
} }
func (*SQLServer) tableNames(q queryable) ([]string, error) { func (*sqlserver) tableNames(q queryable) ([]string, error) {
rows, err := q.Query("SELECT table_schema + '.' + table_name FROM information_schema.tables") rows, err := q.Query("SELECT table_schema + '.' + table_name FROM information_schema.tables WHERE table_name <> 'spt_values'")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -64,36 +76,42 @@ func (*SQLServer) tableNames(q queryable) ([]string, error) {
return tables, nil return tables, nil
} }
func (h *SQLServer) tableHasIdentityColumn(q queryable, tableName string) bool { func (h *sqlserver) tableHasIdentityColumn(q queryable, tableName string) (bool, error) {
sql := ` sql := fmt.Sprintf(`
SELECT COUNT(*) SELECT COUNT(*)
FROM SYS.IDENTITY_COLUMNS FROM SYS.IDENTITY_COLUMNS
WHERE OBJECT_ID = OBJECT_ID(?) WHERE OBJECT_ID = OBJECT_ID('%s')
` `, tableName)
var count int var count int
q.QueryRow(sql, h.quoteKeyword(tableName)).Scan(&count) if err := q.QueryRow(sql).Scan(&count); err != nil {
return count > 0 return false, err
}
return count > 0, nil
} }
func (h *SQLServer) whileInsertOnTable(tx *sql.Tx, tableName string, fn func() error) (err error) { func (h *sqlserver) whileInsertOnTable(tx *sql.Tx, tableName string, fn func() error) (err error) {
if h.tableHasIdentityColumn(tx, tableName) { hasIdentityColumn, err := h.tableHasIdentityColumn(tx, tableName)
if err != nil {
return err
}
if hasIdentityColumn {
defer func() { defer func() {
_, err2 := tx.Exec(fmt.Sprintf("SET IDENTITY_INSERT %s OFF", h.quoteKeyword(tableName))) _, err2 := tx.Exec(fmt.Sprintf("SET IDENTITY_INSERT %s OFF", h.quoteKeyword(tableName)))
if err2 != nil && err == nil { if err2 != nil && err == nil {
err = err2 err = fmt.Errorf("testfixtures: could not disable identity insert: %w", err2)
} }
}() }()
_, err := tx.Exec(fmt.Sprintf("SET IDENTITY_INSERT %s ON", h.quoteKeyword(tableName))) _, err := tx.Exec(fmt.Sprintf("SET IDENTITY_INSERT %s ON", h.quoteKeyword(tableName)))
if err != nil { if err != nil {
return err return fmt.Errorf("testfixtures: could not enable identity insert: %w", err)
} }
} }
return fn() return fn()
} }
func (h *SQLServer) disableReferentialIntegrity(db *sql.DB, loadFn loadFunction) (err error) { func (h *sqlserver) disableReferentialIntegrity(db *sql.DB, loadFn loadFunction) (err error) {
// ensure the triggers are re-enable after all // ensure the triggers are re-enable after all
defer func() { defer func() {
var sql string var sql string
@ -130,6 +148,6 @@ func (h *SQLServer) disableReferentialIntegrity(db *sql.DB, loadFn loadFunction)
// SQL Server because commands like a `CREATE SCHEMA...` and a `CREATE TABLE...` // SQL Server because commands like a `CREATE SCHEMA...` and a `CREATE TABLE...`
// could not be executed in the same batch. // could not be executed in the same batch.
// See https://docs.microsoft.com/en-us/previous-versions/sql/sql-server-2008-r2/ms175502(v=sql.105)#rules-for-using-batches // See https://docs.microsoft.com/en-us/previous-versions/sql/sql-server-2008-r2/ms175502(v=sql.105)#rules-for-using-batches
func (*SQLServer) splitter() []byte { func (*sqlserver) splitter() []byte {
return []byte("GO\n") return []byte("GO\n")
} }

@ -0,0 +1,599 @@
package testfixtures // import "github.com/go-testfixtures/testfixtures/v3"
import (
"bytes"
"database/sql"
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"regexp"
"strings"
"text/template"
"time"
"gopkg.in/yaml.v2"
)
// Loader is the responsible to loading fixtures.
type Loader struct {
db *sql.DB
helper helper
fixturesFiles []*fixtureFile
skipTestDatabaseCheck bool
location *time.Location
template bool
templateFuncs template.FuncMap
templateLeftDelim string
templateRightDelim string
templateOptions []string
templateData interface{}
}
type fixtureFile struct {
path string
fileName string
content []byte
insertSQLs []insertSQL
}
type insertSQL struct {
sql string
params []interface{}
}
var (
testDatabaseRegexp = regexp.MustCompile("(?i)test")
errDatabaseIsRequired = fmt.Errorf("testfixtures: database is required")
errDialectIsRequired = fmt.Errorf("testfixtures: dialect is required")
)
// New instantiates a new Loader instance. The "Database" and "Driver"
// options are required.
func New(options ...func(*Loader) error) (*Loader, error) {
l := &Loader{
templateLeftDelim: "{{",
templateRightDelim: "}}",
templateOptions: []string{"missingkey=zero"},
}
for _, option := range options {
if err := option(l); err != nil {
return nil, err
}
}
if l.db == nil {
return nil, errDatabaseIsRequired
}
if l.helper == nil {
return nil, errDialectIsRequired
}
if err := l.helper.init(l.db); err != nil {
return nil, err
}
if err := l.buildInsertSQLs(); err != nil {
return nil, err
}
return l, nil
}
// Database sets an existing sql.DB instant to Loader.
func Database(db *sql.DB) func(*Loader) error {
return func(l *Loader) error {
l.db = db
return nil
}
}
// Dialect informs Loader about which database dialect you're using.
//
// Possible options are "postgresql", "timescaledb", "mysql", "mariadb",
// "sqlite" and "sqlserver".
func Dialect(dialect string) func(*Loader) error {
return func(l *Loader) error {
h, err := helperForDialect(dialect)
if err != nil {
return err
}
l.helper = h
return nil
}
}
func helperForDialect(dialect string) (helper, error) {
switch dialect {
case "postgres", "postgresql", "timescaledb":
return &postgreSQL{}, nil
case "mysql", "mariadb":
return &mySQL{}, nil
case "sqlite", "sqlite3":
return &sqlite{}, nil
case "mssql", "sqlserver":
return &sqlserver{}, nil
default:
return nil, fmt.Errorf(`testfixtures: unrecognized dialect "%s"`, dialect)
}
}
// UseAlterConstraint If true, the contraint disabling will do
// using ALTER CONTRAINT sintax, only allowed in PG >= 9.4.
// If false, the constraint disabling will use DISABLE TRIGGER ALL,
// which requires SUPERUSER privileges.
//
// Only valid for PostgreSQL. Returns an error otherwise.
func UseAlterConstraint() func(*Loader) error {
return func(l *Loader) error {
pgHelper, ok := l.helper.(*postgreSQL)
if !ok {
return fmt.Errorf("testfixtures: UseAlterConstraint is only valid for PostgreSQL databases")
}
pgHelper.useAlterConstraint = true
return nil
}
}
// SkipResetSequences prevents Loader from reseting sequences after loading
// fixtures.
//
// Only valid for PostgreSQL. Returns an error otherwise.
func SkipResetSequences() func(*Loader) error {
return func(l *Loader) error {
pgHelper, ok := l.helper.(*postgreSQL)
if !ok {
return fmt.Errorf("testfixtures: SkipResetSequences is only valid for PostgreSQL databases")
}
pgHelper.skipResetSequences = true
return nil
}
}
// ResetSequencesTo sets the value the sequences will be reset to.
//
// Defaults to 10000.
//
// Only valid for PostgreSQL. Returns an error otherwise.
func ResetSequencesTo(value int64) func(*Loader) error {
return func(l *Loader) error {
pgHelper, ok := l.helper.(*postgreSQL)
if !ok {
return fmt.Errorf("testfixtures: ResetSequencesTo is only valid for PostgreSQL databases")
}
pgHelper.resetSequencesTo = value
return nil
}
}
// DangerousSkipTestDatabaseCheck will make Loader not check if the database
// name contains "test". Use with caution!
func DangerousSkipTestDatabaseCheck() func(*Loader) error {
return func(l *Loader) error {
l.skipTestDatabaseCheck = true
return nil
}
}
// Directory informs Loader to load YAML files from a given directory.
func Directory(dir string) func(*Loader) error {
return func(l *Loader) error {
fixtures, err := l.fixturesFromDir(dir)
if err != nil {
return err
}
l.fixturesFiles = append(l.fixturesFiles, fixtures...)
return nil
}
}
// Files informs Loader to load a given set of YAML files.
func Files(files ...string) func(*Loader) error {
return func(l *Loader) error {
fixtures, err := l.fixturesFromFiles(files...)
if err != nil {
return err
}
l.fixturesFiles = append(l.fixturesFiles, fixtures...)
return nil
}
}
// Paths inform Loader to load a given set of YAML files and directories.
func Paths(paths ...string) func(*Loader) error {
return func(l *Loader) error {
fixtures, err := l.fixturesFromPaths(paths...)
if err != nil {
return err
}
l.fixturesFiles = append(l.fixturesFiles, fixtures...)
return nil
}
}
// Location makes Loader use the given location by default when parsing
// dates. If not given, by default it uses the value of time.Local.
func Location(location *time.Location) func(*Loader) error {
return func(l *Loader) error {
l.location = location
return nil
}
}
// Template makes loader process each YAML file as an template using the
// text/template package.
//
// For more information on how templates work in Go please read:
// https://golang.org/pkg/text/template/
//
// If not given the YAML files are parsed as is.
func Template() func(*Loader) error {
return func(l *Loader) error {
l.template = true
return nil
}
}
// TemplateFuncs allow choosing which functions will be available
// when processing templates.
//
// For more information see: https://golang.org/pkg/text/template/#Template.Funcs
func TemplateFuncs(funcs template.FuncMap) func(*Loader) error {
return func(l *Loader) error {
if !l.template {
return fmt.Errorf(`testfixtures: the Template() options is required in order to use the TemplateFuns() option`)
}
l.templateFuncs = funcs
return nil
}
}
// TemplateDelims allow choosing which delimiters will be used for templating.
// This defaults to "{{" and "}}".
//
// For more information see https://golang.org/pkg/text/template/#Template.Delims
func TemplateDelims(left, right string) func(*Loader) error {
return func(l *Loader) error {
if !l.template {
return fmt.Errorf(`testfixtures: the Template() options is required in order to use the TemplateDelims() option`)
}
l.templateLeftDelim = left
l.templateRightDelim = right
return nil
}
}
// TemplateOptions allows you to specific which text/template options will
// be enabled when processing templates.
//
// This defaults to "missingkey=zero". Check the available options here:
// https://golang.org/pkg/text/template/#Template.Option
func TemplateOptions(options ...string) func(*Loader) error {
return func(l *Loader) error {
if !l.template {
return fmt.Errorf(`testfixtures: the Template() options is required in order to use the TemplateOptions() option`)
}
l.templateOptions = options
return nil
}
}
// TemplateData allows you to specify which data will be available
// when processing templates. Data is accesible by prefixing it with a "."
// like {{.MyKey}}.
func TemplateData(data interface{}) func(*Loader) error {
return func(l *Loader) error {
if !l.template {
return fmt.Errorf(`testfixtures: the Template() options is required in order to use the TemplateData() option`)
}
l.templateData = data
return nil
}
}
// EnsureTestDatabase returns an error if the database name does not contains
// "test".
func (l *Loader) EnsureTestDatabase() error {
dbName, err := l.helper.databaseName(l.db)
if err != nil {
return err
}
if !testDatabaseRegexp.MatchString(dbName) {
return fmt.Errorf(`testfixtures: database "%s" does not appear to be a test database`, dbName)
}
return nil
}
// Load wipes and after load all fixtures in the database.
// if err := fixtures.Load(); err != nil {
// ...
// }
func (l *Loader) Load() error {
if !l.skipTestDatabaseCheck {
if err := l.EnsureTestDatabase(); err != nil {
return err
}
}
err := l.helper.disableReferentialIntegrity(l.db, func(tx *sql.Tx) error {
for _, file := range l.fixturesFiles {
modified, err := l.helper.isTableModified(tx, file.fileNameWithoutExtension())
if err != nil {
return err
}
if !modified {
continue
}
if err := file.delete(tx, l.helper); err != nil {
return err
}
err = l.helper.whileInsertOnTable(tx, file.fileNameWithoutExtension(), func() error {
for j, i := range file.insertSQLs {
if _, err := tx.Exec(i.sql, i.params...); err != nil {
return &InsertError{
Err: err,
File: file.fileName,
Index: j,
SQL: i.sql,
Params: i.params,
}
}
}
return nil
})
if err != nil {
return err
}
}
return nil
})
if err != nil {
return err
}
return l.helper.afterLoad(l.db)
}
// InsertError will be returned if any error happens on database while
// inserting the record.
type InsertError struct {
Err error
File string
Index int
SQL string
Params []interface{}
}
func (e *InsertError) Error() string {
return fmt.Sprintf(
"testfixtures: error inserting record: %v, on file: %s, index: %d, sql: %s, params: %v",
e.Err,
e.File,
e.Index,
e.SQL,
e.Params,
)
}
func (l *Loader) buildInsertSQLs() error {
for _, f := range l.fixturesFiles {
var records interface{}
if err := yaml.Unmarshal(f.content, &records); err != nil {
return fmt.Errorf("testfixtures: could not unmarshal YAML: %w", err)
}
switch records := records.(type) {
case []interface{}:
f.insertSQLs = make([]insertSQL, 0, len(records))
for _, record := range records {
recordMap, ok := record.(map[interface{}]interface{})
if !ok {
return fmt.Errorf("testfixtures: could not cast record: not a map[interface{}]interface{}")
}
sql, values, err := l.buildInsertSQL(f, recordMap)
if err != nil {
return err
}
f.insertSQLs = append(f.insertSQLs, insertSQL{sql, values})
}
case map[interface{}]interface{}:
f.insertSQLs = make([]insertSQL, 0, len(records))
for _, record := range records {
recordMap, ok := record.(map[interface{}]interface{})
if !ok {
return fmt.Errorf("testfixtures: could not cast record: not a map[interface{}]interface{}")
}
sql, values, err := l.buildInsertSQL(f, recordMap)
if err != nil {
return err
}
f.insertSQLs = append(f.insertSQLs, insertSQL{sql, values})
}
default:
return fmt.Errorf("testfixtures: fixture is not a slice or map")
}
}
return nil
}
func (f *fixtureFile) fileNameWithoutExtension() string {
return strings.Replace(f.fileName, filepath.Ext(f.fileName), "", 1)
}
func (f *fixtureFile) delete(tx *sql.Tx, h helper) error {
if _, err := tx.Exec(fmt.Sprintf("DELETE FROM %s", h.quoteKeyword(f.fileNameWithoutExtension()))); err != nil {
return fmt.Errorf(`testfixtures: could not clean table "%s": %w`, f.fileNameWithoutExtension(), err)
}
return nil
}
func (l *Loader) buildInsertSQL(f *fixtureFile, record map[interface{}]interface{}) (sqlStr string, values []interface{}, err error) {
var (
sqlColumns = make([]string, 0, len(record))
sqlValues = make([]string, 0, len(record))
i = 1
)
for key, value := range record {
keyStr, ok := key.(string)
if !ok {
err = fmt.Errorf("testfixtures: record map key is not a string")
return
}
sqlColumns = append(sqlColumns, l.helper.quoteKeyword(keyStr))
// if string, try convert to SQL or time
// if map or array, convert to json
switch v := value.(type) {
case string:
if strings.HasPrefix(v, "RAW=") {
sqlValues = append(sqlValues, strings.TrimPrefix(v, "RAW="))
continue
}
if t, err := l.tryStrToDate(v); err == nil {
value = t
}
case []interface{}, map[interface{}]interface{}:
value = recursiveToJSON(v)
}
switch l.helper.paramType() {
case paramTypeDollar:
sqlValues = append(sqlValues, fmt.Sprintf("$%d", i))
case paramTypeQuestion:
sqlValues = append(sqlValues, "?")
case paramTypeAtSign:
sqlValues = append(sqlValues, fmt.Sprintf("@p%d", i))
}
values = append(values, value)
i++
}
sqlStr = fmt.Sprintf(
"INSERT INTO %s (%s) VALUES (%s)",
l.helper.quoteKeyword(f.fileNameWithoutExtension()),
strings.Join(sqlColumns, ", "),
strings.Join(sqlValues, ", "),
)
return
}
func (l *Loader) fixturesFromDir(dir string) ([]*fixtureFile, error) {
fileinfos, err := ioutil.ReadDir(dir)
if err != nil {
return nil, fmt.Errorf(`testfixtures: could not stat directory "%s": %w`, dir, err)
}
files := make([]*fixtureFile, 0, len(fileinfos))
for _, fileinfo := range fileinfos {
fileExt := filepath.Ext(fileinfo.Name())
if !fileinfo.IsDir() && (fileExt == ".yml" || fileExt == ".yaml") {
fixture := &fixtureFile{
path: path.Join(dir, fileinfo.Name()),
fileName: fileinfo.Name(),
}
fixture.content, err = ioutil.ReadFile(fixture.path)
if err != nil {
return nil, fmt.Errorf(`testfixtures: could not read file "%s": %w`, fixture.path, err)
}
if err := l.processFileTemplate(fixture); err != nil {
return nil, err
}
files = append(files, fixture)
}
}
return files, nil
}
func (l *Loader) fixturesFromFiles(fileNames ...string) ([]*fixtureFile, error) {
var (
fixtureFiles = make([]*fixtureFile, 0, len(fileNames))
err error
)
for _, f := range fileNames {
fixture := &fixtureFile{
path: f,
fileName: filepath.Base(f),
}
fixture.content, err = ioutil.ReadFile(fixture.path)
if err != nil {
return nil, fmt.Errorf(`testfixtures: could not read file "%s": %w`, fixture.path, err)
}
if err := l.processFileTemplate(fixture); err != nil {
return nil, err
}
fixtureFiles = append(fixtureFiles, fixture)
}
return fixtureFiles, nil
}
func (l *Loader) fixturesFromPaths(paths ...string) ([]*fixtureFile, error) {
fixtureExtractor := func(p string, isDir bool) ([]*fixtureFile, error) {
if isDir {
return l.fixturesFromDir(p)
}
return l.fixturesFromFiles(p)
}
var fixtureFiles []*fixtureFile
for _, p := range paths {
f, err := os.Stat(p)
if err != nil {
return nil, fmt.Errorf(`testfixtures: could not stat path "%s": %w`, p, err)
}
fixtures, err := fixtureExtractor(p, f.IsDir())
if err != nil {
return nil, err
}
fixtureFiles = append(fixtureFiles, fixtures...)
}
return fixtureFiles, nil
}
func (l *Loader) processFileTemplate(f *fixtureFile) error {
if !l.template {
return nil
}
t := template.New("").
Funcs(l.templateFuncs).
Delims(l.templateLeftDelim, l.templateRightDelim).
Option(l.templateOptions...)
t, err := t.Parse(string(f.content))
if err != nil {
return fmt.Errorf(`textfixtures: error on parsing template in %s: %w`, f.fileName, err)
}
var buffer bytes.Buffer
if err := t.Execute(&buffer, l.templateData); err != nil {
return fmt.Errorf(`textfixtures: error on executing template in %s: %w`, f.fileName, err)
}
f.content = buffer.Bytes()
return nil
}

@ -0,0 +1,43 @@
package testfixtures
import (
"fmt"
"time"
)
var timeFormats = [...]string{
"2006-01-02",
"2006-01-02 15:04",
"2006-01-02 15:04:05",
"20060102",
"20060102 15:04",
"20060102 15:04:05",
"02/01/2006",
"02/01/2006 15:04",
"02/01/2006 15:04:05",
"2006-01-02T15:04-07:00",
"2006-01-02T15:04:05-07:00",
"2006-01-02T15:04:05Z07:00",
"2006-01-02 15:04:05Z07:00",
"2006-01-02T15:04:05Z0700",
"2006-01-02 15:04:05Z0700",
"2006-01-02T15:04:05Z07",
"2006-01-02 15:04:05Z07",
"2006-01-02 15:04:05 MST",
}
func (l *Loader) tryStrToDate(s string) (time.Time, error) {
loc := l.location
if loc == nil {
loc = time.Local
}
for _, f := range timeFormats {
t, err := time.ParseInLocation(f, s, loc)
if err != nil {
continue
}
return t, nil
}
return time.Time{}, fmt.Errorf(`testfixtures: could not convert string "%s" to time`, s)
}

@ -12,6 +12,8 @@ go:
- 1.9.x - 1.9.x
- 1.10.x - 1.10.x
- 1.11.x - 1.11.x
- 1.12.x
- 1.13.x
- master - master
before_install: before_install:
@ -27,5 +29,5 @@ script:
- $HOME/gopath/bin/goveralls -repotoken 3qJVUE0iQwqnCbmNcDsjYu1nh4J4KIFXx - $HOME/gopath/bin/goveralls -repotoken 3qJVUE0iQwqnCbmNcDsjYu1nh4J4KIFXx
- go test -race -v . -tags "" - go test -race -v . -tags ""
- go test -race -v . -tags "libsqlite3" - go test -race -v . -tags "libsqlite3"
- go test -race -v . -tags "sqlite_allow_uri_authority sqlite_app_armor sqlite_foreign_keys sqlite_fts5 sqlite_icu sqlite_introspect sqlite_json sqlite_secure_delete sqlite_see sqlite_stat4 sqlite_trace sqlite_userauth sqlite_vacuum_incr sqlite_vtable sqlite_unlock_notify" - go test -race -v . -tags "sqlite_allow_uri_authority sqlite_app_armor sqlite_foreign_keys sqlite_fts5 sqlite_icu sqlite_introspect sqlite_json sqlite_preupdate_hook sqlite_secure_delete sqlite_see sqlite_stat4 sqlite_trace sqlite_userauth sqlite_vacuum_incr sqlite_vtable sqlite_unlock_notify"
- go test -race -v . -tags "sqlite_vacuum_full" - go test -race -v . -tags "sqlite_vacuum_full"

@ -3,6 +3,7 @@ go-sqlite3
[![GoDoc Reference](https://godoc.org/github.com/mattn/go-sqlite3?status.svg)](http://godoc.org/github.com/mattn/go-sqlite3) [![GoDoc Reference](https://godoc.org/github.com/mattn/go-sqlite3?status.svg)](http://godoc.org/github.com/mattn/go-sqlite3)
[![Build Status](https://travis-ci.org/mattn/go-sqlite3.svg?branch=master)](https://travis-ci.org/mattn/go-sqlite3) [![Build Status](https://travis-ci.org/mattn/go-sqlite3.svg?branch=master)](https://travis-ci.org/mattn/go-sqlite3)
[![Financial Contributors on Open Collective](https://opencollective.com/mattn-go-sqlite3/all/badge.svg?label=financial+contributors)](https://opencollective.com/mattn-go-sqlite3)
[![Coverage Status](https://coveralls.io/repos/mattn/go-sqlite3/badge.svg?branch=master)](https://coveralls.io/r/mattn/go-sqlite3?branch=master) [![Coverage Status](https://coveralls.io/repos/mattn/go-sqlite3/badge.svg?branch=master)](https://coveralls.io/r/mattn/go-sqlite3?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/mattn/go-sqlite3)](https://goreportcard.com/report/github.com/mattn/go-sqlite3) [![Go Report Card](https://goreportcard.com/badge/github.com/mattn/go-sqlite3)](https://goreportcard.com/report/github.com/mattn/go-sqlite3)
@ -16,15 +17,21 @@ Supported Golang version: See .travis.yml
### Overview ### Overview
- [go-sqlite3](#go-sqlite3)
- [Description](#description)
- [Overview](#overview)
- [Installation](#installation) - [Installation](#installation)
- [API Reference](#api-reference) - [API Reference](#api-reference)
- [Connection String](#connection-string) - [Connection String](#connection-string)
- [DSN Examples](#dsn-examples)
- [Features](#features) - [Features](#features)
- [Usage](#usage)
- [Feature / Extension List](#feature--extension-list)
- [Compilation](#compilation) - [Compilation](#compilation)
- [Android](#android) - [Android](#android)
- [ARM](#arm) - [ARM](#arm)
- [Cross Compile](#cross-compile) - [Cross Compile](#cross-compile)
- [Google Cloud Platform](#google-cloud-platform) - [Google Cloud Platform](#google-cloud-platform)
- [Linux](#linux) - [Linux](#linux)
- [Alpine](#alpine) - [Alpine](#alpine)
- [Fedora](#fedora) - [Fedora](#fedora)
@ -34,11 +41,22 @@ Supported Golang version: See .travis.yml
- [Errors](#errors) - [Errors](#errors)
- [User Authentication](#user-authentication) - [User Authentication](#user-authentication)
- [Compile](#compile) - [Compile](#compile)
- [Usage](#usage) - [Usage](#usage-1)
- [Create protected database](#create-protected-database)
- [Password Encoding](#password-encoding)
- [Available Encoders](#available-encoders)
- [Restrictions](#restrictions)
- [Support](#support)
- [User Management](#user-management)
- [SQL](#sql)
- [Examples](#examples)
- [*SQLiteConn](#sqliteconn)
- [Attached database](#attached-database)
- [Extensions](#extensions) - [Extensions](#extensions)
- [Spatialite](#spatialite) - [Spatialite](#spatialite)
- [FAQ](#faq) - [FAQ](#faq)
- [License](#license) - [License](#license)
- [Author](#author)
# Installation # Installation
@ -149,6 +167,7 @@ go build --tags "icu json1 fts5 secure_delete"
| International Components for Unicode | sqlite_icu | This option causes the International Components for Unicode or "ICU" extension to SQLite to be added to the build | | International Components for Unicode | sqlite_icu | This option causes the International Components for Unicode or "ICU" extension to SQLite to be added to the build |
| Introspect PRAGMAS | sqlite_introspect | This option adds some extra PRAGMA statements. <ul><li>PRAGMA function_list</li><li>PRAGMA module_list</li><li>PRAGMA pragma_list</li></ul> | | Introspect PRAGMAS | sqlite_introspect | This option adds some extra PRAGMA statements. <ul><li>PRAGMA function_list</li><li>PRAGMA module_list</li><li>PRAGMA pragma_list</li></ul> |
| JSON SQL Functions | sqlite_json | When this option is defined in the amalgamation, the JSON SQL functions are added to the build automatically | | JSON SQL Functions | sqlite_json | When this option is defined in the amalgamation, the JSON SQL functions are added to the build automatically |
| Pre Update Hook | sqlite_preupdate_hook | Registers a callback function that is invoked prior to each INSERT, UPDATE, and DELETE operation on a database table. |
| Secure Delete | sqlite_secure_delete | This compile-time option changes the default setting of the secure_delete pragma.<br><br>When this option is not used, secure_delete defaults to off. When this option is present, secure_delete defaults to on.<br><br>The secure_delete setting causes deleted content to be overwritten with zeros. There is a small performance penalty since additional I/O must occur.<br><br>On the other hand, secure_delete can prevent fragments of sensitive information from lingering in unused parts of the database file after it has been deleted. See the documentation on the secure_delete pragma for additional information | | Secure Delete | sqlite_secure_delete | This compile-time option changes the default setting of the secure_delete pragma.<br><br>When this option is not used, secure_delete defaults to off. When this option is present, secure_delete defaults to on.<br><br>The secure_delete setting causes deleted content to be overwritten with zeros. There is a small performance penalty since additional I/O must occur.<br><br>On the other hand, secure_delete can prevent fragments of sensitive information from lingering in unused parts of the database file after it has been deleted. See the documentation on the secure_delete pragma for additional information |
| Secure Delete (FAST) | sqlite_secure_delete_fast | For more information see [PRAGMA secure_delete](https://www.sqlite.org/pragma.html#pragma_secure_delete) | | Secure Delete (FAST) | sqlite_secure_delete_fast | For more information see [PRAGMA secure_delete](https://www.sqlite.org/pragma.html#pragma_secure_delete) |
| Tracing / Debug | sqlite_trace | Activate trace functions | | Tracing / Debug | sqlite_trace | Activate trace functions |
@ -504,6 +523,36 @@ For an example see [shaxbee/go-spatialite](https://github.com/shaxbee/go-spatial
More information see [#209](https://github.com/mattn/go-sqlite3/issues/209) More information see [#209](https://github.com/mattn/go-sqlite3/issues/209)
## Contributors
### Code Contributors
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
<a href="https://github.com/mattn/go-sqlite3/graphs/contributors"><img src="https://opencollective.com/mattn-go-sqlite3/contributors.svg?width=890&button=false" /></a>
### Financial Contributors
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/mattn-go-sqlite3/contribute)]
#### Individuals
<a href="https://opencollective.com/mattn-go-sqlite3"><img src="https://opencollective.com/mattn-go-sqlite3/individuals.svg?width=890"></a>
#### Organizations
Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/mattn-go-sqlite3/contribute)]
<a href="https://opencollective.com/mattn-go-sqlite3/organization/0/website"><img src="https://opencollective.com/mattn-go-sqlite3/organization/0/avatar.svg"></a>
<a href="https://opencollective.com/mattn-go-sqlite3/organization/1/website"><img src="https://opencollective.com/mattn-go-sqlite3/organization/1/avatar.svg"></a>
<a href="https://opencollective.com/mattn-go-sqlite3/organization/2/website"><img src="https://opencollective.com/mattn-go-sqlite3/organization/2/avatar.svg"></a>
<a href="https://opencollective.com/mattn-go-sqlite3/organization/3/website"><img src="https://opencollective.com/mattn-go-sqlite3/organization/3/avatar.svg"></a>
<a href="https://opencollective.com/mattn-go-sqlite3/organization/4/website"><img src="https://opencollective.com/mattn-go-sqlite3/organization/4/avatar.svg"></a>
<a href="https://opencollective.com/mattn-go-sqlite3/organization/5/website"><img src="https://opencollective.com/mattn-go-sqlite3/organization/5/avatar.svg"></a>
<a href="https://opencollective.com/mattn-go-sqlite3/organization/6/website"><img src="https://opencollective.com/mattn-go-sqlite3/organization/6/avatar.svg"></a>
<a href="https://opencollective.com/mattn-go-sqlite3/organization/7/website"><img src="https://opencollective.com/mattn-go-sqlite3/organization/7/avatar.svg"></a>
<a href="https://opencollective.com/mattn-go-sqlite3/organization/8/website"><img src="https://opencollective.com/mattn-go-sqlite3/organization/8/avatar.svg"></a>
<a href="https://opencollective.com/mattn-go-sqlite3/organization/9/website"><img src="https://opencollective.com/mattn-go-sqlite3/organization/9/avatar.svg"></a>
# License # License
MIT: http://mattn.mit-license.org/2018 MIT: http://mattn.mit-license.org/2018

@ -1,4 +1,4 @@
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>. // Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
// //
// Use of this source code is governed by an MIT-style // Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -25,18 +25,18 @@ type SQLiteBackup struct {
} }
// Backup make backup from src to dest. // Backup make backup from src to dest.
func (c *SQLiteConn) Backup(dest string, conn *SQLiteConn, src string) (*SQLiteBackup, error) { func (destConn *SQLiteConn) Backup(dest string, srcConn *SQLiteConn, src string) (*SQLiteBackup, error) {
destptr := C.CString(dest) destptr := C.CString(dest)
defer C.free(unsafe.Pointer(destptr)) defer C.free(unsafe.Pointer(destptr))
srcptr := C.CString(src) srcptr := C.CString(src)
defer C.free(unsafe.Pointer(srcptr)) defer C.free(unsafe.Pointer(srcptr))
if b := C.sqlite3_backup_init(c.db, destptr, conn.db, srcptr); b != nil { if b := C.sqlite3_backup_init(destConn.db, destptr, srcConn.db, srcptr); b != nil {
bb := &SQLiteBackup{b: b} bb := &SQLiteBackup{b: b}
runtime.SetFinalizer(bb, (*SQLiteBackup).Finish) runtime.SetFinalizer(bb, (*SQLiteBackup).Finish)
return bb, nil return bb, nil
} }
return nil, c.lastError() return nil, destConn.lastError()
} }
// Step to backs up for one step. Calls the underlying `sqlite3_backup_step` // Step to backs up for one step. Calls the underlying `sqlite3_backup_step`

@ -1,4 +1,4 @@
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>. // Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
// //
// Use of this source code is governed by an MIT-style // Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -83,8 +83,22 @@ func authorizerTrampoline(handle uintptr, op int, arg1 *C.char, arg2 *C.char, ar
return callback(op, C.GoString(arg1), C.GoString(arg2), C.GoString(arg3)) return callback(op, C.GoString(arg1), C.GoString(arg2), C.GoString(arg3))
} }
// Use handles to avoid passing Go pointers to C. //export preUpdateHookTrampoline
func preUpdateHookTrampoline(handle uintptr, dbHandle uintptr, op int, db *C.char, table *C.char, oldrowid int64, newrowid int64) {
hval := lookupHandleVal(handle)
data := SQLitePreUpdateData{
Conn: hval.db,
Op: op,
DatabaseName: C.GoString(db),
TableName: C.GoString(table),
OldRowID: oldrowid,
NewRowID: newrowid,
}
callback := hval.val.(func(SQLitePreUpdateData))
callback(data)
}
// Use handles to avoid passing Go pointers to C.
type handleVal struct { type handleVal struct {
db *SQLiteConn db *SQLiteConn
val interface{} val interface{}
@ -103,7 +117,7 @@ func newHandle(db *SQLiteConn, v interface{}) uintptr {
return i return i
} }
func lookupHandle(handle uintptr) interface{} { func lookupHandleVal(handle uintptr) handleVal {
handleLock.Lock() handleLock.Lock()
defer handleLock.Unlock() defer handleLock.Unlock()
r, ok := handleVals[handle] r, ok := handleVals[handle]
@ -114,7 +128,11 @@ func lookupHandle(handle uintptr) interface{} {
panic("invalid handle") panic("invalid handle")
} }
} }
return r.val return r
}
func lookupHandle(handle uintptr) interface{} {
return lookupHandleVal(handle).val
} }
func deleteHandles(db *SQLiteConn) { func deleteHandles(db *SQLiteConn) {

@ -0,0 +1,299 @@
// Extracted from Go database/sql source code
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Type conversions for Scan.
package sqlite3
import (
"database/sql"
"database/sql/driver"
"errors"
"fmt"
"reflect"
"strconv"
"time"
)
var errNilPtr = errors.New("destination pointer is nil") // embedded in descriptive error
// convertAssign copies to dest the value in src, converting it if possible.
// An error is returned if the copy would result in loss of information.
// dest should be a pointer type.
func convertAssign(dest, src interface{}) error {
// Common cases, without reflect.
switch s := src.(type) {
case string:
switch d := dest.(type) {
case *string:
if d == nil {
return errNilPtr
}
*d = s
return nil
case *[]byte:
if d == nil {
return errNilPtr
}
*d = []byte(s)
return nil
case *sql.RawBytes:
if d == nil {
return errNilPtr
}
*d = append((*d)[:0], s...)
return nil
}
case []byte:
switch d := dest.(type) {
case *string:
if d == nil {
return errNilPtr
}
*d = string(s)
return nil
case *interface{}:
if d == nil {
return errNilPtr
}
*d = cloneBytes(s)
return nil
case *[]byte:
if d == nil {
return errNilPtr
}
*d = cloneBytes(s)
return nil
case *sql.RawBytes:
if d == nil {
return errNilPtr
}
*d = s
return nil
}
case time.Time:
switch d := dest.(type) {
case *time.Time:
*d = s
return nil
case *string:
*d = s.Format(time.RFC3339Nano)
return nil
case *[]byte:
if d == nil {
return errNilPtr
}
*d = []byte(s.Format(time.RFC3339Nano))
return nil
case *sql.RawBytes:
if d == nil {
return errNilPtr
}
*d = s.AppendFormat((*d)[:0], time.RFC3339Nano)
return nil
}
case nil:
switch d := dest.(type) {
case *interface{}:
if d == nil {
return errNilPtr
}
*d = nil
return nil
case *[]byte:
if d == nil {
return errNilPtr
}
*d = nil
return nil
case *sql.RawBytes:
if d == nil {
return errNilPtr
}
*d = nil
return nil
}
}
var sv reflect.Value
switch d := dest.(type) {
case *string:
sv = reflect.ValueOf(src)
switch sv.Kind() {
case reflect.Bool,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64:
*d = asString(src)
return nil
}
case *[]byte:
sv = reflect.ValueOf(src)
if b, ok := asBytes(nil, sv); ok {
*d = b
return nil
}
case *sql.RawBytes:
sv = reflect.ValueOf(src)
if b, ok := asBytes([]byte(*d)[:0], sv); ok {
*d = sql.RawBytes(b)
return nil
}
case *bool:
bv, err := driver.Bool.ConvertValue(src)
if err == nil {
*d = bv.(bool)
}
return err
case *interface{}:
*d = src
return nil
}
if scanner, ok := dest.(sql.Scanner); ok {
return scanner.Scan(src)
}
dpv := reflect.ValueOf(dest)
if dpv.Kind() != reflect.Ptr {
return errors.New("destination not a pointer")
}
if dpv.IsNil() {
return errNilPtr
}
if !sv.IsValid() {
sv = reflect.ValueOf(src)
}
dv := reflect.Indirect(dpv)
if sv.IsValid() && sv.Type().AssignableTo(dv.Type()) {
switch b := src.(type) {
case []byte:
dv.Set(reflect.ValueOf(cloneBytes(b)))
default:
dv.Set(sv)
}
return nil
}
if dv.Kind() == sv.Kind() && sv.Type().ConvertibleTo(dv.Type()) {
dv.Set(sv.Convert(dv.Type()))
return nil
}
// The following conversions use a string value as an intermediate representation
// to convert between various numeric types.
//
// This also allows scanning into user defined types such as "type Int int64".
// For symmetry, also check for string destination types.
switch dv.Kind() {
case reflect.Ptr:
if src == nil {
dv.Set(reflect.Zero(dv.Type()))
return nil
}
dv.Set(reflect.New(dv.Type().Elem()))
return convertAssign(dv.Interface(), src)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
s := asString(src)
i64, err := strconv.ParseInt(s, 10, dv.Type().Bits())
if err != nil {
err = strconvErr(err)
return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err)
}
dv.SetInt(i64)
return nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
s := asString(src)
u64, err := strconv.ParseUint(s, 10, dv.Type().Bits())
if err != nil {
err = strconvErr(err)
return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err)
}
dv.SetUint(u64)
return nil
case reflect.Float32, reflect.Float64:
s := asString(src)
f64, err := strconv.ParseFloat(s, dv.Type().Bits())
if err != nil {
err = strconvErr(err)
return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err)
}
dv.SetFloat(f64)
return nil
case reflect.String:
switch v := src.(type) {
case string:
dv.SetString(v)
return nil
case []byte:
dv.SetString(string(v))
return nil
}
}
return fmt.Errorf("unsupported Scan, storing driver.Value type %T into type %T", src, dest)
}
func strconvErr(err error) error {
if ne, ok := err.(*strconv.NumError); ok {
return ne.Err
}
return err
}
func cloneBytes(b []byte) []byte {
if b == nil {
return nil
}
c := make([]byte, len(b))
copy(c, b)
return c
}
func asString(src interface{}) string {
switch v := src.(type) {
case string:
return v
case []byte:
return string(v)
}
rv := reflect.ValueOf(src)
switch rv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.FormatInt(rv.Int(), 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return strconv.FormatUint(rv.Uint(), 10)
case reflect.Float64:
return strconv.FormatFloat(rv.Float(), 'g', -1, 64)
case reflect.Float32:
return strconv.FormatFloat(rv.Float(), 'g', -1, 32)
case reflect.Bool:
return strconv.FormatBool(rv.Bool())
}
return fmt.Sprintf("%v", src)
}
func asBytes(buf []byte, rv reflect.Value) (b []byte, ok bool) {
switch rv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.AppendInt(buf, rv.Int(), 10), true
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return strconv.AppendUint(buf, rv.Uint(), 10), true
case reflect.Float32:
return strconv.AppendFloat(buf, rv.Float(), 'g', -1, 32), true
case reflect.Float64:
return strconv.AppendFloat(buf, rv.Float(), 'g', -1, 64), true
case reflect.Bool:
return strconv.AppendBool(buf, rv.Bool()), true
case reflect.String:
s := rv.String()
return append(buf, s...), true
}
return
}

@ -1,11 +1,19 @@
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>. // Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
// //
// Use of this source code is governed by an MIT-style // Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package sqlite3 package sqlite3
/*
#ifndef USE_LIBSQLITE3
#include <sqlite3-binding.h>
#else
#include <sqlite3.h>
#endif
*/
import "C" import "C"
import "syscall"
// ErrNo inherit errno. // ErrNo inherit errno.
type ErrNo int type ErrNo int
@ -20,6 +28,7 @@ type ErrNoExtended int
type Error struct { type Error struct {
Code ErrNo /* The error code returned by SQLite */ Code ErrNo /* The error code returned by SQLite */
ExtendedCode ErrNoExtended /* The extended error code returned by SQLite */ ExtendedCode ErrNoExtended /* The extended error code returned by SQLite */
SystemErrno syscall.Errno /* The system errno returned by the OS through SQLite, if applicable */
err string /* The error string returned by sqlite3_errmsg(), err string /* The error string returned by sqlite3_errmsg(),
this usually contains more specific details. */ this usually contains more specific details. */
} }
@ -72,10 +81,16 @@ func (err ErrNoExtended) Error() string {
} }
func (err Error) Error() string { func (err Error) Error() string {
var str string
if err.err != "" { if err.err != "" {
return err.err str = err.err
} else {
str = C.GoString(C.sqlite3_errstr(C.int(err.Code)))
} }
return errorString(err) if err.SystemErrno != 0 {
str += ": " + err.SystemErrno.Error()
}
return str
} }
// result codes from http://www.sqlite.org/c3ref/c_abort_rollback.html // result codes from http://www.sqlite.org/c3ref/c_abort_rollback.html

File diff suppressed because it is too large Load Diff

@ -124,9 +124,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()]. ** [sqlite_version()] and [sqlite_source_id()].
*/ */
#define SQLITE_VERSION "3.29.0" #define SQLITE_VERSION "3.30.1"
#define SQLITE_VERSION_NUMBER 3029000 #define SQLITE_VERSION_NUMBER 3030001
#define SQLITE_SOURCE_ID "2019-07-10 17:32:03 fc82b73eaac8b36950e527f12c4b5dc1e147e6f4ad2217ae43ad82882a88bfa6" #define SQLITE_SOURCE_ID "2019-10-10 20:19:45 18db032d058f1436ce3dea84081f4ee5a0f2259ad97301d43c426bc7f3df1b0b"
/* /*
** CAPI3REF: Run-Time Library Version Numbers ** CAPI3REF: Run-Time Library Version Numbers
@ -2094,6 +2094,17 @@ struct sqlite3_mem_methods {
** following this call. The second parameter may be a NULL pointer, in ** following this call. The second parameter may be a NULL pointer, in
** which case the trigger setting is not reported back. </dd> ** which case the trigger setting is not reported back. </dd>
** **
** [[SQLITE_DBCONFIG_ENABLE_VIEW]]
** <dt>SQLITE_DBCONFIG_ENABLE_VIEW</dt>
** <dd> ^This option is used to enable or disable [CREATE VIEW | views].
** There should be two additional arguments.
** The first argument is an integer which is 0 to disable views,
** positive to enable views or negative to leave the setting unchanged.
** The second parameter is a pointer to an integer into which
** is written 0 or 1 to indicate whether views are disabled or enabled
** following this call. The second parameter may be a NULL pointer, in
** which case the view setting is not reported back. </dd>
**
** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]] ** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]]
** <dt>SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER</dt> ** <dt>SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER</dt>
** <dd> ^This option is used to enable or disable the ** <dd> ^This option is used to enable or disable the
@ -2266,7 +2277,8 @@ struct sqlite3_mem_methods {
#define SQLITE_DBCONFIG_LEGACY_ALTER_TABLE 1012 /* int int* */ #define SQLITE_DBCONFIG_LEGACY_ALTER_TABLE 1012 /* int int* */
#define SQLITE_DBCONFIG_DQS_DML 1013 /* int int* */ #define SQLITE_DBCONFIG_DQS_DML 1013 /* int int* */
#define SQLITE_DBCONFIG_DQS_DDL 1014 /* int int* */ #define SQLITE_DBCONFIG_DQS_DDL 1014 /* int int* */
#define SQLITE_DBCONFIG_MAX 1014 /* Largest DBCONFIG */ #define SQLITE_DBCONFIG_ENABLE_VIEW 1015 /* int int* */
#define SQLITE_DBCONFIG_MAX 1015 /* Largest DBCONFIG */
/* /*
** CAPI3REF: Enable Or Disable Extended Result Codes ** CAPI3REF: Enable Or Disable Extended Result Codes
@ -3815,7 +3827,7 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal);
** ^The specific value of WHERE-clause [parameter] might influence the ** ^The specific value of WHERE-clause [parameter] might influence the
** choice of query plan if the parameter is the left-hand side of a [LIKE] ** choice of query plan if the parameter is the left-hand side of a [LIKE]
** or [GLOB] operator or if the parameter is compared to an indexed column ** or [GLOB] operator or if the parameter is compared to an indexed column
** and the [SQLITE_ENABLE_STAT3] compile-time option is enabled. ** and the [SQLITE_ENABLE_STAT4] compile-time option is enabled.
** </li> ** </li>
** </ol> ** </ol>
** **
@ -4850,6 +4862,12 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
** perform additional optimizations on deterministic functions, so use ** perform additional optimizations on deterministic functions, so use
** of the [SQLITE_DETERMINISTIC] flag is recommended where possible. ** of the [SQLITE_DETERMINISTIC] flag is recommended where possible.
** **
** ^The fourth parameter may also optionally include the [SQLITE_DIRECTONLY]
** flag, which if present prevents the function from being invoked from
** within VIEWs or TRIGGERs. For security reasons, the [SQLITE_DIRECTONLY]
** flag is recommended for any application-defined SQL function that has
** side-effects.
**
** ^(The fifth parameter is an arbitrary pointer. The implementation of the ** ^(The fifth parameter is an arbitrary pointer. The implementation of the
** function can gain access to this pointer using [sqlite3_user_data()].)^ ** function can gain access to this pointer using [sqlite3_user_data()].)^
** **
@ -4966,8 +4984,30 @@ SQLITE_API int sqlite3_create_window_function(
** [SQLITE_UTF8 | preferred text encoding] as the fourth argument ** [SQLITE_UTF8 | preferred text encoding] as the fourth argument
** to [sqlite3_create_function()], [sqlite3_create_function16()], or ** to [sqlite3_create_function()], [sqlite3_create_function16()], or
** [sqlite3_create_function_v2()]. ** [sqlite3_create_function_v2()].
**
** The SQLITE_DETERMINISTIC flag means that the new function will always
** maps the same inputs into the same output. The abs() function is
** deterministic, for example, but randomblob() is not.
**
** The SQLITE_DIRECTONLY flag means that the function may only be invoked
** from top-level SQL, and cannot be used in VIEWs or TRIGGERs. This is
** a security feature which is recommended for all
** [application-defined SQL functions] that have side-effects. This flag
** prevents an attacker from adding triggers and views to a schema then
** tricking a high-privilege application into causing unintended side-effects
** while performing ordinary queries.
**
** The SQLITE_SUBTYPE flag indicates to SQLite that a function may call
** [sqlite3_value_subtype()] to inspect the sub-types of its arguments.
** Specifying this flag makes no difference for scalar or aggregate user
** functions. However, if it is not specified for a user-defined window
** function, then any sub-types belonging to arguments passed to the window
** function may be discarded before the window function is called (i.e.
** sqlite3_value_subtype() will always return 0).
*/ */
#define SQLITE_DETERMINISTIC 0x800 #define SQLITE_DETERMINISTIC 0x000000800
#define SQLITE_DIRECTONLY 0x000080000
#define SQLITE_SUBTYPE 0x000100000
/* /*
** CAPI3REF: Deprecated Functions ** CAPI3REF: Deprecated Functions
@ -6613,6 +6653,12 @@ struct sqlite3_index_info {
** ^The sqlite3_create_module() ** ^The sqlite3_create_module()
** interface is equivalent to sqlite3_create_module_v2() with a NULL ** interface is equivalent to sqlite3_create_module_v2() with a NULL
** destructor. ** destructor.
**
** ^If the third parameter (the pointer to the sqlite3_module object) is
** NULL then no new module is create and any existing modules with the
** same name are dropped.
**
** See also: [sqlite3_drop_modules()]
*/ */
SQLITE_API int sqlite3_create_module( SQLITE_API int sqlite3_create_module(
sqlite3 *db, /* SQLite connection to register module with */ sqlite3 *db, /* SQLite connection to register module with */
@ -6628,6 +6674,23 @@ SQLITE_API int sqlite3_create_module_v2(
void(*xDestroy)(void*) /* Module destructor function */ void(*xDestroy)(void*) /* Module destructor function */
); );
/*
** CAPI3REF: Remove Unnecessary Virtual Table Implementations
** METHOD: sqlite3
**
** ^The sqlite3_drop_modules(D,L) interface removes all virtual
** table modules from database connection D except those named on list L.
** The L parameter must be either NULL or a pointer to an array of pointers
** to strings where the array is terminated by a single NULL pointer.
** ^If the L parameter is NULL, then all virtual table modules are removed.
**
** See also: [sqlite3_create_module()]
*/
SQLITE_API int sqlite3_drop_modules(
sqlite3 *db, /* Remove modules from this connection */
const char **azKeep /* Except, do not remove the ones named here */
);
/* /*
** CAPI3REF: Virtual Table Instance Object ** CAPI3REF: Virtual Table Instance Object
** KEYWORDS: sqlite3_vtab ** KEYWORDS: sqlite3_vtab
@ -7336,7 +7399,7 @@ SQLITE_API int sqlite3_test_control(int op, ...);
#define SQLITE_TESTCTRL_FIRST 5 #define SQLITE_TESTCTRL_FIRST 5
#define SQLITE_TESTCTRL_PRNG_SAVE 5 #define SQLITE_TESTCTRL_PRNG_SAVE 5
#define SQLITE_TESTCTRL_PRNG_RESTORE 6 #define SQLITE_TESTCTRL_PRNG_RESTORE 6
#define SQLITE_TESTCTRL_PRNG_RESET 7 #define SQLITE_TESTCTRL_PRNG_RESET 7 /* NOT USED */
#define SQLITE_TESTCTRL_BITVEC_TEST 8 #define SQLITE_TESTCTRL_BITVEC_TEST 8
#define SQLITE_TESTCTRL_FAULT_INSTALL 9 #define SQLITE_TESTCTRL_FAULT_INSTALL 9
#define SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS 10 #define SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS 10
@ -7359,7 +7422,9 @@ SQLITE_API int sqlite3_test_control(int op, ...);
#define SQLITE_TESTCTRL_IMPOSTER 25 #define SQLITE_TESTCTRL_IMPOSTER 25
#define SQLITE_TESTCTRL_PARSER_COVERAGE 26 #define SQLITE_TESTCTRL_PARSER_COVERAGE 26
#define SQLITE_TESTCTRL_RESULT_INTREAL 27 #define SQLITE_TESTCTRL_RESULT_INTREAL 27
#define SQLITE_TESTCTRL_LAST 27 /* Largest TESTCTRL */ #define SQLITE_TESTCTRL_PRNG_SEED 28
#define SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS 29
#define SQLITE_TESTCTRL_LAST 29 /* Largest TESTCTRL */
/* /*
** CAPI3REF: SQL Keyword Checking ** CAPI3REF: SQL Keyword Checking

@ -1,4 +1,4 @@
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>. // Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>. // Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>.
// //
// Use of this source code is governed by an MIT-style // Use of this source code is governed by an MIT-style
@ -183,6 +183,12 @@ static int _sqlite3_limit(sqlite3* db, int limitId, int newLimit) {
return sqlite3_limit(db, limitId, newLimit); return sqlite3_limit(db, limitId, newLimit);
#endif #endif
} }
#if SQLITE_VERSION_NUMBER < 3012000
static int sqlite3_system_errno(sqlite3 *db) {
return 0;
}
#endif
*/ */
import "C" import "C"
import ( import (
@ -198,6 +204,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"syscall"
"time" "time"
"unsafe" "unsafe"
) )
@ -328,7 +335,7 @@ type SQLiteRows struct {
decltype []string decltype []string
cls bool cls bool
closed bool closed bool
done chan struct{} ctx context.Context // no better alternative to pass context into Next() method
} }
type functionInfo struct { type functionInfo struct {
@ -749,15 +756,28 @@ func (c *SQLiteConn) lastError() error {
return lastError(c.db) return lastError(c.db)
} }
// Note: may be called with db == nil
func lastError(db *C.sqlite3) error { func lastError(db *C.sqlite3) error {
rv := C.sqlite3_errcode(db) rv := C.sqlite3_errcode(db) // returns SQLITE_NOMEM if db == nil
if rv == C.SQLITE_OK { if rv == C.SQLITE_OK {
return nil return nil
} }
extrv := C.sqlite3_extended_errcode(db) // returns SQLITE_NOMEM if db == nil
errStr := C.GoString(C.sqlite3_errmsg(db)) // returns "out of memory" if db == nil
// https://www.sqlite.org/c3ref/system_errno.html
// sqlite3_system_errno is only meaningful if the error code was SQLITE_CANTOPEN,
// or it was SQLITE_IOERR and the extended code was not SQLITE_IOERR_NOMEM
var systemErrno syscall.Errno
if rv == C.SQLITE_CANTOPEN || (rv == C.SQLITE_IOERR && extrv != C.SQLITE_IOERR_NOMEM) {
systemErrno = syscall.Errno(C.sqlite3_system_errno(db))
}
return Error{ return Error{
Code: ErrNo(rv), Code: ErrNo(rv),
ExtendedCode: ErrNoExtended(C.sqlite3_extended_errcode(db)), ExtendedCode: ErrNoExtended(extrv),
err: C.GoString(C.sqlite3_errmsg(db)), SystemErrno: systemErrno,
err: errStr,
} }
} }
@ -869,10 +889,6 @@ func (c *SQLiteConn) begin(ctx context.Context) (driver.Tx, error) {
return &SQLiteTx{c}, nil return &SQLiteTx{c}, nil
} }
func errorString(err Error) string {
return C.GoString(C.sqlite3_errstr(C.int(err.Code)))
}
// Open database and return a new connection. // Open database and return a new connection.
// //
// A pragma can take either zero or one argument. // A pragma can take either zero or one argument.
@ -897,7 +913,7 @@ func errorString(err Error) string {
// - rwc // - rwc
// - memory // - memory
// //
// shared // cache
// SQLite Shared-Cache Mode // SQLite Shared-Cache Mode
// https://www.sqlite.org/sharedcache.html // https://www.sqlite.org/sharedcache.html
// Values: // Values:
@ -1000,7 +1016,7 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
deferForeignKeys := -1 deferForeignKeys := -1
foreignKeys := -1 foreignKeys := -1
ignoreCheckConstraints := -1 ignoreCheckConstraints := -1
journalMode := "DELETE" var journalMode string
lockingMode := "NORMAL" lockingMode := "NORMAL"
queryOnly := -1 queryOnly := -1
recursiveTriggers := -1 recursiveTriggers := -1
@ -1232,7 +1248,7 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
if _, ok := params["_locking"]; ok { if _, ok := params["_locking"]; ok {
pkey = "_locking" pkey = "_locking"
} }
if val := params.Get("_locking"); val != "" { if val := params.Get(pkey); val != "" {
switch strings.ToUpper(val) { switch strings.ToUpper(val) {
case "NORMAL", "EXCLUSIVE": case "NORMAL", "EXCLUSIVE":
lockingMode = strings.ToUpper(val) lockingMode = strings.ToUpper(val)
@ -1342,10 +1358,13 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
mutex|C.SQLITE_OPEN_READWRITE|C.SQLITE_OPEN_CREATE, mutex|C.SQLITE_OPEN_READWRITE|C.SQLITE_OPEN_CREATE,
nil) nil)
if rv != 0 { if rv != 0 {
// Save off the error _before_ closing the database.
// This is safe even if db is nil.
err := lastError(db)
if db != nil { if db != nil {
C.sqlite3_close_v2(db) C.sqlite3_close_v2(db)
} }
return nil, Error{Code: ErrNo(rv)} return nil, err
} }
if db == nil { if db == nil {
return nil, errors.New("sqlite succeeded without returning a database") return nil, errors.New("sqlite succeeded without returning a database")
@ -1522,10 +1541,10 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
// Before going any further, we need to check that the user // Before going any further, we need to check that the user
// has provided an username and password within the DSN. // has provided an username and password within the DSN.
// We are not allowed to continue. // We are not allowed to continue.
if len(authUser) < 0 { if len(authUser) == 0 {
return nil, fmt.Errorf("Missing '_auth_user' while user authentication was requested with '_auth'") return nil, fmt.Errorf("Missing '_auth_user' while user authentication was requested with '_auth'")
} }
if len(authPass) < 0 { if len(authPass) == 0 {
return nil, fmt.Errorf("Missing '_auth_pass' while user authentication was requested with '_auth'") return nil, fmt.Errorf("Missing '_auth_pass' while user authentication was requested with '_auth'")
} }
@ -1571,10 +1590,11 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
} }
// Journal Mode // Journal Mode
// Because default Journal Mode is DELETE this PRAGMA can always be executed. if journalMode != "" {
if err := exec(fmt.Sprintf("PRAGMA journal_mode = %s;", journalMode)); err != nil { if err := exec(fmt.Sprintf("PRAGMA journal_mode = %s;", journalMode)); err != nil {
C.sqlite3_close_v2(db) C.sqlite3_close_v2(db)
return nil, err return nil, err
}
} }
// Locking Mode // Locking Mode
@ -1846,28 +1866,13 @@ func (s *SQLiteStmt) query(ctx context.Context, args []namedValue) (driver.Rows,
decltype: nil, decltype: nil,
cls: s.cls, cls: s.cls,
closed: false, closed: false,
done: make(chan struct{}), ctx: ctx,
}
if ctxdone := ctx.Done(); ctxdone != nil {
go func(db *C.sqlite3) {
select {
case <-ctxdone:
select {
case <-rows.done:
default:
C.sqlite3_interrupt(db)
rows.Close()
}
case <-rows.done:
}
}(s.c.db)
} }
return rows, nil return rows, nil
} }
// LastInsertId teturn last inserted ID. // LastInsertId return last inserted ID.
func (r *SQLiteResult) LastInsertId() (int64, error) { func (r *SQLiteResult) LastInsertId() (int64, error) {
return r.id, nil return r.id, nil
} }
@ -1889,29 +1894,43 @@ func (s *SQLiteStmt) Exec(args []driver.Value) (driver.Result, error) {
return s.exec(context.Background(), list) return s.exec(context.Background(), list)
} }
// exec executes a query that doesn't return rows. Attempts to honor context timeout.
func (s *SQLiteStmt) exec(ctx context.Context, args []namedValue) (driver.Result, error) { func (s *SQLiteStmt) exec(ctx context.Context, args []namedValue) (driver.Result, error) {
if ctx.Done() == nil {
return s.execSync(args)
}
type result struct {
r driver.Result
err error
}
resultCh := make(chan result)
go func() {
r, err := s.execSync(args)
resultCh <- result{r, err}
}()
select {
case rv := <-resultCh:
return rv.r, rv.err
case <-ctx.Done():
select {
case <-resultCh: // no need to interrupt
default:
// this is still racy and can be no-op if executed between sqlite3_* calls in execSync.
C.sqlite3_interrupt(s.c.db)
<-resultCh // ensure goroutine completed
}
return nil, ctx.Err()
}
}
func (s *SQLiteStmt) execSync(args []namedValue) (driver.Result, error) {
if err := s.bind(args); err != nil { if err := s.bind(args); err != nil {
C.sqlite3_reset(s.s) C.sqlite3_reset(s.s)
C.sqlite3_clear_bindings(s.s) C.sqlite3_clear_bindings(s.s)
return nil, err return nil, err
} }
if ctxdone := ctx.Done(); ctxdone != nil {
done := make(chan struct{})
defer close(done)
go func(db *C.sqlite3) {
select {
case <-done:
case <-ctxdone:
select {
case <-done:
default:
C.sqlite3_interrupt(db)
}
}
}(s.c.db)
}
var rowid, changes C.longlong var rowid, changes C.longlong
rv := C._sqlite3_step_row_internal(s.s, &rowid, &changes) rv := C._sqlite3_step_row_internal(s.s, &rowid, &changes)
if rv != C.SQLITE_ROW && rv != C.SQLITE_OK && rv != C.SQLITE_DONE { if rv != C.SQLITE_ROW && rv != C.SQLITE_OK && rv != C.SQLITE_DONE {
@ -1932,9 +1951,6 @@ func (rc *SQLiteRows) Close() error {
return nil return nil
} }
rc.closed = true rc.closed = true
if rc.done != nil {
close(rc.done)
}
if rc.cls { if rc.cls {
rc.s.mu.Unlock() rc.s.mu.Unlock()
return rc.s.Close() return rc.s.Close()
@ -1978,13 +1994,39 @@ func (rc *SQLiteRows) DeclTypes() []string {
return rc.declTypes() return rc.declTypes()
} }
// Next move cursor to next. // Next move cursor to next. Attempts to honor context timeout from QueryContext call.
func (rc *SQLiteRows) Next(dest []driver.Value) error { func (rc *SQLiteRows) Next(dest []driver.Value) error {
rc.s.mu.Lock() rc.s.mu.Lock()
defer rc.s.mu.Unlock() defer rc.s.mu.Unlock()
if rc.s.closed { if rc.s.closed {
return io.EOF return io.EOF
} }
if rc.ctx.Done() == nil {
return rc.nextSyncLocked(dest)
}
resultCh := make(chan error)
go func() {
resultCh <- rc.nextSyncLocked(dest)
}()
select {
case err := <-resultCh:
return err
case <-rc.ctx.Done():
select {
case <-resultCh: // no need to interrupt
default:
// this is still racy and can be no-op if executed between sqlite3_* calls in nextSyncLocked.
C.sqlite3_interrupt(rc.s.c.db)
<-resultCh // ensure goroutine completed
}
return rc.ctx.Err()
}
}
// nextSyncLocked moves cursor to next; must be called with locked mutex.
func (rc *SQLiteRows) nextSyncLocked(dest []driver.Value) error {
rv := C._sqlite3_step_internal(rc.s.s) rv := C._sqlite3_step_internal(rc.s.s)
if rv == C.SQLITE_DONE { if rv == C.SQLITE_DONE {
return io.EOF return io.EOF

@ -1,4 +1,4 @@
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>. // Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
// //
// Use of this source code is governed by an MIT-style // Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

@ -1,4 +1,4 @@
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>. // Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
// //
// Use of this source code is governed by an MIT-style // Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

@ -1,4 +1,4 @@
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>. // Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
// //
// Use of this source code is governed by an MIT-style // Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -11,6 +11,7 @@ package sqlite3
#cgo CFLAGS: -DUSE_LIBSQLITE3 #cgo CFLAGS: -DUSE_LIBSQLITE3
#cgo linux LDFLAGS: -lsqlite3 #cgo linux LDFLAGS: -lsqlite3
#cgo darwin LDFLAGS: -L/usr/local/opt/sqlite/lib -lsqlite3 #cgo darwin LDFLAGS: -L/usr/local/opt/sqlite/lib -lsqlite3
#cgo darwin CFLAGS: -I/usr/local/opt/sqlite/include
#cgo openbsd LDFLAGS: -lsqlite3 #cgo openbsd LDFLAGS: -lsqlite3
#cgo solaris LDFLAGS: -lsqlite3 #cgo solaris LDFLAGS: -lsqlite3
*/ */

@ -1,4 +1,4 @@
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>. // Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
// //
// Use of this source code is governed by an MIT-style // Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

@ -1,4 +1,4 @@
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>. // Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
// //
// Use of this source code is governed by an MIT-style // Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

@ -1,4 +1,4 @@
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>. // Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>. // Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>.
// //
// Use of this source code is governed by an MIT-style // Use of this source code is governed by an MIT-style

@ -1,6 +1,6 @@
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>. // Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>. // Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>.
//
// Use of this source code is governed by an MIT-style // Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

@ -1,4 +1,4 @@
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>. // Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>. // Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>.
// //
// Use of this source code is governed by an MIT-style // Use of this source code is governed by an MIT-style

@ -1,4 +1,4 @@
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>. // Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
// //
// Use of this source code is governed by an MIT-style // Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

@ -1,4 +1,4 @@
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>. // Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
// //
// Use of this source code is governed by an MIT-style // Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

@ -1,4 +1,4 @@
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>. // Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>. // Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>.
// Use of this source code is governed by an MIT-style // Use of this source code is governed by an MIT-style

@ -1,4 +1,4 @@
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>. // Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
// //
// Use of this source code is governed by an MIT-style // Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

@ -0,0 +1,20 @@
// Copyright (C) 2019 G.J.R. Timmer <gjr.timmer@gmail.com>.
// Copyright (C) 2018 segment.com <friends@segment.com>
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
// +build cgo
package sqlite3
// SQLitePreUpdateData represents all of the data available during a
// pre-update hook call.
type SQLitePreUpdateData struct {
Conn *SQLiteConn
Op int
DatabaseName string
TableName string
OldRowID int64
NewRowID int64
}

@ -0,0 +1,112 @@
// Copyright (C) 2019 G.J.R. Timmer <gjr.timmer@gmail.com>.
// Copyright (C) 2018 segment.com <friends@segment.com>
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
// +build sqlite_preupdate_hook
package sqlite3
/*
#cgo CFLAGS: -DSQLITE_ENABLE_PREUPDATE_HOOK
#cgo LDFLAGS: -lm
#ifndef USE_LIBSQLITE3
#include <sqlite3-binding.h>
#else
#include <sqlite3.h>
#endif
#include <stdlib.h>
#include <string.h>
void preUpdateHookTrampoline(void*, sqlite3 *, int, char *, char *, sqlite3_int64, sqlite3_int64);
*/
import "C"
import (
"errors"
"unsafe"
)
// RegisterPreUpdateHook sets the pre-update hook for a connection.
//
// The callback is passed a SQLitePreUpdateData struct with the data for
// the update, as well as methods for fetching copies of impacted data.
//
// If there is an existing update hook for this connection, it will be
// removed. If callback is nil the existing hook (if any) will be removed
// without creating a new one.
func (c *SQLiteConn) RegisterPreUpdateHook(callback func(SQLitePreUpdateData)) {
if callback == nil {
C.sqlite3_preupdate_hook(c.db, nil, nil)
} else {
C.sqlite3_preupdate_hook(c.db, (*[0]byte)(unsafe.Pointer(C.preUpdateHookTrampoline)), unsafe.Pointer(newHandle(c, callback)))
}
}
// Depth returns the source path of the write, see sqlite3_preupdate_depth()
func (d *SQLitePreUpdateData) Depth() int {
return int(C.sqlite3_preupdate_depth(d.Conn.db))
}
// Count returns the number of columns in the row
func (d *SQLitePreUpdateData) Count() int {
return int(C.sqlite3_preupdate_count(d.Conn.db))
}
func (d *SQLitePreUpdateData) row(dest []interface{}, new bool) error {
for i := 0; i < d.Count() && i < len(dest); i++ {
var val *C.sqlite3_value
var src interface{}
// Initially I tried making this just a function pointer argument, but
// it's absurdly complicated to pass C function pointers.
if new {
C.sqlite3_preupdate_new(d.Conn.db, C.int(i), &val)
} else {
C.sqlite3_preupdate_old(d.Conn.db, C.int(i), &val)
}
switch C.sqlite3_value_type(val) {
case C.SQLITE_INTEGER:
src = int64(C.sqlite3_value_int64(val))
case C.SQLITE_FLOAT:
src = float64(C.sqlite3_value_double(val))
case C.SQLITE_BLOB:
len := C.sqlite3_value_bytes(val)
blobptr := C.sqlite3_value_blob(val)
src = C.GoBytes(blobptr, len)
case C.SQLITE_TEXT:
len := C.sqlite3_value_bytes(val)
cstrptr := unsafe.Pointer(C.sqlite3_value_text(val))
src = C.GoBytes(cstrptr, len)
case C.SQLITE_NULL:
src = nil
}
err := convertAssign(&dest[i], src)
if err != nil {
return err
}
}
return nil
}
// Old populates dest with the row data to be replaced. This works similar to
// database/sql's Rows.Scan()
func (d *SQLitePreUpdateData) Old(dest ...interface{}) error {
if d.Op == SQLITE_INSERT {
return errors.New("There is no old row for INSERT operations")
}
return d.row(dest, false)
}
// New populates dest with the replacement row data. This works similar to
// database/sql's Rows.Scan()
func (d *SQLitePreUpdateData) New(dest ...interface{}) error {
if d.Op == SQLITE_DELETE {
return errors.New("There is no new row for DELETE operations")
}
return d.row(dest, true)
}

@ -0,0 +1,21 @@
// Copyright (C) 2019 G.J.R. Timmer <gjr.timmer@gmail.com>.
// Copyright (C) 2018 segment.com <friends@segment.com>
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
// +build !sqlite_preupdate_hook,cgo
package sqlite3
// RegisterPreUpdateHook sets the pre-update hook for a connection.
//
// The callback is passed a SQLitePreUpdateData struct with the data for
// the update, as well as methods for fetching copies of impacted data.
//
// If there is an existing update hook for this connection, it will be
// removed. If callback is nil the existing hook (if any) will be removed
// without creating a new one.
func (c *SQLiteConn) RegisterPreUpdateHook(callback func(SQLitePreUpdateData)) {
// NOOP
}

@ -1,4 +1,4 @@
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>. // Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>. // Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>.
// //
// Use of this source code is governed by an MIT-style // Use of this source code is governed by an MIT-style

@ -1,4 +1,4 @@
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>. // Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>. // Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>.
// //
// Use of this source code is governed by an MIT-style // Use of this source code is governed by an MIT-style

@ -1,4 +1,4 @@
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>. // Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>. // Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>.
// //
// Use of this source code is governed by an MIT-style // Use of this source code is governed by an MIT-style

@ -1,4 +1,4 @@
// Copyright (C) 2018 Yasuhiro Matsumoto <mattn.jp@gmail.com>. // Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
// //
// Use of this source code is governed by an MIT-style // Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

@ -1,4 +1,4 @@
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>. // Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>. // Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>.
// //
// Use of this source code is governed by an MIT-style // Use of this source code is governed by an MIT-style

@ -1,4 +1,4 @@
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>. // Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>. // Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>.
// //
// Use of this source code is governed by an MIT-style // Use of this source code is governed by an MIT-style

@ -1,4 +1,4 @@
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>. // Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
// //
// Use of this source code is governed by an MIT-style // Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

@ -1,4 +1,4 @@
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>. // Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
// //
// Use of this source code is governed by an MIT-style // Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

@ -1,4 +1,4 @@
// Copyright (C) 2018 Yasuhiro Matsumoto <mattn.jp@gmail.com>. // Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
// //
// Use of this source code is governed by an MIT-style // Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

@ -1,4 +1,4 @@
// Copyright (C) 2016 Yasuhiro Matsumoto <mattn.jp@gmail.com>. // Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
// //
// Use of this source code is governed by an MIT-style // Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -89,6 +89,7 @@ func fillExpandedSQL(info *TraceInfo, db *C.sqlite3, pStmt unsafe.Pointer) {
} }
expSQLiteCStr := C.sqlite3_expanded_sql((*C.sqlite3_stmt)(pStmt)) expSQLiteCStr := C.sqlite3_expanded_sql((*C.sqlite3_stmt)(pStmt))
defer C.sqlite3_free(unsafe.Pointer(expSQLiteCStr))
if expSQLiteCStr == nil { if expSQLiteCStr == nil {
fillDBError(&info.DBError, db) fillDBError(&info.DBError, db)
return return

@ -1,3 +1,8 @@
// Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package sqlite3 package sqlite3
/* /*

@ -1,4 +1,4 @@
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>. // Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
// //
// Use of this source code is governed by an MIT-style // Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

@ -323,6 +323,8 @@ struct sqlite3_api_routines {
/* Version 3.28.0 and later */ /* Version 3.28.0 and later */
int (*stmt_isexplain)(sqlite3_stmt*); int (*stmt_isexplain)(sqlite3_stmt*);
int (*value_frombind)(sqlite3_value*); int (*value_frombind)(sqlite3_value*);
/* Version 3.30.0 and later */
int (*drop_modules)(sqlite3*,const char**);
}; };
/* /*
@ -615,6 +617,8 @@ typedef int (*sqlite3_loadext_entry)(
/* Version 3.28.0 and later */ /* Version 3.28.0 and later */
#define sqlite3_stmt_isexplain sqlite3_api->isexplain #define sqlite3_stmt_isexplain sqlite3_api->isexplain
#define sqlite3_value_frombind sqlite3_api->frombind #define sqlite3_value_frombind sqlite3_api->frombind
/* Version 3.30.0 and later */
#define sqlite3_drop_modules sqlite3_api->drop_modules
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)

@ -1,3 +1,8 @@
// Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
// +build !cgo // +build !cgo
package sqlite3 package sqlite3

@ -3,8 +3,9 @@ sudo: false
language: go language: go
go: go:
- 1.7.3 - 1.9.x
- 1.8.1 - 1.10.x
- 1.11.x
- tip - tip
matrix: matrix:
@ -12,7 +13,7 @@ matrix:
- go: tip - go: tip
install: install:
- go get github.com/golang/lint/golint - go get golang.org/x/lint/golint
- export PATH=$GOPATH/bin:$PATH - export PATH=$GOPATH/bin:$PATH
- go install ./... - go install ./...

@ -86,8 +86,8 @@ fmt.Println("ip has value ", *ip)
fmt.Println("flagvar has value ", flagvar) fmt.Println("flagvar has value ", flagvar)
``` ```
There are helpers function to get values later if you have the FlagSet but There are helper functions available to get the value stored in a Flag if you have a FlagSet but find
it was difficult to keep up with all of the flag pointers in your code. it difficult to keep up with all of the pointers in your code.
If you have a pflag.FlagSet with a flag called 'flagname' of type int you If you have a pflag.FlagSet with a flag called 'flagname' of type int you
can use GetInt() to get the int value. But notice that 'flagname' must exist can use GetInt() to get the int value. But notice that 'flagname' must exist
and it must be an int. GetString("flagname") will fail. and it must be an int. GetString("flagname") will fail.

@ -71,6 +71,44 @@ func (s *boolSliceValue) String() string {
return "[" + out + "]" return "[" + out + "]"
} }
func (s *boolSliceValue) fromString(val string) (bool, error) {
return strconv.ParseBool(val)
}
func (s *boolSliceValue) toString(val bool) string {
return strconv.FormatBool(val)
}
func (s *boolSliceValue) Append(val string) error {
i, err := s.fromString(val)
if err != nil {
return err
}
*s.value = append(*s.value, i)
return nil
}
func (s *boolSliceValue) Replace(val []string) error {
out := make([]bool, len(val))
for i, d := range val {
var err error
out[i], err = s.fromString(d)
if err != nil {
return err
}
}
*s.value = out
return nil
}
func (s *boolSliceValue) GetSlice() []string {
out := make([]string, len(*s.value))
for i, d := range *s.value {
out[i] = s.toString(d)
}
return out
}
func boolSliceConv(val string) (interface{}, error) { func boolSliceConv(val string) (interface{}, error) {
val = strings.Trim(val, "[]") val = strings.Trim(val, "[]")
// Empty string would cause a slice with one (empty) entry // Empty string would cause a slice with one (empty) entry

@ -46,7 +46,7 @@ func (f *FlagSet) GetCount(name string) (int, error) {
// CountVar defines a count flag with specified name, default value, and usage string. // CountVar defines a count flag with specified name, default value, and usage string.
// The argument p points to an int variable in which to store the value of the flag. // The argument p points to an int variable in which to store the value of the flag.
// A count flag will add 1 to its value evey time it is found on the command line // A count flag will add 1 to its value every time it is found on the command line
func (f *FlagSet) CountVar(p *int, name string, usage string) { func (f *FlagSet) CountVar(p *int, name string, usage string) {
f.CountVarP(p, name, "", usage) f.CountVarP(p, name, "", usage)
} }
@ -69,7 +69,7 @@ func CountVarP(p *int, name, shorthand string, usage string) {
// Count defines a count flag with specified name, default value, and usage string. // Count defines a count flag with specified name, default value, and usage string.
// The return value is the address of an int variable that stores the value of the flag. // The return value is the address of an int variable that stores the value of the flag.
// A count flag will add 1 to its value evey time it is found on the command line // A count flag will add 1 to its value every time it is found on the command line
func (f *FlagSet) Count(name string, usage string) *int { func (f *FlagSet) Count(name string, usage string) *int {
p := new(int) p := new(int)
f.CountVarP(p, name, "", usage) f.CountVarP(p, name, "", usage)

@ -51,6 +51,44 @@ func (s *durationSliceValue) String() string {
return "[" + strings.Join(out, ",") + "]" return "[" + strings.Join(out, ",") + "]"
} }
func (s *durationSliceValue) fromString(val string) (time.Duration, error) {
return time.ParseDuration(val)
}
func (s *durationSliceValue) toString(val time.Duration) string {
return fmt.Sprintf("%s", val)
}
func (s *durationSliceValue) Append(val string) error {
i, err := s.fromString(val)
if err != nil {
return err
}
*s.value = append(*s.value, i)
return nil
}
func (s *durationSliceValue) Replace(val []string) error {
out := make([]time.Duration, len(val))
for i, d := range val {
var err error
out[i], err = s.fromString(d)
if err != nil {
return err
}
}
*s.value = out
return nil
}
func (s *durationSliceValue) GetSlice() []string {
out := make([]string, len(*s.value))
for i, d := range *s.value {
out[i] = s.toString(d)
}
return out
}
func durationSliceConv(val string) (interface{}, error) { func durationSliceConv(val string) (interface{}, error) {
val = strings.Trim(val, "[]") val = strings.Trim(val, "[]")
// Empty string would cause a slice with one (empty) entry // Empty string would cause a slice with one (empty) entry

@ -57,9 +57,9 @@ that give one-letter shorthands for flags. You can use these by appending
var ip = flag.IntP("flagname", "f", 1234, "help message") var ip = flag.IntP("flagname", "f", 1234, "help message")
var flagvar bool var flagvar bool
func init() { func init() {
flag.BoolVarP("boolname", "b", true, "help message") flag.BoolVarP(&flagvar, "boolname", "b", true, "help message")
} }
flag.VarP(&flagVar, "varname", "v", 1234, "help message") flag.VarP(&flagval, "varname", "v", "help message")
Shorthand letters can be used with single dashes on the command line. Shorthand letters can be used with single dashes on the command line.
Boolean shorthand flags can be combined with other shorthand flags. Boolean shorthand flags can be combined with other shorthand flags.
@ -190,6 +190,18 @@ type Value interface {
Type() string Type() string
} }
// SliceValue is a secondary interface to all flags which hold a list
// of values. This allows full control over the value of list flags,
// and avoids complicated marshalling and unmarshalling to csv.
type SliceValue interface {
// Append adds the specified value to the end of the flag value list.
Append(string) error
// Replace will fully overwrite any data currently in the flag value list.
Replace([]string) error
// GetSlice returns the flag value list as an array of strings.
GetSlice() []string
}
// sortFlags returns the flags as a slice in lexicographical sorted order. // sortFlags returns the flags as a slice in lexicographical sorted order.
func sortFlags(flags map[NormalizedName]*Flag) []*Flag { func sortFlags(flags map[NormalizedName]*Flag) []*Flag {
list := make(sort.StringSlice, len(flags)) list := make(sort.StringSlice, len(flags))

@ -0,0 +1,174 @@
package pflag
import (
"fmt"
"strconv"
"strings"
)
// -- float32Slice Value
type float32SliceValue struct {
value *[]float32
changed bool
}
func newFloat32SliceValue(val []float32, p *[]float32) *float32SliceValue {
isv := new(float32SliceValue)
isv.value = p
*isv.value = val
return isv
}
func (s *float32SliceValue) Set(val string) error {
ss := strings.Split(val, ",")
out := make([]float32, len(ss))
for i, d := range ss {
var err error
var temp64 float64
temp64, err = strconv.ParseFloat(d, 32)
if err != nil {
return err
}
out[i] = float32(temp64)
}
if !s.changed {
*s.value = out
} else {
*s.value = append(*s.value, out...)
}
s.changed = true
return nil
}
func (s *float32SliceValue) Type() string {
return "float32Slice"
}
func (s *float32SliceValue) String() string {
out := make([]string, len(*s.value))
for i, d := range *s.value {
out[i] = fmt.Sprintf("%f", d)
}
return "[" + strings.Join(out, ",") + "]"
}
func (s *float32SliceValue) fromString(val string) (float32, error) {
t64, err := strconv.ParseFloat(val, 32)
if err != nil {
return 0, err
}
return float32(t64), nil
}
func (s *float32SliceValue) toString(val float32) string {
return fmt.Sprintf("%f", val)
}
func (s *float32SliceValue) Append(val string) error {
i, err := s.fromString(val)
if err != nil {
return err
}
*s.value = append(*s.value, i)
return nil
}
func (s *float32SliceValue) Replace(val []string) error {
out := make([]float32, len(val))
for i, d := range val {
var err error
out[i], err = s.fromString(d)
if err != nil {
return err
}
}
*s.value = out
return nil
}
func (s *float32SliceValue) GetSlice() []string {
out := make([]string, len(*s.value))
for i, d := range *s.value {
out[i] = s.toString(d)
}
return out
}
func float32SliceConv(val string) (interface{}, error) {
val = strings.Trim(val, "[]")
// Empty string would cause a slice with one (empty) entry
if len(val) == 0 {
return []float32{}, nil
}
ss := strings.Split(val, ",")
out := make([]float32, len(ss))
for i, d := range ss {
var err error
var temp64 float64
temp64, err = strconv.ParseFloat(d, 32)
if err != nil {
return nil, err
}
out[i] = float32(temp64)
}
return out, nil
}
// GetFloat32Slice return the []float32 value of a flag with the given name
func (f *FlagSet) GetFloat32Slice(name string) ([]float32, error) {
val, err := f.getFlagType(name, "float32Slice", float32SliceConv)
if err != nil {
return []float32{}, err
}
return val.([]float32), nil
}
// Float32SliceVar defines a float32Slice flag with specified name, default value, and usage string.
// The argument p points to a []float32 variable in which to store the value of the flag.
func (f *FlagSet) Float32SliceVar(p *[]float32, name string, value []float32, usage string) {
f.VarP(newFloat32SliceValue(value, p), name, "", usage)
}
// Float32SliceVarP is like Float32SliceVar, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) Float32SliceVarP(p *[]float32, name, shorthand string, value []float32, usage string) {
f.VarP(newFloat32SliceValue(value, p), name, shorthand, usage)
}
// Float32SliceVar defines a float32[] flag with specified name, default value, and usage string.
// The argument p points to a float32[] variable in which to store the value of the flag.
func Float32SliceVar(p *[]float32, name string, value []float32, usage string) {
CommandLine.VarP(newFloat32SliceValue(value, p), name, "", usage)
}
// Float32SliceVarP is like Float32SliceVar, but accepts a shorthand letter that can be used after a single dash.
func Float32SliceVarP(p *[]float32, name, shorthand string, value []float32, usage string) {
CommandLine.VarP(newFloat32SliceValue(value, p), name, shorthand, usage)
}
// Float32Slice defines a []float32 flag with specified name, default value, and usage string.
// The return value is the address of a []float32 variable that stores the value of the flag.
func (f *FlagSet) Float32Slice(name string, value []float32, usage string) *[]float32 {
p := []float32{}
f.Float32SliceVarP(&p, name, "", value, usage)
return &p
}
// Float32SliceP is like Float32Slice, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) Float32SliceP(name, shorthand string, value []float32, usage string) *[]float32 {
p := []float32{}
f.Float32SliceVarP(&p, name, shorthand, value, usage)
return &p
}
// Float32Slice defines a []float32 flag with specified name, default value, and usage string.
// The return value is the address of a []float32 variable that stores the value of the flag.
func Float32Slice(name string, value []float32, usage string) *[]float32 {
return CommandLine.Float32SliceP(name, "", value, usage)
}
// Float32SliceP is like Float32Slice, but accepts a shorthand letter that can be used after a single dash.
func Float32SliceP(name, shorthand string, value []float32, usage string) *[]float32 {
return CommandLine.Float32SliceP(name, shorthand, value, usage)
}

@ -0,0 +1,166 @@
package pflag
import (
"fmt"
"strconv"
"strings"
)
// -- float64Slice Value
type float64SliceValue struct {
value *[]float64
changed bool
}
func newFloat64SliceValue(val []float64, p *[]float64) *float64SliceValue {
isv := new(float64SliceValue)
isv.value = p
*isv.value = val
return isv
}
func (s *float64SliceValue) Set(val string) error {
ss := strings.Split(val, ",")
out := make([]float64, len(ss))
for i, d := range ss {
var err error
out[i], err = strconv.ParseFloat(d, 64)
if err != nil {
return err
}
}
if !s.changed {
*s.value = out
} else {
*s.value = append(*s.value, out...)
}
s.changed = true
return nil
}
func (s *float64SliceValue) Type() string {
return "float64Slice"
}
func (s *float64SliceValue) String() string {
out := make([]string, len(*s.value))
for i, d := range *s.value {
out[i] = fmt.Sprintf("%f", d)
}
return "[" + strings.Join(out, ",") + "]"
}
func (s *float64SliceValue) fromString(val string) (float64, error) {
return strconv.ParseFloat(val, 64)
}
func (s *float64SliceValue) toString(val float64) string {
return fmt.Sprintf("%f", val)
}
func (s *float64SliceValue) Append(val string) error {
i, err := s.fromString(val)
if err != nil {
return err
}
*s.value = append(*s.value, i)
return nil
}
func (s *float64SliceValue) Replace(val []string) error {
out := make([]float64, len(val))
for i, d := range val {
var err error
out[i], err = s.fromString(d)
if err != nil {
return err
}
}
*s.value = out
return nil
}
func (s *float64SliceValue) GetSlice() []string {
out := make([]string, len(*s.value))
for i, d := range *s.value {
out[i] = s.toString(d)
}
return out
}
func float64SliceConv(val string) (interface{}, error) {
val = strings.Trim(val, "[]")
// Empty string would cause a slice with one (empty) entry
if len(val) == 0 {
return []float64{}, nil
}
ss := strings.Split(val, ",")
out := make([]float64, len(ss))
for i, d := range ss {
var err error
out[i], err = strconv.ParseFloat(d, 64)
if err != nil {
return nil, err
}
}
return out, nil
}
// GetFloat64Slice return the []float64 value of a flag with the given name
func (f *FlagSet) GetFloat64Slice(name string) ([]float64, error) {
val, err := f.getFlagType(name, "float64Slice", float64SliceConv)
if err != nil {
return []float64{}, err
}
return val.([]float64), nil
}
// Float64SliceVar defines a float64Slice flag with specified name, default value, and usage string.
// The argument p points to a []float64 variable in which to store the value of the flag.
func (f *FlagSet) Float64SliceVar(p *[]float64, name string, value []float64, usage string) {
f.VarP(newFloat64SliceValue(value, p), name, "", usage)
}
// Float64SliceVarP is like Float64SliceVar, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) Float64SliceVarP(p *[]float64, name, shorthand string, value []float64, usage string) {
f.VarP(newFloat64SliceValue(value, p), name, shorthand, usage)
}
// Float64SliceVar defines a float64[] flag with specified name, default value, and usage string.
// The argument p points to a float64[] variable in which to store the value of the flag.
func Float64SliceVar(p *[]float64, name string, value []float64, usage string) {
CommandLine.VarP(newFloat64SliceValue(value, p), name, "", usage)
}
// Float64SliceVarP is like Float64SliceVar, but accepts a shorthand letter that can be used after a single dash.
func Float64SliceVarP(p *[]float64, name, shorthand string, value []float64, usage string) {
CommandLine.VarP(newFloat64SliceValue(value, p), name, shorthand, usage)
}
// Float64Slice defines a []float64 flag with specified name, default value, and usage string.
// The return value is the address of a []float64 variable that stores the value of the flag.
func (f *FlagSet) Float64Slice(name string, value []float64, usage string) *[]float64 {
p := []float64{}
f.Float64SliceVarP(&p, name, "", value, usage)
return &p
}
// Float64SliceP is like Float64Slice, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) Float64SliceP(name, shorthand string, value []float64, usage string) *[]float64 {
p := []float64{}
f.Float64SliceVarP(&p, name, shorthand, value, usage)
return &p
}
// Float64Slice defines a []float64 flag with specified name, default value, and usage string.
// The return value is the address of a []float64 variable that stores the value of the flag.
func Float64Slice(name string, value []float64, usage string) *[]float64 {
return CommandLine.Float64SliceP(name, "", value, usage)
}
// Float64SliceP is like Float64Slice, but accepts a shorthand letter that can be used after a single dash.
func Float64SliceP(name, shorthand string, value []float64, usage string) *[]float64 {
return CommandLine.Float64SliceP(name, shorthand, value, usage)
}

@ -0,0 +1,3 @@
module github.com/spf13/pflag
go 1.12

@ -0,0 +1,174 @@
package pflag
import (
"fmt"
"strconv"
"strings"
)
// -- int32Slice Value
type int32SliceValue struct {
value *[]int32
changed bool
}
func newInt32SliceValue(val []int32, p *[]int32) *int32SliceValue {
isv := new(int32SliceValue)
isv.value = p
*isv.value = val
return isv
}
func (s *int32SliceValue) Set(val string) error {
ss := strings.Split(val, ",")
out := make([]int32, len(ss))
for i, d := range ss {
var err error
var temp64 int64
temp64, err = strconv.ParseInt(d, 0, 32)
if err != nil {
return err
}
out[i] = int32(temp64)
}
if !s.changed {
*s.value = out
} else {
*s.value = append(*s.value, out...)
}
s.changed = true
return nil
}
func (s *int32SliceValue) Type() string {
return "int32Slice"
}
func (s *int32SliceValue) String() string {
out := make([]string, len(*s.value))
for i, d := range *s.value {
out[i] = fmt.Sprintf("%d", d)
}
return "[" + strings.Join(out, ",") + "]"
}
func (s *int32SliceValue) fromString(val string) (int32, error) {
t64, err := strconv.ParseInt(val, 0, 32)
if err != nil {
return 0, err
}
return int32(t64), nil
}
func (s *int32SliceValue) toString(val int32) string {
return fmt.Sprintf("%d", val)
}
func (s *int32SliceValue) Append(val string) error {
i, err := s.fromString(val)
if err != nil {
return err
}
*s.value = append(*s.value, i)
return nil
}
func (s *int32SliceValue) Replace(val []string) error {
out := make([]int32, len(val))
for i, d := range val {
var err error
out[i], err = s.fromString(d)
if err != nil {
return err
}
}
*s.value = out
return nil
}
func (s *int32SliceValue) GetSlice() []string {
out := make([]string, len(*s.value))
for i, d := range *s.value {
out[i] = s.toString(d)
}
return out
}
func int32SliceConv(val string) (interface{}, error) {
val = strings.Trim(val, "[]")
// Empty string would cause a slice with one (empty) entry
if len(val) == 0 {
return []int32{}, nil
}
ss := strings.Split(val, ",")
out := make([]int32, len(ss))
for i, d := range ss {
var err error
var temp64 int64
temp64, err = strconv.ParseInt(d, 0, 32)
if err != nil {
return nil, err
}
out[i] = int32(temp64)
}
return out, nil
}
// GetInt32Slice return the []int32 value of a flag with the given name
func (f *FlagSet) GetInt32Slice(name string) ([]int32, error) {
val, err := f.getFlagType(name, "int32Slice", int32SliceConv)
if err != nil {
return []int32{}, err
}
return val.([]int32), nil
}
// Int32SliceVar defines a int32Slice flag with specified name, default value, and usage string.
// The argument p points to a []int32 variable in which to store the value of the flag.
func (f *FlagSet) Int32SliceVar(p *[]int32, name string, value []int32, usage string) {
f.VarP(newInt32SliceValue(value, p), name, "", usage)
}
// Int32SliceVarP is like Int32SliceVar, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) Int32SliceVarP(p *[]int32, name, shorthand string, value []int32, usage string) {
f.VarP(newInt32SliceValue(value, p), name, shorthand, usage)
}
// Int32SliceVar defines a int32[] flag with specified name, default value, and usage string.
// The argument p points to a int32[] variable in which to store the value of the flag.
func Int32SliceVar(p *[]int32, name string, value []int32, usage string) {
CommandLine.VarP(newInt32SliceValue(value, p), name, "", usage)
}
// Int32SliceVarP is like Int32SliceVar, but accepts a shorthand letter that can be used after a single dash.
func Int32SliceVarP(p *[]int32, name, shorthand string, value []int32, usage string) {
CommandLine.VarP(newInt32SliceValue(value, p), name, shorthand, usage)
}
// Int32Slice defines a []int32 flag with specified name, default value, and usage string.
// The return value is the address of a []int32 variable that stores the value of the flag.
func (f *FlagSet) Int32Slice(name string, value []int32, usage string) *[]int32 {
p := []int32{}
f.Int32SliceVarP(&p, name, "", value, usage)
return &p
}
// Int32SliceP is like Int32Slice, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) Int32SliceP(name, shorthand string, value []int32, usage string) *[]int32 {
p := []int32{}
f.Int32SliceVarP(&p, name, shorthand, value, usage)
return &p
}
// Int32Slice defines a []int32 flag with specified name, default value, and usage string.
// The return value is the address of a []int32 variable that stores the value of the flag.
func Int32Slice(name string, value []int32, usage string) *[]int32 {
return CommandLine.Int32SliceP(name, "", value, usage)
}
// Int32SliceP is like Int32Slice, but accepts a shorthand letter that can be used after a single dash.
func Int32SliceP(name, shorthand string, value []int32, usage string) *[]int32 {
return CommandLine.Int32SliceP(name, shorthand, value, usage)
}

@ -0,0 +1,166 @@
package pflag
import (
"fmt"
"strconv"
"strings"
)
// -- int64Slice Value
type int64SliceValue struct {
value *[]int64
changed bool
}
func newInt64SliceValue(val []int64, p *[]int64) *int64SliceValue {
isv := new(int64SliceValue)
isv.value = p
*isv.value = val
return isv
}
func (s *int64SliceValue) Set(val string) error {
ss := strings.Split(val, ",")
out := make([]int64, len(ss))
for i, d := range ss {
var err error
out[i], err = strconv.ParseInt(d, 0, 64)
if err != nil {
return err
}
}
if !s.changed {
*s.value = out
} else {
*s.value = append(*s.value, out...)
}
s.changed = true
return nil
}
func (s *int64SliceValue) Type() string {
return "int64Slice"
}
func (s *int64SliceValue) String() string {
out := make([]string, len(*s.value))
for i, d := range *s.value {
out[i] = fmt.Sprintf("%d", d)
}
return "[" + strings.Join(out, ",") + "]"
}
func (s *int64SliceValue) fromString(val string) (int64, error) {
return strconv.ParseInt(val, 0, 64)
}
func (s *int64SliceValue) toString(val int64) string {
return fmt.Sprintf("%d", val)
}
func (s *int64SliceValue) Append(val string) error {
i, err := s.fromString(val)
if err != nil {
return err
}
*s.value = append(*s.value, i)
return nil
}
func (s *int64SliceValue) Replace(val []string) error {
out := make([]int64, len(val))
for i, d := range val {
var err error
out[i], err = s.fromString(d)
if err != nil {
return err
}
}
*s.value = out
return nil
}
func (s *int64SliceValue) GetSlice() []string {
out := make([]string, len(*s.value))
for i, d := range *s.value {
out[i] = s.toString(d)
}
return out
}
func int64SliceConv(val string) (interface{}, error) {
val = strings.Trim(val, "[]")
// Empty string would cause a slice with one (empty) entry
if len(val) == 0 {
return []int64{}, nil
}
ss := strings.Split(val, ",")
out := make([]int64, len(ss))
for i, d := range ss {
var err error
out[i], err = strconv.ParseInt(d, 0, 64)
if err != nil {
return nil, err
}
}
return out, nil
}
// GetInt64Slice return the []int64 value of a flag with the given name
func (f *FlagSet) GetInt64Slice(name string) ([]int64, error) {
val, err := f.getFlagType(name, "int64Slice", int64SliceConv)
if err != nil {
return []int64{}, err
}
return val.([]int64), nil
}
// Int64SliceVar defines a int64Slice flag with specified name, default value, and usage string.
// The argument p points to a []int64 variable in which to store the value of the flag.
func (f *FlagSet) Int64SliceVar(p *[]int64, name string, value []int64, usage string) {
f.VarP(newInt64SliceValue(value, p), name, "", usage)
}
// Int64SliceVarP is like Int64SliceVar, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) Int64SliceVarP(p *[]int64, name, shorthand string, value []int64, usage string) {
f.VarP(newInt64SliceValue(value, p), name, shorthand, usage)
}
// Int64SliceVar defines a int64[] flag with specified name, default value, and usage string.
// The argument p points to a int64[] variable in which to store the value of the flag.
func Int64SliceVar(p *[]int64, name string, value []int64, usage string) {
CommandLine.VarP(newInt64SliceValue(value, p), name, "", usage)
}
// Int64SliceVarP is like Int64SliceVar, but accepts a shorthand letter that can be used after a single dash.
func Int64SliceVarP(p *[]int64, name, shorthand string, value []int64, usage string) {
CommandLine.VarP(newInt64SliceValue(value, p), name, shorthand, usage)
}
// Int64Slice defines a []int64 flag with specified name, default value, and usage string.
// The return value is the address of a []int64 variable that stores the value of the flag.
func (f *FlagSet) Int64Slice(name string, value []int64, usage string) *[]int64 {
p := []int64{}
f.Int64SliceVarP(&p, name, "", value, usage)
return &p
}
// Int64SliceP is like Int64Slice, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) Int64SliceP(name, shorthand string, value []int64, usage string) *[]int64 {
p := []int64{}
f.Int64SliceVarP(&p, name, shorthand, value, usage)
return &p
}
// Int64Slice defines a []int64 flag with specified name, default value, and usage string.
// The return value is the address of a []int64 variable that stores the value of the flag.
func Int64Slice(name string, value []int64, usage string) *[]int64 {
return CommandLine.Int64SliceP(name, "", value, usage)
}
// Int64SliceP is like Int64Slice, but accepts a shorthand letter that can be used after a single dash.
func Int64SliceP(name, shorthand string, value []int64, usage string) *[]int64 {
return CommandLine.Int64SliceP(name, shorthand, value, usage)
}

@ -51,6 +51,36 @@ func (s *intSliceValue) String() string {
return "[" + strings.Join(out, ",") + "]" return "[" + strings.Join(out, ",") + "]"
} }
func (s *intSliceValue) Append(val string) error {
i, err := strconv.Atoi(val)
if err != nil {
return err
}
*s.value = append(*s.value, i)
return nil
}
func (s *intSliceValue) Replace(val []string) error {
out := make([]int, len(val))
for i, d := range val {
var err error
out[i], err = strconv.Atoi(d)
if err != nil {
return err
}
}
*s.value = out
return nil
}
func (s *intSliceValue) GetSlice() []string {
out := make([]string, len(*s.value))
for i, d := range *s.value {
out[i] = strconv.Itoa(d)
}
return out
}
func intSliceConv(val string) (interface{}, error) { func intSliceConv(val string) (interface{}, error) {
val = strings.Trim(val, "[]") val = strings.Trim(val, "[]")
// Empty string would cause a slice with one (empty) entry // Empty string would cause a slice with one (empty) entry

@ -72,9 +72,47 @@ func (s *ipSliceValue) String() string {
return "[" + out + "]" return "[" + out + "]"
} }
func (s *ipSliceValue) fromString(val string) (net.IP, error) {
return net.ParseIP(strings.TrimSpace(val)), nil
}
func (s *ipSliceValue) toString(val net.IP) string {
return val.String()
}
func (s *ipSliceValue) Append(val string) error {
i, err := s.fromString(val)
if err != nil {
return err
}
*s.value = append(*s.value, i)
return nil
}
func (s *ipSliceValue) Replace(val []string) error {
out := make([]net.IP, len(val))
for i, d := range val {
var err error
out[i], err = s.fromString(d)
if err != nil {
return err
}
}
*s.value = out
return nil
}
func (s *ipSliceValue) GetSlice() []string {
out := make([]string, len(*s.value))
for i, d := range *s.value {
out[i] = s.toString(d)
}
return out
}
func ipSliceConv(val string) (interface{}, error) { func ipSliceConv(val string) (interface{}, error) {
val = strings.Trim(val, "[]") val = strings.Trim(val, "[]")
// Emtpy string would cause a slice with one (empty) entry // Empty string would cause a slice with one (empty) entry
if len(val) == 0 { if len(val) == 0 {
return []net.IP{}, nil return []net.IP{}, nil
} }

@ -23,6 +23,32 @@ func (s *stringArrayValue) Set(val string) error {
return nil return nil
} }
func (s *stringArrayValue) Append(val string) error {
*s.value = append(*s.value, val)
return nil
}
func (s *stringArrayValue) Replace(val []string) error {
out := make([]string, len(val))
for i, d := range val {
var err error
out[i] = d
if err != nil {
return err
}
}
*s.value = out
return nil
}
func (s *stringArrayValue) GetSlice() []string {
out := make([]string, len(*s.value))
for i, d := range *s.value {
out[i] = d
}
return out
}
func (s *stringArrayValue) Type() string { func (s *stringArrayValue) Type() string {
return "stringArray" return "stringArray"
} }

@ -62,6 +62,20 @@ func (s *stringSliceValue) String() string {
return "[" + str + "]" return "[" + str + "]"
} }
func (s *stringSliceValue) Append(val string) error {
*s.value = append(*s.value, val)
return nil
}
func (s *stringSliceValue) Replace(val []string) error {
*s.value = val
return nil
}
func (s *stringSliceValue) GetSlice() []string {
return *s.value
}
func stringSliceConv(sval string) (interface{}, error) { func stringSliceConv(sval string) (interface{}, error) {
sval = sval[1 : len(sval)-1] sval = sval[1 : len(sval)-1]
// An empty string would cause a slice with one (empty) string // An empty string would cause a slice with one (empty) string
@ -84,7 +98,7 @@ func (f *FlagSet) GetStringSlice(name string) ([]string, error) {
// The argument p points to a []string variable in which to store the value of the flag. // The argument p points to a []string variable in which to store the value of the flag.
// Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly. // Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly.
// For example: // For example:
// --ss="v1,v2" -ss="v3" // --ss="v1,v2" --ss="v3"
// will result in // will result in
// []string{"v1", "v2", "v3"} // []string{"v1", "v2", "v3"}
func (f *FlagSet) StringSliceVar(p *[]string, name string, value []string, usage string) { func (f *FlagSet) StringSliceVar(p *[]string, name string, value []string, usage string) {
@ -100,7 +114,7 @@ func (f *FlagSet) StringSliceVarP(p *[]string, name, shorthand string, value []s
// The argument p points to a []string variable in which to store the value of the flag. // The argument p points to a []string variable in which to store the value of the flag.
// Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly. // Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly.
// For example: // For example:
// --ss="v1,v2" -ss="v3" // --ss="v1,v2" --ss="v3"
// will result in // will result in
// []string{"v1", "v2", "v3"} // []string{"v1", "v2", "v3"}
func StringSliceVar(p *[]string, name string, value []string, usage string) { func StringSliceVar(p *[]string, name string, value []string, usage string) {
@ -116,7 +130,7 @@ func StringSliceVarP(p *[]string, name, shorthand string, value []string, usage
// The return value is the address of a []string variable that stores the value of the flag. // The return value is the address of a []string variable that stores the value of the flag.
// Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly. // Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly.
// For example: // For example:
// --ss="v1,v2" -ss="v3" // --ss="v1,v2" --ss="v3"
// will result in // will result in
// []string{"v1", "v2", "v3"} // []string{"v1", "v2", "v3"}
func (f *FlagSet) StringSlice(name string, value []string, usage string) *[]string { func (f *FlagSet) StringSlice(name string, value []string, usage string) *[]string {
@ -136,7 +150,7 @@ func (f *FlagSet) StringSliceP(name, shorthand string, value []string, usage str
// The return value is the address of a []string variable that stores the value of the flag. // The return value is the address of a []string variable that stores the value of the flag.
// Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly. // Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly.
// For example: // For example:
// --ss="v1,v2" -ss="v3" // --ss="v1,v2" --ss="v3"
// will result in // will result in
// []string{"v1", "v2", "v3"} // []string{"v1", "v2", "v3"}
func StringSlice(name string, value []string, usage string) *[]string { func StringSlice(name string, value []string, usage string) *[]string {

@ -0,0 +1,149 @@
package pflag
import (
"bytes"
"fmt"
"strconv"
"strings"
)
// -- stringToInt64 Value
type stringToInt64Value struct {
value *map[string]int64
changed bool
}
func newStringToInt64Value(val map[string]int64, p *map[string]int64) *stringToInt64Value {
ssv := new(stringToInt64Value)
ssv.value = p
*ssv.value = val
return ssv
}
// Format: a=1,b=2
func (s *stringToInt64Value) Set(val string) error {
ss := strings.Split(val, ",")
out := make(map[string]int64, len(ss))
for _, pair := range ss {
kv := strings.SplitN(pair, "=", 2)
if len(kv) != 2 {
return fmt.Errorf("%s must be formatted as key=value", pair)
}
var err error
out[kv[0]], err = strconv.ParseInt(kv[1], 10, 64)
if err != nil {
return err
}
}
if !s.changed {
*s.value = out
} else {
for k, v := range out {
(*s.value)[k] = v
}
}
s.changed = true
return nil
}
func (s *stringToInt64Value) Type() string {
return "stringToInt64"
}
func (s *stringToInt64Value) String() string {
var buf bytes.Buffer
i := 0
for k, v := range *s.value {
if i > 0 {
buf.WriteRune(',')
}
buf.WriteString(k)
buf.WriteRune('=')
buf.WriteString(strconv.FormatInt(v, 10))
i++
}
return "[" + buf.String() + "]"
}
func stringToInt64Conv(val string) (interface{}, error) {
val = strings.Trim(val, "[]")
// An empty string would cause an empty map
if len(val) == 0 {
return map[string]int64{}, nil
}
ss := strings.Split(val, ",")
out := make(map[string]int64, len(ss))
for _, pair := range ss {
kv := strings.SplitN(pair, "=", 2)
if len(kv) != 2 {
return nil, fmt.Errorf("%s must be formatted as key=value", pair)
}
var err error
out[kv[0]], err = strconv.ParseInt(kv[1], 10, 64)
if err != nil {
return nil, err
}
}
return out, nil
}
// GetStringToInt64 return the map[string]int64 value of a flag with the given name
func (f *FlagSet) GetStringToInt64(name string) (map[string]int64, error) {
val, err := f.getFlagType(name, "stringToInt64", stringToInt64Conv)
if err != nil {
return map[string]int64{}, err
}
return val.(map[string]int64), nil
}
// StringToInt64Var defines a string flag with specified name, default value, and usage string.
// The argument p point64s to a map[string]int64 variable in which to store the values of the multiple flags.
// The value of each argument will not try to be separated by comma
func (f *FlagSet) StringToInt64Var(p *map[string]int64, name string, value map[string]int64, usage string) {
f.VarP(newStringToInt64Value(value, p), name, "", usage)
}
// StringToInt64VarP is like StringToInt64Var, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) StringToInt64VarP(p *map[string]int64, name, shorthand string, value map[string]int64, usage string) {
f.VarP(newStringToInt64Value(value, p), name, shorthand, usage)
}
// StringToInt64Var defines a string flag with specified name, default value, and usage string.
// The argument p point64s to a map[string]int64 variable in which to store the value of the flag.
// The value of each argument will not try to be separated by comma
func StringToInt64Var(p *map[string]int64, name string, value map[string]int64, usage string) {
CommandLine.VarP(newStringToInt64Value(value, p), name, "", usage)
}
// StringToInt64VarP is like StringToInt64Var, but accepts a shorthand letter that can be used after a single dash.
func StringToInt64VarP(p *map[string]int64, name, shorthand string, value map[string]int64, usage string) {
CommandLine.VarP(newStringToInt64Value(value, p), name, shorthand, usage)
}
// StringToInt64 defines a string flag with specified name, default value, and usage string.
// The return value is the address of a map[string]int64 variable that stores the value of the flag.
// The value of each argument will not try to be separated by comma
func (f *FlagSet) StringToInt64(name string, value map[string]int64, usage string) *map[string]int64 {
p := map[string]int64{}
f.StringToInt64VarP(&p, name, "", value, usage)
return &p
}
// StringToInt64P is like StringToInt64, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) StringToInt64P(name, shorthand string, value map[string]int64, usage string) *map[string]int64 {
p := map[string]int64{}
f.StringToInt64VarP(&p, name, shorthand, value, usage)
return &p
}
// StringToInt64 defines a string flag with specified name, default value, and usage string.
// The return value is the address of a map[string]int64 variable that stores the value of the flag.
// The value of each argument will not try to be separated by comma
func StringToInt64(name string, value map[string]int64, usage string) *map[string]int64 {
return CommandLine.StringToInt64P(name, "", value, usage)
}
// StringToInt64P is like StringToInt64, but accepts a shorthand letter that can be used after a single dash.
func StringToInt64P(name, shorthand string, value map[string]int64, usage string) *map[string]int64 {
return CommandLine.StringToInt64P(name, shorthand, value, usage)
}

@ -50,6 +50,48 @@ func (s *uintSliceValue) String() string {
return "[" + strings.Join(out, ",") + "]" return "[" + strings.Join(out, ",") + "]"
} }
func (s *uintSliceValue) fromString(val string) (uint, error) {
t, err := strconv.ParseUint(val, 10, 0)
if err != nil {
return 0, err
}
return uint(t), nil
}
func (s *uintSliceValue) toString(val uint) string {
return fmt.Sprintf("%d", val)
}
func (s *uintSliceValue) Append(val string) error {
i, err := s.fromString(val)
if err != nil {
return err
}
*s.value = append(*s.value, i)
return nil
}
func (s *uintSliceValue) Replace(val []string) error {
out := make([]uint, len(val))
for i, d := range val {
var err error
out[i], err = s.fromString(d)
if err != nil {
return err
}
}
*s.value = out
return nil
}
func (s *uintSliceValue) GetSlice() []string {
out := make([]string, len(*s.value))
for i, d := range *s.value {
out[i] = s.toString(d)
}
return out
}
func uintSliceConv(val string) (interface{}, error) { func uintSliceConv(val string) (interface{}, error) {
val = strings.Trim(val, "[]") val = strings.Trim(val, "[]")
// Empty string would cause a slice with one (empty) entry // Empty string would cause a slice with one (empty) entry

@ -1,26 +0,0 @@
language: go
go:
- '1.9'
- '1.10'
services:
- postgresql
- mysql
addons:
postgresql: "9.4"
before_script:
- mysql -e 'CREATE DATABASE testfixtures_test;'
- psql -c 'CREATE DATABASE testfixtures_test;' -U postgres
install:
- go get -t -tags 'sqlite postgresql mysql' ./...
- curl -s https://raw.githubusercontent.com/go-task/task/master/install-task.sh | sh
- bin/task dl-deps
- cp .sample.env .env
script:
- bin/task lint
- bin/task test-free

@ -1,358 +0,0 @@
# Go Test Fixtures
[![GoDoc](https://godoc.org/gopkg.in/testfixtures.v2?status.svg)](https://godoc.org/gopkg.in/testfixtures.v2)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-testfixtures/testfixtures)](https://goreportcard.com/report/github.com/go-testfixtures/testfixtures)
[![Build Status](https://travis-ci.org/go-testfixtures/testfixtures.svg?branch=master)](https://travis-ci.org/go-testfixtures/testfixtures)
[![Build status](https://ci.appveyor.com/api/projects/status/d2h6gq37wxbus1x7?svg=true)](https://ci.appveyor.com/project/andreynering/testfixtures)
> ***Warning***: this package will wipe the database data before loading the
fixtures! It is supposed to be used on a test database. Please, double check
if you are running it against the correct database.
Writing tests is hard, even more when you have to deal with an SQL database.
This package aims to make writing functional tests for web apps written in
Go easier.
Basically this package mimics the ["Rails' way"][railstests] of writing tests
for database applications, where sample data is kept in fixtures files. Before
the execution of every test, the test database is cleaned and the fixture data
is loaded into the database.
The idea is running tests against a real database, instead of relying in mocks,
which is boring to setup and may lead to production bugs not being caught in
the tests.
## Installation
First, get it:
```bash
go get -u -v gopkg.in/testfixtures.v2
```
## Usage
Create a folder for the fixture files. Each file should contain data for a
single table and have the name `<table_name>.yml`:
```
myapp/
myapp.go
myapp_test.go
...
fixtures/
posts.yml
comments.yml
tags.yml
posts_tags.yml
...
```
The file would look like this (it can have as many record you want):
```yml
# comments.yml
- id: 1
post_id: 1
content: A comment...
author_name: John Doe
author_email: john@doe.com
created_at: 2016-01-01 12:30:12
updated_at: 2016-01-01 12:30:12
- id: 2
post_id: 2
content: Another comment...
author_name: John Doe
author_email: john@doe.com
created_at: 2016-01-01 12:30:12
updated_at: 2016-01-01 12:30:12
# ...
```
An YAML object or array will be converted to JSON. It can be stored on a native
JSON type like JSONB on PostgreSQL or as a TEXT or VARCHAR column on other
databases.
```yml
- id: 1
post_attributes:
author: John Due
author_email: john@due.com
title: "..."
tags:
- programming
- go
- testing
post: "..."
```
If you need to write raw SQL, probably to call a function, prefix the value
of the column with `RAW=`:
```yml
- id: 1
uuid_column: RAW=uuid_generate_v4()
postgis_type_column: RAW=ST_GeomFromText('params...')
created_at: RAW=NOW()
updated_at: RAW=NOW()
```
Your tests would look like this:
```go
package myapp
import (
"database/sql"
"log"
_ "github.com/lib/pq"
"gopkg.in/testfixtures.v2"
)
var (
db *sql.DB
fixtures *testfixtures.Context
)
func TestMain(m *testing.M) {
var err error
// Open connection with the test database.
// Do NOT import fixtures in a production database!
// Existing data would be deleted
db, err = sql.Open("postgres", "dbname=myapp_test")
if err != nil {
log.Fatal(err)
}
// creating the context that hold the fixtures
// see about all compatible databases in this page below
   fixtures, err = testfixtures.NewFolder(db, &testfixtures.PostgreSQL{}, "testdata/fixtures")
if err != nil {
log.Fatal(err)
}
os.Exit(m.Run())
}
func prepareTestDatabase() {
if err := fixtures.Load(); err != nil {
log.Fatal(err)
}
}
func TestX(t *testing.T) {
prepareTestDatabase()
// your test here ...
}
func TestY(t *testing.T) {
prepareTestDatabase()
// your test here ...
}
func TestZ(t *testing.T) {
prepareTestDatabase()
// your test here ...
}
```
Alternatively, you can use the `NewFiles` function, to specify which
files you want to load into the database:
```go
fixtures, err := testfixtures.NewFiles(db, &testfixtures.PostgreSQL{},
"fixtures/orders.yml",
"fixtures/customers.yml",
// add as many files you want
)
if err != nil {
log.Fatal(err)
}
```
## Security check
In order to prevent you from accidentally wiping the wrong database, this
package will refuse to load fixtures if the database name (or database
filename for SQLite) doesn't contains "test". If you want to disable this
check, use:
```go
testfixtures.SkipDatabaseNameCheck(true)
```
## Sequences
For PostgreSQL or Oracle, this package also resets all sequences to a high
number to prevent duplicated primary keys while running the tests.
The default is 10000, but you can change that with:
```go
testfixtures.ResetSequencesTo(10000)
```
## Compatible databases
### PostgreSQL
This package has two approaches to disable foreign keys while importing fixtures
in PostgreSQL databases:
#### With `DISABLE TRIGGER`
This is the default approach. For that use:
```go
&testfixtures.PostgreSQL{}
```
With the above snippet this package will use `DISABLE TRIGGER` to temporarily
disabling foreign key constraints while loading fixtures. This work with any
version of PostgreSQL, but it is **required** to be connected in the database
as a SUPERUSER. You can make a PostgreSQL user a SUPERUSER with:
```sql
ALTER USER your_user SUPERUSER;
```
#### With `ALTER CONSTRAINT`
This approach don't require to be connected as a SUPERUSER, but only work with
PostgreSQL versions >= 9.4. Try this if you are getting foreign key violation
errors with the previous approach. It is as simple as using:
```go
&testfixtures.PostgreSQL{UseAlterConstraint: true}
```
### MySQL / MariaDB
Just make sure the connection string have
[the multistatement parameter](https://github.com/go-sql-driver/mysql#multistatements)
set to true, and use:
```go
&testfixtures.MySQL{}
```
### SQLite
SQLite is also supported. It is recommended to create foreign keys as
`DEFERRABLE` (the default) to prevent problems. See more
[on the SQLite documentation](https://www.sqlite.org/foreignkeys.html#fk_deferred).
(Foreign key constraints are no-op by default on SQLite, but enabling it is
recommended).
```go
&testfixtures.SQLite{}
```
### Microsoft SQL Server
SQL Server support requires SQL Server >= 2008. Inserting on `IDENTITY` columns
are handled as well. Just make sure you are logged in with a user with
`ALTER TABLE` permission.
```go
&testfixtures.SQLServer{}
```
### Oracle
Oracle is supported as well. Use:
```go
&testfixtures.Oracle{}
```
## Generating fixtures for a existing database (experimental)
The following code will generate a YAML file for each table of the database in
the given folder. It may be useful to boostrap a test scenario from a sample
database of your app.
```go
err := testfixtures.GenerateFixtures(db, &testfixtures.PostgreSQL{}, "testdata/fixtures")
if err != nil {
log.Fatalf("Error generating fixtures: %v", err)
}
```
Or
```go
err := testfixtures.GenerateFixturesForTables(
db,
[]*TableInfo{
&TableInfo{Name: "table_name", Where: "foo = 'bar'"},
// ...
},
&testfixtures.PostgreSQL{},
"testdata/fixtures",
)
if err != nil {
log.Fatalf("Error generating fixtures: %v", err)
}
```
> This was thought to run in small sample databases. It will likely break
if run in a production/big database.
## Contributing
Tests were written to ensure everything work as expected. You can run the tests
with:
```bash
# running tests for PostgreSQL
go test -tags postgresql
# running test for MySQL
go test -tags mysql
# running tests for SQLite
go test -tags sqlite
# running tests for SQL Server
go test -tags sqlserver
# running tests for Oracle
go test -tags oracle
# running test for multiple databases at once
go test -tags 'sqlite postgresql mysql'
# running tests + benchmark
go test -v -bench=. -tags postgresql
```
Travis runs tests for PostgreSQL, MySQL and SQLite. AppVeyor run for all
these and also Microsoft SQL Server.
To set the connection string of tests for each database, copy the `.sample.env`
file as `.env` and edit it according to your environment.
## Alternatives
If you don't think using fixtures is a good idea, you can try one of these
packages instead:
- [factory-go][factorygo]: Factory for Go. Inspired by Python's Factory Boy
and Ruby's Factory Girl
- [go-txdb (Single transaction SQL driver for Go)][gotxdb]: Use a single
database transaction for each functional test, so you can rollback to
previous state between tests to have the same database state in all tests
- [go-sqlmock][gosqlmock]: A mock for the sql.DB interface. This allow you to
unit test database code without having to connect to a real database
- [dbcleaner][dbcleaner] - Clean database for testing, inspired by
database_cleaner for Ruby
[railstests]: http://guides.rubyonrails.org/testing.html#the-test-database
[gotxdb]: https://github.com/DATA-DOG/go-txdb
[gosqlmock]: https://github.com/DATA-DOG/go-sqlmock
[factorygo]: https://github.com/bluele/factory-go
[dbcleaner]: https://github.com/khaiql/dbcleaner

@ -1,64 +0,0 @@
# github.com/go-task/task
version: '2'
tasks:
dl-deps:
desc: Download cli deps
cmds:
- go get -u github.com/golang/lint/golint
lint:
desc: Runs golint
cmds:
- golint .
test-free:
desc: Test free databases (PG, MySQL and SQLite)
cmds:
- task: test-pg
- task: test-mysql
- task: test-sqlite
test-all:
desc: Test all databases (PG, MySQL, SQLite, SQLServer and Oracle)
cmds:
- task: test-pg
- task: test-mysql
- task: test-sqlite
- task: test-sqlserver
- task: test-oracle
test-pg:
desc: Test PostgreSQL
cmds:
- task: test-db
vars: {DATABASE: postgresql}
test-mysql:
desc: Test MySQL
cmds:
- task: test-db
vars: {DATABASE: mysql}
test-sqlite:
desc: Test SQLite
cmds:
- task: test-db
vars: {DATABASE: sqlite}
test-sqlserver:
desc: Test SQLServer
cmds:
- task: test-db
vars: {DATABASE: sqlserver}
test-oracle:
desc: Test Oracle
cmds:
- task: test-db
vars: {DATABASE: oracle}
test-db:
cmds:
- go test -v -tags {{.DATABASE}}

@ -1,51 +0,0 @@
version: '{build}'
clone_folder: C:\GOPATH\src\gopkg.in\testfixtures.v2
build: false
deploy: false
services:
- postgresql96
- mysql
- mssql2017
environment:
POSTGRES_PATH: C:\Program Files\PostgreSQL\9.6
PGUSER: postgres
PGPASSWORD: Password12!
PG_CONN_STRING: 'user=postgres password=Password12! dbname=testfixtures_test sslmode=disable'
MYSQL_PATH: C:\Program Files\MySql\MySQL Server 5.7
MYSQL_PWD: Password12!
MYSQL_CONN_STRING: 'root:Password12!@/testfixtures_test?multiStatements=true'
SQLITE_CONN_STRING: 'testdb.sqlite3'
SQLSERVER_CONN_STRING: 'server=localhost;database=testfixtures_test;user id=sa;password=Password12!;encrypt=disable'
MINGW_PATH: C:\MinGW
GOPATH: C:\GOPATH
GOVERSION: 1.10.3
install:
- SET PATH=%POSTGRES_PATH%\bin;%MYSQL_PATH%\bin;%MINGW_PATH%\bin;%PATH%
- rmdir C:\go /s /q
- appveyor DownloadFile https://storage.googleapis.com/golang/go%GOVERSION%.windows-386.msi
- msiexec /i go%GOVERSION%.windows-386.msi /q
- go version
build_script:
- createdb testfixtures_test
- mysql -e "CREATE DATABASE testfixtures_test;" --user=root
- sqlcmd -S localhost,1433 -U sa -P Password12! -Q "CREATE DATABASE testfixtures_test" -d "master"
test_script:
- go get -t -tags "sqlite postgresql mysql sqlserver" ./...
- go install -v ./...
- go test -v -tags postgresql
- go test -v -tags mysql
- go test -v -tags sqlserver
- go test -v -tags sqlite

@ -1,75 +0,0 @@
package testfixtures
import (
"database/sql"
)
type (
// DataBaseHelper is the helper interface
// Deprecated: Use Helper instead
DataBaseHelper Helper
// PostgreSQLHelper is the PostgreSQL helper
// Deprecated: Use PostgreSQL{} instead
PostgreSQLHelper struct {
PostgreSQL
UseAlterConstraint bool
}
// MySQLHelper is the MySQL helper
// Deprecated: Use MySQL{} instead
MySQLHelper struct {
MySQL
}
// SQLiteHelper is the SQLite helper
// Deprecated: Use SQLite{} instead
SQLiteHelper struct {
SQLite
}
// SQLServerHelper is the SQLServer helper
// Deprecated: Use SQLServer{} instead
SQLServerHelper struct {
SQLServer
}
// OracleHelper is the Oracle helper
// Deprecated: Use Oracle{} instead
OracleHelper struct {
Oracle
}
)
func (h *PostgreSQLHelper) disableReferentialIntegrity(db *sql.DB, loadFn loadFunction) error {
h.PostgreSQL.UseAlterConstraint = h.UseAlterConstraint
return h.PostgreSQL.disableReferentialIntegrity(db, loadFn)
}
// LoadFixtureFiles load all specified fixtures files into database:
// LoadFixtureFiles(db, &PostgreSQL{},
// "fixtures/customers.yml", "fixtures/orders.yml")
// // add as many files you want
//
// Deprecated: Use NewFiles() and Load() instead.
func LoadFixtureFiles(db *sql.DB, helper Helper, files ...string) error {
c, err := NewFiles(db, helper, files...)
if err != nil {
return err
}
return c.Load()
}
// LoadFixtures loads all fixtures in a given folder into the database:
// LoadFixtures("myfixturesfolder", db, &PostgreSQL{})
//
// Deprecated: Use NewFolder() and Load() instead.
func LoadFixtures(folderName string, db *sql.DB, helper Helper) error {
c, err := NewFolder(db, helper, folderName)
if err != nil {
return err
}
return c.Load()
}

@ -1,41 +0,0 @@
package testfixtures
import (
"errors"
"fmt"
)
var (
// ErrWrongCastNotAMap is returned when a map is not a map[interface{}]interface{}
ErrWrongCastNotAMap = errors.New("Could not cast record: not a map[interface{}]interface{}")
// ErrFileIsNotSliceOrMap is returned the the fixture file is not a slice or map.
ErrFileIsNotSliceOrMap = errors.New("The fixture file is not a slice or map")
// ErrKeyIsNotString is returned when a record is not of type string
ErrKeyIsNotString = errors.New("Record map key is not string")
// ErrNotTestDatabase is returned when the database name doesn't contains "test"
ErrNotTestDatabase = errors.New(`Loading aborted because the database name does not contains "test"`)
)
// InsertError will be returned if any error happens on database while
// inserting the record
type InsertError struct {
Err error
File string
Index int
SQL string
Params []interface{}
}
func (e *InsertError) Error() string {
return fmt.Sprintf(
"testfixtures: error inserting record: %v, on file: %s, index: %d, sql: %s, params: %v",
e.Err,
e.File,
e.Index,
e.SQL,
e.Params,
)
}

@ -1,110 +0,0 @@
package testfixtures
import (
"database/sql"
"fmt"
"os"
"path"
"unicode/utf8"
"gopkg.in/yaml.v2"
)
// TableInfo is settings for generating a fixture for table.
type TableInfo struct {
Name string // Table name
Where string // A condition for extracting records. If this value is empty, extracts all records.
}
func (ti *TableInfo) whereClause() string {
if ti.Where == "" {
return ""
}
return fmt.Sprintf(" WHERE %s", ti.Where)
}
// GenerateFixtures generates fixtures for the current contents of a database, and saves
// them to the specified directory
func GenerateFixtures(db *sql.DB, helper Helper, dir string) error {
tables, err := helper.tableNames(db)
if err != nil {
return err
}
for _, table := range tables {
filename := path.Join(dir, table+".yml")
if err := generateFixturesForTable(db, helper, &TableInfo{Name: table}, filename); err != nil {
return err
}
}
return nil
}
// GenerateFixturesForTables generates fixtures for the current contents of specified tables in a database, and saves
// them to the specified directory
func GenerateFixturesForTables(db *sql.DB, tables []*TableInfo, helper Helper, dir string) error {
for _, table := range tables {
filename := path.Join(dir, table.Name+".yml")
if err := generateFixturesForTable(db, helper, table, filename); err != nil {
return err
}
}
return nil
}
func generateFixturesForTable(db *sql.DB, h Helper, table *TableInfo, filename string) error {
query := fmt.Sprintf("SELECT * FROM %s%s", h.quoteKeyword(table.Name), table.whereClause())
rows, err := db.Query(query)
if err != nil {
return err
}
defer rows.Close()
columns, err := rows.Columns()
if err != nil {
return err
}
fixtures := make([]interface{}, 0, 10)
for rows.Next() {
entries := make([]interface{}, len(columns))
entryPtrs := make([]interface{}, len(entries))
for i := range entries {
entryPtrs[i] = &entries[i]
}
if err := rows.Scan(entryPtrs...); err != nil {
return err
}
entryMap := make(map[string]interface{}, len(entries))
for i, column := range columns {
entryMap[column] = convertValue(entries[i])
}
fixtures = append(fixtures, entryMap)
}
if err = rows.Err(); err != nil {
return err
}
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
marshaled, err := yaml.Marshal(fixtures)
if err != nil {
return err
}
_, err = f.Write(marshaled)
return err
}
func convertValue(value interface{}) interface{} {
switch v := value.(type) {
case []byte:
if utf8.Valid(v) {
return string(v)
}
}
return value
}

@ -1,19 +0,0 @@
package testfixtures
var (
skipDatabaseNameCheck bool
resetSequencesTo int64 = 10000
)
// SkipDatabaseNameCheck If true, loading fixtures will not check if the database
// name constaint "test". Use with caution!
func SkipDatabaseNameCheck(value bool) {
skipDatabaseNameCheck = value
}
// ResetSequencesTo sets the value the sequences will be reset to.
// This is used by PostgreSQL and Oracle.
// Defaults to 10000.
func ResetSequencesTo(value int64) {
resetSequencesTo = value
}

@ -1,171 +0,0 @@
package testfixtures
import (
"database/sql"
"fmt"
"strings"
)
// Oracle is the Oracle database helper for this package
type Oracle struct {
baseHelper
enabledConstraints []oracleConstraint
sequences []string
}
type oracleConstraint struct {
tableName string
constraintName string
}
func (h *Oracle) init(db *sql.DB) error {
var err error
h.enabledConstraints, err = h.getEnabledConstraints(db)
if err != nil {
return err
}
h.sequences, err = h.getSequences(db)
if err != nil {
return err
}
return nil
}
func (*Oracle) paramType() int {
return paramTypeColon
}
func (*Oracle) quoteKeyword(str string) string {
return fmt.Sprintf("\"%s\"", strings.ToUpper(str))
}
func (*Oracle) databaseName(q queryable) (string, error) {
var dbName string
err := q.QueryRow("SELECT user FROM DUAL").Scan(&dbName)
return dbName, err
}
func (*Oracle) tableNames(q queryable) ([]string, error) {
query := `
SELECT TABLE_NAME
FROM USER_TABLES
`
rows, err := q.Query(query)
if err != nil {
return nil, err
}
defer rows.Close()
var tables []string
for rows.Next() {
var table string
if err = rows.Scan(&table); err != nil {
return nil, err
}
tables = append(tables, table)
}
if err = rows.Err(); err != nil {
return nil, err
}
return tables, nil
}
func (*Oracle) getEnabledConstraints(q queryable) ([]oracleConstraint, error) {
var constraints []oracleConstraint
rows, err := q.Query(`
SELECT table_name, constraint_name
FROM user_constraints
WHERE constraint_type = 'R'
AND status = 'ENABLED'
`)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var constraint oracleConstraint
rows.Scan(&constraint.tableName, &constraint.constraintName)
constraints = append(constraints, constraint)
}
if err = rows.Err(); err != nil {
return nil, err
}
return constraints, nil
}
func (*Oracle) getSequences(q queryable) ([]string, error) {
var sequences []string
rows, err := q.Query("SELECT sequence_name FROM user_sequences")
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var sequence string
if err = rows.Scan(&sequence); err != nil {
return nil, err
}
sequences = append(sequences, sequence)
}
if err = rows.Err(); err != nil {
return nil, err
}
return sequences, nil
}
func (h *Oracle) resetSequences(q queryable) error {
for _, sequence := range h.sequences {
_, err := q.Exec(fmt.Sprintf("DROP SEQUENCE %s", h.quoteKeyword(sequence)))
if err != nil {
return err
}
_, err = q.Exec(fmt.Sprintf("CREATE SEQUENCE %s START WITH %d", h.quoteKeyword(sequence), resetSequencesTo))
if err != nil {
return err
}
}
return nil
}
func (h *Oracle) disableReferentialIntegrity(db *sql.DB, loadFn loadFunction) (err error) {
// re-enable after load
defer func() {
for _, c := range h.enabledConstraints {
_, err2 := db.Exec(fmt.Sprintf("ALTER TABLE %s ENABLE CONSTRAINT %s", h.quoteKeyword(c.tableName), h.quoteKeyword(c.constraintName)))
if err2 != nil && err == nil {
err = err2
}
}
}()
// disable foreign keys
for _, c := range h.enabledConstraints {
_, err := db.Exec(fmt.Sprintf("ALTER TABLE %s DISABLE CONSTRAINT %s", h.quoteKeyword(c.tableName), h.quoteKeyword(c.constraintName)))
if err != nil {
return err
}
}
tx, err := db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
if err = loadFn(tx); err != nil {
return err
}
if err = tx.Commit(); err != nil {
return err
}
return h.resetSequences(db)
}

@ -1,305 +0,0 @@
package testfixtures
import (
"database/sql"
"fmt"
"io/ioutil"
"path"
"path/filepath"
"regexp"
"strings"
"gopkg.in/yaml.v2"
)
// Context holds the fixtures to be loaded in the database.
type Context struct {
db *sql.DB
helper Helper
fixturesFiles []*fixtureFile
}
type fixtureFile struct {
path string
fileName string
content []byte
insertSQLs []insertSQL
}
type insertSQL struct {
sql string
params []interface{}
}
var (
dbnameRegexp = regexp.MustCompile("(?i)test")
)
// NewFolder creates a context for all fixtures in a given folder into the database:
// NewFolder(db, &PostgreSQL{}, "my/fixtures/folder")
func NewFolder(db *sql.DB, helper Helper, folderName string) (*Context, error) {
fixtures, err := fixturesFromFolder(folderName)
if err != nil {
return nil, err
}
c, err := newContext(db, helper, fixtures)
if err != nil {
return nil, err
}
return c, nil
}
// NewFiles creates a context for all specified fixtures files into database:
// NewFiles(db, &PostgreSQL{},
// "fixtures/customers.yml",
// "fixtures/orders.yml"
// // add as many files you want
// )
func NewFiles(db *sql.DB, helper Helper, fileNames ...string) (*Context, error) {
fixtures, err := fixturesFromFiles(fileNames...)
if err != nil {
return nil, err
}
c, err := newContext(db, helper, fixtures)
if err != nil {
return nil, err
}
return c, nil
}
func newContext(db *sql.DB, helper Helper, fixtures []*fixtureFile) (*Context, error) {
c := &Context{
db: db,
helper: helper,
fixturesFiles: fixtures,
}
if err := c.helper.init(c.db); err != nil {
return nil, err
}
if err := c.buildInsertSQLs(); err != nil {
return nil, err
}
return c, nil
}
// DetectTestDatabase returns nil if databaseName matches regexp
// if err := fixtures.DetectTestDatabase(); err != nil {
// log.Fatal(err)
// }
func (c *Context) DetectTestDatabase() error {
dbName, err := c.helper.databaseName(c.db)
if err != nil {
return err
}
if !dbnameRegexp.MatchString(dbName) {
return ErrNotTestDatabase
}
return nil
}
// Load wipes and after load all fixtures in the database.
// if err := fixtures.Load(); err != nil {
// log.Fatal(err)
// }
func (c *Context) Load() error {
if !skipDatabaseNameCheck {
if err := c.DetectTestDatabase(); err != nil {
return err
}
}
err := c.helper.disableReferentialIntegrity(c.db, func(tx *sql.Tx) error {
for _, file := range c.fixturesFiles {
modified, err := c.helper.isTableModified(tx, file.fileNameWithoutExtension())
if err != nil {
return err
}
if !modified {
continue
}
if err := file.delete(tx, c.helper); err != nil {
return err
}
err = c.helper.whileInsertOnTable(tx, file.fileNameWithoutExtension(), func() error {
for j, i := range file.insertSQLs {
if _, err := tx.Exec(i.sql, i.params...); err != nil {
return &InsertError{
Err: err,
File: file.fileName,
Index: j,
SQL: i.sql,
Params: i.params,
}
}
}
return nil
})
if err != nil {
return err
}
}
return nil
})
if err != nil {
return err
}
return c.helper.afterLoad(c.db)
}
func (c *Context) buildInsertSQLs() error {
for _, f := range c.fixturesFiles {
var records interface{}
if err := yaml.Unmarshal(f.content, &records); err != nil {
return err
}
switch records := records.(type) {
case []interface{}:
for _, record := range records {
recordMap, ok := record.(map[interface{}]interface{})
if !ok {
return ErrWrongCastNotAMap
}
sql, values, err := f.buildInsertSQL(c.helper, recordMap)
if err != nil {
return err
}
f.insertSQLs = append(f.insertSQLs, insertSQL{sql, values})
}
case map[interface{}]interface{}:
for _, record := range records {
recordMap, ok := record.(map[interface{}]interface{})
if !ok {
return ErrWrongCastNotAMap
}
sql, values, err := f.buildInsertSQL(c.helper, recordMap)
if err != nil {
return err
}
f.insertSQLs = append(f.insertSQLs, insertSQL{sql, values})
}
default:
return ErrFileIsNotSliceOrMap
}
}
return nil
}
func (f *fixtureFile) fileNameWithoutExtension() string {
return strings.Replace(f.fileName, filepath.Ext(f.fileName), "", 1)
}
func (f *fixtureFile) delete(tx *sql.Tx, h Helper) error {
_, err := tx.Exec(fmt.Sprintf("DELETE FROM %s", h.quoteKeyword(f.fileNameWithoutExtension())))
return err
}
func (f *fixtureFile) buildInsertSQL(h Helper, record map[interface{}]interface{}) (sqlStr string, values []interface{}, err error) {
var (
sqlColumns []string
sqlValues []string
i = 1
)
for key, value := range record {
keyStr, ok := key.(string)
if !ok {
err = ErrKeyIsNotString
return
}
sqlColumns = append(sqlColumns, h.quoteKeyword(keyStr))
// if string, try convert to SQL or time
// if map or array, convert to json
switch v := value.(type) {
case string:
if strings.HasPrefix(v, "RAW=") {
sqlValues = append(sqlValues, strings.TrimPrefix(v, "RAW="))
continue
}
if t, err := tryStrToDate(v); err == nil {
value = t
}
case []interface{}, map[interface{}]interface{}:
value = recursiveToJSON(v)
}
switch h.paramType() {
case paramTypeDollar:
sqlValues = append(sqlValues, fmt.Sprintf("$%d", i))
case paramTypeQuestion:
sqlValues = append(sqlValues, "?")
case paramTypeColon:
sqlValues = append(sqlValues, fmt.Sprintf(":%d", i))
}
values = append(values, value)
i++
}
sqlStr = fmt.Sprintf(
"INSERT INTO %s (%s) VALUES (%s)",
h.quoteKeyword(f.fileNameWithoutExtension()),
strings.Join(sqlColumns, ", "),
strings.Join(sqlValues, ", "),
)
return
}
func fixturesFromFolder(folderName string) ([]*fixtureFile, error) {
var files []*fixtureFile
fileinfos, err := ioutil.ReadDir(folderName)
if err != nil {
return nil, err
}
for _, fileinfo := range fileinfos {
if !fileinfo.IsDir() && filepath.Ext(fileinfo.Name()) == ".yml" {
fixture := &fixtureFile{
path: path.Join(folderName, fileinfo.Name()),
fileName: fileinfo.Name(),
}
fixture.content, err = ioutil.ReadFile(fixture.path)
if err != nil {
return nil, err
}
files = append(files, fixture)
}
}
return files, nil
}
func fixturesFromFiles(fileNames ...string) ([]*fixtureFile, error) {
var (
fixtureFiles []*fixtureFile
err error
)
for _, f := range fileNames {
fixture := &fixtureFile{
path: f,
fileName: filepath.Base(f),
}
fixture.content, err = ioutil.ReadFile(fixture.path)
if err != nil {
return nil, err
}
fixtureFiles = append(fixtureFiles, fixture)
}
return fixtureFiles, nil
}

@ -1,34 +0,0 @@
package testfixtures
import (
"errors"
"time"
)
var timeFormats = []string{
"2006-01-02",
"2006-01-02 15:04",
"2006-01-02 15:04:05",
"20060102",
"20060102 15:04",
"20060102 15:04:05",
"02/01/2006",
"02/01/2006 15:04",
"02/01/2006 15:04:05",
"2006-01-02T15:04-07:00",
"2006-01-02T15:04:05-07:00",
}
// ErrCouldNotConvertToTime is returns when a string is not a reconizable time format
var ErrCouldNotConvertToTime = errors.New("Could not convert string to time")
func tryStrToDate(s string) (time.Time, error) {
for _, f := range timeFormats {
t, err := time.ParseInLocation(f, s, time.Local)
if err != nil {
continue
}
return t, nil
}
return time.Time{}, ErrCouldNotConvertToTime
}

1
vendor/gopkg.in/yaml.v2/apic.go generated vendored

@ -86,6 +86,7 @@ func yaml_emitter_initialize(emitter *yaml_emitter_t) {
raw_buffer: make([]byte, 0, output_raw_buffer_size), raw_buffer: make([]byte, 0, output_raw_buffer_size),
states: make([]yaml_emitter_state_t, 0, initial_stack_size), states: make([]yaml_emitter_state_t, 0, initial_stack_size),
events: make([]yaml_event_t, 0, initial_queue_size), events: make([]yaml_event_t, 0, initial_queue_size),
best_width: -1,
} }
} }

16
vendor/modules.txt vendored

@ -333,6 +333,9 @@ github.com/go-swagger/go-swagger/cmd/swagger/commands/initcmd
github.com/go-swagger/go-swagger/codescan github.com/go-swagger/go-swagger/codescan
github.com/go-swagger/go-swagger/generator github.com/go-swagger/go-swagger/generator
github.com/go-swagger/go-swagger/scan github.com/go-swagger/go-swagger/scan
# github.com/go-testfixtures/testfixtures/v3 v3.2.0
## explicit
github.com/go-testfixtures/testfixtures/v3
# github.com/gobwas/glob v0.2.3 # github.com/gobwas/glob v0.2.3
## explicit ## explicit
github.com/gobwas/glob github.com/gobwas/glob
@ -410,8 +413,6 @@ github.com/jbenet/go-context/io
github.com/jessevdk/go-flags github.com/jessevdk/go-flags
# github.com/jmhodges/levigo v1.0.0 # github.com/jmhodges/levigo v1.0.0
## explicit ## explicit
# github.com/joho/godotenv v1.3.0
## explicit
# github.com/kballard/go-shellquote v0.0.0-20170619183022-cd60e84ee657 # github.com/kballard/go-shellquote v0.0.0-20170619183022-cd60e84ee657
## explicit ## explicit
github.com/kballard/go-shellquote github.com/kballard/go-shellquote
@ -496,11 +497,9 @@ github.com/mattn/go-colorable
# github.com/mattn/go-isatty v0.0.11 # github.com/mattn/go-isatty v0.0.11
## explicit ## explicit
github.com/mattn/go-isatty github.com/mattn/go-isatty
# github.com/mattn/go-oci8 v0.0.0-20190320171441-14ba190cf52d
## explicit
# github.com/mattn/go-runewidth v0.0.7 # github.com/mattn/go-runewidth v0.0.7
github.com/mattn/go-runewidth github.com/mattn/go-runewidth
# github.com/mattn/go-sqlite3 v1.11.0 # github.com/mattn/go-sqlite3 v2.0.2+incompatible
## explicit ## explicit
github.com/mattn/go-sqlite3 github.com/mattn/go-sqlite3
# github.com/matttproud/golang_protobuf_extensions v1.0.1 # github.com/matttproud/golang_protobuf_extensions v1.0.1
@ -613,7 +612,7 @@ github.com/spf13/afero/mem
github.com/spf13/cast github.com/spf13/cast
# github.com/spf13/jwalterweatherman v1.1.0 # github.com/spf13/jwalterweatherman v1.1.0
github.com/spf13/jwalterweatherman github.com/spf13/jwalterweatherman
# github.com/spf13/pflag v1.0.3 # github.com/spf13/pflag v1.0.5
github.com/spf13/pflag github.com/spf13/pflag
# github.com/spf13/viper v1.4.0 # github.com/spf13/viper v1.4.0
github.com/spf13/viper github.com/spf13/viper
@ -861,12 +860,9 @@ gopkg.in/ini.v1
# gopkg.in/ldap.v3 v3.0.2 # gopkg.in/ldap.v3 v3.0.2
## explicit ## explicit
gopkg.in/ldap.v3 gopkg.in/ldap.v3
# gopkg.in/testfixtures.v2 v2.5.0
## explicit
gopkg.in/testfixtures.v2
# gopkg.in/warnings.v0 v0.1.2 # gopkg.in/warnings.v0 v0.1.2
gopkg.in/warnings.v0 gopkg.in/warnings.v0
# gopkg.in/yaml.v2 v2.2.8 # gopkg.in/yaml.v2 v2.3.0
## explicit ## explicit
gopkg.in/yaml.v2 gopkg.in/yaml.v2
# mvdan.cc/xurls/v2 v2.1.0 # mvdan.cc/xurls/v2 v2.1.0

Loading…
Cancel
Save