Skip to content

Commit b7cf11c

Browse files
committed
feat(appender): AppendBytes types for zero-copy VARCHAR/JSON columns
Plain []byte in AppendRow keeps VectorAssignStringElementLen (C.CBytes copy). AppendBytes / AppendBytesUnsafe opt into VectorAssignByteElement when available in duckdb-go-bindings (requires duckdb/duckdb-go-bindings#84). json.RawMessage in setJSON maps to AppendBytesUnsafe.
1 parent 8a1cdd7 commit b7cf11c

4 files changed

Lines changed: 81 additions & 3 deletions

File tree

append_bytes.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package duckdb
2+
3+
import "github.com/duckdb/duckdb-go/v2/mapping"
4+
5+
// AppendBytes marks a []byte column value for zero-copy Appender insert.
6+
// The slice must remain valid until Appender.Flush (or the current chunk is committed).
7+
// Plain []byte in AppendRow still uses the copying VectorAssignStringElementLen path.
8+
type AppendBytes []byte
9+
10+
// AppendBytesUnsafe skips UTF-8 validation (caller must provide valid UTF-8 JSON/text).
11+
type AppendBytesUnsafe []byte
12+
13+
// UTF8Bytes is an alias for the bindings UTF8Bytes marker type.
14+
type UTF8Bytes = mapping.UTF8Bytes
15+
16+
// UnsafeUTF8Bytes is an alias for the bindings UnsafeUTF8Bytes marker type.
17+
type UnsafeUTF8Bytes = mapping.UnsafeUTF8Bytes

append_bytes_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package duckdb
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestAppendBytes_varcharRoundTrip(t *testing.T) {
11+
c := newConnectorWrapper(t, ":memory:", nil)
12+
defer closeConnectorWrapper(t, c)
13+
14+
conn := openDriverConnWrapper(t, c)
15+
defer closeDriverConnWrapper(t, &conn)
16+
17+
_, err := conn.ExecContext(context.Background(), `CREATE TABLE t (payload VARCHAR)`, nil)
18+
require.NoError(t, err)
19+
20+
a := newAppenderWrapper(t, &conn, "", "t")
21+
defer closeAppenderWrapper(t, a)
22+
23+
payload := []byte(`{"k":"v"}`)
24+
require.NoError(t, a.AppendRow(AppendBytesUnsafe(payload)))
25+
require.NoError(t, a.Close())
26+
27+
rows, err := conn.QueryContext(context.Background(), `SELECT payload FROM t`, nil)
28+
require.NoError(t, err)
29+
defer closeRowsWrapper(t, rows)
30+
31+
require.True(t, rows.Next())
32+
var got string
33+
require.NoError(t, rows.Scan(&got))
34+
require.Equal(t, string(payload), got)
35+
}

mapping/mapping.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ const (
5151
TypeTimeNS = bindings.TypeTimeNS
5252
)
5353

54+
// UTF8Bytes and UnsafeUTF8Bytes mark zero-copy []byte payloads for vector assign.
55+
type UTF8Bytes = bindings.UTF8Bytes
56+
type UnsafeUTF8Bytes = bindings.UnsafeUTF8Bytes
57+
5458
type State = bindings.State
5559

5660
const (
@@ -613,9 +617,11 @@ var (
613617
VectorGetData = bindings.VectorGetData
614618
VectorGetValidity = bindings.VectorGetValidity
615619
VectorEnsureValidityWritable = bindings.VectorEnsureValidityWritable
616-
VectorAssignStringElement = bindings.VectorAssignStringElement
617-
VectorAssignStringElementLen = bindings.VectorAssignStringElementLen
618-
ListVectorGetChild = bindings.ListVectorGetChild
620+
VectorAssignStringElement = bindings.VectorAssignStringElement
621+
VectorAssignStringElementLen = bindings.VectorAssignStringElementLen
622+
VectorAssignByteElement = bindings.VectorAssignByteElement
623+
UnsafeVectorAssignStringElementLen = bindings.UnsafeVectorAssignStringElementLen
624+
ListVectorGetChild = bindings.ListVectorGetChild
619625
ListVectorGetSize = bindings.ListVectorGetSize
620626
ListVectorSetSize = bindings.ListVectorSetSize
621627
ListVectorReserve = bindings.ListVectorReserve

vector_setters.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,13 +223,33 @@ func setBytes[S any](vec *vector, rowIdx mapping.IdxT, val S) error {
223223
mapping.VectorAssignStringElementLen(vec.vec, rowIdx, []byte(v))
224224
case []byte:
225225
mapping.VectorAssignStringElementLen(vec.vec, rowIdx, v)
226+
case AppendBytes:
227+
mapping.VectorAssignByteElement(vec.vec, rowIdx, []byte(v))
228+
case AppendBytesUnsafe:
229+
mapping.UnsafeVectorAssignStringElementLen(vec.vec, rowIdx, []byte(v))
230+
case mapping.UTF8Bytes:
231+
mapping.VectorAssignByteElement(vec.vec, rowIdx, []byte(v))
232+
case mapping.UnsafeUTF8Bytes:
233+
mapping.UnsafeVectorAssignStringElementLen(vec.vec, rowIdx, []byte(v))
226234
default:
227235
return castError(reflect.TypeOf(val).String(), reflect.String.String())
228236
}
229237
return nil
230238
}
231239

232240
func setJSON[S any](vec *vector, rowIdx mapping.IdxT, val S) error {
241+
switch v := any(val).(type) {
242+
case json.RawMessage:
243+
return setBytes(vec, rowIdx, AppendBytesUnsafe(v))
244+
case AppendBytes:
245+
return setBytes(vec, rowIdx, v)
246+
case AppendBytesUnsafe:
247+
return setBytes(vec, rowIdx, v)
248+
case mapping.UTF8Bytes:
249+
return setBytes(vec, rowIdx, v)
250+
case mapping.UnsafeUTF8Bytes:
251+
return setBytes(vec, rowIdx, v)
252+
}
233253
bytes, err := json.Marshal(val)
234254
if err != nil {
235255
return err

0 commit comments

Comments
 (0)