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

auto uuid scanning #726

Closed
wants to merge 2 commits into from
Closed

auto uuid scanning #726

wants to merge 2 commits into from

Conversation

brennanjl
Copy link
Collaborator

This PR makes it such that our UUID types are automatically scanned from the database. It also adds json marshalling for our UUID types, so that they are sent as strings over json.

@jchappelow
Copy link
Member

This PR makes it such that our UUID types are automatically scanned from the database

..for the UUID OID? We already could scan/value with our UUID type for BYTEA, columns, so I gather we're now enabling it to work with the special UUID type from the standard postgres extension?

I'm all for controlling the implemention, but to document motivation, what are some of the considerations with making our own scanner vs. one of the existing like https://pkg.go.dev/github.com/jackc/pgx/v5@v5.5.5/pgtype#UUID or https://github.com/jackc/pgx/wiki/UUID-Support?


uuid := UUID{}
copy(uuid[:], src)
reflect.ValueOf(target).Elem().Set(reflect.ValueOf(uuid))
Copy link
Member

@jchappelow jchappelow May 12, 2024

Choose a reason for hiding this comment

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

What's this doing? In the case where !ok above, what's an example with concrete types where this does something sensible?
I'm not sure where target comes from or is used later

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'll document this. When testing, it sometimes did not pass a UUID, but instead passed []byte. I believe this is because of how we pass uuids internally (such as in the vote store), where we convert them to []byte.

}

func (u *UUID) PreferredFormat() int16 {
return pgtype.TextFormatCode
Copy link
Member

Choose a reason for hiding this comment

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

The UUID type built in to pgype prefers binary.
Will you document some of the decisions in this code and/or like to reference implementations? There's a lot of logic add with no obvious reasons, although I'm sure there are indeed good ones.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oh this is a mistake, meant to be binary

core/go.mod Outdated Show resolved Hide resolved
@brennanjl
Copy link
Collaborator Author

what are some of the considerations with making our own scanner vs. one of the existing

The main reason is to control what gets returned to the client. @KwilLuke thought it was weird that UUIDs were not returned as strings (I agree). Doing this allows us to json marshal our own UUID type without iterating over all of the results to convert it.

@brennanjl
Copy link
Collaborator Author

While creating tests for this, I noticed that we actually had a bug for sending arrays via flags, as well as with RLP encoding. I have added into this PR fixes for these. These are breaking changes, but given the other breaking changes in this PR, I figured it was not a big deal. I also could not think of a way to properly support arrays without doing this.

The last outstanding thing on this PR is some ridiculous bug with writing UUIDs to postgres. For some reason, in one of the integration tests, I get a primary key violation for having duplicate primary keys, despite the fact that they certainly aren't.

Will finish this up tomorrow for the beta.

Copy link
Member

@jchappelow jchappelow left a comment

Choose a reason for hiding this comment

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

Just looking at the client array encoding/decoding updates now.

Comment on lines +241 to +258
// EncodeValue encodes a value to its detected type.
// It will reflect the value of the passed argument to determine its type.
func EncodeValue(v any) (*EncodedValue, error) {
if v == nil {
return &EncodedValue{
Type: DataType{
Name: types.NullType.Name,
},
Data: nil,
}, nil
}

// encodeScalar encodes a scalar value into a byte slice.
// It also returns the data type of the value.
encodeScalar := func(v any) ([]byte, *types.DataType, error) {
switch t := v.(type) {
case string:
return []byte(t), types.TextType, nil
Copy link
Member

Choose a reason for hiding this comment

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

With our client tooling at least, I only ever see v being a string.

Backing up through core/client.encodeTuple -> core/client.(*Client).Execute (or Call), I see kwil-cli preparing inputs from where the any is always going to be a string (or []string which ends up here too).

Am I reading this wrong? Or is this planning for different client use?

Also, I'm noting that on client both call and execute use encodeTuple this way, but server side only in txapp does execute have the complementary decoding with core/types/transactions.(*EncodedValue).Decode

@@ -228,16 +227,19 @@ func (c *Client) ExecuteAction(ctx context.Context, dbid string, action string,
// It can take any number of inputs, and if multiple tuples of inputs are passed,
// it will execute them in the same transaction.
func (c *Client) Execute(ctx context.Context, dbid string, procedure string, tuples [][]any, opts ...clientType.TxOpt) (transactions.TxHash, error) {
Copy link
Member

Choose a reason for hiding this comment

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

kwil-cli still uses the deprecated ExecuteAction instead of this one directly.

case string:
return []byte(t), types.TextType, nil
case int, int16, int32, int64, int8, uint, uint16, uint32, uint64, uint8:
i64, err := conv.Int64(v)
Copy link
Member

Choose a reason for hiding this comment

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

Our internal/conv if possible, please.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We can't because this is in core.

Copy link
Member

Choose a reason for hiding this comment

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

Oh, well we definitely don't want to use stockton here. We can move conv to core or copy/paste what's needed here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've got a way around it, will push up

if e.Type.IsArray {
decoded := make([]any, 0)
for _, elem := range e.Data {
dec, err := decodeScalar(elem, e.Type.toTypes())
Copy link
Member

Choose a reason for hiding this comment

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

Probably spare some conversions and allocations and just pass e.Type.Name to decodeScalar because that's all it needs. Array context is already established here.


// check if it is an array
if e.Type.IsArray {
decoded := make([]any, 0)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
decoded := make([]any, 0)
decoded := make([]any, 0, len(e.Data))

@brennanjl brennanjl mentioned this pull request May 22, 2024
@brennanjl
Copy link
Collaborator Author

Closing in favor of #761. I have addressed your comments thus far there as well.

@brennanjl brennanjl closed this May 22, 2024
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.

2 participants