Files
dbx/ksuid_binary.go
T
end cc44931ea9 feat(ksuid): add Equal and IsZero for ApplyInterfacePtr
StringKSUID and BinaryKSUID now implement ApplyInterface with nil-nil
equality semantics, enabling ApplyInterfacePtr in patch logic. Adds nil
sentinel vars and tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-29 18:24:07 -07:00

91 lines
2.4 KiB
Go

package dbx
import (
"database/sql/driver"
"fmt"
"io"
"github.com/segmentio/ksuid"
)
// BinaryKSUID wraps segmentio/ksuid for binary-column storage (BINARY(20), BYTEA).
// SQL Scan/Value use the raw 20-byte encoding. GraphQL transit uses a string
// scalar (base62 KSUID).
type BinaryKSUID struct {
ksuid.KSUID
}
// NilBinaryKSUID is the zero/nil BinaryKSUID value.
var NilBinaryKSUID = BinaryKSUID{KSUID: ksuid.Nil}
// Equal reports whether two BinaryKSUID values represent the same identifier,
// treating two nil values as equal. Implements [ApplyInterface].
func (b BinaryKSUID) Equal(rh BinaryKSUID) bool {
return equalKSUID(b.KSUID, rh.KSUID)
}
// IsZero reports whether b is nil. Implements [ApplyInterface].
func (b BinaryKSUID) IsZero() bool {
return b.IsNil()
}
// NewBinaryKSUID generates a new BinaryKSUID.
func NewBinaryKSUID() BinaryKSUID {
return BinaryKSUID{KSUID: ksuid.New()}
}
// ParseBinaryKSUID parses a BinaryKSUID from its raw 20-byte form.
func ParseBinaryKSUID(b []byte) (BinaryKSUID, error) {
id, err := ksuid.FromBytes(b)
if err != nil {
return BinaryKSUID{}, err
}
return BinaryKSUID{KSUID: id}, nil
}
// AsStringKSUID returns a StringKSUID view of the same identifier.
func (b BinaryKSUID) AsStringKSUID() StringKSUID {
return StringKSUID{KSUID: b.KSUID}
}
// Scan implements sql.Scanner for binary-backed KSUID columns.
func (b *BinaryKSUID) Scan(src any) error {
switch v := src.(type) {
case nil:
*b = BinaryKSUID{}
return nil
case []byte:
if len(v) == ksuidStringLength {
return fmt.Errorf("BinaryKSUID.Scan: string-encoded KSUID (%d bytes); use StringKSUID", len(v))
}
id, err := ksuid.FromBytes(v)
if err != nil {
return fmt.Errorf("BinaryKSUID.Scan: %w", err)
}
*b = BinaryKSUID{KSUID: id}
return nil
case string:
return fmt.Errorf("BinaryKSUID.Scan: string value %q; use StringKSUID or store raw bytes", v)
default:
return fmt.Errorf("BinaryKSUID.Scan: unable to scan type %T", v)
}
}
// Value implements driver.Valuer for binary-backed KSUID columns.
func (b BinaryKSUID) Value() (driver.Value, error) {
if b.IsNil() {
return nil, nil
}
return b.Bytes(), nil
}
// UnmarshalGQL implements the graphql.Unmarshaler interface.
func (b *BinaryKSUID) UnmarshalGQL(value any) error {
return unmarshalKSUIDFromGQL(value, b.UnmarshalText)
}
// MarshalGQL implements the graphql.Marshaler interface.
func (b BinaryKSUID) MarshalGQL(w io.Writer) {
marshalKSUIDToGQL(w, b.KSUID)
}