Skip to content

Handling NULL Values in TypeID Parsing and Encoding #12

Open
@Le0Developer

Description

@Le0Developer

Problem Description:

We are encountering an issue when parsing a nullable typeid from the database and then using it in another query. When we parse a NULL value, we expect to get NULL back, but instead, we get prefix_00000000000000000000000000, which disrupts our query.

Currently, scanning nils/empty strings results in a zero-initialized typeid:

typeid-go/sql.go

Lines 13 to 18 in dd4ff4b

case nil:
return nil
case string:
if src == "" {
return nil
}

However, when encoding it back, we get prefix_00000000000000000000000000, which is neither NULL nor an empty string:

typeid-go/sql.go

Lines 31 to 33 in dd4ff4b

func (tid TypeID[P]) Value() (driver.Value, error) {
return tid.String(), nil
}

The discussion in #11 mainly focuses on parsing but does not address putting the ID back into the database.

Question:

How should we handle NULL values?


Option 1: Wrapping typeid in a Parent Struct to Handle NULL Explicitly

Example wrapping code:

type NullableTypeID[P typeid.PrefixType] struct {
	Valid  bool
	TypeID typeid.TypeID[P]
}

func (ntid *NullableTypeID[P]) Scan(src any) error {
	switch obj := src.(type) {
	case nil:
		return nil
	case string:
		if err := ntid.TypeID.Scan(src); err != nil {
			return err
		}
		ntid.Valid = true
		return nil
	default:
		return fmt.Errorf("unsupported scan type %T", obj)
	}
}

func (ntid NullableTypeID[P]) Value() (driver.Value, error) {
	if !ntid.Valid {
		return nil, nil
	}
	return ntid.TypeID.String(), nil
}

This approach has some disadvantages like the fact that NullableTypeID.TypeID is not comparable to a regular TypeID.

In this case, I would either like to get some documentation that recommends this approach or even include the wrapper in the package directly.

Option 2: typeid.Value() Returns NULL Based on isZero

The Value() method is updated to:

func (tid TypeID[P]) Value() (driver.Value, error) {
        if tid.IsZero() {
                return nil, nil
        }
	return tid.String(), nil
}

This could cause an issue if someone was relying on "" being converted to "prefix_00000000000000000000000000" if the column wasn't nullable.


Just an extra note:

From the description of sql.Scanner the current Scan() implementation is invalid as we are losing the distinction between NULL and "".

// An error should be returned if the value cannot be stored
// without loss of information.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions