`driver.Value` interface is ignored when uninitialized typed map is passed as an argument to `conn.Exec`
jastkand opened this issue · comments
Describe the bug
Given:
- the pg database with a table having jsonb column (
not null default '{}'
) - a typed map that implements a
driver.Valuer
interface
When the value is passed as an argument in some scenarios the value doesn't respect the driver.Valuer
implementation.
To Reproduce
Runnable example showing the issue:
package main
import (
"context"
"database/sql/driver"
"encoding/json"
"fmt"
"log"
"os"
"github.com/jackc/pgx/v5"
)
type overrides map[string]string
func (f overrides) Value() (driver.Value, error) {
if len(f) == 0 {
return []byte("{}"), nil
}
raw, err := json.Marshal(f)
if err != nil {
return nil, err
}
return raw, nil
}
func (f *overrides) Scan(value interface{}) error {
bytes, ok := value.([]byte)
if !ok {
return fmt.Errorf("failed to unmarshal JSONB value: %v", value)
}
return json.Unmarshal(bytes, f)
}
func main() {
conn, err := pgx.Connect(context.Background(), os.Getenv("DATABASE_URL"))
if err != nil {
log.Fatal(err)
}
defer conn.Close(context.Background())
_, err = conn.Exec(context.Background(), "DROP TABLE IF EXISTS typedmapbug")
if err != nil {
log.Fatal("drop table", err)
}
_, err = conn.Exec(context.Background(), "CREATE TABLE typedmapbug(id serial, overrides jsonb not null default '{}')")
if err != nil {
log.Fatal(err)
}
_, err = conn.Exec(context.Background(), `INSERT INTO typedmapbug (id, overrides) VALUES (1, '{"a":"a"}'), (2, '{"a":"a"}')`)
if err != nil {
log.Fatal(err)
}
// works
newOverrides1 := make(overrides)
if val, ok := newOverrides1.Value(); ok == nil {
log.Printf("newOverrides1 to driver.Value %s\n", val)
}
tag1, err := conn.Exec(context.Background(), "UPDATE typedmapbug SET overrides = $1 WHERE id = $2", newOverrides1, 1)
if err != nil {
log.Fatal(err)
}
log.Println("update succeds:", tag1.RowsAffected())
// doesn't work
var newOverrides2 overrides
if val, ok := newOverrides2.Value(); ok == nil {
log.Printf("newOverrides2 to driver.Value %s\n", val)
}
tag2, err := conn.Exec(context.Background(), "UPDATE typedmapbug SET overrides = $1 WHERE id = $2", newOverrides2, 2)
if err != nil {
log.Fatal(err)
}
log.Println("update fails:", tag2.RowsAffected())
}
Expected behavior
The code shouldn't fail on the second update call and the column had to be updated with the '{}'
.
Actual behavior
ERROR: null value in column "overrides" violates not-null constraint (SQLSTATE 23502)
error is returned instead
Version
- Go: go version go1.20.13 darwin/amd64
- PostgreSQL: PostgreSQL 12.9 (Debian 12.9-1.pgdg110+1) on aarch64-unknown-linux-gnu, compiled by gcc (Debian 10.2.1-6) 10.2.1 20210110, 64-bit
- pgx: v5.5.2
Additional
While debugging it I've found this line https://github.com/jackc/pgx/blob/v5.5.2/extended_query_builder.go#L26 which converts all typed nils into untyped ones. This seems to be causing the issue and the Value()
in never called in second scenario.
Duplicate of #1566.