refactor: use GraphQL string scalar for KSUID transit
Marshal and unmarshal both use the base62 KSUID string, replacing the legacy JSON object wrapper and aligning gqlgen scalar behavior. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
### Breaking changes
|
### Breaking changes
|
||||||
|
|
||||||
- Removed `UUID`, `NewUUID`, and `ParseUUID`.
|
- Removed `UUID`, `NewUUID`, and `ParseUUID`.
|
||||||
- Added `StringKSUID` and `BinaryKSUID` with distinct SQL encodings and shared GraphQL transit (`ksuid_str` JSON field, replacing `uuid_str`).
|
- Added `StringKSUID` and `BinaryKSUID` with distinct SQL encodings and shared GraphQL string scalar transit.
|
||||||
- Removed `DBConfig.AutoMigrate`, `SQLOFunc`, and `JSONB.ToMap()`.
|
- Removed `DBConfig.AutoMigrate`, `SQLOFunc`, and `JSONB.ToMap()`.
|
||||||
- Replaced `Column` / `ColumnList` intersection interfaces with dialect-neutral type aliases.
|
- Replaced `Column` / `ColumnList` intersection interfaces with dialect-neutral type aliases.
|
||||||
- `Apply*` and `Expr*` helpers now accept `Column` / `ColumnList` / `Expression` aliases instead of `mysql.*` types.
|
- `Apply*` and `Expr*` helpers now accept `Column` / `ColumnList` / `Expression` aliases instead of `mysql.*` types.
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ For Postgres, use `dbx.DialectPostgres` and blank-import `dbxp` instead of `dbxm
|
|||||||
| `StringKSUID` | `VARCHAR(27)`, `TEXT` | Base62 string |
|
| `StringKSUID` | `VARCHAR(27)`, `TEXT` | Base62 string |
|
||||||
| `BinaryKSUID` | `BINARY(20)`, `BYTEA` | Raw 20 bytes |
|
| `BinaryKSUID` | `BINARY(20)`, `BYTEA` | Raw 20 bytes |
|
||||||
|
|
||||||
Both types use identical GraphQL JSON transit (`ksuid_str` field). Pick the type that matches your column encoding — Scan rejects ambiguous payloads.
|
Both types use identical GraphQL string scalar transit. Pick the type that matches your column encoding — Scan rejects ambiguous payloads.
|
||||||
|
|
||||||
## Dialect notes
|
## Dialect notes
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
// # Identifier types
|
// # Identifier types
|
||||||
//
|
//
|
||||||
// [StringKSUID] and [BinaryKSUID] wrap segmentio/ksuid with storage-specific
|
// [StringKSUID] and [BinaryKSUID] wrap segmentio/ksuid with storage-specific
|
||||||
// SQL encoding. Both share identical GraphQL string transit. Choose StringKSUID
|
// SQL encoding. Both share identical GraphQL string scalar transit. Choose StringKSUID
|
||||||
// for text columns (VARCHAR, TEXT); choose BinaryKSUID for binary columns
|
// for text columns (VARCHAR, TEXT); choose BinaryKSUID for binary columns
|
||||||
// (BINARY(20), BYTEA).
|
// (BINARY(20), BYTEA).
|
||||||
//
|
//
|
||||||
|
|||||||
+2
-2
@@ -9,8 +9,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// BinaryKSUID wraps segmentio/ksuid for binary-column storage (BINARY(20), BYTEA).
|
// BinaryKSUID wraps segmentio/ksuid for binary-column storage (BINARY(20), BYTEA).
|
||||||
// SQL Scan/Value use the raw 20-byte encoding. GraphQL transit uses the shared
|
// SQL Scan/Value use the raw 20-byte encoding. GraphQL transit uses a string
|
||||||
// string JSON transport (see ksuid_gql.go).
|
// scalar (base62 KSUID).
|
||||||
type BinaryKSUID struct {
|
type BinaryKSUID struct {
|
||||||
ksuid.KSUID
|
ksuid.KSUID
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-12
@@ -1,17 +1,13 @@
|
|||||||
package dbx
|
package dbx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/segmentio/ksuid"
|
"github.com/segmentio/ksuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ksuidGQLTransport struct {
|
|
||||||
KSUIDStr string `json:"ksuid_str"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalKSUIDFromGQL(value any, unmarshal func([]byte) error) error {
|
func unmarshalKSUIDFromGQL(value any, unmarshal func([]byte) error) error {
|
||||||
str, ok := value.(string)
|
str, ok := value.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -20,14 +16,11 @@ func unmarshalKSUIDFromGQL(value any, unmarshal func([]byte) error) error {
|
|||||||
return unmarshal([]byte(str))
|
return unmarshal([]byte(str))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// marshalKSUIDToGQL writes a JSON-encoded string scalar for gqlgen.
|
||||||
func marshalKSUIDToGQL(w io.Writer, id ksuid.KSUID) {
|
func marshalKSUIDToGQL(w io.Writer, id ksuid.KSUID) {
|
||||||
transport := ksuidGQLTransport{KSUIDStr: id.String()}
|
quoted := strconv.Quote(id.String())
|
||||||
data, err := json.Marshal(transport)
|
if _, err := io.WriteString(w, quoted); err != nil {
|
||||||
if err != nil {
|
panic(fmt.Errorf("GraphQL failed to write KSUID value: %w", err))
|
||||||
panic(fmt.Errorf("GraphQL failed to JSON-marshal KSUID value: %s", err))
|
|
||||||
}
|
|
||||||
if _, err := w.Write(data); err != nil {
|
|
||||||
panic(fmt.Errorf("GraphQL failed to write KSUID value: %s", string(data)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -9,8 +9,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// StringKSUID wraps segmentio/ksuid for text-column storage (VARCHAR, TEXT).
|
// StringKSUID wraps segmentio/ksuid for text-column storage (VARCHAR, TEXT).
|
||||||
// SQL Scan/Value use the base62 string encoding. GraphQL transit uses the
|
// SQL Scan/Value use the base62 string encoding. GraphQL transit uses a string
|
||||||
// shared string JSON transport (see ksuid_gql.go).
|
// scalar (base62 KSUID).
|
||||||
type StringKSUID struct {
|
type StringKSUID struct {
|
||||||
ksuid.KSUID
|
ksuid.KSUID
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-20
@@ -3,6 +3,7 @@ package dbx
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/segmentio/ksuid"
|
"github.com/segmentio/ksuid"
|
||||||
@@ -50,16 +51,32 @@ func TestStringKSUID_Conversion(t *testing.T) {
|
|||||||
|
|
||||||
func TestStringKSUID_GQL(t *testing.T) {
|
func TestStringKSUID_GQL(t *testing.T) {
|
||||||
id := NewStringKSUID()
|
id := NewStringKSUID()
|
||||||
|
wire := assertKSUIDGQLMarshal(t, id)
|
||||||
|
|
||||||
|
var parsed StringKSUID
|
||||||
|
require.NoError(t, parsed.UnmarshalGQL(wire))
|
||||||
|
assert.Equal(t, id, parsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBinaryKSUID_GQL(t *testing.T) {
|
||||||
|
id := NewBinaryKSUID()
|
||||||
|
wire := assertKSUIDGQLMarshal(t, id)
|
||||||
|
|
||||||
|
var parsed BinaryKSUID
|
||||||
|
require.NoError(t, parsed.UnmarshalGQL(wire))
|
||||||
|
assert.Equal(t, id, parsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertKSUIDGQLMarshal(t *testing.T, id interface{ String() string; MarshalGQL(w io.Writer) }) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
id.MarshalGQL(&buf)
|
id.MarshalGQL(&buf)
|
||||||
|
|
||||||
var transport ksuidGQLTransport
|
var wire string
|
||||||
require.NoError(t, json.Unmarshal(buf.Bytes(), &transport))
|
require.NoError(t, json.Unmarshal(buf.Bytes(), &wire))
|
||||||
assert.Equal(t, id.String(), transport.KSUIDStr)
|
assert.Equal(t, id.String(), wire)
|
||||||
|
return wire
|
||||||
var parsed StringKSUID
|
|
||||||
require.NoError(t, parsed.UnmarshalGQL(id.String()))
|
|
||||||
assert.Equal(t, id, parsed)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBinaryKSUID_ScanValue(t *testing.T) {
|
func TestBinaryKSUID_ScanValue(t *testing.T) {
|
||||||
@@ -100,19 +117,6 @@ func TestBinaryKSUID_Conversion(t *testing.T) {
|
|||||||
assert.Equal(t, b, s.AsBinaryKSUID())
|
assert.Equal(t, b, s.AsBinaryKSUID())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBinaryKSUID_GQL(t *testing.T) {
|
|
||||||
id := NewBinaryKSUID()
|
|
||||||
var buf bytes.Buffer
|
|
||||||
id.MarshalGQL(&buf)
|
|
||||||
|
|
||||||
var transport ksuidGQLTransport
|
|
||||||
require.NoError(t, json.Unmarshal(buf.Bytes(), &transport))
|
|
||||||
assert.Equal(t, id.String(), transport.KSUIDStr)
|
|
||||||
|
|
||||||
var parsed BinaryKSUID
|
|
||||||
require.NoError(t, parsed.UnmarshalGQL(id.String()))
|
|
||||||
assert.Equal(t, id, parsed)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseStringKSUID(t *testing.T) {
|
func TestParseStringKSUID(t *testing.T) {
|
||||||
id := ksuid.New()
|
id := ksuid.New()
|
||||||
|
|||||||
Reference in New Issue
Block a user