package ffi
import "C"
import (
"errors"
"fmt"
"runtime"
"unsafe"
)
var errFreeingValue = errors.New("unexpected error while freeing value")
type Borrower interface {
BorrowedBytes() []byte
CopiedBytes() []byte
Free() error
}
var _ Borrower = (*ownedBytes)(nil)
func newBorrowedBytes(slice []byte, pinner *runtime.Pinner) C.BorrowedBytes {
ptr := unsafe.SliceData(slice)
sliceLen := len(slice)
if ptr == nil {
return C.BorrowedBytes{ptr: nil, len: 0}
}
if sliceLen > 0 {
pinner.Pin(ptr)
}
return C.BorrowedBytes{
ptr: (*C.uint8_t)(ptr),
len: C.size_t(sliceLen),
}
}
func newCBatchOp(op BatchOp, pinner *runtime.Pinner) C.BatchOp {
var cOp C.BatchOp
cOp.tag = op.tag
switch op.tag {
case C.BatchOp_Put:
*(*C.BatchOp_Put_Body)(unsafe.Pointer(&cOp.anon0)) = C.BatchOp_Put_Body{
key: newBorrowedBytes(op.key, pinner),
value: newBorrowedBytes(op.value, pinner),
}
case C.BatchOp_Delete:
*(*C.BatchOp_Delete_Body)(unsafe.Pointer(&cOp.anon0)) = C.BatchOp_Delete_Body{
key: newBorrowedBytes(op.key, pinner),
}
case C.BatchOp_DeleteRange:
*(*C.BatchOp_DeleteRange_Body)(unsafe.Pointer(&cOp.anon0)) = C.BatchOp_DeleteRange_Body{
prefix: newBorrowedBytes(op.key, pinner),
}
}
return cOp
}
func newBorrowedBatchOps(ops []C.BatchOp, pinner *runtime.Pinner) C.BorrowedBatchOps {
sliceLen := len(ops)
if sliceLen == 0 {
return C.BorrowedBatchOps{ptr: nil, len: 0}
}
ptr := unsafe.SliceData(ops)
if ptr == nil {
return C.BorrowedBatchOps{ptr: nil, len: 0}
}
pinner.Pin(ptr)
return C.BorrowedBatchOps{
ptr: ptr,
len: C.size_t(sliceLen),
}
}
func newKeyValuePairsFromBatch(batch []BatchOp, pinner *runtime.Pinner) C.BorrowedBatchOps {
if len(batch) == 0 {
return C.BorrowedBatchOps{ptr: nil, len: 0}
}
ops := make([]C.BatchOp, len(batch))
for i, op := range batch {
ops[i] = newCBatchOp(op, pinner)
}
return newBorrowedBatchOps(ops, pinner)
}
type ownedBytes struct {
owned C.OwnedBytes
}
func (b *ownedBytes) Free() error {
if b.owned.ptr == nil {
return nil
}
if err := getErrorFromVoidResult(C.fwd_free_owned_bytes(b.owned)); err != nil {
return fmt.Errorf("%w: %w", errFreeingValue, err)
}
b.owned = C.OwnedBytes{}
return nil
}
func (b *ownedBytes) BorrowedBytes() []byte {
if b.owned.ptr == nil {
return nil
}
return unsafe.Slice((*byte)(b.owned.ptr), b.owned.len)
}
func (b *ownedBytes) CopiedBytes() []byte {
if b.owned.ptr == nil {
return nil
}
return C.GoBytes(unsafe.Pointer(b.owned.ptr), C.int(b.owned.len))
}
func (b *ownedBytes) intoError() error {
if b.owned.ptr == nil {
return nil
}
err := errors.New(string(b.CopiedBytes()))
if err2 := b.Free(); err2 != nil {
return fmt.Errorf("%w: %w (original error: %w)", errFreeingValue, err, err2)
}
return err
}
func newOwnedBytes(owned C.OwnedBytes) *ownedBytes {
return &ownedBytes{owned: owned}
}
func getHashKeyFromHashResult(result C.HashResult) (Hash, error) {
switch result.tag {
case C.HashResult_NullHandlePointer:
return EmptyRoot, errDBClosed
case C.HashResult_None:
return EmptyRoot, nil
case C.HashResult_Some:
cHashKey := (*C.HashKey)(unsafe.Pointer(&result.anon0))
hashKey := *(*Hash)(unsafe.Pointer(&cHashKey._0))
return hashKey, nil
case C.HashResult_Err:
ownedBytes := newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0)))
return EmptyRoot, ownedBytes.intoError()
default:
return EmptyRoot, fmt.Errorf("unknown C.HashResult tag: %d", result.tag)
}
}
func getErrorFromVoidResult(result C.VoidResult) error {
switch result.tag {
case C.VoidResult_NullHandlePointer:
return errDBClosed
case C.VoidResult_Ok:
return nil
case C.VoidResult_Err:
return newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))).intoError()
default:
return fmt.Errorf("unknown C.VoidResult tag: %d", result.tag)
}
}
func getValueFromValueResult(result C.ValueResult) ([]byte, error) {
switch result.tag {
case C.ValueResult_NullHandlePointer:
return nil, errDBClosed
case C.ValueResult_RevisionNotFound:
return nil, errRevisionNotFound
case C.ValueResult_None:
return nil, nil
case C.ValueResult_Some:
ownedBytes := newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0)))
bytes := ownedBytes.CopiedBytes()
if err := ownedBytes.Free(); err != nil {
return nil, fmt.Errorf("%w: %w", errFreeingValue, err)
}
return bytes, nil
case C.ValueResult_Err:
err := newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))).intoError()
return nil, err
default:
return nil, fmt.Errorf("unknown C.ValueResult tag: %d", result.tag)
}
}
type ownedKeyValueBatch struct {
owned C.OwnedKeyValueBatch
}
func (b *ownedKeyValueBatch) copy() []*ownedKeyValue {
if b.owned.ptr == nil {
return nil
}
borrowed := b.borrow()
copied := make([]*ownedKeyValue, len(borrowed))
for i, borrow := range borrowed {
copied[i] = newOwnedKeyValue(borrow)
}
return copied
}
func (b *ownedKeyValueBatch) borrow() []C.OwnedKeyValuePair {
if b.owned.ptr == nil {
return nil
}
return unsafe.Slice((*C.OwnedKeyValuePair)(unsafe.Pointer(b.owned.ptr)), b.owned.len)
}
func (b *ownedKeyValueBatch) free() error {
if b == nil || b.owned.ptr == nil {
return nil
}
if err := getErrorFromVoidResult(C.fwd_free_owned_key_value_batch(b.owned)); err != nil {
return fmt.Errorf("%w: %w", errFreeingValue, err)
}
b.owned = C.OwnedKeyValueBatch{}
return nil
}
func newOwnedKeyValueBatch(owned C.OwnedKeyValueBatch) *ownedKeyValueBatch {
return &ownedKeyValueBatch{
owned: owned,
}
}
type ownedKeyValue struct {
owned C.OwnedKeyValuePair
key *ownedBytes
value *ownedBytes
}
func (kv *ownedKeyValue) copy() ([]byte, []byte) {
key := kv.key.CopiedBytes()
value := kv.value.CopiedBytes()
return key, value
}
func (kv *ownedKeyValue) free() error {
if kv == nil {
return nil
}
if err := getErrorFromVoidResult(C.fwd_free_owned_kv_pair(kv.owned)); err != nil {
return fmt.Errorf("%w: %w", errFreeingValue, err)
}
kv.owned = C.OwnedKeyValuePair{}
kv.key = nil
kv.value = nil
return nil
}
func newOwnedKeyValue(owned C.OwnedKeyValuePair) *ownedKeyValue {
return &ownedKeyValue{
owned: owned,
key: newOwnedBytes(owned.key),
value: newOwnedBytes(owned.value),
}
}
func getKeyValueFromResult(result C.KeyValueResult) (*ownedKeyValue, error) {
switch result.tag {
case C.KeyValueResult_NullHandlePointer:
return nil, errDBClosed
case C.KeyValueResult_None:
return nil, nil
case C.KeyValueResult_Some:
ownedKvp := newOwnedKeyValue(*(*C.OwnedKeyValuePair)(unsafe.Pointer(&result.anon0)))
return ownedKvp, nil
case C.KeyValueResult_Err:
err := newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))).intoError()
return nil, err
default:
return nil, fmt.Errorf("unknown C.KeyValueResult tag: %d", result.tag)
}
}
func getKeyValueBatchFromResult(result C.KeyValueBatchResult) (*ownedKeyValueBatch, error) {
switch result.tag {
case C.KeyValueBatchResult_NullHandlePointer:
return nil, errDBClosed
case C.KeyValueBatchResult_Some:
ownedBatch := newOwnedKeyValueBatch(*(*C.OwnedKeyValueBatch)(unsafe.Pointer(&result.anon0)))
return ownedBatch, nil
case C.KeyValueBatchResult_Err:
err := newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))).intoError()
return nil, err
default:
return nil, fmt.Errorf("unknown C.KeyValueBatchResult tag: %d", result.tag)
}
}
func getDatabaseFromHandleResult(result C.HandleResult) (*Database, error) {
switch result.tag {
case C.HandleResult_Ok:
ptr := *(**C.DatabaseHandle)(unsafe.Pointer(&result.anon0))
db := &Database{handle: ptr}
return db, nil
case C.HandleResult_Err:
err := newOwnedBytes(*(*C.OwnedBytes)(unsafe.Pointer(&result.anon0))).intoError()
return nil, err
default:
return nil, fmt.Errorf("unknown C.HandleResult tag: %d", result.tag)
}
}
func newCHashKey(hash Hash) C.HashKey {
return *(*C.HashKey)(unsafe.Pointer(&hash))
}