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

breaking change in v5: Value() no longer called on nil args #1367

Closed
kszafran opened this issue Nov 3, 2022 · 6 comments
Closed

breaking change in v5: Value() no longer called on nil args #1367

kszafran opened this issue Nov 3, 2022 · 6 comments
Labels

Comments

@kszafran
Copy link

kszafran commented Nov 3, 2022

Describe the bug
v5 introduced this line https://github.com/jackc/pgx/blob/master/extended_query_builder.go#L25 with the result being that Valuer.Value() is no longer called on nil arguments.

To Reproduce
I have some types like the following:

type MyThing struct {
	// ...some fields
}

type MyThings []MyThing

func (i MyThings) Value() (driver.Value, error) {
	if len(i) == 0 {
		return []byte("[]"), nil
	}
	return json.Marshal(i)
}

func (i *MyThings) Scan(value any) error {
	bs, ok := value.([]byte)
	if !ok {
		return fmt.Errorf("failed to scan MyThings, expected []byte, got: %T", value)
	}
	return json.Unmarshal(bs, &i)
}

and a column like things JSONB NOT NULL. In v4, if I passed MyThings(nil) as a query arg, Value() would be called and an empty JSONB array would be stored. In v5, Value() is not called and I end up with an error:

ERROR: null value in column \"things\" of relation \"test\" violates not-null constraint (SQLSTATE 23502)

Expected behavior
Valuer.Value() should be called on typed nil values. Otherwise, this breaking change should be described in the changelog.

Actual behavior
This line https://github.com/jackc/pgx/blob/master/extended_query_builder.go#L25 turns all types nils into nil interfaces, which prevents calling Valuer.Value().

Version

  • Go: go version go1.19.2 darwin/amd64
  • PostgreSQL: PostgreSQL 14.3 on x86_64-pc-linux-musl, compiled by gcc (Alpine 11.2.1_git20220219) 11.2.1 20220219, 64-bit
  • pgx: v5.0.4

Additional context
Question: I'm planning on eventually moving to the pgx native interface. Would I encounter the same behavior there, or would I be able to provide default values for nils?

@jackc
Copy link
Owner

jackc commented Nov 4, 2022

I think the v5 behavior is correct.

To be honest I'm surprised that was v4's behavior. This means that pointer to a driver.Valuer would behave differently that a pointer to anything else. And it risks a panic if the driver.Valuer wasn't expecting to be called with a nil receiver.

Furthermore, allowing typed nils would cascade through the entire type system. Normalizing nils as is done now removes a lot of edge cases.

Valuer.Value() should be called on typed nil values. Otherwise, this breaking change should be described in the changelog.

That's fair. I've added a note (a968ce3).

@jackc jackc closed this as completed Nov 4, 2022
@kszafran
Copy link
Author

kszafran commented Nov 4, 2022

I wonder how other database/sql drivers handle nil values (e.g. mysql drivers). We were using lib/pq before, and I think it was also calling Valuer.Value() on nil arguments.

@joshma
Copy link

joshma commented Mar 5, 2023

We have this assumption spread across our codebase, so it's currently blocking our upgrade to v5.

We have non-null constraints on our JSONB columns, preferring a JSON "null" over a SQL null just to remove a redundant state. But now all our nil maps are getting stored as SQL null, breaking the non-null constraints. The only solution seems to be to find all call sites and initiate an empty map.

Does anyone happen to have a workaround, to restore the old behavior?

@jackc
Copy link
Owner

jackc commented Mar 7, 2023

@joshma Sorry, but I don't think there is an easy way to do this. The typed nil into nil conversion happens early in the encoding process and the rest of the system expects to not receive any typed nils.

...

I started writing about the harder way to do this, but as I thought about it a little more, I determined it wouldn't work either. So I actually don't know any way to do this.

@joshma
Copy link

joshma commented Mar 7, 2023

😢 - well I appreciate the time and consideration!

@kszafran
Copy link
Author

kszafran commented Sep 3, 2024

@joshma Looks like this has been fixed in 5.6.0: #2019

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

3 participants