diff --git a/README.md b/README.md
index 3585d8f..608ec78 100644
--- a/README.md
+++ b/README.md
@@ -61,6 +61,44 @@ See [godoc examples](https://pkg.go.dev/github.com/akm/sql-slog#example-Open) fo
## EXAMPLES
+### [examples/with-sqlc](./examples/with-sqlc/)
+
+An example showing how sql-slog works with [sqlc](https://sqlc.dev/).
+This example is almost same as [Getting started with SQLite](https://docs.sqlc.dev/en/latest/tutorials/getting-started-sqlite.html) but uses [sqlslog.Open](https://pkg.go.dev/github.com/akm/sql-slog#Open) instead of [sql.Open](https://pkg.go.dev/database/sql#Open).
+
+ Stdout with sqlslog package
+
+```
+$ make -C examples/with-sqlc run
+go build ./...
+go run .
+time=2025-03-19T21:23:36.992+09:00 level=INFO msg=Open driver=sqlite dsn=:memory: duration=22083
+time=2025-03-19T21:23:36.992+09:00 level=INFO msg=Driver.Open dsn=:memory: duration=274042 conn_id=_hMZDi7TQfEgBKN_
+time=2025-03-19T21:23:36.992+09:00 level=INFO msg=Connector.Connect duration=294292
+time=2025-03-19T21:23:36.993+09:00 level=INFO msg=Conn.ExecContext conn_id=_hMZDi7TQfEgBKN_ query="CREATE TABLE authors (\n id INTEGER PRIMARY KEY,\n name text NOT NULL,\n bio text\n);\n" args=[] duration=537125
+time=2025-03-19T21:23:36.993+09:00 level=INFO msg=Conn.QueryContext conn_id=_hMZDi7TQfEgBKN_ query="-- name: ListAuthors :many\nSELECT id, name, bio FROM authors\nORDER BY name\n" args=[] duration=23250
+2025/03/19 21:23:36 []
+time=2025-03-19T21:23:36.993+09:00 level=INFO msg=Conn.QueryContext conn_id=_hMZDi7TQfEgBKN_ query="-- name: CreateAuthor :one\nINSERT INTO authors (\n name, bio\n) VALUES (\n ?, ?\n)\nRETURNING id, name, bio\n" args="[{Name: Ordinal:1 Value:Brian Kernighan} {Name: Ordinal:2 Value:Co-author of The C Programming Language and The Go Programming Language}]" duration=20375
+2025/03/19 21:23:36 {1 Brian Kernighan {Co-author of The C Programming Language and The Go Programming Language true}}
+time=2025-03-19T21:23:36.993+09:00 level=INFO msg=Conn.QueryContext conn_id=_hMZDi7TQfEgBKN_ query="-- name: GetAuthor :one\nSELECT id, name, bio FROM authors\nWHERE id = ? LIMIT 1\n" args="[{Name: Ordinal:1 Value:1}]" duration=8083
+2025/03/19 21:23:36 true
+```
+
+
+
+ Stdout without sqlslog package
+
+```
+$ SKIP_SQLSLOG=1 make -C examples/with-sqlc run
+go build ./...
+go run .
+2025/03/19 21:23:19 []
+2025/03/19 21:23:19 {1 Brian Kernighan {Co-author of The C Programming Language and The Go Programming Language true}}
+2025/03/19 21:23:19 true
+```
+
+
+
### [examples/with-go-requestid](./examples/with-go-requestid/)
An example showing how sql-slog works with [go-requestid](https://github.com/akm/go-requestid).
diff --git a/conn.go b/conn.go
index a82225c..43b9bd4 100644
--- a/conn.go
+++ b/conn.go
@@ -65,13 +65,24 @@ func wrapConn(original driver.Conn, logger *stepLogger, options *connOptions) dr
if original == nil {
return nil
}
- if _, ok := original.(*connWithContextWrapper); ok {
+ switch original.(type) {
+ case *connWrapper:
+ return original
+ // case *connNvcWrapper:
+ // return original
+ case *connWithContextWrapper:
+ return original
+ case *connNvcWithContextWrapper:
return original
}
connWrapper := connWrapper{original: original, logger: logger, options: options}
if cwc, ok := original.(connWithContext); ok {
- return &connWithContextWrapper{connWrapper, cwc}
+ connWrapper2 := connWithContextWrapper{connWrapper, cwc}
+ if nvc, ok := original.(driver.NamedValueChecker); ok {
+ return &connNvcWithContextWrapper{connWrapper2, nvc}
+ }
+ return &connWrapper2
}
// Commented out because it's not used.
@@ -109,11 +120,6 @@ var (
_ driver.Validator = (*connWrapper)(nil)
)
-// To support custom data types, implement NamedValueChecker.
-// NamedValueChecker also allows queries to accept per-query
-// options as a parameter by returning ErrRemoveArgument from CheckNamedValue.
-var _ driver.NamedValueChecker = (*connWrapper)(nil)
-
// Begin implements driver.Conn.
func (c *connWrapper) Begin() (driver.Tx, error) {
var origTx driver.Tx
@@ -172,14 +178,6 @@ func (c *connWrapper) IsValid() bool {
return true
}
-// CheckNamedValue implements driver.NamedValueChecker.
-func (c *connWrapper) CheckNamedValue(namedValue *driver.NamedValue) error {
- if v, ok := c.original.(driver.NamedValueChecker); ok {
- return v.CheckNamedValue(namedValue)
- }
- return nil
-}
-
type connWithContext interface {
driver.Conn
driver.ExecerContext
@@ -349,3 +347,23 @@ func ConnQueryContextErrorHandler(driverName string) func(err error) (bool, []sl
return nil
}
}
+
+type connNvcWrapper struct {
+ connWrapper
+ driver.NamedValueChecker
+}
+
+var (
+ _ driver.Conn = (*connNvcWrapper)(nil)
+ _ driver.NamedValueChecker = (*connNvcWrapper)(nil)
+)
+
+type connNvcWithContextWrapper struct {
+ connWithContextWrapper
+ driver.NamedValueChecker
+}
+
+var (
+ _ driver.Conn = (*connNvcWithContextWrapper)(nil)
+ _ driver.NamedValueChecker = (*connNvcWithContextWrapper)(nil)
+)
diff --git a/conn_test.go b/conn_test.go
index d8f3982..28faaa8 100644
--- a/conn_test.go
+++ b/conn_test.go
@@ -44,6 +44,13 @@ func TestWrapConn(t *testing.T) {
if conn == nil {
t.Fatal("Expected non-nil")
}
+
+ t.Run("skip wrapped driver.Conn object", func(t *testing.T) {
+ res := wrapConn(conn, logger, connOptions)
+ if res != conn {
+ t.Fatal("Expected same object")
+ }
+ })
})
}
diff --git a/examples/with-sqlc/Makefile b/examples/with-sqlc/Makefile
new file mode 100644
index 0000000..b0c99d9
--- /dev/null
+++ b/examples/with-sqlc/Makefile
@@ -0,0 +1,10 @@
+.PHONY: default
+default: run
+
+.PHONY: build
+build:
+ go build ./...
+
+.PHONY: run
+run: build
+ go run .
diff --git a/examples/with-sqlc/go.mod b/examples/with-sqlc/go.mod
new file mode 100644
index 0000000..1cbea99
--- /dev/null
+++ b/examples/with-sqlc/go.mod
@@ -0,0 +1,20 @@
+module tutorial.sqlc.dev/app
+
+go 1.23.2
+
+require (
+ github.com/akm/sql-slog v0.2.0 // indirect
+ github.com/dustin/go-humanize v1.0.1 // indirect
+ github.com/google/uuid v1.6.0 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/ncruces/go-strftime v0.1.9 // indirect
+ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
+ golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 // indirect
+ golang.org/x/sys v0.30.0 // indirect
+ modernc.org/libc v1.61.13 // indirect
+ modernc.org/mathutil v1.7.1 // indirect
+ modernc.org/memory v1.8.2 // indirect
+ modernc.org/sqlite v1.36.1 // indirect
+)
+
+replace github.com/akm/sql-slog => ../..
diff --git a/examples/with-sqlc/go.sum b/examples/with-sqlc/go.sum
new file mode 100644
index 0000000..b838c33
--- /dev/null
+++ b/examples/with-sqlc/go.sum
@@ -0,0 +1,23 @@
+github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
+github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
+github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
+github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
+github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
+golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo=
+golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
+golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+modernc.org/libc v1.61.13 h1:3LRd6ZO1ezsFiX1y+bHd1ipyEHIJKvuprv0sLTBwLW8=
+modernc.org/libc v1.61.13/go.mod h1:8F/uJWL/3nNil0Lgt1Dpz+GgkApWh04N3el3hxJcA6E=
+modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
+modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
+modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI=
+modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU=
+modernc.org/sqlite v1.36.1 h1:bDa8BJUH4lg6EGkLbahKe/8QqoF8p9gArSc6fTqYhyQ=
+modernc.org/sqlite v1.36.1/go.mod h1:7MPwH7Z6bREicF9ZVUR78P1IKuxfZ8mRIDHD0iD+8TU=
diff --git a/examples/with-sqlc/query.sql b/examples/with-sqlc/query.sql
new file mode 100644
index 0000000..212a226
--- /dev/null
+++ b/examples/with-sqlc/query.sql
@@ -0,0 +1,25 @@
+-- name: GetAuthor :one
+SELECT * FROM authors
+WHERE id = ? LIMIT 1;
+
+-- name: ListAuthors :many
+SELECT * FROM authors
+ORDER BY name;
+
+-- name: CreateAuthor :one
+INSERT INTO authors (
+ name, bio
+) VALUES (
+ ?, ?
+)
+RETURNING *;
+
+-- name: UpdateAuthor :exec
+UPDATE authors
+set name = ?,
+bio = ?
+WHERE id = ?;
+
+-- name: DeleteAuthor :exec
+DELETE FROM authors
+WHERE id = ?;
diff --git a/examples/with-sqlc/schema.sql b/examples/with-sqlc/schema.sql
new file mode 100644
index 0000000..9d2770b
--- /dev/null
+++ b/examples/with-sqlc/schema.sql
@@ -0,0 +1,5 @@
+CREATE TABLE authors (
+ id INTEGER PRIMARY KEY,
+ name text NOT NULL,
+ bio text
+);
diff --git a/examples/with-sqlc/sqlc.yaml b/examples/with-sqlc/sqlc.yaml
new file mode 100644
index 0000000..d46738d
--- /dev/null
+++ b/examples/with-sqlc/sqlc.yaml
@@ -0,0 +1,9 @@
+version: "2"
+sql:
+ - engine: "sqlite"
+ queries: "query.sql"
+ schema: "schema.sql"
+ gen:
+ go:
+ package: "tutorial"
+ out: "tutorial"
diff --git a/examples/with-sqlc/tutorial.go b/examples/with-sqlc/tutorial.go
new file mode 100644
index 0000000..387359a
--- /dev/null
+++ b/examples/with-sqlc/tutorial.go
@@ -0,0 +1,73 @@
+package main
+
+import (
+ "context"
+ "database/sql"
+ _ "embed"
+ "log"
+ "os"
+ "reflect"
+
+ sqlslog "github.com/akm/sql-slog"
+ _ "modernc.org/sqlite"
+
+ "tutorial.sqlc.dev/app/tutorial"
+)
+
+//go:embed schema.sql
+var ddl string
+
+func run() error {
+ ctx := context.Background()
+
+ var db *sql.DB
+ var err error
+ if os.Getenv("SKIP_SQLSLOG") != "" {
+ db, err = sql.Open("sqlite", ":memory:")
+ } else {
+ db, _, err = sqlslog.Open(ctx, "sqlite", ":memory:")
+ }
+ if err != nil {
+ return err
+ }
+
+ // create tables
+ if _, err := db.ExecContext(ctx, ddl); err != nil {
+ return err
+ }
+
+ queries := tutorial.New(db)
+
+ // list all authors
+ authors, err := queries.ListAuthors(ctx)
+ if err != nil {
+ return err
+ }
+ log.Println(authors)
+
+ // create an author
+ insertedAuthor, err := queries.CreateAuthor(ctx, tutorial.CreateAuthorParams{
+ Name: "Brian Kernighan",
+ Bio: sql.NullString{String: "Co-author of The C Programming Language and The Go Programming Language", Valid: true},
+ })
+ if err != nil {
+ return err
+ }
+ log.Println(insertedAuthor)
+
+ // get the author we just inserted
+ fetchedAuthor, err := queries.GetAuthor(ctx, insertedAuthor.ID)
+ if err != nil {
+ return err
+ }
+
+ // prints true
+ log.Println(reflect.DeepEqual(insertedAuthor, fetchedAuthor))
+ return nil
+}
+
+func main() {
+ if err := run(); err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/examples/with-sqlc/tutorial/db.go b/examples/with-sqlc/tutorial/db.go
new file mode 100644
index 0000000..11d58e5
--- /dev/null
+++ b/examples/with-sqlc/tutorial/db.go
@@ -0,0 +1,31 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+// sqlc v1.28.0
+
+package tutorial
+
+import (
+ "context"
+ "database/sql"
+)
+
+type DBTX interface {
+ ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
+ PrepareContext(context.Context, string) (*sql.Stmt, error)
+ QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
+ QueryRowContext(context.Context, string, ...interface{}) *sql.Row
+}
+
+func New(db DBTX) *Queries {
+ return &Queries{db: db}
+}
+
+type Queries struct {
+ db DBTX
+}
+
+func (q *Queries) WithTx(tx *sql.Tx) *Queries {
+ return &Queries{
+ db: tx,
+ }
+}
diff --git a/examples/with-sqlc/tutorial/models.go b/examples/with-sqlc/tutorial/models.go
new file mode 100644
index 0000000..246392a
--- /dev/null
+++ b/examples/with-sqlc/tutorial/models.go
@@ -0,0 +1,15 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+// sqlc v1.28.0
+
+package tutorial
+
+import (
+ "database/sql"
+)
+
+type Author struct {
+ ID int64
+ Name string
+ Bio sql.NullString
+}
diff --git a/examples/with-sqlc/tutorial/query.sql.go b/examples/with-sqlc/tutorial/query.sql.go
new file mode 100644
index 0000000..4d7a6c6
--- /dev/null
+++ b/examples/with-sqlc/tutorial/query.sql.go
@@ -0,0 +1,100 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+// sqlc v1.28.0
+// source: query.sql
+
+package tutorial
+
+import (
+ "context"
+ "database/sql"
+)
+
+const createAuthor = `-- name: CreateAuthor :one
+INSERT INTO authors (
+ name, bio
+) VALUES (
+ ?, ?
+)
+RETURNING id, name, bio
+`
+
+type CreateAuthorParams struct {
+ Name string
+ Bio sql.NullString
+}
+
+func (q *Queries) CreateAuthor(ctx context.Context, arg CreateAuthorParams) (Author, error) {
+ row := q.db.QueryRowContext(ctx, createAuthor, arg.Name, arg.Bio)
+ var i Author
+ err := row.Scan(&i.ID, &i.Name, &i.Bio)
+ return i, err
+}
+
+const deleteAuthor = `-- name: DeleteAuthor :exec
+DELETE FROM authors
+WHERE id = ?
+`
+
+func (q *Queries) DeleteAuthor(ctx context.Context, id int64) error {
+ _, err := q.db.ExecContext(ctx, deleteAuthor, id)
+ return err
+}
+
+const getAuthor = `-- name: GetAuthor :one
+SELECT id, name, bio FROM authors
+WHERE id = ? LIMIT 1
+`
+
+func (q *Queries) GetAuthor(ctx context.Context, id int64) (Author, error) {
+ row := q.db.QueryRowContext(ctx, getAuthor, id)
+ var i Author
+ err := row.Scan(&i.ID, &i.Name, &i.Bio)
+ return i, err
+}
+
+const listAuthors = `-- name: ListAuthors :many
+SELECT id, name, bio FROM authors
+ORDER BY name
+`
+
+func (q *Queries) ListAuthors(ctx context.Context) ([]Author, error) {
+ rows, err := q.db.QueryContext(ctx, listAuthors)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+ var items []Author
+ for rows.Next() {
+ var i Author
+ if err := rows.Scan(&i.ID, &i.Name, &i.Bio); err != nil {
+ return nil, err
+ }
+ items = append(items, i)
+ }
+ if err := rows.Close(); err != nil {
+ return nil, err
+ }
+ if err := rows.Err(); err != nil {
+ return nil, err
+ }
+ return items, nil
+}
+
+const updateAuthor = `-- name: UpdateAuthor :exec
+UPDATE authors
+set name = ?,
+bio = ?
+WHERE id = ?
+`
+
+type UpdateAuthorParams struct {
+ Name string
+ Bio sql.NullString
+ ID int64
+}
+
+func (q *Queries) UpdateAuthor(ctx context.Context, arg UpdateAuthorParams) error {
+ _, err := q.db.ExecContext(ctx, updateAuthor, arg.Name, arg.Bio, arg.ID)
+ return err
+}
diff --git a/stmt.go b/stmt.go
index d3b0c77..2a0eb14 100644
--- a/stmt.go
+++ b/stmt.go
@@ -37,11 +37,18 @@ func wrapStmt(original driver.Stmt, logger *stepLogger, options *stmtOptions) dr
stmtExec, withExecContext := original.(driver.StmtExecContext)
stmtQuery, withQueryContext := original.(driver.StmtQueryContext)
if withExecContext && withQueryContext {
- return &stmtContextWrapper{
+ stmtCtxW := &stmtContextWrapper{
stmtWrapper: stmtWrapper,
stmtExecContextWrapperImpl: stmtExecContextWrapperImpl{original: stmtExec, logger: logger, options: options},
stmtQueryContextWrapperImpl: stmtQueryContextWrapperImpl{original: stmtQuery, logger: logger, options: options},
}
+ if nvc, ok := original.(driver.NamedValueChecker); ok {
+ return &stmtContextNvcWrapper{
+ stmtContextWrapper: *stmtCtxW,
+ NamedValueChecker: nvc,
+ }
+ }
+ return stmtCtxW
}
// Commented out because the original implementation does not have this check.
//
@@ -57,6 +64,12 @@ func wrapStmt(original driver.Stmt, logger *stepLogger, options *stmtOptions) dr
// stmtQueryContextWrapperImpl: stmtQueryContextWrapperImpl{original: stmtQuery, logger: logger},
// }
// }
+ // if nvc, ok := original.(driver.NamedValueChecker); ok {
+ // return &stmtNvcWrapper{
+ // stmtWrapper: stmtWrapper,
+ // NamedValueChecker: nvc,
+ // }
+ // }
return &stmtWrapper
}
@@ -185,3 +198,49 @@ var (
_ driver.StmtExecContext = (*stmtContextWrapper)(nil)
_ driver.StmtQueryContext = (*stmtContextWrapper)(nil)
)
+
+// With driver.NamedValueChecker
+
+type stmtNvcWrapper struct {
+ stmtWrapper
+ driver.NamedValueChecker
+}
+
+var (
+ _ driver.Stmt = (*stmtNvcWrapper)(nil)
+ _ driver.NamedValueChecker = (*stmtNvcWrapper)(nil)
+)
+
+type stmtExecContextNvcWrapper struct {
+ stmtExecContextWrapper
+ driver.NamedValueChecker
+}
+
+var (
+ _ driver.Stmt = (*stmtExecContextNvcWrapper)(nil)
+ _ driver.StmtExecContext = (*stmtExecContextNvcWrapper)(nil)
+ _ driver.NamedValueChecker = (*stmtExecContextNvcWrapper)(nil)
+)
+
+type stmtQueryContextNvcWrapper struct {
+ stmtQueryContextWrapper
+ driver.NamedValueChecker
+}
+
+var (
+ _ driver.Stmt = (*stmtQueryContextNvcWrapper)(nil)
+ _ driver.StmtQueryContext = (*stmtQueryContextNvcWrapper)(nil)
+ _ driver.NamedValueChecker = (*stmtQueryContextNvcWrapper)(nil)
+)
+
+type stmtContextNvcWrapper struct {
+ stmtContextWrapper
+ driver.NamedValueChecker
+}
+
+var (
+ _ driver.Stmt = (*stmtContextNvcWrapper)(nil)
+ _ driver.StmtExecContext = (*stmtContextNvcWrapper)(nil)
+ _ driver.StmtQueryContext = (*stmtContextNvcWrapper)(nil)
+ _ driver.NamedValueChecker = (*stmtContextNvcWrapper)(nil)
+)