Skip to content

Commit 9724042

Browse files
Merge pull request #1649 from rbroggi/issue_1648
bug: deserializing into nullable field
2 parents 5343347 + d46b80e commit 9724042

File tree

2 files changed

+115
-1
lines changed

2 files changed

+115
-1
lines changed

lib/column/tuple.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,15 @@ func (col *Tuple) scanStruct(targetStruct reflect.Value, row int) error {
366366
}
367367
sField.Set(subSlice)
368368
default:
369-
value := reflect.ValueOf(c.Row(row, false))
369+
// --- PROPOSED FIX ---
370+
v := c.Row(row, false)
371+
// If the database value is NULL, the corresponding struct field
372+
// is already its zero-value (e.g. nil for a pointer), which is correct.
373+
if v == nil {
374+
continue
375+
}
376+
// Only create a reflect.Value if v is not nil.
377+
value := reflect.ValueOf(v)
370378
if err := setJSONFieldValue(sField, value); err != nil {
371379
return err
372380
}

tests/scan_struct_test.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,109 @@ func TestSelectScanStruct(t *testing.T) {
9999
}
100100
})
101101
}
102+
103+
func TestArrayTupleNullableFieldPanic(t *testing.T) {
104+
TestProtocols(t, func(t *testing.T, protocol clickhouse.Protocol) {
105+
conn, err := GetNativeConnection(t, protocol, nil, nil, &clickhouse.Compression{
106+
Method: clickhouse.CompressionLZ4,
107+
})
108+
require.NoError(t, err)
109+
110+
ctx := context.Background()
111+
require.NoError(t, conn.Exec(ctx, `
112+
CREATE TABLE test_table (
113+
c1 Int32,
114+
c2 Array(Tuple(c3 String, c4 Nullable(Int32)))
115+
) Engine MergeTree() ORDER BY tuple()
116+
`))
117+
defer func() {
118+
conn.Exec(ctx, "DROP TABLE IF EXISTS test_table")
119+
}()
120+
type subRowType struct {
121+
Col3 string `ch:"c3"`
122+
// THIS FIELD causes PANIC IF IT'S NULLABLE
123+
Col4 *int32 `ch:"c4"`
124+
}
125+
type rowType struct {
126+
Col1 int32 `ch:"c1"`
127+
Col2 []subRowType `ch:"c2"`
128+
}
129+
nonNull := int32(42)
130+
element1 := rowType{
131+
Col1: 1,
132+
Col2: []subRowType{
133+
{Col3: "a", Col4: &nonNull},
134+
{Col3: "b", Col4: nil},
135+
},
136+
}
137+
138+
batch, err := conn.PrepareBatch(ctx, "INSERT INTO test_table")
139+
require.NoError(t, err)
140+
require.NoError(t, batch.AppendStruct(&element1))
141+
require.NoError(t, batch.Send())
142+
143+
rows, err := conn.Query(ctx, "SELECT c1, c2 FROM test_table;")
144+
require.NoError(t, err)
145+
var results []rowType
146+
for rows.Next() {
147+
var result rowType
148+
assert.NoError(t, rows.ScanStruct(&result))
149+
results = append(results, result)
150+
}
151+
require.ElementsMatch(t, results, []rowType{element1})
152+
require.NoError(t, rows.Close())
153+
require.NoError(t, rows.Err())
154+
})
155+
}
156+
157+
func TestArrayTupleNonNull(t *testing.T) {
158+
TestProtocols(t, func(t *testing.T, protocol clickhouse.Protocol) {
159+
conn, err := GetNativeConnection(t, protocol, nil, nil, &clickhouse.Compression{
160+
Method: clickhouse.CompressionLZ4,
161+
})
162+
require.NoError(t, err)
163+
164+
ctx := context.Background()
165+
require.NoError(t, conn.Exec(ctx, `
166+
CREATE TABLE test_table (
167+
c1 Int32,
168+
c2 Array(Tuple(c3 String, c4 Int32))
169+
) Engine MergeTree() ORDER BY tuple()
170+
`))
171+
defer func() {
172+
conn.Exec(ctx, "DROP TABLE IF EXISTS test_table")
173+
}()
174+
type subRowType struct {
175+
Col3 string `ch:"c3"`
176+
Col4 int32 `ch:"c4"`
177+
}
178+
type rowType struct {
179+
Col1 int32 `ch:"c1"`
180+
Col2 []subRowType `ch:"c2"`
181+
}
182+
element1 := rowType{
183+
Col1: 1,
184+
Col2: []subRowType{
185+
{Col3: "a", Col4: 42},
186+
{Col3: "b", Col4: 52},
187+
},
188+
}
189+
190+
batch, err := conn.PrepareBatch(ctx, "INSERT INTO test_table")
191+
require.NoError(t, err)
192+
require.NoError(t, batch.AppendStruct(&element1))
193+
require.NoError(t, batch.Send())
194+
195+
rows, err := conn.Query(ctx, "SELECT c1, c2 FROM test_table;")
196+
require.NoError(t, err)
197+
var results []rowType
198+
for rows.Next() {
199+
var result rowType
200+
assert.NoError(t, rows.ScanStruct(&result))
201+
results = append(results, result)
202+
}
203+
require.ElementsMatch(t, results, []rowType{element1})
204+
require.NoError(t, rows.Close())
205+
require.NoError(t, rows.Err())
206+
})
207+
}

0 commit comments

Comments
 (0)