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

Fix encode driver.Valuer on pointer #2019

Merged
merged 9 commits into from
May 25, 2024
Merged

Conversation

jackc
Copy link
Owner

@jackc jackc commented May 18, 2024

pgx v5 introduced nil normalization for typed nils. This means that []byte(nil) is normalized to nil at the edge of the encoding system. This simplified encoding logic as nil could be encoded as NULL and type specific handling was unneeded.

However, database/sql compatibility requires Value to be called on a nil pointer that implements driver.Valuer. This was broken by normalizing to nil.

This commit changes the normalization logic to not normalize pointers that directly implement driver.Valuer to nil. It still normalizes pointers that implement driver.Valuer through implicit derefence.

e.g.

type T struct{}

func (t *T) Value() (driver.Value, error) {
  return nil, nil
}

type S struct{}

func (s S) Value() (driver.Value, error) {
  return nil, nil
}

(*T)(nil) will not be normalized to nil but (*S)(nil) will be.

#1566

pgx v5 introduced nil normalization for typed nils. This means that
[]byte(nil) is normalized to nil at the edge of the encoding system.
This simplified encoding logic as nil could be encoded as NULL and type
specific handling was unneeded.

However, database/sql compatibility requires Value to be called on a
nil pointer that implements driver.Valuer. This was broken by
normalizing to nil.

This commit changes the normalization logic to not normalize pointers
that directly implement driver.Valuer to nil. It still normalizes
pointers that implement driver.Valuer through implicit derefence.

e.g.

type T struct{}

func (t *T) Value() (driver.Value, error) {
  return nil, nil
}

type S struct{}

func (s S) Value() (driver.Value, error) {
  return nil, nil
}

(*T)(nil) will not be normalized to nil but (*S)(nil) will be.

#1566
return false
}

if kind == reflect.Ptr {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For my use-case, the restriction to just pointers won't work since the type I'm using is an alias to a slice. See #1860 for some example code.

Allowing reflect.Slice here would fix my use-case, but it would probably make sense to allow for others as well.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. It now extends the same logic to all nilable values.

This extends the behavior change a little further than I had anticipated. But it is a fix for database/sql compatibility and I don't anticipate the change breaking any real world code.

jackc added 3 commits May 18, 2024 16:47
Future commit will be using bytes.Clone which was implemented in Go
1.20.

Also update README.md to reflect that minimum supported Go version is
1.21. But only requiring Go 1.20 in go.mod to avoid needlessly breaking
old Go when it still works.
Extract logic for finding OID and converting argument to encodable
value. This is in preparation for a future change for better supporting
nil driver.Valuer values.
Copy link

@sajanalexander sajanalexander left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks for fixing this!

jackc added 5 commits May 18, 2024 20:37
anynil.Is was already being called in all paths that
anynil.NormalizeSlice was used.
The new logic checks for any type of nil at the beginning of Encode and
then either treats it as NULL or calls the driver.Valuer method if
appropriate.

This should preserve the existing nil normalization while restoring the
ability to encode nil driver.Valuer values.
@jackc
Copy link
Owner Author

jackc commented May 19, 2024

I just refactored the whole typed nil system to exist entirely in pgtype.Map.Encode. This change keeps the original nil driver.Valuer available while still treating other typed nils as SQL NULL.

If my design and implementation is correct, this approach should restore full driver.Valuer support while still not leaking any typed nils into the Codec system.

@sajanalexander
Copy link

I like the refactor. Tracing how nils were handled prior to the change was a bit complicated and this definitely simplifies it.

@jackc jackc merged commit b4911f1 into master May 25, 2024
14 checks passed
@jackc jackc deleted the fix-encode-driver-valuer-on-pointer branch May 25, 2024 16:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants