Skip to content

Commit d2ed1bb

Browse files
committed
Marshal missing UDT fields as null instead of failing
We can't return an error in case a field is added to the UDT, otherwise existing code would break by simply altering the UDT in the database. For extra fields at the end of the UDT, we can either omit them or put nulls. The Java driver[1] and Python driver[2] serialize nulls when they don't have a value for a field even for fields in the middle, so let's do that. This behaviour matches even gocql when serializing structs. [1] https://github.com/datastax/java-driver/blob/ef56d561d97adcae48e0e6e8807f334aedc0d783/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodec.java#L86 [2] https://github.com/datastax/python-driver/blob/15d715f4e686032b02ce785eca1d176d2b25e32b/cassandra/cqltypes.py#L1036
1 parent 9185ce1 commit d2ed1bb

File tree

2 files changed

+101
-10
lines changed

2 files changed

+101
-10
lines changed

marshal.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2210,13 +2210,15 @@ func marshalUDT(info TypeInfo, value interface{}) ([]byte, error) {
22102210
var buf []byte
22112211
for _, e := range udt.Elements {
22122212
val, ok := v[e.Name]
2213-
if !ok {
2214-
return nil, marshalErrorf("marshal missing map key %q", e.Name)
2215-
}
22162213

2217-
data, err := Marshal(e.Type, val)
2218-
if err != nil {
2219-
return nil, err
2214+
var data []byte
2215+
2216+
if ok {
2217+
var err error
2218+
data, err = Marshal(e.Type, val)
2219+
if err != nil {
2220+
return nil, err
2221+
}
22202222
}
22212223

22222224
buf = appendBytes(buf, data)

marshal_test.go

Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2003,9 +2003,29 @@ func TestMarshalUDTMap(t *testing.T) {
20032003
"y": 2,
20042004
"z": 3,
20052005
}
2006-
me := MarshalError(`marshal missing map key "x"`)
2007-
if _, err := Marshal(typeInfo, value); err != me {
2008-
t.Errorf("got error %#v, want %#v", err, me)
2006+
expected := []byte("\xff\xff\xff\xff\x00\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\x04\x00\x00\x00\x03")
2007+
2008+
data, err := Marshal(typeInfo, value)
2009+
if err != nil {
2010+
t.Errorf("got error %#v", err)
2011+
}
2012+
if !bytes.Equal(data, expected) {
2013+
t.Errorf("got value %x", data)
2014+
}
2015+
})
2016+
t.Run("partially bound from the beginning", func(t *testing.T) {
2017+
value := map[string]interface{}{
2018+
"x": 1,
2019+
"y": 2,
2020+
}
2021+
expected := []byte("\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00\x02\xff\xff\xff\xff")
2022+
2023+
data, err := Marshal(typeInfo, value)
2024+
if err != nil {
2025+
t.Errorf("got error %#v", err)
2026+
}
2027+
if !bytes.Equal(data, expected) {
2028+
t.Errorf("got value %x", data)
20092029
}
20102030
})
20112031
t.Run("fully bound", func(t *testing.T) {
@@ -2021,7 +2041,76 @@ func TestMarshalUDTMap(t *testing.T) {
20212041
t.Errorf("got error %#v", err)
20222042
}
20232043
if !bytes.Equal(data, expected) {
2024-
t.Errorf("got error %x", data)
2044+
t.Errorf("got value %x", data)
2045+
}
2046+
})
2047+
}
2048+
2049+
func TestMarshalUDTStruct(t *testing.T) {
2050+
typeInfo := UDTTypeInfo{NativeType{proto: 3, typ: TypeUDT}, "", "xyz", []UDTField{
2051+
{Name: "x", Type: NativeType{proto: 3, typ: TypeInt}},
2052+
{Name: "y", Type: NativeType{proto: 3, typ: TypeInt}},
2053+
{Name: "z", Type: NativeType{proto: 3, typ: TypeInt}},
2054+
}}
2055+
2056+
type xyzStruct struct {
2057+
X int32 `cql:"x"`
2058+
Y int32 `cql:"y"`
2059+
Z int32 `cql:"z"`
2060+
}
2061+
type xyStruct struct {
2062+
X int32 `cql:"x"`
2063+
Y int32 `cql:"y"`
2064+
}
2065+
type yzStruct struct {
2066+
Y int32 `cql:"y"`
2067+
Z int32 `cql:"z"`
2068+
}
2069+
2070+
t.Run("partially bound", func(t *testing.T) {
2071+
value := yzStruct{
2072+
Y: 2,
2073+
Z: 3,
2074+
}
2075+
expected := []byte("\xff\xff\xff\xff\x00\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\x04\x00\x00\x00\x03")
2076+
2077+
data, err := Marshal(typeInfo, value)
2078+
if err != nil {
2079+
t.Errorf("got error %#v", err)
2080+
}
2081+
if !bytes.Equal(data, expected) {
2082+
t.Errorf("got value %x", data)
2083+
}
2084+
})
2085+
t.Run("partially bound from the beginning", func(t *testing.T) {
2086+
value := xyStruct{
2087+
X: 1,
2088+
Y: 2,
2089+
}
2090+
expected := []byte("\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00\x02\xff\xff\xff\xff")
2091+
2092+
data, err := Marshal(typeInfo, value)
2093+
if err != nil {
2094+
t.Errorf("got error %#v", err)
2095+
}
2096+
if !bytes.Equal(data, expected) {
2097+
t.Errorf("got value %x", data)
2098+
}
2099+
})
2100+
t.Run("fully bound", func(t *testing.T) {
2101+
value := xyzStruct{
2102+
X: 1,
2103+
Y: 2,
2104+
Z: 3,
2105+
}
2106+
expected := []byte("\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\x04\x00\x00\x00\x03")
2107+
2108+
data, err := Marshal(typeInfo, value)
2109+
if err != nil {
2110+
t.Errorf("got error %#v", err)
2111+
}
2112+
if !bytes.Equal(data, expected) {
2113+
t.Errorf("got value %x", data)
20252114
}
20262115
})
20272116
}

0 commit comments

Comments
 (0)