Codebase list golang-github-pzhin-go-sophia / b651ed5c-2de9-4f3b-be92-7c572e27f8c9/main upsert.go
b651ed5c-2de9-4f3b-be92-7c572e27f8c9/main

Tree @b651ed5c-2de9-4f3b-be92-7c572e27f8c9/main (Download .tar.gz)

upsert.go @b651ed5c-2de9-4f3b-be92-7c572e27f8c9/mainraw · history · blame

package sophia

import (
	"reflect"
	"sync"
	"unsafe"
)

/*
#include <inttypes.h>
#include <stdio.h>
extern void goUpsertCall(int count,
		char **src,    uint32_t *src_size,
		char **upsert, uint32_t *upsert_size,
		char **result, uint32_t *result_size,
		void *arg);
*/
import "C"

// keyUpsertTemplate template for upsert settings key
const (
	keyUpsertTemplate    = "db.%v.upsert"
	keyUpsertArgTemplate = "db.%v.upsert_arg"
)

// upsertFunc golang binding to upsert_callback.
// It is a wrapper for UpsertFunc, that converts C types to golang ones
type upsertFunc func(count C.int,
	src **C.char, srcSize *C.uint32_t,
	upsert **C.char, upsertSize *C.uint32_t,
	result **C.char, resultSize *C.uint32_t,
	arg unsafe.Pointer) C.int

// UpsertFunc golang equivalent of upsert_callback.
// Should return 0 in case of success, otherwise -1.
type UpsertFunc func(count int,
	src []unsafe.Pointer, srcSize []uint32,
	upsert []unsafe.Pointer, upsertSize []uint32,
	result []unsafe.Pointer, resultSize []uint32,
	arg unsafe.Pointer) int

//export goUpsertCall
func goUpsertCall(count C.int,
	src **C.char, src_size *C.uint32_t,
	upsert **C.char, upsert_size *C.uint32_t,
	result **C.char, result_size *C.uint32_t,
	arg unsafe.Pointer) {

	index := (*int)(arg)
	fn := getUpsert(index)
	upsertArg := getUpsertArg(index)
	fn(count, src, src_size, upsert, upsert_size, result, result_size, upsertArg)
}

var upsertMap = make(map[*int]upsertFunc)
var upsertMu sync.RWMutex
var upsertIndex int

var upsertArgMap = make(map[*int]unsafe.Pointer)
var upsertArgMu sync.RWMutex

func getUpsertArg(index *int) unsafe.Pointer {
	upsertArgMu.RLock()
	defer upsertArgMu.RUnlock()
	return upsertArgMap[index]
}

func registerUpsertArg(index *int, arg interface{}) {
	upsertArgMu.Lock()
	defer upsertArgMu.Unlock()
	if arg == nil {
		return
	}
	val := reflect.ValueOf(arg)
	if val.CanAddr() {
		upsertArgMap[index] = unsafe.Pointer(val.Pointer())
		return
	}

	switch val.Kind() {
	case reflect.String:
		str := val.String()
		upsertArgMap[index] = unsafe.Pointer(&str)
	case reflect.Int, reflect.Int64, reflect.Int8, reflect.Int16, reflect.Int32:
		i := val.Int()
		upsertArgMap[index] = unsafe.Pointer(&i)
	case reflect.Uint, reflect.Uint64, reflect.Uint8, reflect.Uint16, reflect.Uint32:
		i := val.Uint()
		upsertArgMap[index] = unsafe.Pointer(&i)
	}
}

func registerUpsert(upsertFunc UpsertFunc) (unsafe.Pointer, *int) {
	upsertMu.Lock()
	defer upsertMu.Unlock()

	index := upsertIndex
	upsertIndex++
	indexPtr := &index

	upsertMap[indexPtr] = func(count C.int,
		srcC **C.char, srcSizesC *C.uint32_t,
		upsertC **C.char, upsertSizesC *C.uint32_t,
		resultC **C.char, resultSizesC *C.uint32_t,
		arg unsafe.Pointer) C.int {

		countN := int(count)

		// We receive C pointer to pointer which can be interpreted as an array of pointers.
		// Here we cast C pointer to pointer to Go slice of pointers.
		upsertSizes := (*[16]uint32)(unsafe.Pointer(upsertSizesC))[:countN]
		resultSizes := (*[16]uint32)(unsafe.Pointer(resultSizesC))[:countN]

		upsert := (*[16]unsafe.Pointer)(unsafe.Pointer(upsertC))[:countN]
		result := (*[16]unsafe.Pointer)(unsafe.Pointer(resultC))[:countN]

		var src []unsafe.Pointer
		var srcSizes []uint32
		if srcC != nil {
			srcSizes = (*[16]uint32)(unsafe.Pointer(srcSizesC))[:countN]
			src = (*[16]unsafe.Pointer)(unsafe.Pointer(srcC))[:countN]
		}

		res := upsertFunc(countN,
			src, srcSizes,
			upsert, upsertSizes,
			result, resultSizes,
			arg)

		return C.int(res)
	}
	ptr := C.goUpsertCall
	return ptr, indexPtr
}

func getUpsert(index *int) upsertFunc {
	upsertMu.RLock()
	defer upsertMu.RUnlock()
	return upsertMap[index]
}

func unregisterUpsert(index *int) {
	upsertMu.Lock()
	defer upsertMu.Unlock()
	delete(upsertMap, index)
}