From cc44931ea979728ecee918e1f416d1272bdee34c Mon Sep 17 00:00:00 2001 From: Elijah Duffy Date: Mon, 29 Jun 2026 18:24:07 -0700 Subject: [PATCH] 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 --- CHANGELOG.md | 1 + doc.go | 7 ++++--- ksuid_binary.go | 14 ++++++++++++++ ksuid_gql.go | 7 +++++++ ksuid_string.go | 14 ++++++++++++++ ksuid_test.go | 29 +++++++++++++++++++++++++++++ 6 files changed, 69 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 702702e..d8eea3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ ### Added +- `StringKSUID.Equal` / `IsZero`, `BinaryKSUID.Equal` / `IsZero`, and nil sentinel vars for [ApplyInterfacePtr]. - Context-aware variants for all query and mutation helpers. - `Delete`, `DeleteAffected`, `WithTx`, `ContainsCol`, and `CurrentDialect()`. - Package documentation (`doc.go`), expanded README, and subpackage docs for `dbxm` / `dbxp`. diff --git a/doc.go b/doc.go index 5329520..a9ae93d 100644 --- a/doc.go +++ b/doc.go @@ -23,9 +23,10 @@ // # Identifier types // // [StringKSUID] and [BinaryKSUID] wrap segmentio/ksuid with storage-specific -// SQL encoding. Both share identical GraphQL string scalar transit. Choose StringKSUID -// for text columns (VARCHAR, TEXT); choose BinaryKSUID for binary columns -// (BINARY(20), BYTEA). +// SQL encoding. Both implement [ApplyInterface] via Equal and IsZero for use +// with [ApplyInterfacePtr]. Both share identical GraphQL string scalar transit. +// Choose StringKSUID for text columns (VARCHAR, TEXT); choose BinaryKSUID for +// binary columns (BINARY(20), BYTEA). // // [JSONB] provides map-based JSON column scanning for Postgres JSONB and MySQL JSON. // diff --git a/ksuid_binary.go b/ksuid_binary.go index c30d043..d305428 100644 --- a/ksuid_binary.go +++ b/ksuid_binary.go @@ -15,6 +15,20 @@ 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()} diff --git a/ksuid_gql.go b/ksuid_gql.go index 8213020..a61437e 100644 --- a/ksuid_gql.go +++ b/ksuid_gql.go @@ -28,3 +28,10 @@ const ( ksuidBinaryLength = 20 ksuidStringLength = 27 ) + +func equalKSUID(lh, rh ksuid.KSUID) bool { + if lh.IsNil() && rh.IsNil() { + return true + } + return lh.String() == rh.String() +} diff --git a/ksuid_string.go b/ksuid_string.go index f1e28db..6278e50 100644 --- a/ksuid_string.go +++ b/ksuid_string.go @@ -15,6 +15,20 @@ type StringKSUID struct { ksuid.KSUID } +// NilStringKSUID is the zero/nil StringKSUID value. +var NilStringKSUID = StringKSUID{KSUID: ksuid.Nil} + +// Equal reports whether two StringKSUID values represent the same identifier, +// treating two nil values as equal. Implements [ApplyInterface]. +func (s StringKSUID) Equal(rh StringKSUID) bool { + return equalKSUID(s.KSUID, rh.KSUID) +} + +// IsZero reports whether s is nil. Implements [ApplyInterface]. +func (s StringKSUID) IsZero() bool { + return s.IsNil() +} + // NewStringKSUID generates a new StringKSUID. func NewStringKSUID() StringKSUID { return StringKSUID{KSUID: ksuid.New()} diff --git a/ksuid_test.go b/ksuid_test.go index 6f5bad5..c93649b 100644 --- a/ksuid_test.go +++ b/ksuid_test.go @@ -6,6 +6,7 @@ import ( "io" "testing" + "github.com/go-jet/jet/v2/mysql" "github.com/segmentio/ksuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -118,6 +119,34 @@ func TestBinaryKSUID_Conversion(t *testing.T) { } +func TestStringKSUID_EqualIsZero(t *testing.T) { + id := NewStringKSUID() + assert.True(t, NilStringKSUID.Equal(NilStringKSUID)) + assert.True(t, id.Equal(id)) + assert.False(t, id.Equal(NilStringKSUID)) + assert.True(t, NilStringKSUID.IsZero()) + assert.False(t, id.IsZero()) +} + +func TestBinaryKSUID_EqualIsZero(t *testing.T) { + id := NewBinaryKSUID() + assert.True(t, NilBinaryKSUID.Equal(NilBinaryKSUID)) + assert.True(t, id.Equal(id)) + assert.False(t, id.Equal(NilBinaryKSUID)) + assert.True(t, NilBinaryKSUID.IsZero()) + assert.False(t, id.IsZero()) +} + +func TestStringKSUID_ApplyInterfacePtr(t *testing.T) { + targetCol := mysql.StringColumn("id") + modified := make(ColumnList, 0) + current := Ptr(NewStringKSUID()) + updated := ApplyInterfacePtr(current, Ptr(NewStringKSUID()), &modified, targetCol) + require.NotNil(t, updated) + assert.Len(t, modified, 1) + assert.False(t, current.Equal(*updated)) +} + func TestParseStringKSUID(t *testing.T) { id := ksuid.New() parsed, err := ParseStringKSUID(id.String())