[
  {
    "path": ".github/workflows/lint.yml",
    "content": "name: lint\non:\n  pull_request:\njobs:\n  golangci:\n    name: lint\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - name: golangci-lint\n        uses: golangci/golangci-lint-action@v2\n        with:\n          # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version\n          version: latest\n\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: \"test\"\non: [\"push\",\"pull_request\"]\njobs:\n  test:\n    name: \"Run unit tests\"\n    strategy:\n      matrix:\n        os: [ubuntu-latest]\n        go-version: [\"1.15.x\", \"1.16.x\", \"1.17.x\"]\n    runs-on: ${{ matrix.os }}\n\n    services:\n      mysql:\n        image: mysql\n        env:\n          MYSQL_USER: test\n          MYSQL_PASSWORD: test\n          MYSQL_DATABASE: sqlhooks\n          MYSQL_ALLOW_EMPTY_PASSWORD: true\n        ports:\n          - 3306:3306\n        options: >-\n          --health-cmd=\"mysqladmin -v ping\"\n          --health-interval=10s\n          --health-timeout=5s\n          --health-retries=5\n\n      postgres:\n        image: postgres\n        env:\n          POSTGRES_PASSWORD: test\n          POSTGRES_DB: sqlhooks\n        ports:\n          - 5432:5432\n        options: >-\n          --health-cmd pg_isready\n          --health-interval 10s\n          --health-timeout 5s\n          --health-retries 5\n\n    steps:\n    - name: Install Go\n      uses: actions/setup-go@v2\n      with:\n        go-version: ${{ matrix.go-version }}\n\n    - name: Checkout code\n      uses: actions/checkout@v2\n      with:\n        fetch-depth: 1\n\n    - uses: actions/cache@v2\n      with:\n        path: |\n          ~/go/pkg/mod\n          ~/.cache/go-build\n        key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}\n        restore-keys: |\n          ${{ runner.os }}-go-\n\n    - name: Test\n      env:\n        SQLHOOKS_MYSQL_DSN: \"test:test@/sqlhooks?interpolateParams=true\"\n        SQLHOOKS_POSTGRES_DSN: \"postgres://postgres:test@localhost/sqlhooks?sslmode=disable\"\n      run: go test -race -covermode atomic -coverprofile=covprofile ./...\n    - name: Install goveralls\n      run: go get github.com/mattn/goveralls@v0.0.11\n    - name: Send coverage\n      env:\n        COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      run: goveralls -coverprofile=covprofile -service=github\n"
  },
  {
    "path": ".gitignore",
    "content": "# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\n\n# Folders\n_obj\n_test\n\n# Architecture specific extensions/prefixes\n*.[568vq]\n[568vq].out\n\n*.cgo1.go\n*.cgo2.c\n_cgo_defun.c\n_cgo_gotypes.go\n_cgo_export.*\n\n_testmain.go\n\n*.exe\n*.test\n*.prof\n"
  },
  {
    "path": ".golangci.yml",
    "content": "linters-settings:\n  staticcheck:\n    checks: [\"all\", \"-SA1019\"]\nissues:\n  exclude-rules:\n    - path: example_test.go\n      linters:\n        - errcheck\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Change Log\n\n## [Unreleased](https://github.com/qustavo/sqlhooks/tree/HEAD)\n\n[Full Changelog](https://github.com/qustavo/sqlhooks/compare/v1.0.0...HEAD)\n\n**Closed issues:**\n\n- Add Benchmarks [\\#9](https://github.com/qustavo/sqlhooks/issues/9)\n## [v1.0.0](https://github.com/qustavo/sqlhooks/tree/v1.0.0) (2017-05-08)\n[Full Changelog](https://github.com/qustavo/sqlhooks/compare/v0.4...v1.0.0)\n\n**Merged pull requests:**\n\n- Godoc [\\#7](https://github.com/qustavo/sqlhooks/pull/7) ([qustavo](https://github.com/qustavo))\n- Make covermode=count [\\#6](https://github.com/qustavo/sqlhooks/pull/6) ([qustavo](https://github.com/qustavo))\n- V1 [\\#5](https://github.com/qustavo/sqlhooks/pull/5) ([qustavo](https://github.com/qustavo))\n- Expose a WrapDriver function [\\#4](https://github.com/qustavo/sqlhooks/issues/4)\n- Implement new 1.8 interfaces [\\#3](https://github.com/qustavo/sqlhooks/issues/3)\n\n## [v0.4](https://github.com/qustavo/sqlhooks/tree/v0.4) (2017-03-23)\n[Full Changelog](https://github.com/qustavo/sqlhooks/compare/v0.3...v0.4)\n\n## [v0.3](https://github.com/qustavo/sqlhooks/tree/v0.3) (2016-06-02)\n[Full Changelog](https://github.com/qustavo/sqlhooks/compare/v0.2...v0.3)\n\n**Closed issues:**\n\n- Change Notifications [\\#2](https://github.com/qustavo/sqlhooks/issues/2)\n\n## [v0.2](https://github.com/qustavo/sqlhooks/tree/v0.2) (2016-05-01)\n[Full Changelog](https://github.com/qustavo/sqlhooks/compare/v0.1...v0.2)\n\n## [v0.1](https://github.com/qustavo/sqlhooks/tree/v0.1) (2016-04-25)\n**Merged pull requests:**\n\n- Sqlite3 [\\#1](https://github.com/qustavo/sqlhooks/pull/1) ([qustavo](https://github.com/qustavo))\n\n\n\n\\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2016 Gustavo Chaín\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# sqlhooks\n![Build Status](https://github.com/qustavo/sqlhooks/actions/workflows/test.yml/badge.svg)\n[![Go Report Card](https://goreportcard.com/badge/github.com/qustavo/sqlhooks)](https://goreportcard.com/report/github.com/qustavo/sqlhooks)\n[![Coverage Status](https://coveralls.io/repos/github/qustavo/sqlhooks/badge.svg?branch=master)](https://coveralls.io/github/qustavo/sqlhooks?branch=master)\n\nAttach hooks to any database/sql driver.\n\nThe purpose of sqlhooks is to provide a way to instrument your sql statements, making really easy to log queries or measure execution time without modifying your actual code.\n\n# Install\n```bash\ngo get github.com/qustavo/sqlhooks/v2\n```\nRequires Go >= 1.14.x\n\n## Breaking changes\n`V2` isn't backward compatible with previous versions, if you want to fetch old versions, you can use go modules or get them from [gopkg.in](http://gopkg.in/)\n```bash\ngo get github.com/qustavo/sqlhooks\ngo get gopkg.in/qustavo/sqlhooks.v1\n```\n\n# Usage [![GoDoc](https://godoc.org/github.com/qustavo/dotsql?status.svg)](https://godoc.org/github.com/qustavo/sqlhooks)\n\n```go\n// This example shows how to instrument sql queries in order to display the time that they consume\npackage main\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/qustavo/sqlhooks/v2\"\n\t\"github.com/mattn/go-sqlite3\"\n)\n\n// Hooks satisfies the sqlhook.Hooks interface\ntype Hooks struct {}\n\n// Before hook will print the query with it's args and return the context with the timestamp\nfunc (h *Hooks) Before(ctx context.Context, query string, args ...interface{}) (context.Context, error) {\n\tfmt.Printf(\"> %s %q\", query, args)\n\treturn context.WithValue(ctx, \"begin\", time.Now()), nil\n}\n\n// After hook will get the timestamp registered on the Before hook and print the elapsed time\nfunc (h *Hooks) After(ctx context.Context, query string, args ...interface{}) (context.Context, error) {\n\tbegin := ctx.Value(\"begin\").(time.Time)\n\tfmt.Printf(\". took: %s\\n\", time.Since(begin))\n\treturn ctx, nil\n}\n\nfunc main() {\n\t// First, register the wrapper\n\tsql.Register(\"sqlite3WithHooks\", sqlhooks.Wrap(&sqlite3.SQLiteDriver{}, &Hooks{}))\n\n\t// Connect to the registered wrapped driver\n\tdb, _ := sql.Open(\"sqlite3WithHooks\", \":memory:\")\n\n\t// Do you're stuff\n\tdb.Exec(\"CREATE TABLE t (id INTEGER, text VARCHAR(16))\")\n\tdb.Exec(\"INSERT into t (text) VALUES(?), (?)\", \"foo\", \"bar\")\n\tdb.Query(\"SELECT id, text FROM t\")\n}\n\n/*\nOutput should look like:\n> CREATE TABLE t (id INTEGER, text VARCHAR(16)) []. took: 121.238µs\n> INSERT into t (text) VALUES(?), (?) [\"foo\" \"bar\"]. took: 36.364µs\n> SELECT id, text FROM t []. took: 4.653µs\n*/\n```\n\n# Benchmarks\n```\n go test -bench=. -benchmem\n goos: linux\n goarch: amd64\n pkg: github.com/qustavo/sqlhooks/v2\n cpu: Intel(R) Xeon(R) W-10885M CPU @ 2.40GHz\n BenchmarkSQLite3/Without_Hooks-16                 191196              6163 ns/op             456 B/op         14 allocs/op\n BenchmarkSQLite3/With_Hooks-16                    189997              6329 ns/op             456 B/op         14 allocs/op\n BenchmarkMySQL/Without_Hooks-16                    13278             83462 ns/op             309 B/op          7 allocs/op\n BenchmarkMySQL/With_Hooks-16                       13460             87331 ns/op             309 B/op          7 allocs/op\n BenchmarkPostgres/Without_Hooks-16                 13016             91421 ns/op             401 B/op         10 allocs/op\n BenchmarkPostgres/With_Hooks-16                    12339             94033 ns/op             401 B/op         10 allocs/op\n PASS\n ok      github.com/qustavo/sqlhooks/v2  10.294s\n```\n"
  },
  {
    "path": "benchmark_test.go",
    "content": "package sqlhooks\n\nimport (\n\t\"database/sql\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/go-sql-driver/mysql\"\n\t\"github.com/lib/pq\"\n\t\"github.com/mattn/go-sqlite3\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc init() {\n\thooks := &testHooks{}\n\thooks.reset()\n\n\tsql.Register(\"sqlite3-benchmark\", Wrap(&sqlite3.SQLiteDriver{}, hooks))\n\tsql.Register(\"mysql-benchmark\", Wrap(&mysql.MySQLDriver{}, hooks))\n\tsql.Register(\"postgres-benchmark\", Wrap(&pq.Driver{}, hooks))\n}\n\nfunc benchmark(b *testing.B, driver, dsn string) {\n\tdb, err := sql.Open(driver, dsn)\n\trequire.NoError(b, err)\n\tdefer db.Close()\n\n\tvar query = \"SELECT 'hello'\"\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\trows, err := db.Query(query)\n\t\trequire.NoError(b, err)\n\t\trequire.NoError(b, rows.Close())\n\t}\n}\n\nfunc BenchmarkSQLite3(b *testing.B) {\n\tb.Run(\"Without Hooks\", func(b *testing.B) {\n\t\tbenchmark(b, \"sqlite3\", \":memory:\")\n\t})\n\n\tb.Run(\"With Hooks\", func(b *testing.B) {\n\t\tbenchmark(b, \"sqlite3-benchmark\", \":memory:\")\n\t})\n}\n\nfunc BenchmarkMySQL(b *testing.B) {\n\tdsn := os.Getenv(\"SQLHOOKS_MYSQL_DSN\")\n\tif dsn == \"\" {\n\t\tb.Skipf(\"SQLHOOKS_MYSQL_DSN not set\")\n\t}\n\n\tb.Run(\"Without Hooks\", func(b *testing.B) {\n\t\tbenchmark(b, \"mysql\", dsn)\n\t})\n\n\tb.Run(\"With Hooks\", func(b *testing.B) {\n\t\tbenchmark(b, \"mysql-benchmark\", dsn)\n\t})\n}\n\nfunc BenchmarkPostgres(b *testing.B) {\n\tdsn := os.Getenv(\"SQLHOOKS_POSTGRES_DSN\")\n\tif dsn == \"\" {\n\t\tb.Skipf(\"SQLHOOKS_POSTGRES_DSN not set\")\n\t}\n\n\tb.Run(\"Without Hooks\", func(b *testing.B) {\n\t\tbenchmark(b, \"postgres\", dsn)\n\t})\n\n\tb.Run(\"With Hooks\", func(b *testing.B) {\n\t\tbenchmark(b, \"postgres-benchmark\", dsn)\n\t})\n}\n"
  },
  {
    "path": "compose.go",
    "content": "package sqlhooks\n\nimport (\n\t\"context\"\n\t\"fmt\"\n)\n\n// Compose allows for composing multiple Hooks into one.\n// It runs every callback on every hook in argument order,\n// even if previous hooks return an error.\n// If multiple hooks return errors, the error return value will be\n// MultipleErrors, which allows for introspecting the errors if necessary.\nfunc Compose(hooks ...Hooks) Hooks {\n\treturn composed(hooks)\n}\n\ntype composed []Hooks\n\nfunc (c composed) Before(ctx context.Context, query string, args ...interface{}) (context.Context, error) {\n\tvar errors []error\n\tfor _, hook := range c {\n\t\tc, err := hook.Before(ctx, query, args...)\n\t\tif err != nil {\n\t\t\terrors = append(errors, err)\n\t\t}\n\t\tif c != nil {\n\t\t\tctx = c\n\t\t}\n\t}\n\treturn ctx, wrapErrors(nil, errors)\n}\n\nfunc (c composed) After(ctx context.Context, query string, args ...interface{}) (context.Context, error) {\n\tvar errors []error\n\tfor _, hook := range c {\n\t\tvar err error\n\t\tc, err := hook.After(ctx, query, args...)\n\t\tif err != nil {\n\t\t\terrors = append(errors, err)\n\t\t}\n\t\tif c != nil {\n\t\t\tctx = c\n\t\t}\n\t}\n\treturn ctx, wrapErrors(nil, errors)\n}\n\nfunc (c composed) OnError(ctx context.Context, cause error, query string, args ...interface{}) error {\n\tvar errors []error\n\tfor _, hook := range c {\n\t\tif onErrorer, ok := hook.(OnErrorer); ok {\n\t\t\tif err := onErrorer.OnError(ctx, cause, query, args...); err != nil && err != cause {\n\t\t\t\terrors = append(errors, err)\n\t\t\t}\n\t\t}\n\t}\n\treturn wrapErrors(cause, errors)\n}\n\nfunc wrapErrors(def error, errors []error) error {\n\tswitch len(errors) {\n\tcase 0:\n\t\treturn def\n\tcase 1:\n\t\treturn errors[0]\n\tdefault:\n\t\treturn MultipleErrors(errors)\n\t}\n}\n\n// MultipleErrors is an error that contains multiple errors.\ntype MultipleErrors []error\n\nfunc (m MultipleErrors) Error() string {\n\treturn fmt.Sprint(\"multiple errors:\", []error(m))\n}\n"
  },
  {
    "path": "compose_test.go",
    "content": "package sqlhooks\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nvar (\n\toops     = errors.New(\"oops\")\n\toopsHook = &testHooks{\n\t\tbefore: func(ctx context.Context, query string, args ...interface{}) (context.Context, error) {\n\t\t\treturn ctx, oops\n\t\t},\n\t\tafter: func(ctx context.Context, query string, args ...interface{}) (context.Context, error) {\n\t\t\treturn ctx, oops\n\t\t},\n\t\tonError: func(ctx context.Context, err error, query string, args ...interface{}) error {\n\t\t\treturn oops\n\t\t},\n\t}\n\tokHook = &testHooks{\n\t\tbefore: func(ctx context.Context, query string, args ...interface{}) (context.Context, error) {\n\t\t\treturn ctx, nil\n\t\t},\n\t\tafter: func(ctx context.Context, query string, args ...interface{}) (context.Context, error) {\n\t\t\treturn ctx, nil\n\t\t},\n\t\tonError: func(ctx context.Context, err error, query string, args ...interface{}) error {\n\t\t\treturn nil\n\t\t},\n\t}\n)\n\nfunc TestCompose(t *testing.T) {\n\tfor _, it := range []struct {\n\t\tname  string\n\t\thooks Hooks\n\t\twant  error\n\t}{\n\t\t{\"happy case\", Compose(okHook, okHook), nil},\n\t\t{\"no hooks\", Compose(), nil},\n\t\t{\"multiple errors\", Compose(oopsHook, okHook, oopsHook), MultipleErrors([]error{oops, oops})},\n\t\t{\"single error\", Compose(okHook, oopsHook, okHook), oops},\n\t} {\n\t\tt.Run(it.name, func(t *testing.T) {\n\t\t\tt.Run(\"Before\", func(t *testing.T) {\n\t\t\t\t_, got := it.hooks.Before(context.Background(), \"query\")\n\t\t\t\tif !reflect.DeepEqual(it.want, got) {\n\t\t\t\t\tt.Errorf(\"unexpected error. want: %q, got: %q\", it.want, got)\n\t\t\t\t}\n\t\t\t})\n\t\t\tt.Run(\"After\", func(t *testing.T) {\n\t\t\t\t_, got := it.hooks.After(context.Background(), \"query\")\n\t\t\t\tif !reflect.DeepEqual(it.want, got) {\n\t\t\t\t\tt.Errorf(\"unexpected error. want: %q, got: %q\", it.want, got)\n\t\t\t\t}\n\t\t\t})\n\t\t\tt.Run(\"OnError\", func(t *testing.T) {\n\t\t\t\tcause := errors.New(\"crikey\")\n\t\t\t\twant := it.want\n\t\t\t\tif want == nil {\n\t\t\t\t\twant = cause\n\t\t\t\t}\n\t\t\t\tgot := it.hooks.(OnErrorer).OnError(context.Background(), cause, \"query\")\n\t\t\t\tif !reflect.DeepEqual(want, got) {\n\t\t\t\t\tt.Errorf(\"unexpected error. want: %q, got: %q\", want, got)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestWrapErrors(t *testing.T) {\n\tvar (\n\t\terr1 = errors.New(\"oops\")\n\t\terr2 = errors.New(\"oops2\")\n\t)\n\tfor _, it := range []struct {\n\t\tname   string\n\t\tdef    error\n\t\terrors []error\n\t\twant   error\n\t}{\n\t\t{\"no errors\", err1, nil, err1},\n\t\t{\"single error\", nil, []error{err1}, err1},\n\t\t{\"multiple errors\", nil, []error{err1, err2}, MultipleErrors([]error{err1, err2})},\n\t} {\n\t\tt.Run(it.name, func(t *testing.T) {\n\t\t\tif want, got := it.want, wrapErrors(it.def, it.errors); !reflect.DeepEqual(want, got) {\n\t\t\t\tt.Errorf(\"unexpected wrapping. want: %q, got %q\", want, got)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "doc.go",
    "content": "// package sqlhooks allows you to attach hooks to any database/sql driver.\n// The purpose of sqlhooks is to provide a way to instrument your sql statements, making really easy to log queries or measure execution time without modifying your actual code.\n\n// This example shows how to instrument sql queries in order to display the time that they consume\n// package main\n//\n// import (\n// \t\"context\"\n// \t\"database/sql\"\n// \t\"fmt\"\n// \t\"time\"\n//\n// \t\"github.com/qustavo/sqlhooks/v2\"\n// \t\"github.com/mattn/go-sqlite3\"\n// )\n//\n// // Hooks satisfies the sqlhook.Hooks interface\n// type Hooks struct {}\n//\n// // Before hook will print the query with it's args and return the context with the timestamp\n// func (h *Hooks) Before(ctx context.Context, query string, args ...interface{}) (context.Context, error) {\n// \tfmt.Printf(\"> %s %q\", query, args)\n// \treturn context.WithValue(ctx, \"begin\", time.Now()), nil\n// }\n//\n// // After hook will get the timestamp registered on the Before hook and print the elapsed time\n// func (h *Hooks) After(ctx context.Context, query string, args ...interface{}) (context.Context, error) {\n// \tbegin := ctx.Value(\"begin\").(time.Time)\n// \tfmt.Printf(\". took: %s\\n\", time.Since(begin))\n// \treturn ctx, nil\n// }\n//\n// func main() {\n// \t// First, register the wrapper\n// \tsql.Register(\"sqlite3WithHooks\", sqlhooks.Wrap(&sqlite3.SQLiteDriver{}, &Hooks{}))\n//\n// \t// Connect to the registered wrapped driver\n// \tdb, _ := sql.Open(\"sqlite3WithHooks\", \":memory:\")\n//\n// \t// Do you're stuff\n// \tdb.Exec(\"CREATE TABLE t (id INTEGER, text VARCHAR(16))\")\n// \tdb.Exec(\"INSERT into t (text) VALUES(?), (?)\", \"foo\", \"bar\")\n// \tdb.Query(\"SELECT id, text FROM t\")\n// }\n//\n// /*\n// Output should look like:\n// > CREATE TABLE t (id INTEGER, text VARCHAR(16)) []. took: 121.238µs\n// > INSERT into t (text) VALUES(?), (?) [\"foo\" \"bar\"]. took: 36.364µs\n// > SELECT id, text FROM t []. took: 4.653µs\n// */\npackage sqlhooks\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/qustavo/sqlhooks/v2\n\ngo 1.13\n\nrequire (\n\tgithub.com/go-sql-driver/mysql v1.4.1\n\tgithub.com/lib/pq v1.2.0\n\tgithub.com/mattn/go-sqlite3 v1.10.0\n\tgithub.com/opentracing/opentracing-go v1.1.0\n\tgithub.com/stretchr/testify v1.4.0\n\tgolang.org/x/tools v0.1.7 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=\ngithub.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=\ngithub.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=\ngithub.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=\ngithub.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=\ngithub.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=\ngithub.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=\ngithub.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=\ngithub.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=\ngithub.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA=\ngolang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ=\ngolang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\n"
  },
  {
    "path": "hooks/loghooks/example_test.go",
    "content": "package loghooks\n\nimport (\n\t\"database/sql\"\n\n\t\"github.com/qustavo/sqlhooks/v2\"\n\tsqlite3 \"github.com/mattn/go-sqlite3\"\n)\n\nfunc Example() {\n\tdriver := sqlhooks.Wrap(&sqlite3.SQLiteDriver{}, New())\n\tsql.Register(\"sqlite3-logger\", driver)\n\tdb, _ := sql.Open(\"sqlite3-logger\", \":memory:\")\n\n\t// This query will output logs\n\tdb.Query(\"SELECT 1+1\")\n}\n"
  },
  {
    "path": "hooks/loghooks/examples/main.go",
    "content": "package main\n\nimport (\n\t\"database/sql\"\n\t\"log\"\n\n\t\"github.com/qustavo/sqlhooks/v2\"\n\t\"github.com/qustavo/sqlhooks/v2/hooks/loghooks\"\n\t\"github.com/mattn/go-sqlite3\"\n)\n\nfunc main() {\n\tsql.Register(\"sqlite3log\", sqlhooks.Wrap(&sqlite3.SQLiteDriver{}, loghooks.New()))\n\tdb, err := sql.Open(\"sqlite3log\", \":memory:\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tif _, err := db.Exec(\"CREATE TABLE users(ID int, name text)\"); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tif _, err := db.Exec(`INSERT INTO users (id, name) VALUES(?, ?)`, 1, \"gus\"); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tif _, err := db.Query(`SELECT id, name FROM users`); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n}\n"
  },
  {
    "path": "hooks/loghooks/loghooks.go",
    "content": "package loghooks\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"os\"\n\t\"time\"\n)\n\nvar started int\n\ntype logger interface {\n\tPrintf(string, ...interface{})\n}\n\ntype Hook struct {\n\tlog logger\n}\n\nfunc New() *Hook {\n\treturn &Hook{\n\t\tlog: log.New(os.Stderr, \"\", log.LstdFlags),\n\t}\n}\nfunc (h *Hook) Before(ctx context.Context, query string, args ...interface{}) (context.Context, error) {\n\treturn context.WithValue(ctx, &started, time.Now()), nil\n}\n\nfunc (h *Hook) After(ctx context.Context, query string, args ...interface{}) (context.Context, error) {\n\th.log.Printf(\"Query: `%s`, Args: `%q`. took: %s\", query, args, time.Since(ctx.Value(&started).(time.Time)))\n\treturn ctx, nil\n}\n\nfunc (h *Hook) OnError(ctx context.Context, err error, query string, args ...interface{}) error {\n\th.log.Printf(\"Error: %v, Query: `%s`, Args: `%q`, Took: %s\",\n\t\terr, query, args, time.Since(ctx.Value(&started).(time.Time)))\n\treturn err\n}\n"
  },
  {
    "path": "hooks/othooks/examples/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"log\"\n\n\t\"github.com/qustavo/sqlhooks/v2\"\n\t\"github.com/qustavo/sqlhooks/v2/hooks/othooks\"\n\t\"github.com/mattn/go-sqlite3\"\n\t\"github.com/opentracing/opentracing-go\"\n)\n\nfunc main() {\n\ttracer := opentracing.GlobalTracer()\n\thooks := othooks.New(tracer)\n\tsql.Register(\"sqlite3ot\", sqlhooks.Wrap(&sqlite3.SQLiteDriver{}, hooks))\n\tdb, err := sql.Open(\"sqlite3ot\", \":memory:\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tspan := tracer.StartSpan(\"sql\")\n\tdefer span.Finish()\n\tctx := opentracing.ContextWithSpan(context.Background(), span)\n\n\tif _, err := db.ExecContext(ctx, \"CREATE TABLE users(ID int, name text)\"); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tif _, err := db.ExecContext(ctx, `INSERT INTO users (id, name) VALUES(?, ?)`, 1, \"gus\"); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tif _, err := db.QueryContext(ctx, `SELECT id, name FROM users`); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n}\n"
  },
  {
    "path": "hooks/othooks/othooks.go",
    "content": "package othooks\n\nimport (\n\t\"context\"\n\n\t\"github.com/opentracing/opentracing-go\"\n\t\"github.com/opentracing/opentracing-go/log\"\n)\n\ntype Hook struct {\n\ttracer opentracing.Tracer\n}\n\nfunc New(tracer opentracing.Tracer) *Hook {\n\treturn &Hook{tracer: tracer}\n}\n\nfunc (h *Hook) Before(ctx context.Context, query string, args ...interface{}) (context.Context, error) {\n\tparent := opentracing.SpanFromContext(ctx)\n\tif parent == nil {\n\t\treturn ctx, nil\n\t}\n\n\tspan := h.tracer.StartSpan(\"sql\", opentracing.ChildOf(parent.Context()))\n\tspan.LogFields(\n\t\tlog.String(\"query\", query),\n\t\tlog.Object(\"args\", args),\n\t)\n\n\treturn opentracing.ContextWithSpan(ctx, span), nil\n}\n\nfunc (h *Hook) After(ctx context.Context, query string, args ...interface{}) (context.Context, error) {\n\tspan := opentracing.SpanFromContext(ctx)\n\tif span != nil {\n\t\tdefer span.Finish()\n\t}\n\n\treturn ctx, nil\n}\n\nfunc (h *Hook) OnError(ctx context.Context, err error, query string, args ...interface{}) error {\n\tspan := opentracing.SpanFromContext(ctx)\n\tif span != nil {\n\t\tdefer span.Finish()\n\t\tspan.SetTag(\"error\", true)\n\t\tspan.LogFields(\n\t\t\tlog.Error(err),\n\t\t)\n\t}\n\n\treturn err\n}\n"
  },
  {
    "path": "hooks/othooks/othooks_test.go",
    "content": "package othooks\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"testing\"\n\n\t\"github.com/qustavo/sqlhooks/v2\"\n\tsqlite3 \"github.com/mattn/go-sqlite3\"\n\topentracing \"github.com/opentracing/opentracing-go\"\n\t\"github.com/opentracing/opentracing-go/mocktracer\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar (\n\ttracer *mocktracer.MockTracer\n)\n\nfunc init() {\n\ttracer = mocktracer.New()\n\tdriver := sqlhooks.Wrap(&sqlite3.SQLiteDriver{}, New(tracer))\n\tsql.Register(\"ot\", driver)\n}\n\nfunc TestSpansAreRecorded(t *testing.T) {\n\tdb, err := sql.Open(\"ot\", \":memory:\")\n\trequire.NoError(t, err)\n\tdefer db.Close()\n\ttracer.Reset()\n\n\tparent := tracer.StartSpan(\"parent\")\n\tctx := opentracing.ContextWithSpan(context.Background(), parent)\n\n\t{\n\t\trows, err := db.QueryContext(ctx, \"SELECT 1+?\", \"1\")\n\t\trequire.NoError(t, err)\n\t\trows.Close()\n\t}\n\n\t{\n\t\trows, err := db.QueryContext(ctx, \"SELECT 1+?\", \"1\")\n\t\trequire.NoError(t, err)\n\t\trows.Close()\n\t}\n\n\tparent.Finish()\n\n\tspans := tracer.FinishedSpans()\n\trequire.Len(t, spans, 3)\n\n\tspan := spans[1]\n\tassert.Equal(t, \"sql\", span.OperationName)\n\n\tlogFields := span.Logs()[0].Fields\n\tassert.Equal(t, \"query\", logFields[0].Key)\n\tassert.Equal(t, \"SELECT 1+?\", logFields[0].ValueString)\n\tassert.Equal(t, \"args\", logFields[1].Key)\n\tassert.Equal(t, \"[1]\", logFields[1].ValueString)\n\tassert.NotEmpty(t, span.FinishTime)\n}\n\nfunc TestNoSpansAreRecorded(t *testing.T) {\n\tdb, err := sql.Open(\"ot\", \":memory:\")\n\trequire.NoError(t, err)\n\tdefer db.Close()\n\ttracer.Reset()\n\n\trows, err := db.QueryContext(context.Background(), \"SELECT 1\")\n\trequire.NoError(t, err)\n\trows.Close()\n\n\tassert.Empty(t, tracer.FinishedSpans())\n}\n"
  },
  {
    "path": "sqlhooks.go",
    "content": "package sqlhooks\n\nimport (\n\t\"context\"\n\t\"database/sql/driver\"\n\t\"errors\"\n)\n\n// Hook is the hook callback signature\ntype Hook func(ctx context.Context, query string, args ...interface{}) (context.Context, error)\n\n// ErrorHook is the error handling callback signature\ntype ErrorHook func(ctx context.Context, err error, query string, args ...interface{}) error\n\n// Hooks instances may be passed to Wrap() to define an instrumented driver\ntype Hooks interface {\n\tBefore(ctx context.Context, query string, args ...interface{}) (context.Context, error)\n\tAfter(ctx context.Context, query string, args ...interface{}) (context.Context, error)\n}\n\n// OnErrorer instances will be called if any error happens\ntype OnErrorer interface {\n\tOnError(ctx context.Context, err error, query string, args ...interface{}) error\n}\n\nfunc handlerErr(ctx context.Context, hooks Hooks, err error, query string, args ...interface{}) error {\n\th, ok := hooks.(OnErrorer)\n\tif !ok {\n\t\treturn err\n\t}\n\n\tif err := h.OnError(ctx, err, query, args...); err != nil {\n\t\treturn err\n\t}\n\n\treturn err\n}\n\n// Driver implements a database/sql/driver.Driver\ntype Driver struct {\n\tdriver.Driver\n\thooks Hooks\n}\n\n// Open opens a connection\nfunc (drv *Driver) Open(name string) (driver.Conn, error) {\n\tconn, err := drv.Driver.Open(name)\n\tif err != nil {\n\t\treturn conn, err\n\t}\n\n\t// Drivers that don't implement driver.ConnBeginTx are not supported.\n\tif _, ok := conn.(driver.ConnBeginTx); !ok {\n\t\treturn nil, errors.New(\"driver must implement driver.ConnBeginTx\")\n\t}\n\n\twrapped := &Conn{conn, drv.hooks}\n\tif isExecer(conn) && isQueryer(conn) && isSessionResetter(conn) {\n\t\treturn &ExecerQueryerContextWithSessionResetter{wrapped,\n\t\t\t&ExecerContext{wrapped}, &QueryerContext{wrapped},\n\t\t\t&SessionResetter{wrapped}}, nil\n\t} else if isExecer(conn) && isQueryer(conn) {\n\t\treturn &ExecerQueryerContext{wrapped, &ExecerContext{wrapped},\n\t\t\t&QueryerContext{wrapped}}, nil\n\t} else if isExecer(conn) {\n\t\t// If conn implements an Execer interface, return a driver.Conn which\n\t\t// also implements Execer\n\t\treturn &ExecerContext{wrapped}, nil\n\t} else if isQueryer(conn) {\n\t\t// If conn implements an Queryer interface, return a driver.Conn which\n\t\t// also implements Queryer\n\t\treturn &QueryerContext{wrapped}, nil\n\t}\n\treturn wrapped, nil\n}\n\n// Conn implements a database/sql.driver.Conn\ntype Conn struct {\n\tConn  driver.Conn\n\thooks Hooks\n}\n\nfunc (conn *Conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {\n\tvar (\n\t\tstmt driver.Stmt\n\t\terr  error\n\t)\n\n\tif c, ok := conn.Conn.(driver.ConnPrepareContext); ok {\n\t\tstmt, err = c.PrepareContext(ctx, query)\n\t} else {\n\t\tstmt, err = conn.Prepare(query)\n\t}\n\n\tif err != nil {\n\t\treturn stmt, err\n\t}\n\n\treturn &Stmt{stmt, conn.hooks, query}, nil\n}\n\nfunc (conn *Conn) Prepare(query string) (driver.Stmt, error) { return conn.Conn.Prepare(query) }\nfunc (conn *Conn) Close() error                              { return conn.Conn.Close() }\nfunc (conn *Conn) Begin() (driver.Tx, error)                 { return conn.Conn.Begin() }\nfunc (conn *Conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {\n\treturn conn.Conn.(driver.ConnBeginTx).BeginTx(ctx, opts)\n}\n\n// ExecerContext implements a database/sql.driver.ExecerContext\ntype ExecerContext struct {\n\t*Conn\n}\n\nfunc isExecer(conn driver.Conn) bool {\n\tswitch conn.(type) {\n\tcase driver.ExecerContext:\n\t\treturn true\n\tcase driver.Execer:\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc (conn *ExecerContext) execContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {\n\tswitch c := conn.Conn.Conn.(type) {\n\tcase driver.ExecerContext:\n\t\treturn c.ExecContext(ctx, query, args)\n\tcase driver.Execer:\n\t\tdargs, err := namedValueToValue(args)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn c.Exec(query, dargs)\n\tdefault:\n\t\t// This should not happen\n\t\treturn nil, errors.New(\"ExecerContext created for a non Execer driver.Conn\")\n\t}\n}\n\nfunc (conn *ExecerContext) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {\n\tvar err error\n\n\tlist := namedToInterface(args)\n\n\t// Exec `Before` Hooks\n\tif ctx, err = conn.hooks.Before(ctx, query, list...); err != nil {\n\t\treturn nil, err\n\t}\n\n\tresults, err := conn.execContext(ctx, query, args)\n\tif err != nil {\n\t\treturn results, handlerErr(ctx, conn.hooks, err, query, list...)\n\t}\n\n\tif _, err := conn.hooks.After(ctx, query, list...); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn results, err\n}\n\nfunc (conn *ExecerContext) Exec(query string, args []driver.Value) (driver.Result, error) {\n\t// We have to implement Exec since it is required in the current version of\n\t// Go for it to run ExecContext. From Go 10 it will be optional. However,\n\t// this code should never run since database/sql always prefers to run\n\t// ExecContext.\n\treturn nil, errors.New(\"Exec was called when ExecContext was implemented\")\n}\n\n// QueryerContext implements a database/sql.driver.QueryerContext\ntype QueryerContext struct {\n\t*Conn\n}\n\nfunc isQueryer(conn driver.Conn) bool {\n\tswitch conn.(type) {\n\tcase driver.QueryerContext:\n\t\treturn true\n\tcase driver.Queryer:\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc (conn *QueryerContext) queryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {\n\tswitch c := conn.Conn.Conn.(type) {\n\tcase driver.QueryerContext:\n\t\treturn c.QueryContext(ctx, query, args)\n\tcase driver.Queryer:\n\t\tdargs, err := namedValueToValue(args)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn c.Query(query, dargs)\n\tdefault:\n\t\t// This should not happen\n\t\treturn nil, errors.New(\"QueryerContext created for a non Queryer driver.Conn\")\n\t}\n}\n\nfunc (conn *QueryerContext) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {\n\tvar err error\n\n\tlist := namedToInterface(args)\n\n\t// Query `Before` Hooks\n\tif ctx, err = conn.hooks.Before(ctx, query, list...); err != nil {\n\t\treturn nil, err\n\t}\n\n\tresults, err := conn.queryContext(ctx, query, args)\n\tif err != nil {\n\t\treturn results, handlerErr(ctx, conn.hooks, err, query, list...)\n\t}\n\n\tif _, err := conn.hooks.After(ctx, query, list...); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn results, err\n}\n\n// ExecerQueryerContext implements database/sql.driver.ExecerContext and\n// database/sql.driver.QueryerContext\ntype ExecerQueryerContext struct {\n\t*Conn\n\t*ExecerContext\n\t*QueryerContext\n}\n\n// ExecerQueryerContext implements database/sql.driver.ExecerContext and\n// database/sql.driver.QueryerContext\ntype ExecerQueryerContextWithSessionResetter struct {\n\t*Conn\n\t*ExecerContext\n\t*QueryerContext\n\t*SessionResetter\n}\n\ntype SessionResetter struct {\n\t*Conn\n}\n\n// Stmt implements a database/sql/driver.Stmt\ntype Stmt struct {\n\tStmt  driver.Stmt\n\thooks Hooks\n\tquery string\n}\n\nfunc (stmt *Stmt) execContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {\n\tif s, ok := stmt.Stmt.(driver.StmtExecContext); ok {\n\t\treturn s.ExecContext(ctx, args)\n\t}\n\n\tvalues := make([]driver.Value, len(args))\n\tfor _, arg := range args {\n\t\tvalues[arg.Ordinal-1] = arg.Value\n\t}\n\n\treturn stmt.Exec(values)\n}\n\nfunc (stmt *Stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {\n\tvar err error\n\n\tlist := namedToInterface(args)\n\n\t// Exec `Before` Hooks\n\tif ctx, err = stmt.hooks.Before(ctx, stmt.query, list...); err != nil {\n\t\treturn nil, err\n\t}\n\n\tresults, err := stmt.execContext(ctx, args)\n\tif err != nil {\n\t\treturn results, handlerErr(ctx, stmt.hooks, err, stmt.query, list...)\n\t}\n\n\tif _, err := stmt.hooks.After(ctx, stmt.query, list...); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn results, err\n}\n\nfunc (stmt *Stmt) queryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {\n\tif s, ok := stmt.Stmt.(driver.StmtQueryContext); ok {\n\t\treturn s.QueryContext(ctx, args)\n\t}\n\n\tvalues := make([]driver.Value, len(args))\n\tfor _, arg := range args {\n\t\tvalues[arg.Ordinal-1] = arg.Value\n\t}\n\treturn stmt.Query(values)\n}\n\nfunc (stmt *Stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {\n\tvar err error\n\n\tlist := namedToInterface(args)\n\n\t// Exec Before Hooks\n\tif ctx, err = stmt.hooks.Before(ctx, stmt.query, list...); err != nil {\n\t\treturn nil, err\n\t}\n\n\trows, err := stmt.queryContext(ctx, args)\n\tif err != nil {\n\t\treturn rows, handlerErr(ctx, stmt.hooks, err, stmt.query, list...)\n\t}\n\n\tif _, err := stmt.hooks.After(ctx, stmt.query, list...); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn rows, err\n}\n\nfunc (stmt *Stmt) Close() error                                    { return stmt.Stmt.Close() }\nfunc (stmt *Stmt) NumInput() int                                   { return stmt.Stmt.NumInput() }\nfunc (stmt *Stmt) Exec(args []driver.Value) (driver.Result, error) { return stmt.Stmt.Exec(args) }\nfunc (stmt *Stmt) Query(args []driver.Value) (driver.Rows, error)  { return stmt.Stmt.Query(args) }\n\n// Wrap is used to create a new instrumented driver, it takes a vendor specific driver, and a Hooks instance to produce a new driver instance.\n// It's usually used inside a sql.Register() statement\nfunc Wrap(driver driver.Driver, hooks Hooks) driver.Driver {\n\treturn &Driver{driver, hooks}\n}\n\nfunc namedToInterface(args []driver.NamedValue) []interface{} {\n\tlist := make([]interface{}, len(args))\n\tfor i, a := range args {\n\t\tlist[i] = a.Value\n\t}\n\treturn list\n}\n\n// namedValueToValue copied from database/sql\nfunc namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) {\n\tdargs := make([]driver.Value, len(named))\n\tfor n, param := range named {\n\t\tif len(param.Name) > 0 {\n\t\t\treturn nil, errors.New(\"sql: driver does not support the use of Named Parameters\")\n\t\t}\n\t\tdargs[n] = param.Value\n\t}\n\treturn dargs, nil\n}\n\n/*\ntype hooks struct {\n}\n\nfunc (h *hooks) Before(ctx context.Context, query string, args ...interface{}) error {\n\tlog.Printf(\"before> ctx = %+v, q=%s, args = %+v\\n\", ctx, query, args)\n\treturn nil\n}\n\nfunc (h *hooks) After(ctx context.Context, query string, args ...interface{}) error {\n\tlog.Printf(\"after>  ctx = %+v, q=%s, args = %+v\\n\", ctx, query, args)\n\treturn nil\n}\n\nfunc main() {\n\tsql.Register(\"sqlite3-proxy\", Wrap(&sqlite3.SQLiteDriver{}, &hooks{}))\n\tdb, err := sql.Open(\"sqlite3-proxy\", \":memory:\")\n\tif err != nil {\n\t\tlog.Fatalln(err)\n\t}\n\n\tif _, ok := driver.Stmt(&Stmt{}).(driver.StmtExecContext); !ok {\n\t\tpanic(\"NOPE\")\n\t}\n\n\tif _, err := db.Exec(\"CREATE table users(id int)\"); err != nil {\n\t\tlog.Printf(\"|err| = %+v\\n\", err)\n\t}\n\n\tif _, err := db.QueryContext(context.Background(), \"SELECT * FROM users WHERE id = ?\", 1); err != nil {\n\t\tlog.Printf(\"err = %+v\\n\", err)\n\t}\n\n}\n*/\n"
  },
  {
    "path": "sqlhooks_1_10.go",
    "content": "// +build go1.10\n\npackage sqlhooks\n\nimport (\n\t\"context\"\n\t\"database/sql/driver\"\n)\n\nfunc isSessionResetter(conn driver.Conn) bool {\n\t_, ok := conn.(driver.SessionResetter)\n\treturn ok\n}\n\nfunc (s *SessionResetter) ResetSession(ctx context.Context) error {\n\tc := s.Conn.Conn.(driver.SessionResetter)\n\treturn c.ResetSession(ctx)\n}\n"
  },
  {
    "path": "sqlhooks_1_10_interface_test.go",
    "content": "// +build go1.10\n\npackage sqlhooks\n\nimport \"database/sql/driver\"\n\nfunc init() {\n\tinterfaceTestCases = append(interfaceTestCases,\n\t\tstruct {\n\t\t\tname               string\n\t\t\texpectedInterfaces []interface{}\n\t\t}{\n\t\t\t\"ExecerQueryerContextSessionResetter\", []interface{}{\n\t\t\t\t(*driver.ExecerContext)(nil),\n\t\t\t\t(*driver.QueryerContext)(nil),\n\t\t\t\t(*driver.SessionResetter)(nil)}})\n}\n"
  },
  {
    "path": "sqlhooks_interface_test.go",
    "content": "package sqlhooks\n\nimport (\n\t\"context\"\n\t\"database/sql/driver\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar interfaceTestCases = []struct {\n\tname               string\n\texpectedInterfaces []interface{}\n}{\n\t{\"Basic\", []interface{}{(*driver.Conn)(nil)}},\n\t{\"Execer\", []interface{}{(*driver.Execer)(nil)}},\n\t{\"ExecerContext\", []interface{}{(*driver.ExecerContext)(nil)}},\n\t{\"Queryer\", []interface{}{(*driver.QueryerContext)(nil)}},\n\t{\"QueryerContext\", []interface{}{(*driver.QueryerContext)(nil)}},\n\t{\"ExecerQueryerContext\", []interface{}{\n\t\t(*driver.ExecerContext)(nil),\n\t\t(*driver.QueryerContext)(nil)}},\n}\n\ntype fakeDriver struct{}\n\nfunc (d *fakeDriver) Open(dsn string) (driver.Conn, error) {\n\tswitch dsn {\n\tcase \"Basic\":\n\t\treturn &struct{ *FakeConnBasic }{}, nil\n\tcase \"Execer\":\n\t\treturn &struct {\n\t\t\t*FakeConnBasic\n\t\t\t*FakeConnExecer\n\t\t}{}, nil\n\tcase \"ExecerContext\":\n\t\treturn &struct {\n\t\t\t*FakeConnBasic\n\t\t\t*FakeConnExecerContext\n\t\t}{}, nil\n\tcase \"Queryer\":\n\t\treturn &struct {\n\t\t\t*FakeConnBasic\n\t\t\t*FakeConnQueryer\n\t\t}{}, nil\n\tcase \"QueryerContext\":\n\t\treturn &struct {\n\t\t\t*FakeConnBasic\n\t\t\t*FakeConnQueryerContext\n\t\t}{}, nil\n\tcase \"ExecerQueryerContext\":\n\t\treturn &struct {\n\t\t\t*FakeConnBasic\n\t\t\t*FakeConnExecerContext\n\t\t\t*FakeConnQueryerContext\n\t\t}{}, nil\n\tcase \"ExecerQueryerContextSessionResetter\":\n\t\treturn &struct {\n\t\t\t*FakeConnBasic\n\t\t\t*FakeConnExecer\n\t\t\t*FakeConnQueryer\n\t\t\t*FakeConnSessionResetter\n\t\t}{}, nil\n\tcase \"NonConnBeginTx\":\n\t\treturn &FakeConnUnsupported{}, nil\n\t}\n\n\treturn nil, errors.New(\"Fake driver not implemented\")\n}\n\n// Conn implements a database/sql.driver.Conn\ntype FakeConnBasic struct{}\n\nfunc (*FakeConnBasic) Prepare(query string) (driver.Stmt, error) {\n\treturn nil, errors.New(\"Not implemented\")\n}\nfunc (*FakeConnBasic) Close() error {\n\treturn errors.New(\"Not implemented\")\n}\nfunc (*FakeConnBasic) Begin() (driver.Tx, error) {\n\treturn nil, errors.New(\"Not implemented\")\n}\nfunc (*FakeConnBasic) BeginTx(context.Context, driver.TxOptions) (driver.Tx, error) {\n\treturn nil, errors.New(\"Not implemented\")\n}\n\ntype FakeConnExecer struct{}\n\nfunc (*FakeConnExecer) Exec(query string, args []driver.Value) (driver.Result, error) {\n\treturn nil, errors.New(\"Not implemented\")\n}\n\ntype FakeConnExecerContext struct{}\n\nfunc (*FakeConnExecerContext) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {\n\treturn nil, errors.New(\"Not implemented\")\n}\n\ntype FakeConnQueryer struct{}\n\nfunc (*FakeConnQueryer) Query(query string, args []driver.Value) (driver.Rows, error) {\n\treturn nil, errors.New(\"Not implemented\")\n}\n\ntype FakeConnQueryerContext struct{}\n\nfunc (*FakeConnQueryerContext) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {\n\treturn nil, errors.New(\"Not implemented\")\n}\n\ntype FakeConnSessionResetter struct{}\n\nfunc (*FakeConnSessionResetter) ResetSession(ctx context.Context) error {\n\treturn errors.New(\"Not implemented\")\n}\n\n// FakeConnUnsupported implements a database/sql.driver.Conn but doesn't implement\n// driver.ConnBeginTx.\ntype FakeConnUnsupported struct{}\n\nfunc (*FakeConnUnsupported) Prepare(query string) (driver.Stmt, error) {\n\treturn nil, errors.New(\"Not implemented\")\n}\nfunc (*FakeConnUnsupported) Close() error {\n\treturn errors.New(\"Not implemented\")\n}\nfunc (*FakeConnUnsupported) Begin() (driver.Tx, error) {\n\treturn nil, errors.New(\"Not implemented\")\n}\n\nfunc TestInterfaces(t *testing.T) {\n\tdrv := Wrap(&fakeDriver{}, &testHooks{})\n\n\tfor _, c := range interfaceTestCases {\n\t\tconn, err := drv.Open(c.name)\n\t\trequire.NoErrorf(t, err, \"Driver name %s\", c.name)\n\n\t\tfor _, i := range c.expectedInterfaces {\n\t\t\tassert.Implements(t, i, conn)\n\t\t}\n\t}\n}\n\nfunc TestUnsupportedDrivers(t *testing.T) {\n\tdrv := Wrap(&fakeDriver{}, &testHooks{})\n\t_, err := drv.Open(\"NonConnBeginTx\")\n\trequire.EqualError(t, err, \"driver must implement driver.ConnBeginTx\")\n}\n"
  },
  {
    "path": "sqlhooks_mysql_test.go",
    "content": "package sqlhooks\n\nimport (\n\t\"database/sql\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/go-sql-driver/mysql\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc setUpMySQL(t *testing.T, dsn string) {\n\tdb, err := sql.Open(\"mysql\", dsn)\n\trequire.NoError(t, err)\n\trequire.NoError(t, db.Ping())\n\tdefer db.Close()\n\n\t_, err = db.Exec(\"CREATE table IF NOT EXISTS users(id int, name text)\")\n\trequire.NoError(t, err)\n}\n\nfunc TestMySQL(t *testing.T) {\n\tdsn := os.Getenv(\"SQLHOOKS_MYSQL_DSN\")\n\tif dsn == \"\" {\n\t\tt.Skipf(\"SQLHOOKS_MYSQL_DSN not set\")\n\t}\n\n\tsetUpMySQL(t, dsn)\n\n\ts := newSuite(t, &mysql.MySQLDriver{}, dsn)\n\n\ts.TestHooksExecution(t, \"SELECT * FROM users WHERE id = ?\", 1)\n\ts.TestHooksArguments(t, \"SELECT * FROM users WHERE id = ? AND name = ?\", int64(1), \"Gus\")\n\ts.TestHooksErrors(t, \"SELECT 1+1\")\n\ts.TestErrHookHook(t, \"SELECT * FROM users WHERE id = $2\", \"INVALID_ARGS\")\n\n\tt.Run(\"DBWorks\", func(t *testing.T) {\n\t\ts.hooks.reset()\n\t\tif _, err := s.db.Exec(\"DELETE FROM users\"); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tstmt, err := s.db.Prepare(\"INSERT INTO users (id, name) VALUES(?, ?)\")\n\t\trequire.NoError(t, err)\n\t\tfor i := range [5]struct{}{} {\n\t\t\t_, err := stmt.Exec(i, \"gus\")\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\tvar count int\n\t\trequire.NoError(t,\n\t\t\ts.db.QueryRow(\"SELECT COUNT(*) FROM users\").Scan(&count),\n\t\t)\n\t\tassert.Equal(t, 5, count)\n\t})\n}\n"
  },
  {
    "path": "sqlhooks_postgres_test.go",
    "content": "package sqlhooks\n\nimport (\n\t\"database/sql\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/lib/pq\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc setUpPostgres(t *testing.T, dsn string) {\n\tdb, err := sql.Open(\"postgres\", dsn)\n\trequire.NoError(t, err)\n\trequire.NoError(t, db.Ping())\n\tdefer db.Close()\n\n\t_, err = db.Exec(\"CREATE table IF NOT EXISTS users(id int, name text)\")\n\trequire.NoError(t, err)\n}\n\nfunc TestPostgres(t *testing.T) {\n\tdsn := os.Getenv(\"SQLHOOKS_POSTGRES_DSN\")\n\tif dsn == \"\" {\n\t\tt.Skipf(\"SQLHOOKS_POSTGRES_DSN not set\")\n\t}\n\n\tsetUpPostgres(t, dsn)\n\n\ts := newSuite(t, &pq.Driver{}, dsn)\n\n\ts.TestHooksExecution(t, \"SELECT * FROM users WHERE id = $1\", 1)\n\ts.TestHooksArguments(t, \"SELECT * FROM users WHERE id = $1 AND name = $2\", int64(1), \"Gus\")\n\ts.TestHooksErrors(t, \"SELECT 1+1\")\n\ts.TestErrHookHook(t, \"SELECT * FROM users WHERE id = $2\", \"INVALID_ARGS\")\n\n\tt.Run(\"DBWorks\", func(t *testing.T) {\n\t\ts.hooks.reset()\n\t\tif _, err := s.db.Exec(\"DELETE FROM users\"); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tstmt, err := s.db.Prepare(\"INSERT INTO users (id, name) VALUES($1, $2)\")\n\t\trequire.NoError(t, err)\n\t\tfor i := range [5]struct{}{} {\n\t\t\t_, err := stmt.Exec(i, \"gus\")\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\tvar count int\n\t\trequire.NoError(t,\n\t\t\ts.db.QueryRow(\"SELECT COUNT(*) FROM users\").Scan(&count),\n\t\t)\n\t\tassert.Equal(t, 5, count)\n\t})\n}\n"
  },
  {
    "path": "sqlhooks_pre_1_10.go",
    "content": "// +build !go1.10\n\npackage sqlhooks\n\nimport (\n\t\"context\"\n\t\"database/sql/driver\"\n\t\"errors\"\n)\n\nfunc isSessionResetter(conn driver.Conn) bool {\n\treturn false\n}\n\nfunc (s *SessionResetter) ResetSession(ctx context.Context) error {\n\treturn errors.New(\"SessionResetter not implemented\")\n}\n"
  },
  {
    "path": "sqlhooks_sqlite3_test.go",
    "content": "package sqlhooks\n\nimport (\n\t\"database/sql\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/mattn/go-sqlite3\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc setUp(t *testing.T) func() {\n\tdbName := \"sqlite3test.db\"\n\n\tdb, err := sql.Open(\"sqlite3\", dbName)\n\trequire.NoError(t, err)\n\tdefer db.Close()\n\n\t_, err = db.Exec(\"CREATE table users(id int, name text)\")\n\trequire.NoError(t, err)\n\n\treturn func() { os.Remove(dbName) }\n}\n\nfunc TestSQLite3(t *testing.T) {\n\tdefer setUp(t)()\n\ts := newSuite(t, &sqlite3.SQLiteDriver{}, \"sqlite3test.db\")\n\n\ts.TestHooksExecution(t, \"SELECT * FROM users WHERE id = ?\", 1)\n\ts.TestHooksArguments(t, \"SELECT * FROM users WHERE id = ? AND name = ?\", int64(1), \"Gus\")\n\ts.TestHooksErrors(t, \"SELECT 1+1\")\n\ts.TestErrHookHook(t, \"SELECT * FROM users WHERE id = $2\", \"INVALID_ARGS\")\n\n\tt.Run(\"DBWorks\", func(t *testing.T) {\n\t\ts.hooks.reset()\n\t\tif _, err := s.db.Exec(\"DELETE FROM users\"); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tstmt, err := s.db.Prepare(\"INSERT INTO users (id, name) VALUES(?, ?)\")\n\t\trequire.NoError(t, err)\n\t\tfor range [5]struct{}{} {\n\t\t\t_, err := stmt.Exec(time.Now().UnixNano(), \"gus\")\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\tvar count int\n\t\trequire.NoError(t,\n\t\t\ts.db.QueryRow(\"SELECT COUNT(*) FROM users\").Scan(&count),\n\t\t)\n\t\tassert.Equal(t, 5, count)\n\t})\n}\n"
  },
  {
    "path": "sqlhooks_test.go",
    "content": "package sqlhooks\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"database/sql/driver\"\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype testHooks struct {\n\tbefore  Hook\n\tafter   Hook\n\tonError ErrorHook\n}\n\nfunc newTestHooks() *testHooks {\n\tth := &testHooks{}\n\tth.reset()\n\treturn th\n}\n\nfunc (h *testHooks) reset() {\n\tnoop := func(ctx context.Context, _ string, _ ...interface{}) (context.Context, error) {\n\t\treturn ctx, nil\n\t}\n\n\tnoopErr := func(_ context.Context, err error, _ string, _ ...interface{}) error {\n\t\treturn err\n\t}\n\n\th.before, h.after, h.onError = noop, noop, noopErr\n}\n\nfunc (h *testHooks) Before(ctx context.Context, query string, args ...interface{}) (context.Context, error) {\n\treturn h.before(ctx, query, args...)\n}\n\nfunc (h *testHooks) After(ctx context.Context, query string, args ...interface{}) (context.Context, error) {\n\treturn h.after(ctx, query, args...)\n}\n\nfunc (h *testHooks) OnError(ctx context.Context, err error, query string, args ...interface{}) error {\n\treturn h.onError(ctx, err, query, args...)\n}\n\ntype suite struct {\n\tdb    *sql.DB\n\thooks *testHooks\n}\n\nfunc newSuite(t *testing.T, driver driver.Driver, dsn string) *suite {\n\thooks := newTestHooks()\n\n\tdriverName := fmt.Sprintf(\"sqlhooks-%s\", time.Now().String())\n\tsql.Register(driverName, Wrap(driver, hooks))\n\n\tdb, err := sql.Open(driverName, dsn)\n\trequire.NoError(t, err)\n\trequire.NoError(t, db.Ping())\n\n\treturn &suite{db, hooks}\n}\n\nfunc (s *suite) TestHooksExecution(t *testing.T, query string, args ...interface{}) {\n\tvar before, after bool\n\n\ts.hooks.before = func(ctx context.Context, q string, a ...interface{}) (context.Context, error) {\n\t\tbefore = true\n\t\treturn ctx, nil\n\t}\n\ts.hooks.after = func(ctx context.Context, q string, a ...interface{}) (context.Context, error) {\n\t\tafter = true\n\t\treturn ctx, nil\n\t}\n\n\tt.Run(\"Query\", func(t *testing.T) {\n\t\tbefore, after = false, false\n\t\t_, err := s.db.Query(query, args...)\n\t\trequire.NoError(t, err)\n\t\tassert.True(t, before, \"Before Hook did not run for query: \"+query)\n\t\tassert.True(t, after, \"After Hook did not run for query:  \"+query)\n\t})\n\n\tt.Run(\"QueryContext\", func(t *testing.T) {\n\t\tbefore, after = false, false\n\t\t_, err := s.db.QueryContext(context.Background(), query, args...)\n\t\trequire.NoError(t, err)\n\t\tassert.True(t, before, \"Before Hook did not run for query: \"+query)\n\t\tassert.True(t, after, \"After Hook did not run for query:  \"+query)\n\t})\n\n\tt.Run(\"Exec\", func(t *testing.T) {\n\t\tbefore, after = false, false\n\t\t_, err := s.db.Exec(query, args...)\n\t\trequire.NoError(t, err)\n\t\tassert.True(t, before, \"Before Hook did not run for query: \"+query)\n\t\tassert.True(t, after, \"After Hook did not run for query:  \"+query)\n\t})\n\n\tt.Run(\"ExecContext\", func(t *testing.T) {\n\t\tbefore, after = false, false\n\t\t_, err := s.db.ExecContext(context.Background(), query, args...)\n\t\trequire.NoError(t, err)\n\t\tassert.True(t, before, \"Before Hook did not run for query: \"+query)\n\t\tassert.True(t, after, \"After Hook did not run for query:  \"+query)\n\t})\n\n\tt.Run(\"Statements\", func(t *testing.T) {\n\t\tbefore, after = false, false\n\t\tstmt, err := s.db.Prepare(query)\n\t\trequire.NoError(t, err)\n\n\t\t// Hooks just run when the stmt is executed (Query or Exec)\n\t\tassert.False(t, before, \"Before Hook run before execution: \"+query)\n\t\tassert.False(t, after, \"After Hook run before execution:  \"+query)\n\n\t\t_, err = stmt.Query(args...)\n\t\trequire.NoError(t, err)\n\t\tassert.True(t, before, \"Before Hook did not run for query: \"+query)\n\t\tassert.True(t, after, \"After Hook did not run for query:  \"+query)\n\t})\n}\n\nfunc (s *suite) testHooksArguments(t *testing.T, query string, args ...interface{}) {\n\thook := func(ctx context.Context, q string, a ...interface{}) (context.Context, error) {\n\t\tassert.Equal(t, query, q)\n\t\tassert.Equal(t, args, a)\n\t\tassert.Equal(t, \"val\", ctx.Value(\"key\").(string))\n\t\treturn ctx, nil\n\t}\n\ts.hooks.before = hook\n\ts.hooks.after = hook\n\n\tctx := context.WithValue(context.Background(), \"key\", \"val\") //nolint:staticcheck\n\t{\n\t\t_, err := s.db.QueryContext(ctx, query, args...)\n\t\trequire.NoError(t, err)\n\t}\n\n\t{\n\t\t_, err := s.db.ExecContext(ctx, query, args...)\n\t\trequire.NoError(t, err)\n\t}\n}\n\nfunc (s *suite) TestHooksArguments(t *testing.T, query string, args ...interface{}) {\n\tt.Run(\"TestHooksArguments\", func(t *testing.T) { s.testHooksArguments(t, query, args...) })\n}\n\nfunc (s *suite) testHooksErrors(t *testing.T, query string) {\n\tboom := errors.New(\"boom\")\n\ts.hooks.before = func(ctx context.Context, query string, args ...interface{}) (context.Context, error) {\n\t\treturn ctx, boom\n\t}\n\n\ts.hooks.after = func(ctx context.Context, query string, args ...interface{}) (context.Context, error) {\n\t\tassert.False(t, true, \"this should not run\")\n\t\treturn ctx, nil\n\t}\n\n\t_, err := s.db.Query(query)\n\tassert.Equal(t, boom, err)\n}\n\nfunc (s *suite) TestHooksErrors(t *testing.T, query string) {\n\tt.Run(\"TestHooksErrors\", func(t *testing.T) { s.testHooksErrors(t, query) })\n}\n\nfunc (s *suite) testErrHookHook(t *testing.T, query string, args ...interface{}) {\n\ts.hooks.before = func(ctx context.Context, query string, args ...interface{}) (context.Context, error) {\n\t\treturn ctx, nil\n\t}\n\n\ts.hooks.after = func(ctx context.Context, query string, args ...interface{}) (context.Context, error) {\n\t\tassert.False(t, true, \"after hook should not run\")\n\t\treturn ctx, nil\n\t}\n\n\ts.hooks.onError = func(ctx context.Context, err error, query string, args ...interface{}) error {\n\t\tassert.True(t, true, \"onError hook should run\")\n\t\treturn err\n\t}\n\n\t_, err := s.db.Query(query)\n\trequire.Error(t, err)\n}\n\nfunc (s *suite) TestErrHookHook(t *testing.T, query string, args ...interface{}) {\n\tt.Run(\"TestErrHookHook\", func(t *testing.T) { s.testErrHookHook(t, query, args...) })\n}\n\nfunc TestNamedValueToValue(t *testing.T) {\n\tnamed := []driver.NamedValue{\n\t\t{Ordinal: 1, Value: \"foo\"},\n\t\t{Ordinal: 2, Value: 42},\n\t}\n\twant := []driver.Value{\"foo\", 42}\n\tdargs, err := namedValueToValue(named)\n\trequire.NoError(t, err)\n\tassert.Equal(t, want, dargs)\n}\n"
  }
]