Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions append_bytes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package duckdb

import "github.com/duckdb/duckdb-go/v2/mapping"

// AppendBytes marks a []byte column value for zero-copy Appender insert.
// The slice must remain valid until Appender.Flush (or the current chunk is committed).
// Plain []byte in AppendRow still uses the copying VectorAssignStringElementLen path.
type AppendBytes []byte

// AppendBytesUnsafe skips UTF-8 validation (caller must provide valid UTF-8 JSON/text).
type AppendBytesUnsafe []byte

// UTF8Bytes is an alias for the bindings UTF8Bytes marker type.
type UTF8Bytes = mapping.UTF8Bytes

// UnsafeUTF8Bytes is an alias for the bindings UnsafeUTF8Bytes marker type.
type UnsafeUTF8Bytes = mapping.UnsafeUTF8Bytes
35 changes: 35 additions & 0 deletions append_bytes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package duckdb

import (
"context"
"testing"

"github.com/stretchr/testify/require"
)

func TestAppendBytes_varcharRoundTrip(t *testing.T) {
c := newConnectorWrapper(t, ":memory:", nil)
defer closeConnectorWrapper(t, c)

conn := openDriverConnWrapper(t, c)
defer closeDriverConnWrapper(t, &conn)

_, err := conn.ExecContext(context.Background(), `CREATE TABLE t (payload VARCHAR)`, nil)
require.NoError(t, err)

a := newAppenderWrapper(t, &conn, "", "t")
defer closeAppenderWrapper(t, a)

payload := []byte(`{"k":"v"}`)
require.NoError(t, a.AppendRow(AppendBytesUnsafe(payload)))
require.NoError(t, a.Close())

rows, err := conn.QueryContext(context.Background(), `SELECT payload FROM t`, nil)
require.NoError(t, err)
defer closeRowsWrapper(t, rows)

require.True(t, rows.Next())
var got string
require.NoError(t, rows.Scan(&got))
require.Equal(t, string(payload), got)
}
12 changes: 9 additions & 3 deletions mapping/mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ const (
TypeVariant = bindings.TypeVariant
)

// UTF8Bytes and UnsafeUTF8Bytes mark zero-copy []byte payloads for vector assign.
type UTF8Bytes = bindings.UTF8Bytes
type UnsafeUTF8Bytes = bindings.UnsafeUTF8Bytes

type State = bindings.State

const (
Expand Down Expand Up @@ -617,9 +621,11 @@ var (
VectorGetData = bindings.VectorGetData
VectorGetValidity = bindings.VectorGetValidity
VectorEnsureValidityWritable = bindings.VectorEnsureValidityWritable
VectorAssignStringElement = bindings.VectorAssignStringElement
VectorAssignStringElementLen = bindings.VectorAssignStringElementLen
ListVectorGetChild = bindings.ListVectorGetChild
VectorAssignStringElement = bindings.VectorAssignStringElement
VectorAssignStringElementLen = bindings.VectorAssignStringElementLen
VectorAssignByteElement = bindings.VectorAssignByteElement
UnsafeVectorAssignStringElementLen = bindings.UnsafeVectorAssignStringElementLen
ListVectorGetChild = bindings.ListVectorGetChild
ListVectorGetSize = bindings.ListVectorGetSize
ListVectorSetSize = bindings.ListVectorSetSize
ListVectorReserve = bindings.ListVectorReserve
Expand Down
20 changes: 20 additions & 0 deletions vector_setters.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,14 @@ func setBytes[S any](vec *vector, rowIdx mapping.IdxT, val S) error {
mapping.VectorAssignStringElementLen(vec.vec, rowIdx, []byte(v))
case []byte:
mapping.VectorAssignStringElementLen(vec.vec, rowIdx, v)
case AppendBytes:
mapping.VectorAssignByteElement(vec.vec, rowIdx, []byte(v))
case AppendBytesUnsafe:
mapping.UnsafeVectorAssignStringElementLen(vec.vec, rowIdx, []byte(v))
case mapping.UTF8Bytes:
mapping.VectorAssignByteElement(vec.vec, rowIdx, []byte(v))
case mapping.UnsafeUTF8Bytes:
mapping.UnsafeVectorAssignStringElementLen(vec.vec, rowIdx, []byte(v))
default:
return castError(reflect.TypeOf(val).String(), reflect.String.String())
}
Expand Down Expand Up @@ -259,6 +267,18 @@ func setBit[S any](vec *vector, rowIdx mapping.IdxT, val S) error {
}

func setJSON[S any](vec *vector, rowIdx mapping.IdxT, val S) error {
switch v := any(val).(type) {
case json.RawMessage:
return setBytes(vec, rowIdx, AppendBytesUnsafe(v))
case AppendBytes:
return setBytes(vec, rowIdx, v)
case AppendBytesUnsafe:
return setBytes(vec, rowIdx, v)
case mapping.UTF8Bytes:
return setBytes(vec, rowIdx, v)
case mapping.UnsafeUTF8Bytes:
return setBytes(vec, rowIdx, v)
}
bytes, err := json.Marshal(val)
if err != nil {
return err
Expand Down