Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Valuer interface is not respected for nil values #1860

Closed
SajanAlexander-okta opened this issue Jan 2, 2024 · 1 comment
Closed

Valuer interface is not respected for nil values #1860

SajanAlexander-okta opened this issue Jan 2, 2024 · 1 comment
Labels

Comments

@SajanAlexander-okta
Copy link

Describe the bug
When using a struct that implements driver.Valuer but is backed by a nilable type, Value is never called when the value is nil. This is used by some types where a default should be stored within the DB. The default converter for database/sql will call this on a nil.

I'm using the stdlib compatibility layer. When tracing through the code, the database/sql code will call the CheckNamedValue method on *stdlib.Conn, which will then just pass through the values directly.

Later on, when the prepared statement is being built, anynil.NormalizeSlice is called on the arguments, which then sets the value of the arg as nil and erases any type information about the arg.

The nil value hits a nil check, preventing any Encode plan from being executed to convert the value / call Value on it.

To Reproduce
See code below. The first insert will result in a NULL value to be inserted, while the second one will insert an empty object of {}.

package main

import (
	"database/sql"
	"database/sql/driver"
	"encoding/json"
	"fmt"
	"os"

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/stdlib"
)

// JSONText code was copied from github.com/jmoiron/sqlx types.JSONText so there's not another import required outside of pgx and the Go stdlib
type JSONText json.RawMessage

var emptyJSON = JSONText("{}")

func (j JSONText) Value() (driver.Value, error) {
	var m json.RawMessage
	var err = j.Unmarshal(&m)
	if err != nil {
		return []byte{}, err
	}
	return []byte(j), nil
}

func (j *JSONText) Unmarshal(v interface{}) error {
	if len(*j) == 0 {
		*j = emptyJSON
	}
	return json.Unmarshal([]byte(*j), v)
}

func main() {
	connConfig, err := pgx.ParseConfig(os.Getenv("PGX_TEST_DATABASE"))
	if err != nil {
		fmt.Println("error parsing config: ", err)
		return
	}

	dn := stdlib.RegisterConnConfig(connConfig)

	db, err := sql.Open("pgx", dn)
	if err != nil {
		fmt.Println("error opening database: ", err)
		return
	}

	_, err = db.Exec("CREATE TABLE IF NOT EXISTS pgx_nil_test_table (data jsonb)")
	if err != nil {
		fmt.Println("error creating table: ", err)
	}

	var data JSONText

	_, err = db.Exec("INSERT INTO pgx_nil_test_table (data) VALUES ($1)", data)
	if err != nil {
		fmt.Println("error inserting data: ", err)
	}

	data = JSONText(make([]byte, 0))
	_, err = db.Exec("INSERT INTO pgx_nil_test_table (data) VALUES ($1)", data)
	if err != nil {
		fmt.Println("error inserting data: ", err)
	}
}

Please run your example with the race detector enabled. For example, go run -race main.go or go test -race.

Expected behavior
The Value method is called on a nil type that implements driver.Valuer to allow for any default value

Actual behavior
The Value method is not called / the value is not converted

Version

  • Go: $ go version -> go1.20.4 darwin/arm64
  • PostgreSQL: $ psql --no-psqlrc --tuples-only -c 'select version()' -> [e.g. PostgreSQL 14.4 on x86_64-apple-darwin21.5.0, compiled by Apple clang version 13.1.6 (clang-1316.0.21.2.5), 64-bit]
  • pgx: $ grep 'github.com/jackc/pgx/v[0-9]' go.mod -> v5.5.1

Additional context
Add any other context about the problem here.

@jackc
Copy link
Owner

jackc commented Jan 13, 2024

Duplicate of #1566.

This was changed in v5. There's a few possible changes that would bring back that behavior from v4, but progress seems stalled at the moment.

@jackc jackc closed this as not planned Won't fix, can't repro, duplicate, stale Jan 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants