//////////////////////////////////////////////////////////////////
// //
// Copyright (c) 2018-2025 YottaDB LLC and/or its subsidiaries. //
// All rights reserved. //
// //
// This source code contains the intellectual property //
// of its copyright holder(s), and is made available //
// under a license. If you do not know the terms of //
// the license, please stop and do not read further. //
// //
//////////////////////////////////////////////////////////////////
package yottadb
import (
"fmt"
"io"
"os"
"runtime"
"sync/atomic"
"unsafe"
)
// #include <stdlib.h>
// #include <string.h>
// #include "libyottadb.h"
import "C"
// BufferT is a Go structure that serves as an anchor point for a C allocated ydb_buffer_t structure used
// to call the YottaDB C Simple APIs. Because this structure's contents contain pointers to C allocated storage,
// this structure is NOT safe for concurrent access.
type BufferT struct {
cbuft *internalBufferT // Allows BufferT to be copied via assignment without causing double free problems
ownsBuff bool // If true, we should clean the cbuft when Free'd
}
type internalBufferT struct {
cbuft *C.ydb_buffer_t // C flavor of the ydb_buffer_t struct
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Data manipulation methods for BufferT
//
////////////////////////////////////////////////////////////////////////////////////////////////////
// bufferTFromPtr sets this BufferT internal structure to point to the given buffer.
//
// Intended for use by functions implementing transaction logic, the method sets cbuft in the BufferT structure to errstr.
//
// Note: Modifying errstr, or accessing memory it references may lead to code that behaves unpredictably and is hard to debug. Always
// "wrap" it using bufferTFromPtr() and use the methods for the BufferT structure.
func (buft *BufferT) bufferTFromPtr(pointer unsafe.Pointer) {
if nil == buft {
panic("YDB: *BufferT receiver of BufferTFromPtr() cannot be nil")
}
// Note that we don't set a Finalizer here because another process has already done
// so under a different BufferT; the lifespan of this object must be a subset of that
// object
buft.cbuft = &internalBufferT{(*C.ydb_buffer_t)(pointer)}
buft.ownsBuff = false
}
// Alloc is a method to allocate the ydb_buffer_t C storage and allocate or re-allocate the buffer pointed
// to by that struct.
//
// It allocates a buffer in YottaDB heap space of size nBytes; and a C.ydb_buffer_t structure, also in YottaDB heap space, with
// its buf_addr referencing the buffer, its len_alloc set to nBytes and its len_used set to zero. Set cbuft in the BufferT
// structure to reference the C.ydb_buffer_t structure.
func (buft *BufferT) Alloc(nBytes uint32) {
var cbuftptr *C.ydb_buffer_t
printEntry("BufferT.Alloc()")
if nil == buft {
panic("YDB: *BufferT receiver of Alloc() cannot be nil")
}
// Allocate a C flavor ydb_buffer_t struct to pass to simpleAPI
buft.cbuft = &internalBufferT{(*C.ydb_buffer_t)(allocMem(C.size_t(C.sizeof_ydb_buffer_t)))}
cbuftptr = buft.getCPtr()
cbuftptr.len_used = 0
cbuftptr.len_alloc = C.uint(nBytes)
cbuftptr.buf_addr = nil
// Allocate a new buffer of the given size; if size is 0, we just leave it as nil
if 0 < nBytes {
cbuftptr.buf_addr = (*C.char)(allocMem(C.size_t(nBytes)))
}
buft.ownsBuff = true
// Set a finalizer
runtime.SetFinalizer(buft.cbuft, func(o *internalBufferT) {
o.Free()
})
}
// Dump is a method to dump the contents of a BufferT block for debugging purposes.
//
// For debugging purposes, dump on stdout:
//
// - cbuft as a hexadecimal address;
//
// - for the C.ydb_buffer_t structure referenced by cbuft: buf_addr as a hexadecimal address, and len_alloc and len_used as integers; and
//
// - at the address buf_addr, the lower of len_used or len_alloc bytes in zwrite format.
func (buft *BufferT) Dump() {
if nil == buft {
panic("YDB: *BufferT receiver of Dump() cannot be nil")
}
buft.DumpToWriter(os.Stdout)
}
// DumpToWriter dumps a textual representation of this buffer to the writer
func (buft *BufferT) DumpToWriter(writer io.Writer) {
printEntry("BufferT.Dump()")
if nil == buft {
panic("YDB: *BufferT receiver of DumpToWriter() cannot be nil")
}
cbuftptr := buft.getCPtr()
fmt.Fprintf(writer, "BufferT.Dump(): cbuftptr: %p", cbuftptr)
if nil != cbuftptr {
fmt.Fprintf(writer, ", buf_addr: %v, len_alloc: %v, len_used: %v", cbuftptr.buf_addr,
cbuftptr.len_alloc, cbuftptr.len_used)
// It is possible len_used is greater than len_alloc (if this buffer was populated by SimpleAPI C code)
// Ensure we do not overrun the allocated buffer while dumping this object in that case.
min := cbuftptr.len_used
if min > cbuftptr.len_alloc {
min = cbuftptr.len_alloc
}
if 0 < min {
strval := C.GoStringN(cbuftptr.buf_addr, C.int(min))
fmt.Fprintf(writer, ", value: %s", strval)
}
}
fmt.Fprintf(writer, "\n")
runtime.KeepAlive(buft) // Make sure buft hangs around through the YDB call
}
// Free is a method to release both the buffer and ydb_buffer_t block associated with the BufferT block.
//
// The inverse of the Alloc() method: release the buffer in YottaDB heap space referenced by the C.ydb_buffer_t structure,
// release the C.ydb_buffer_t, and set cbuft in the BufferT structure to nil.
func (buft *BufferT) Free() {
if nil == buft {
return
}
if buft.ownsBuff {
buft.cbuft.Free()
}
buft.cbuft = nil
}
// Calls freeMem on any C memory owned by this internalBuffer
func (ibuft *internalBufferT) Free() {
printEntry("internalBufferT.Free()")
if nil == ibuft {
return
}
cbuftptr := ibuft.cbuft
if nil != cbuftptr {
// ydb_buffer_t block exists - free its buffer first if it exists
if nil != cbuftptr.buf_addr {
freeMem(unsafe.Pointer(cbuftptr.buf_addr), C.size_t(cbuftptr.len_alloc))
}
freeMem(unsafe.Pointer(cbuftptr), C.sizeof_ydb_buffer_t)
// The below keeps ibuft around long enough to get rid of this block's C memory. No KeepAlive() necessary.
ibuft.cbuft = nil
}
}
// getCPtr returns a pointer to the internal ydb_buffer_t
func (buft *BufferT) getCPtr() *C.ydb_buffer_t {
ptr := (*C.ydb_buffer_t)(nil)
if nil != buft && nil != buft.cbuft {
ptr = buft.cbuft.cbuft
}
return ptr
}
// LenAlloc is a method to fetch the ydb_buffer_t.len_alloc field containing the allocated length of the buffer.
//
// If the C.ydb_buffer_t structure referenced by cbuft has not yet been allocated, return the STRUCTUNALLOCD error.
// Otherwise, return the len_alloc field of the C.ydb_buffer_t structure referenced by cbuft.
func (buft *BufferT) LenAlloc(tptoken uint64, errstr *BufferT) (uint32, error) {
printEntry("BufferT.LenAlloc()")
if nil == buft {
panic("YDB: *BufferT receiver of LenAlloc() cannot be nil")
}
cbuftptr := buft.getCPtr()
if nil == cbuftptr {
// Create an error to return
errmsg, err := MessageT(tptoken, errstr, (int)(YDB_ERR_STRUCTUNALLOCD))
if nil != err {
panic(fmt.Sprintf("YDB: Error fetching STRUCTUNALLOCD: %s", err))
}
return 0, &YDBError{(int)(YDB_ERR_STRUCTUNALLOCD), errmsg}
}
retval := uint32(cbuftptr.len_alloc)
runtime.KeepAlive(buft)
return retval, nil
}
// LenUsed is a method to fetch the ydb_buffer_t.len_used field containing the used length of the buffer. Note
// that if len_used > len_alloc, thus indicating a previous issue, an INVSTRLEN error is raised.
//
// If the C.ydb_buffer_t structure referenced by cbuft has not yet been allocated, return the STRUCTUNALLOCD error.
// Otherwise, return the len_used field of the C.ydb_buffer_t structure referenced by cbuft.
func (buft *BufferT) LenUsed(tptoken uint64, errstr *BufferT) (uint32, error) {
printEntry("BufferT.LenUsed()")
if nil == buft {
panic("YDB: *BufferT receiver of LenUsed() cannot be nil")
}
cbuftptr := buft.getCPtr()
if nil == cbuftptr {
// Create an error to return
errmsg, err := MessageT(tptoken, errstr, (int)(YDB_ERR_STRUCTUNALLOCD))
if nil != err {
panic(fmt.Sprintf("YDB: Error fetching STRUCTUNALLOCD: %s", err))
}
return 0, &YDBError{(int)(YDB_ERR_STRUCTUNALLOCD), errmsg}
}
lenused := cbuftptr.len_used
runtime.KeepAlive(buft)
return uint32(lenused), nil
}
// ValBAry is a method to fetch the buffer contents as a byte array.
//
// If the C.ydb_buffer_t structure referenced by cbuft has not yet been allocated, return the STRUCTUNALLOCD error.
// If the len_used field of the C.ydb_buffer_t structure is greater than its len_alloc field (owing to a prior
// INVSTRLEN error), return an INVSTRLEN error. Otherwise, return len_used bytes of the buffer as a byte array.
func (buft *BufferT) ValBAry(tptoken uint64, errstr *BufferT) ([]byte, error) {
var bary []byte
printEntry("BufferT.ValBAry()")
if nil == buft {
panic("YDB: *BufferT receiver of ValBAry() cannot be nil")
}
cbuftptr := buft.getCPtr()
if nil == cbuftptr {
// Create an error to return
errmsg, err := MessageT(tptoken, errstr, (int)(YDB_ERR_STRUCTUNALLOCD))
if nil != err {
panic(fmt.Sprintf("YDB: Error fetching STRUCTUNALLOCD: %s", err))
}
return nil, &YDBError{(int)(YDB_ERR_STRUCTUNALLOCD), errmsg}
}
lenalloc := cbuftptr.len_alloc
lenused := cbuftptr.len_used
cbufptr := cbuftptr.buf_addr
if lenused > lenalloc { // INVSTRLEN from last operation - return what we can and give error
bary = C.GoBytes(unsafe.Pointer(cbufptr), C.int(lenalloc)) // Return what we can (alloc size)
errmsg := formatINVSTRLEN(tptoken, errstr, lenalloc, lenused)
return bary, &YDBError{(int)(YDB_ERR_INVSTRLEN), errmsg}
}
// The entire buffer is there so return that.
bary = C.GoBytes(unsafe.Pointer(cbufptr), C.int(lenused))
runtime.KeepAlive(buft) // Make sure buft hangs around
return bary, nil
}
// ValStr is a method to fetch the buffer contents as a string.
//
// If the C.ydb_buffer_t structure referenced by cbuft has not yet been allocated, return the STRUCTUNALLOCD error.
// If the len_used field of the C.ydb_buffer_t structure is greater than its len_alloc field (owing to a prior
// INVSTRLEN error), return an INVSTRLEN error. Otherwise, return len_used bytes of the buffer as a string.
func (buft *BufferT) ValStr(tptoken uint64, errstr *BufferT) (string, error) {
var str string
printEntry("BufferT.ValStr()")
if nil == buft {
panic("YDB: *BufferT receiver of ValStr() cannot be nil")
}
cbuftptr := buft.getCPtr()
if nil == cbuftptr {
// Create an error to return
errmsg, err := MessageT(tptoken, errstr, (int)(YDB_ERR_STRUCTUNALLOCD))
if nil != err {
panic(fmt.Sprintf("YDB: Error fetching STRUCTUNALLOCD: %s", err))
}
return "", &YDBError{(int)(YDB_ERR_STRUCTUNALLOCD), errmsg}
}
lenalloc := cbuftptr.len_alloc
lenused := cbuftptr.len_used
cbufptr := cbuftptr.buf_addr
if lenused > lenalloc { // INVSTRLEN from last operation - return what we can and give error
str = C.GoStringN(cbufptr, C.int(lenalloc)) // Return what we can (alloc size)
errmsg := formatINVSTRLEN(tptoken, errstr, lenalloc, lenused)
return str, &YDBError{(int)(YDB_ERR_INVSTRLEN), errmsg}
}
// The entire buffer is there so return that
str = C.GoStringN(cbufptr, C.int(lenused))
runtime.KeepAlive(buft) // Make sure buft hangs around
return str, nil
}
// SetLenUsed is a method to set the used length of buffer in the ydb_buffer_t block (must be <= alloclen).
//
// Use this method to change the length of a used substring of the contents of the buffer referenced by the buf_addr field of the
// referenced C.ydb_buffer_t.
//
// If the C.ydb_buffer_t structure referenced by cbuft has not yet been allocated, return the STRUCTUNALLOCD error.
// If newLen is greater than the len_alloc field of the referenced C.ydb_buffer_t, make no changes and return with
// an error return of INVSTRLEN. Otherwise, set the len_used field of the referenced C.ydb_buffer_t to newLen.
//
// Note that even if newLen is not greater than the value of len_alloc, setting a len_used value greater than the
// number of meaningful bytes in the buffer will likely lead to hard-to-debug errors.
func (buft *BufferT) SetLenUsed(tptoken uint64, errstr *BufferT, newLen uint32) error {
printEntry("BufferT.SetLenUsed()")
if nil == buft {
panic("YDB: *BufferT receiver of SetLenUsed() cannot be nil")
}
cbuftptr := buft.getCPtr()
if nil == cbuftptr {
// Create an error to return
errmsg, err := MessageT(tptoken, errstr, (int)(YDB_ERR_STRUCTUNALLOCD))
if nil != err {
panic(fmt.Sprintf("YDB: Error fetching STRUCTUNALLOCD: %s", err))
}
return &YDBError{(int)(YDB_ERR_STRUCTUNALLOCD), errmsg}
}
lenalloc := cbuftptr.len_alloc
if newLen > uint32(lenalloc) {
errmsg := formatINVSTRLEN(tptoken, errstr, lenalloc, C.uint(newLen))
return &YDBError{(int)(YDB_ERR_INVSTRLEN), errmsg}
}
cbuftptr.len_used = C.uint(newLen)
runtime.KeepAlive(buft) // Make sure buft hangs around
return nil
}
// SetValBAry is a method to set a []byte array into the given buffer.
//
// If the C.ydb_buffer_t structure referenced by cbuft has not yet been allocated, return the STRUCTUNALLOCD error.
// If the length of value is greater than the len_alloc field of the C.ydb_buffer_t structure referenced by
// cbuft, make no changes and return INVSTRLEN. Otherwise, copy the bytes of value to the location referenced
// by the buf_addr field of the C.ydbbuffer_t structure, set the len_used field to the length of value.
func (buft *BufferT) SetValBAry(tptoken uint64, errstr *BufferT, value []byte) error {
printEntry("BufferT.SetValBAry()")
if nil == buft {
panic("YDB: *BufferT receiver of SetValBAry() cannot be nil")
}
cbuftptr := buft.getCPtr()
if nil == cbuftptr {
// Create an error to return
errmsg, err := MessageT(tptoken, errstr, (int)(YDB_ERR_STRUCTUNALLOCD))
if nil != err {
panic(fmt.Sprintf("YDB: Error fetching STRUCTUNALLOCD: %s", err))
}
return &YDBError{(int)(YDB_ERR_STRUCTUNALLOCD), errmsg}
}
vallen := C.uint(len(value))
lenalloc := cbuftptr.len_alloc
if vallen > lenalloc {
errmsg := formatINVSTRLEN(tptoken, errstr, lenalloc, vallen)
return &YDBError{(int)(YDB_ERR_INVSTRLEN), errmsg}
}
// Copy the Go buffer to the C buffer
if 0 < vallen {
C.memcpy(unsafe.Pointer(cbuftptr.buf_addr), unsafe.Pointer(&value[0]), C.size_t(vallen))
}
cbuftptr.len_used = vallen
runtime.KeepAlive(buft) // Make sure buft hangs around
return nil
}
// SetValStr is a method to set a string into the given buffer.
//
// If the C.ydb_buffer_t structure referenced by cbuft has not yet been allocated, return the STRUCTUNALLOCD error.
// If the length of value is greater than the len_alloc field of the C.ydb_buffer_t structure referenced by
// cbuft, make no changes and return INVSTRLEN. Otherwise, copy the bytes of value to the location referenced
// by the buf_addr field of the C.ydbbuffer_t structure, set the len_used field to the length of value.
func (buft *BufferT) SetValStr(tptoken uint64, errstr *BufferT, value string) error {
printEntry("BufferT.SetValStr()")
if nil == buft {
panic("YDB: *BufferT receiver of SetValStr() cannot be nil")
}
valuebary := []byte(value)
return buft.SetValBAry(tptoken, errstr, valuebary)
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Simple (Threaded) API methods for BufferT
//
////////////////////////////////////////////////////////////////////////////////////////////////////
// Str2ZwrST is a STAPI method to take the given string and return it in ZWRITE format.
//
// If the C.ydb_buffer_t structure referenced by cbuft has not yet been allocated, return the STRUCTUNALLOCD error.
// If len_alloc is not large enough, set len_used to the required length, and return an INVSTRLEN error. In this case,
// len_used will be greater than len_alloc until corrected by application code. Otherwise, set the buffer referenced by buf_addr
// to the zwrite format string, and set len_used to the length.
func (buft *BufferT) Str2ZwrST(tptoken uint64, errstr *BufferT, zwr *BufferT) error {
var cbuft *C.ydb_buffer_t
printEntry("BufferT.Str2ZwrST()")
if nil == buft {
panic("YDB: *BufferT receiver of Str2ZwrST() cannot be nil")
}
if nil == zwr {
panic("YDB: *BufferT 'zwr' parameter to Str2ZwrST() cannot be nil")
}
if 1 != atomic.LoadUint32(&ydbInitialized) {
initializeYottaDB()
}
if nil == buft.getCPtr() || nil == zwr.getCPtr() {
// Create an error to return
errmsg, err := MessageT(tptoken, errstr, (int)(YDB_ERR_STRUCTUNALLOCD))
if nil != err {
panic(fmt.Sprintf("YDB: Error fetching STRUCTUNALLOCD: %s", err))
}
return &YDBError{(int)(YDB_ERR_STRUCTUNALLOCD), errmsg}
}
if nil != errstr {
cbuft = errstr.getCPtr()
}
rc := C.ydb_str2zwr_st(C.uint64_t(tptoken), cbuft, buft.getCPtr(), zwr.getCPtr())
if YDB_OK != rc {
err := NewError(tptoken, errstr, int(rc))
return err
}
runtime.KeepAlive(buft)
runtime.KeepAlive(errstr)
runtime.KeepAlive(zwr)
return nil
}
// Zwr2StrST is a STAPI method to take the given ZWRITE format string and return it as a normal ASCII string.
//
// If the C.ydb_buffer_t structure referenced by cbuft has not yet been allocated, return the STRUCTUNALLOCD error.
// If len_alloc is not large enough, set len_used to the required length, and return an INVSTRLEN error. In this case,
// len_used will be greater than len_alloc until corrected by application code. If str has errors and is not in valid zwrite format, set
// len_used to zero. Otherwise, set the buffer referenced by buf_addr to the unencoded string and set len_used to its length.
//
// Note that the length of a string in zwrite format is always greater than or equal to the string in its original, unencoded format.
func (buft *BufferT) Zwr2StrST(tptoken uint64, errstr *BufferT, str *BufferT) error {
var cbuft *C.ydb_buffer_t
printEntry("BufferT.Zwr2StrST()")
if nil == buft {
panic("YDB: *BufferT receiver of Zwr2StrST() cannot be nil")
}
if nil == str {
panic("YDB: *BufferT 'str' parameter to Zwr2StrST() cannot be nil")
}
if 1 != atomic.LoadUint32(&ydbInitialized) {
initializeYottaDB()
}
if nil == buft.getCPtr() || nil == str.getCPtr() {
// Create an error to return
errmsg, err := MessageT(tptoken, errstr, (int)(YDB_ERR_STRUCTUNALLOCD))
if nil != err {
panic(fmt.Sprintf("YDB: Error fetching STRUCTUNALLOCD: %s", err))
}
return &YDBError{(int)(YDB_ERR_STRUCTUNALLOCD), errmsg}
}
if nil != errstr {
cbuft = errstr.getCPtr()
}
rc := C.ydb_zwr2str_st(C.uint64_t(tptoken), cbuft, buft.getCPtr(), str.getCPtr())
if YDB_OK != rc {
err := NewError(tptoken, errstr, int(rc))
return err
}
runtime.KeepAlive(buft)
runtime.KeepAlive(errstr)
runtime.KeepAlive(str)
return nil
}
//////////////////////////////////////////////////////////////////
// //
// Copyright (c) 2018-2022 YottaDB LLC and/or its subsidiaries. //
// All rights reserved. //
// //
// This source code contains the intellectual property //
// of its copyright holder(s), and is made available //
// under a license. If you do not know the terms of //
// the license, please stop and do not read further. //
// //
//////////////////////////////////////////////////////////////////
package yottadb
import (
"fmt"
"io"
"os"
"runtime"
"sync"
"sync/atomic"
"unsafe"
)
// #include <stdlib.h>
// #include <string.h>
// #include "libyottadb.h"
// int ydb_tp_st_wrapper_cgo(uint64_t tptoken, void *tpfnparm);
import "C"
// Variables used by TpST to wrap passing in func so the callback can retrieve it without passing pointers to C.
var tpIndex uint64
var tpMap sync.Map
// BufferTArray is an array of ydb_buffer_t structures. The reason this is not an array of BufferT structures is because
// we can't pass a pointer to those Go structures to a C routine (cgo restriction) so we have to have this separate
// array of the C structures instead. Also, cgo doesn't support indexing of C structures so we have to do that ourselves
// as well. Because this structure's contents contain pointers to C allocated storage, this structure is NOT safe for
// concurrent access unless those accesses are to different array elements and do not affect the overall structure.
type BufferTArray struct {
cbuftary *internalBufferTArray
}
type internalBufferTArray struct {
elemUsed uint32 // Number of elements used
elemAlloc uint32 // Number of elements in array
cbuftary *[]C.ydb_buffer_t // C flavor of ydb_buffer_t array
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Data manipulation methods for BufferTArray
//
////////////////////////////////////////////////////////////////////////////////////////////////////
// Alloc is a method to allocate an array of 'numBufs' ydb_buffer_t structures anchored in this BufferTArray and also
// for each of those buffers, allocate 'nBytes' byte buffers anchoring them in the ydb_buffer_t structure.
func (buftary *BufferTArray) Alloc(numBufs, nBytes uint32) {
var i uint32
printEntry("BufferTArray.Alloc()")
if nil == buftary {
panic("YDB: *BufferTArray receiver of Alloc() cannot be nil")
}
// Forget the previous structure, then allocate a new one if needed
buftary.cbuftary = nil
if 0 != numBufs {
// Allocate new ydb_buffer_t array and initialize
len := C.size_t(uint32(C.sizeof_ydb_buffer_t) * numBufs)
cbuftary := (*[]C.ydb_buffer_t)(allocMem(len))
buftary.cbuftary = &internalBufferTArray{0, numBufs, cbuftary}
// Allocate a buffer for each ydb_buffer_t structure of nBytes bytes
for i = 0; numBufs > i; i++ {
elemptr := (*C.ydb_buffer_t)(unsafe.Pointer(uintptr(unsafe.Pointer(cbuftary)) +
uintptr(C.sizeof_ydb_buffer_t*i)))
elemptr.buf_addr = (*C.char)(allocMem(C.size_t(nBytes)))
elemptr.len_alloc = C.uint(nBytes)
elemptr.len_used = 0
}
runtime.SetFinalizer(buftary.cbuftary, func(o *internalBufferTArray) {
o.Free()
})
}
runtime.KeepAlive(buftary)
}
// Dump is a STAPI method to dump (print) the contents of this BufferTArray block for debugging purposes. It dumps to stdout
// - cbuftary as a hexadecimal address,
// - elemAlloc and elemUsed as integers,
// - and for each element of the smaller of elemAlloc and elemUsed elements of the ydb_buffer_t array referenced by cbuftary,
// buf_addr as a hexadecimal address, len_alloc and len_used as integers and the smaller of len_used and len_alloc bytes at
// the address buf_addr in zwrite format.
func (buftary *BufferTArray) Dump() {
buftary.DumpToWriter(os.Stdout)
}
//DumpToWriter is a writer that allows the tests or user code to dump to other than stdout.
func (buftary *BufferTArray) DumpToWriter(writer io.Writer) {
printEntry("BufferTArray.Dump()")
if nil == buftary {
panic("YDB: *BufferTArray receiver of Dump() cannot be nil")
}
cbuftary := buftary.getCPtr()
if nil != cbuftary {
fmt.Fprintf(writer, "BufferTArray.Dump(): cbuftary: %p, elemAlloc: %d, elemUsed: %d, elemLenAlloc: %d\n", cbuftary,
buftary.ElemAlloc(), buftary.ElemUsed(), buftary.ElemLenAlloc())
for i := 0; int(buftary.ElemUsed()) > i; i++ {
elemptr := (*C.ydb_buffer_t)(unsafe.Pointer((uintptr(unsafe.Pointer(cbuftary)) +
uintptr(C.sizeof_ydb_buffer_t*i))))
// It is possible len_used is greater than len_alloc (if this buffer was populated by SimpleAPI C code)
// Ensure we do not overrun the allocated buffer while dumping this object in that case.
min := elemptr.len_used
if min > elemptr.len_alloc {
min = elemptr.len_alloc
}
valstr := C.GoStringN(elemptr.buf_addr, C.int(min))
fmt.Fprintf(writer, " %d: %s\n", i, valstr)
}
}
runtime.KeepAlive(buftary)
}
// Free is a method to release all allocated C storage in a BufferTArray. It is the inverse of the Alloc() method: release the numSubs buffers
// and the ydb_buffer_t array. Set cbuftary to nil, and elemAlloc and elemUsed to zero.
func (buftary *BufferTArray) Free() {
printEntry("BufferTArray.Free()")
if nil == buftary {
return
}
buftary.cbuftary.Free()
buftary.cbuftary = nil
}
// Free cleans up C memory for the internal BufferTArray object
func (ibuftary *internalBufferTArray) Free() {
printEntry("internalBufferTArray.Free()")
if nil == ibuftary {
return
}
// Deallocate the buffers in each ydb_buffer_t
cbuftary := ibuftary.cbuftary
if nil == cbuftary {
return // Nothing to do
}
for i := 0; int(ibuftary.elemAlloc) > i; i++ {
elemptr := (*C.ydb_buffer_t)(unsafe.Pointer(uintptr(unsafe.Pointer(cbuftary)) +
uintptr(C.sizeof_ydb_buffer_t*i)))
if 0 != elemptr.len_alloc {
freeMem(unsafe.Pointer(elemptr.buf_addr), C.size_t(elemptr.len_alloc))
}
}
// Array buffers are freed, now free the array of ydb_buffer_t structs if it exists
freeMem(unsafe.Pointer(cbuftary), C.size_t(C.sizeof_ydb_buffer_t*ibuftary.elemAlloc))
// The below keeps ibuftary around long enough to get rid of this block's C memory. No KeepAlive() necessary.
ibuftary.cbuftary = nil
}
// getCPtr returns a pointer to the internal C.ydb_buffer_t
func (buftary *BufferTArray) getCPtr() *C.ydb_buffer_t {
ptr := (*C.ydb_buffer_t)(nil)
if (nil != buftary) && (nil != buftary.cbuftary) {
ptr = (*C.ydb_buffer_t)(unsafe.Pointer(buftary.cbuftary.cbuftary))
}
return ptr
}
// ElemAlloc is a method to return elemAlloc from a BufferTArray.
func (buftary *BufferTArray) ElemAlloc() uint32 {
printEntry("BufferTArray.ElemAlloc()")
if nil == buftary {
panic("YDB: *BufferTArray receiver of ElemAlloc() cannot be nil")
}
if nil == buftary.cbuftary {
return 0
}
return buftary.cbuftary.elemAlloc
}
// ElemLenAlloc is a method to retrieve the buffer allocation length associated with our BufferTArray.
// Since all buffers are the same size in this array, just return the value from the first array entry.
// If nothing is allocated yet, return 0.
func (buftary *BufferTArray) ElemLenAlloc() uint32 {
var retlen uint32
printEntry("BufferTArray.ElemLenAlloc()")
if nil == buftary {
panic("YDB: *BufferTArray receiver of ElemLenAlloc() cannot be nil")
}
cbuftary := buftary.getCPtr()
if nil != cbuftary {
elemptr := (*C.ydb_buffer_t)(unsafe.Pointer(cbuftary))
retlen = uint32(elemptr.len_alloc)
} else { // Nothing is allocated yet so "allocated length" is 0
retlen = 0
}
runtime.KeepAlive(buftary)
return retlen
}
// ElemLenUsed is a method to retrieve the buffer used length associated with a given buffer referenced by its index.
func (buftary *BufferTArray) ElemLenUsed(tptoken uint64, errstr *BufferT, idx uint32) (uint32, error) {
printEntry("BufferTArray.ElemLenUsed()")
if nil == buftary {
panic("YDB: *BufferTArray receiver of ElemLenUsed() cannot be nil")
}
cbuftary := buftary.getCPtr()
if nil == cbuftary {
// Create an error to return
errmsg, err := MessageT(tptoken, errstr, (int)(YDB_ERR_STRUCTUNALLOCD))
if nil != err {
panic(fmt.Sprintf("YDB: Error fetching STRUCTUNALLOCD: %s", err))
}
return 0, &YDBError{(int)(YDB_ERR_STRUCTUNALLOCD), errmsg}
}
elemcnt := buftary.ElemAlloc()
if idx >= elemcnt { // Request for non-existant element
// Create an error to return
errmsg, err := MessageT(tptoken, errstr, (int)(YDB_ERR_INSUFFSUBS))
if nil != err {
panic(fmt.Sprintf("YDB: Error fetching INSUFFSUBS: %s", err))
}
return 0, &YDBError{(int)(YDB_ERR_INSUFFSUBS), errmsg}
}
elemptr := (*C.ydb_buffer_t)(unsafe.Pointer(uintptr(unsafe.Pointer(cbuftary)) + uintptr(C.sizeof_ydb_buffer_t*idx)))
retlen := uint32(elemptr.len_used)
runtime.KeepAlive(buftary)
return retlen, nil
}
// ElemUsed is a method to return elemUsed from a BufferTArray.
func (buftary *BufferTArray) ElemUsed() uint32 {
printEntry("BufferTArray.ElemUsed()")
if nil == buftary {
panic("YDB: *BufferTArray receiver of ElemUsed() cannot be nil")
}
if nil == buftary.cbuftary {
return 0
}
return buftary.cbuftary.elemUsed
}
// ValBAry is a method to fetch the buffer of the indicated array element and return it as a byte array.
func (buftary *BufferTArray) ValBAry(tptoken uint64, errstr *BufferT, idx uint32) ([]byte, error) {
var bary []byte
printEntry("BufferTArray.ValBAry()")
if nil == buftary {
panic("YDB: *BufferTArray receiver of ValBAry() cannot be nil")
}
elemcnt := buftary.ElemAlloc()
if !(idx < elemcnt) {
// Create an error to return
errmsg, err := MessageT(tptoken, errstr, (int)(YDB_ERR_INSUFFSUBS))
if nil != err {
panic(fmt.Sprintf("YDB: Error fetching INSUFFSUBS: %s", err))
}
return nil, &YDBError{(int)(YDB_ERR_INSUFFSUBS), errmsg}
}
cbuftary := buftary.getCPtr()
if nil == cbuftary {
// Create an error to return
errmsg, err := MessageT(tptoken, errstr, (int)(YDB_ERR_STRUCTUNALLOCD))
if nil != err {
panic(fmt.Sprintf("YDB: Error fetching STRUCTUNALLOCD: %s", err))
}
return nil, &YDBError{(int)(YDB_ERR_STRUCTUNALLOCD), errmsg}
}
elemptr := (*C.ydb_buffer_t)(unsafe.Pointer(uintptr(unsafe.Pointer(cbuftary)) + uintptr(C.sizeof_ydb_buffer_t*idx)))
lenalloc := elemptr.len_alloc
lenused := elemptr.len_used
cbufptr := elemptr.buf_addr
if lenused > lenalloc { // INVSTRLEN from last operation return what we can and give error
bary = C.GoBytes(unsafe.Pointer(cbufptr), C.int(lenalloc)) // Return what we can (alloc size)
errmsg := formatINVSTRLEN(tptoken, errstr, lenalloc, lenused)
return bary, &YDBError{(int)(YDB_ERR_INVSTRLEN), errmsg}
}
// The entire buffer is there so return that
bary = C.GoBytes(unsafe.Pointer(cbufptr), C.int(lenused))
runtime.KeepAlive(buftary)
return bary, nil
}
// ValStr is a method to fetch the buffer of the indicated array element and return it as a string.
func (buftary *BufferTArray) ValStr(tptoken uint64, errstr *BufferT, idx uint32) (string, error) {
var str string
printEntry("BufferTArray.ValStr()")
if nil == buftary {
panic("YDB: *BufferTArray receiver of ValStr() cannot be nil")
}
elemcnt := buftary.ElemAlloc()
if !(idx < elemcnt) {
// Create an error to return
errmsg, err := MessageT(tptoken, errstr, (int)(YDB_ERR_INSUFFSUBS))
if nil != err {
panic(fmt.Sprintf("YDB: Error fetching INSUFFSUBS: %s", err))
}
return "", &YDBError{(int)(YDB_ERR_INSUFFSUBS), errmsg}
}
cbuftary := buftary.getCPtr()
if nil == cbuftary {
// Create an error to return
errmsg, err := MessageT(tptoken, errstr, (int)(YDB_ERR_STRUCTUNALLOCD))
if nil != err {
panic(fmt.Sprintf("YDB: Error fetching STRUCTUNALLOCD: %s", err))
}
return "", &YDBError{(int)(YDB_ERR_STRUCTUNALLOCD), errmsg}
}
elemptr := (*C.ydb_buffer_t)(unsafe.Pointer(uintptr(unsafe.Pointer(cbuftary)) + uintptr(C.sizeof_ydb_buffer_t*idx)))
lenalloc := elemptr.len_alloc
lenused := elemptr.len_used
cbufptr := elemptr.buf_addr
if lenused > lenalloc { // INVSTRLEN from last operation return what we can and give error
str = C.GoStringN(cbufptr, C.int(lenalloc)) // Return what we can (alloc size)
errmsg := formatINVSTRLEN(tptoken, errstr, lenalloc, lenused)
return str, &YDBError{(int)(YDB_ERR_INVSTRLEN), errmsg}
}
// The entire buffer is there so return that
str = C.GoStringN(cbufptr, C.int(lenused))
runtime.KeepAlive(buftary)
return str, nil
}
// SetElemLenUsed is a method to set the len_used field of a given ydb_buffer_t struct in the BufferTArray.
func (buftary *BufferTArray) SetElemLenUsed(tptoken uint64, errstr *BufferT, idx, newLen uint32) error {
printEntry("BufferTArray.SetElemLenUsed()")
if nil == buftary {
panic("YDB: *BufferTArray receiver of SetElemLenUsed() cannot be nil")
}
elemcnt := buftary.ElemAlloc()
if !(idx < elemcnt) {
// Create an error to return
errmsg, err := MessageT(tptoken, errstr, (int)(YDB_ERR_INSUFFSUBS))
if nil != err {
panic(fmt.Sprintf("YDB: Error fetching INSUFFSUBS: %s", err))
}
return &YDBError{(int)(YDB_ERR_INSUFFSUBS), errmsg}
}
cbuftary := buftary.getCPtr()
if nil == cbuftary {
// Create an error to return
errmsg, err := MessageT(tptoken, errstr, (int)(YDB_ERR_STRUCTUNALLOCD))
if nil != err {
panic(fmt.Sprintf("YDB: Error fetching STRUCTUNALLOCD: %s", err))
}
return &YDBError{(int)(YDB_ERR_STRUCTUNALLOCD), errmsg}
}
elemptr := (*C.ydb_buffer_t)(unsafe.Pointer(uintptr(unsafe.Pointer(cbuftary)) + uintptr(C.sizeof_ydb_buffer_t*idx)))
lenalloc := elemptr.len_alloc
if newLen > uint32(lenalloc) { // INVSTRLEN from last operation - return what we can and give error
errmsg := formatINVSTRLEN(tptoken, errstr, lenalloc, C.uint(newLen))
return &YDBError{(int)(YDB_ERR_INVSTRLEN), errmsg}
}
// Set the new used length
elemptr.len_used = C.uint(newLen)
runtime.KeepAlive(buftary)
return nil
}
// SetElemUsed is a method to set the number of used buffers in the BufferTArray.
func (buftary *BufferTArray) SetElemUsed(tptoken uint64, errstr *BufferT, newUsed uint32) error {
printEntry("BufferTArray.SetElemUsed()")
if nil == buftary {
panic("YDB: *BufferTArray receiver of SetElemUsed() cannot be nil")
}
elemcnt := buftary.ElemAlloc()
if newUsed > elemcnt {
// Create an error to return
errmsg, err := MessageT(tptoken, errstr, (int)(YDB_ERR_INSUFFSUBS))
if nil != err {
panic(fmt.Sprintf("YDB: Error fetching INSUFFSUBS: %s", err))
}
return &YDBError{(int)(YDB_ERR_INSUFFSUBS), errmsg}
}
// Set the number of used buffers
if nil != buftary.cbuftary {
buftary.cbuftary.elemUsed = newUsed
}
return nil
}
// SetValBAry is a method to set a byte array (value) into the buffer at the given index (idx).
func (buftary *BufferTArray) SetValBAry(tptoken uint64, errstr *BufferT, idx uint32, value []byte) error {
printEntry("BufferTArray.SetValBAry()")
if nil == buftary {
panic("YDB: *BufferTArray receiver of SetValBAry() cannot be nil")
}
elemcnt := buftary.ElemAlloc()
if !(idx < elemcnt) {
// Create an error to return
errmsg, err := MessageT(tptoken, errstr, (int)(YDB_ERR_INSUFFSUBS))
if nil != err {
panic(fmt.Sprintf("YDB: Error fetching INSUFFSUBS: %s", err))
}
return &YDBError{(int)(YDB_ERR_INSUFFSUBS), errmsg}
}
cbuftary := buftary.getCPtr()
if nil == cbuftary {
// Create an error to return
errmsg, err := MessageT(tptoken, errstr, (int)(YDB_ERR_STRUCTUNALLOCD))
if nil != err {
panic(fmt.Sprintf("YDB: Error fetching STRUCTUNALLOCD: %s", err))
}
return &YDBError{(int)(YDB_ERR_STRUCTUNALLOCD), errmsg}
}
elemptr := (*C.ydb_buffer_t)(unsafe.Pointer(uintptr(unsafe.Pointer(cbuftary)) + uintptr(C.sizeof_ydb_buffer_t*idx)))
lenalloc := uint32(elemptr.len_alloc)
vallen := uint32(len(value))
if vallen > lenalloc { // INVSTRLEN from last operation - return what we can and give error
errmsg := formatINVSTRLEN(tptoken, errstr, C.uint(lenalloc), C.uint(vallen))
return &YDBError{(int)(YDB_ERR_INVSTRLEN), errmsg}
}
// Copy the Go buffer to the C buffer
if 0 < vallen {
C.memcpy(unsafe.Pointer(elemptr.buf_addr), unsafe.Pointer(&value[0]), C.size_t(vallen))
}
elemptr.len_used = C.uint(vallen) // Set the used length of the buffer for this element
runtime.KeepAlive(buftary)
return nil
}
// SetValStr is a method to set a string (value) into the buffer at the given index (idx).
func (buftary *BufferTArray) SetValStr(tptoken uint64, errstr *BufferT, idx uint32, value string) error {
printEntry("BufferTArray.SetValStr()")
if nil == buftary {
panic("YDB: *BufferTArray receiver of SetValBAry() cannot be nil")
}
valuebary := []byte(value)
return buftary.SetValBAry(tptoken, errstr, idx, valuebary)
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Simple (Threaded) API methods for BufferTArray
//
////////////////////////////////////////////////////////////////////////////////////////////////////
// DeleteExclST is a method to delete all local variables EXCEPT the variables listed in the method BufferTArray.
// If the input array is empty, then ALL local variables are deleted. DeleteExclST() wraps ydb_delete_excl_st() to delete all
// local variable trees except those of local variables whose names are specified in the BufferTArray structure. In the special case
// where elemUsed is zero, the method deletes all local variable trees.
//
// In the event that the elemUsed exceeds YDB_MAX_NAMES, the error return is ERRNAMECOUNT2HI.
//
// As M and Go application code cannot be mixed in the same process, the warning in ydb_delete_excl_s() does not apply.
func (buftary *BufferTArray) DeleteExclST(tptoken uint64, errstr *BufferT) error {
var cbuft *C.ydb_buffer_t
printEntry("BufferTArray.DeleteExclST()")
if nil == buftary {
panic("YDB: *BufferTArray receiver of DeleteExclST() cannot be nil")
}
if 1 != atomic.LoadUint32(&ydbInitialized) {
initializeYottaDB()
}
if nil != errstr {
cbuft = errstr.getCPtr()
}
rc := C.ydb_delete_excl_st(C.uint64_t(tptoken), cbuft, C.int(buftary.ElemUsed()), buftary.getCPtr())
if YDB_OK != rc {
err := NewError(tptoken, errstr, int(rc))
return err
}
runtime.KeepAlive(buftary)
runtime.KeepAlive(errstr)
return nil
}
// TpST wraps ydb_tp_st() to implement transaction processing.
//
// Any function implementing logic for a transaction should return an error code with one of the following:
//
// - A normal return (nil) to indicate that per application logic, the transaction can be committed. The YottaDB database engine
// will commit the transaction if it is able to, and if not, will call the function again.
//
// - TPRESTART to indicate that the transaction should restart, either because application logic has so determined or because a YottaDB
// function called by the function has returned TPRESTART.
//
// - ROLLBACK to indicate that TpST() should not commit the transaction, and should return ROLLBACK to the caller.
//
// The BufferTArray receiving the TpST() method is a list of local variables whose values should be saved, and restored to their
// original values when the transaction restarts. If the cbuftary structures have not been allocated or elemUsed is zero, no
// local variables are saved and restored; and if elemUsed is 1, and that sole element references the string "*" all local variables
// are saved and restored.
//
// A case-insensitive value of "BA" or "BATCH" for transid indicates to YottaDB that it need not ensure Durability for this
// transaction (it continues to ensure Atomicity, Consistency, and Isolation)
//
// Parameters:
//
// tptoken - the token used to identify nested transaction; start with yottadb.NOTTP
// errstr - Buffer to hold error string that is used to report errors and avoid race conditions with setting $ZSTATUS.
// tpfn - the closure function which will be run during the transaction. This closure function may get invoked multiple times
// if a transaction fails for some reason (concurrent changes, for example), so should not change any data outside of
// the database.
// transid - See docs for ydb_tp_s() in the MLPG.
func (buftary *BufferTArray) TpST(tptoken uint64, errstr *BufferT, tpfn func(uint64, *BufferT) int32, transid string) error {
var cbuft *C.ydb_buffer_t
printEntry("TpST()")
if nil == buftary {
panic("YDB: *BufferTArray receiver of TpST() cannot be nil")
}
if 1 != atomic.LoadUint32(&ydbInitialized) {
initializeYottaDB()
}
tid := C.CString(transid)
defer freeMem(unsafe.Pointer(tid), C.size_t(len(transid)))
tpfnparm := atomic.AddUint64(&tpIndex, 1)
tpMap.Store(tpfnparm, tpfn)
if nil != errstr {
cbuft = errstr.getCPtr()
}
cbuftary := buftary.getCPtr()
rc := C.ydb_tp_st(C.uint64_t(tptoken), cbuft, (C.ydb_tpfnptr_t)(C.ydb_tp_st_wrapper_cgo),
unsafe.Pointer(&tpfnparm), tid, C.int(buftary.ElemUsed()), cbuftary)
tpMap.Delete(tpfnparm)
if YDB_OK != rc {
err := NewError(tptoken, errstr, int(rc))
return err
}
runtime.KeepAlive(buftary)
runtime.KeepAlive(errstr)
return nil
}
// YdbTpStWrapper is a private callback to wrap calls to the Go closure required for TpST.
//export ydbTpStWrapper
func ydbTpStWrapper(tptoken uint64, errstr *C.ydb_buffer_t, tpfnparm unsafe.Pointer) int32 {
var errbuff BufferT
index := *((*uint64)(tpfnparm))
v, ok := tpMap.Load(index)
if !ok {
panic("YDB: Could not find callback routine")
}
errbuff.bufferTFromPtr((unsafe.Pointer)(errstr))
retval := (v.(func(uint64, *BufferT) int32))(tptoken, &errbuff)
runtime.KeepAlive(errstr)
return retval
}
//////////////////////////////////////////////////////////////////
// //
// Copyright (c) 2018-2025 YottaDB LLC and/or its subsidiaries. //
// All rights reserved. //
// //
// This source code contains the intellectual property //
// of its copyright holder(s), and is made available //
// under a license. If you do not know the terms of //
// the license, please stop and do not read further. //
// //
//////////////////////////////////////////////////////////////////
package yottadb
import (
"fmt"
"runtime"
"strings"
"sync/atomic"
)
// #include "libyottadb.h"
import "C"
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Functions making up the EasyAPI
//
////////////////////////////////////////////////////////////////////////////////////////////////////
// DataE is a STAPI function to return $DATA() value for a given variable subscripted or not.
//
// Matching DataST(), DataE() function wraps and returns the result of ydb_data_st(). In the event of an error, the return
// value is unspecified.
func DataE(tptoken uint64, errstr *BufferT, varname string, subary []string) (uint32, error) {
var retval C.uint
var dbkey KeyT
var err error
var cbuft *C.ydb_buffer_t
printEntry("DataE()")
if 1 != atomic.LoadUint32(&ydbInitialized) {
initializeYottaDB()
}
defer dbkey.Free()
initkey(tptoken, errstr, &dbkey, varname, subary)
if nil != errstr {
cbuft = errstr.getCPtr()
}
vargobuft := dbkey.Varnm.getCPtr()
subbuftary := dbkey.Subary.getCPtr()
rc := C.ydb_data_st(C.uint64_t(tptoken), cbuft, vargobuft, C.int(dbkey.Subary.ElemUsed()), subbuftary, &retval)
if YDB_OK != rc {
err = NewError(tptoken, errstr, int(rc))
return uint32(retval), err
}
runtime.KeepAlive(dbkey) // Make sure dbkey stays in tact through the call into YDB
runtime.KeepAlive(errstr)
return uint32(retval), nil
}
// DeleteE is a STAPI function to delete a node or a subtree (see DeleteST) given a deletion type and a varname/subscript set.
//
// Matching DeleteST(), DeleteE() wraps ydb_delete_st() to
// delete a local or global variable node or (sub)tree, with a value of
// YDB_DEL_NODE for deltype specifying that only the node should be deleted, leaving the (sub)tree untouched, and a value
// of YDB_DEL_TREE specifying that the node as well as the(sub)tree are to be deleted.
func DeleteE(tptoken uint64, errstr *BufferT, deltype int, varname string, subary []string) error {
var dbkey KeyT
var err error
var cbuft *C.ydb_buffer_t
printEntry("DeleteE()")
if 1 != atomic.LoadUint32(&ydbInitialized) {
initializeYottaDB()
}
defer dbkey.Free()
initkey(tptoken, errstr, &dbkey, varname, subary)
if nil != errstr {
cbuft = errstr.getCPtr()
}
vargobuft := dbkey.Varnm.getCPtr()
subbuftary := dbkey.Subary.getCPtr()
rc := C.ydb_delete_st(C.uint64_t(tptoken), cbuft, vargobuft, C.int(dbkey.Subary.ElemUsed()), subbuftary,
C.int(deltype))
if YDB_OK != rc {
err = NewError(tptoken, errstr, int(rc))
return err
}
runtime.KeepAlive(dbkey) // Make sure dbkey stays in tact through the call into YDB
runtime.KeepAlive(errstr)
return nil
}
// DeleteExclE is a STAPI function to do an exclusive delete by deleting all local variables except those root vars specified
// in the variable name array. If the varname array is empty, all local variables are deleted.
//
// Matching DeleteExclST(), DeleteExclE() wraps ydb_delete_excl_st() to delete all local variables except those
// specified. In the event varnames has no elements (i.e.,[]string{}), DeleteExclE() deletes all local variables.
//
// In the event that the number of variable names in varnames exceeds YDB_MAX_NAMES, the error return is
// ERRNAMECOUNT2HI. Otherwise, if ydb_delete_excl_st() returns an error, the function returns the error.
//
// As M and Go application code cannot be mixed in the same process, the warning in ydb_delete_excl_s() does not apply.
func DeleteExclE(tptoken uint64, errstr *BufferT, varnames []string) error {
var vnames BufferTArray
var maxvarnmlen, varnmcnt, varnmlen uint32
var i int
var err error
var varname string
printEntry("DeleteExclE()")
defer vnames.Free()
varnmcnt = uint32(len(varnames))
// Find maximum length varname so know how much to allocate
maxvarnmlen = 0
for _, varname = range varnames {
varnmlen = uint32(len(varname))
if varnmlen > maxvarnmlen {
maxvarnmlen = varnmlen
}
}
vnames.Alloc(varnmcnt, maxvarnmlen)
for i, varname = range varnames {
err = vnames.SetValStr(tptoken, errstr, uint32(i), varname)
if nil != err {
panic(fmt.Sprintf("YDB: Unexpected error with SetValStr(): %s", err))
}
}
err = vnames.SetElemUsed(tptoken, errstr, varnmcnt)
if nil != err {
panic(fmt.Sprintf("YDB: Unexpected error with SetUsed(): %s", err))
}
// Drive simpleAPI wrapper and return its return code
return vnames.DeleteExclST(tptoken, errstr)
}
// ValE is an STAPI function to return the value found for varname(subary...)
//
// Matching ValST(), ValE() wraps ydb_get_st() to return
// the value at the referenced global or local variable node, or intrinsic special variable.
//
// If ydb_get_s() returns an error such as GVUNDEF, INVSVN, LVUNDEF,
// the function returns the error. Otherwise, it returns the value at the node.
func ValE(tptoken uint64, errstr *BufferT, varname string, subary []string) (string, error) {
var dbkey KeyT
var dbvalue BufferT
var err error
var dataSize uint32
printEntry("ValE()")
defer dbkey.Free()
defer dbvalue.Free()
initkey(tptoken, errstr, &dbkey, varname, subary)
dataSize = easyAPIDefaultDataSize
dbvalue.Alloc(dataSize)
// Attempt to fetch the value multiple times. We do not know how big the incoming record is so loop till it fits.
for {
// dbvalue is allocated with current best-guess size of returning data
err = dbkey.ValST(tptoken, errstr, &dbvalue)
if nil != err {
// Check if we had an INVSTRLEN error (too small an output buffer)
errorcode := ErrorCode(err)
if int(YDB_ERR_INVSTRLEN) == errorcode {
// This is INVSTRLEN - reallocate the size we need
dataSize = uint32(dbvalue.getCPtr().len_used)
dbvalue.Free()
dbvalue.Alloc(dataSize)
continue
}
// Otherwise an unexpected error occurred. Return that.
return "", err
}
break // No error so success and we are done!
}
retval, err := dbvalue.ValStr(tptoken, errstr)
if nil != err {
panic(fmt.Sprintf("YDB: Unexpected error with ValStr(): %s", err))
}
return retval, nil
}
// IncrE is a STAPI function to increment the given value by the given amount and return the new value.
//
// Matching IncrST(), IncrE() wraps ydb_incr_st() to atomically increment the referenced global or local variable node
// coerced to a number with incr coerced to a number, with the result stored in the node and returned by the function.
//
// If ydb_incr_st() returns an error such as NUMOFLOW or INVSTRLEN, the function returns the error. Otherwise, it returns the incremented value of the node.
//
// With a nil value for incr, the default increment is 1. Note that the value of the empty string coerced to an integer is zero.
func IncrE(tptoken uint64, errstr *BufferT, incr, varname string, subary []string) (string, error) {
var dbkey KeyT
var dbvalue, incrval BufferT
var err error
var cbuft *C.ydb_buffer_t
printEntry("IncrE()")
if 1 != atomic.LoadUint32(&ydbInitialized) {
initializeYottaDB()
}
defer dbkey.Free()
defer dbvalue.Free()
defer incrval.Free()
initkey(tptoken, errstr, &dbkey, varname, subary)
dbvalue.Alloc(easyAPIDefaultDataSize)
incrval.Alloc(uint32(len(incr)))
err = incrval.SetValStr(tptoken, errstr, incr)
if nil != err {
panic(fmt.Sprintf("YDB: Unexpected error with SetValStr(): %s", err))
}
// Since the return value is only checked to see if it is big enough AFTER the increment
// is done, we cannot repeat the operation with a larger buffer (or the value would be incremented
// again) so for this call, whatever happens just happens though the default buffer should be
// large enough for any reasonable value being incremented.
vargobuft := dbkey.Varnm.getCPtr()
subbuftary := dbkey.Subary.getCPtr()
if nil != errstr {
cbuft = errstr.getCPtr()
}
rc := C.ydb_incr_st(C.uint64_t(tptoken), cbuft, vargobuft, C.int(dbkey.Subary.ElemUsed()), subbuftary,
incrval.getCPtr(), dbvalue.getCPtr())
if YDB_OK != rc {
err = NewError(tptoken, errstr, int(rc))
return "", err
}
retval, err := dbvalue.ValStr(tptoken, errstr)
if nil != err {
panic(fmt.Sprintf("YDB: Unexpected error with ValStr(): %s", err))
}
runtime.KeepAlive(dbkey) // Make sure dbkey and incrval stays in tact through the call into YDB
runtime.KeepAlive(incrval)
return retval, nil
}
// LockE is a STAPI function whose purpose is to release all locks and then lock the locks designated. The variadic list
// is pairs of arguments with the first being a string containing the variable name and the second being a string array
// containing the subscripts, if any, for that variable (null list for no subscripts).
//
// Matching LockST(), LockE() releases all lock resources currently held and then attempt to acquire the named lock resources
// referenced. If no lock resources are specified, it simply releases all lock resources currently held and returns.
//
// interface{} is a series of pairs of varname string and subary []string parameters, where a null subary parameter
// ([]string{}) specifies the unsubscripted lock resource name.
//
// If lock resources are specified, upon return, the process will have acquired all of the named lock resources or none of the
// named lock resources.
//
// If timeoutNsec exceeds YDB_MAX_TIME_NSEC, the function returns with an error return of TIME2LONG.
// If the lock resource names exceeds the maximum number supported (currently eleven), the function returns a PARMOFLOW error.
// If namesubs is not a series of alternating string and []string parameters, the function returns the INVLKNMPAIRLIST error.
// If it is able to aquire the lock resource(s) within timeoutNsec nanoseconds, the function returns holding the lock
// resource(s); otherwise it returns LOCKTIMEOUT. If timeoutNsec is zero, the function makes exactly one attempt to acquire the
// lock resource(s).
func LockE(tptoken uint64, errstr *BufferT, timeoutNsec uint64, namesnsubs ...interface{}) error {
printEntry("LockE()")
if 0 != (uint32(len(namesnsubs)) & 1) {
errmsg, err := MessageT(tptoken, nil, (int)(YDB_ERR_INVLKNMPAIRLIST))
if nil != err {
panic(fmt.Sprintf("YDB: Error fetching INVLKNMPAIRLIST: %s", err))
}
return &YDBError{(int)(YDB_ERR_INVLKNMPAIRLIST), errmsg}
}
lckparms := len(namesnsubs)
parmlst := make([]*KeyT, lckparms/2) // Allocate parameter list of *KeyT values
for i := 0; lckparms > i; i += 2 {
// Pull in the next varname and verify it is a string
newVarname, ok := namesnsubs[i].(string)
if !ok {
errmsg, err := MessageT(tptoken, nil, (int)(YDB_ERR_PARAMINVALID))
if nil != err {
panic(fmt.Sprintf("YDB: Error fetching PARAMINVALID: %s", err))
}
errmsg = strings.Replace(errmsg, "!AD", fmt.Sprintf("%v", namesnsubs[i]), 1) // Replace first with varname
errmsg = strings.Replace(errmsg, "!AD", "LockE()", -1) // Replace second with function name
return &YDBError{(int)(YDB_ERR_PARAMINVALID), errmsg}
}
// Pull in the next subscript array and verify it is an array of strings
newSubs, ok := namesnsubs[i+1].([]string)
if !ok {
errmsg, err := MessageT(tptoken, nil, (int)(YDB_ERR_PARAMINVALID))
if nil != err {
panic(fmt.Sprintf("YDB: Error fetching PARAMINVALID: %s", err))
}
errmsg = strings.Replace(errmsg, "!AD", fmt.Sprintf("%v", namesnsubs[i+1]), 1) // Replace first with varname
errmsg = strings.Replace(errmsg, "!AD", "LockE()", -1) // Replace second with function name
return &YDBError{(int)(YDB_ERR_PARAMINVALID), errmsg}
}
newKey := new(KeyT)
// Run through subscripts to find the biggest
maxsublen := 0
for _, sub := range newSubs {
if len(sub) > maxsublen {
maxsublen = len(sub)
}
}
newKey.Alloc(uint32(len(newVarname)), uint32(len(newSubs)), uint32(maxsublen))
defer newKey.Free() // Force release of C storage without waiting for GC
err := newKey.Varnm.SetValStr(tptoken, errstr, newVarname)
if nil != err {
panic(fmt.Sprintf("YDB: Unexpected error with SetValStr(): %s", err))
}
subcnt := len(newSubs)
for j := 0; subcnt > j; j++ {
err := newKey.Subary.SetValStr(tptoken, errstr, uint32(j), newSubs[j])
if nil != err {
panic(fmt.Sprintf("YDB: Unexpected error with SetValStr(): %s", err))
}
}
err = newKey.Subary.SetElemUsed(tptoken, errstr, uint32(subcnt))
if nil != err {
panic(fmt.Sprintf("YDB: Unexpected error with SetValStr(): %s", err))
}
parmlst[i/2] = newKey
}
return LockST(tptoken, errstr, timeoutNsec, parmlst...)
}
// LockDecrE is a STAPI function to decrement the lock count of the given lock. When the count goes to 0, the lock
// is considered released.
//
// Matching LockDecrST(), LockDecrE() wraps ydb_lock_decr_st() to decrement the count of the lock name
// referenced, releasing it if the count goes to zero or ignoring the invocation if the process does not hold the lock.
func LockDecrE(tptoken uint64, errstr *BufferT, varname string, subary []string) error {
var dbkey KeyT
var err error
var cbuft *C.ydb_buffer_t
printEntry("LockDecrE()")
if 1 != atomic.LoadUint32(&ydbInitialized) {
initializeYottaDB()
}
defer dbkey.Free()
initkey(tptoken, errstr, &dbkey, varname, subary)
if nil != errstr {
cbuft = errstr.getCPtr()
}
vargobuft := dbkey.Varnm.getCPtr()
subbuftary := dbkey.Subary.getCPtr()
rc := C.ydb_lock_decr_st(C.uint64_t(tptoken), cbuft, vargobuft, C.int(dbkey.Subary.ElemUsed()), subbuftary)
if YDB_OK != rc {
err = NewError(tptoken, errstr, int(rc))
return err
}
runtime.KeepAlive(dbkey) // Make sure dbkey stays in tact through the call into YDB
runtime.KeepAlive(errstr)
return nil
}
// LockIncrE is a STAPI function to increase the lock count of a given node within the specified timeout in
// nanoseconds.
//
// Matching LockIncrST(), LockIncrE() wraps ydb_lock_incr_st() to attempt to acquire the referenced lock
// resource name without releasing any locks the process already holds.
//
// If the process already holds the named lock resource, the function increments its count and returns.
// If timeoutNsec exceeds YDB_MAX_TIME_NSEC, the function returns with an error return TIME2LONG.
// If it is able to aquire the lock resource within timeoutNsec nanoseconds, it returns holding the lock, otherwise it returns
// LOCKTIMEOUT. If timeoutNsec is zero, the function makes exactly one attempt to acquire the lock.
func LockIncrE(tptoken uint64, errstr *BufferT, timeoutNsec uint64, varname string, subary []string) error {
var dbkey KeyT
var err error
var cbuft *C.ydb_buffer_t
printEntry("LockIncrE()")
if 1 != atomic.LoadUint32(&ydbInitialized) {
initializeYottaDB()
}
defer dbkey.Free()
initkey(tptoken, errstr, &dbkey, varname, subary)
if nil != errstr {
cbuft = errstr.getCPtr()
}
vargobuft := dbkey.Varnm.getCPtr()
subbuftary := dbkey.Subary.getCPtr()
rc := C.ydb_lock_incr_st(C.uint64_t(tptoken), cbuft, C.ulonglong(timeoutNsec), vargobuft,
C.int(dbkey.Subary.ElemUsed()), subbuftary)
if YDB_OK != rc {
err = NewError(tptoken, errstr, int(rc))
return err
}
runtime.KeepAlive(dbkey) // Make sure dbkey stays in tact through the call into YDB
runtime.KeepAlive(errstr)
return nil
}
// NodeNextE is a STAPI function to return a string array of the subscripts that describe the next node.
//
// Matching NodeNextST(), NodeNextE() wraps ydb_node_next_st() to facilitate depth first traversal of a local or global variable tree.
//
// If there is a next node, it returns the subscripts of that next node. If the node is the last in the tree, the function returns the NODEEND error.
func NodeNextE(tptoken uint64, errstr *BufferT, varname string, subary []string) ([]string, error) {
var dbkey KeyT
var dbsubs BufferTArray
var err error
var subscrCnt uint32
var subscrSize uint32
printEntry("NodeNextE()")
defer dbkey.Free()
defer dbsubs.Free()
initkey(tptoken, errstr, &dbkey, varname, subary)
subscrCnt = easyAPIDefaultSubscrCnt
subscrSize = easyAPIDefaultSubscrSize
dbsubs.Alloc(subscrCnt, subscrSize)
// Attempt to fetch the next subscript set multiple times. We do not know how big the incoming subscripts are
// so loop till they fit.
for {
// dbvalue is allocated with current best-guess size of returning data
err = dbkey.NodeNextST(tptoken, errstr, &dbsubs)
if nil != err {
// Check if we had an INVSTRLEN error (too small an output buffer)
errorcode := ErrorCode(err)
if int(YDB_ERR_INSUFFSUBS) == errorcode {
// This is INSUFFSUBS - pickup number of subscripts we actually need and reallocate
subscrCnt = dbsubs.ElemUsed()
dbsubs.Free()
dbsubs.Alloc(subscrCnt, subscrSize) // Reallocate and reset dbsubs
continue
}
if int(YDB_ERR_INVSTRLEN) == errorcode {
// This is INVSTRLEN - the last valid subscript (as shown by elemUsed) is the element
neededlen, err := dbsubs.ElemLenUsed(tptoken, errstr, dbsubs.ElemUsed())
if nil != err {
panic(fmt.Sprintf("YDB: Unexpected error with ElemLenUsed(): %s", err))
}
subscrSize = neededlen
dbsubs.Free()
dbsubs.Alloc(subscrCnt, subscrSize) // Reallocate and reset dbsubs
continue
}
// Otherwise some error happened so return that
return []string{}, err
}
break // No error so we had success and we are done!
}
// Transfer return BufferTArray to our return string array and return to user
subcnt := int(dbsubs.ElemUsed())
nextsubs := make([]string, subcnt)
for i := 0; i < subcnt; i++ {
nextsub, err := dbsubs.ValStr(tptoken, errstr, uint32(i))
if nil != err {
panic(fmt.Sprintf("YDB: Unexpected error with ValStr(): %s", err))
}
nextsubs[i] = nextsub
}
return nextsubs, nil
}
// NodePrevE is a STAPI function to return a string array of the subscripts that describe the next node.
//
// Matching NodePrevST(), NodePrevE() wraps ydb_node_previous_st() to facilitate reverse depth first traversal
// of a local or global variable tree.
//
// If there is a previous node, it returns the subscripts of that previous node; an empty string array if that previous node is the root.
// If the node is the first in the tree, the function returns the NODEEND error.
func NodePrevE(tptoken uint64, errstr *BufferT, varname string, subary []string) ([]string, error) {
var dbkey KeyT
var dbsubs BufferTArray
var err error
var subscrCnt uint32
var subscrSize uint32
printEntry("NodePrevE()")
defer dbkey.Free()
defer dbsubs.Free()
initkey(tptoken, errstr, &dbkey, varname, subary)
subscrCnt = easyAPIDefaultSubscrCnt
subscrSize = easyAPIDefaultSubscrSize
dbsubs.Alloc(subscrCnt, subscrSize)
// Attempt to fetch the next subscript set multiple times. We do not know how big the incoming subscripts are
// so loop till they fit.
for {
// dbvalue is allocated with current best-guess size of returning data
err = dbkey.NodePrevST(tptoken, errstr, &dbsubs)
if nil != err {
// Check if we had an INVSTRLEN error (too small an output buffer)
errorcode := ErrorCode(err)
if int(YDB_ERR_INSUFFSUBS) == errorcode {
// This is INSUFFSUBS - pickup number of subscripts we actually need and reallocate
subscrCnt = dbsubs.ElemUsed()
dbsubs.Free()
dbsubs.Alloc(subscrCnt, subscrSize)
continue
}
if int(YDB_ERR_INVSTRLEN) == errorcode {
// This is INVSTRLEN - the last valid subscript (as shown by elemUsed) is the element
neededlen, err := dbsubs.ElemLenUsed(tptoken, errstr, dbsubs.ElemUsed())
if nil != err {
panic(fmt.Sprintf("YDB: Unexpected error with ElemLenUsed(): %s", err))
}
subscrSize = neededlen
dbsubs.Free()
dbsubs.Alloc(subscrCnt, subscrSize)
continue
}
// Otherwise some error happened so return that
return []string{}, err
}
break // No error so success and we are done!
}
// Transfer return BufferTArray to our return string array and return to user
subcnt := int(dbsubs.ElemUsed())
nextsubs := make([]string, subcnt)
for i := 0; i < int(dbsubs.ElemUsed()); i++ {
nextsub, err := dbsubs.ValStr(tptoken, errstr, uint32(i))
if nil != err {
panic(fmt.Sprintf("YDB: Unexpected error with ValStr(): %s", err))
}
nextsubs[i] = nextsub
}
return nextsubs, nil
}
// SetValE is a STAPI function to set a value into the given node (varname and subscripts).
//
// Matching SetValST(), at the referenced local or global variable node, or the intrinsic special variable, SetValE() wraps
// ydb_set_st() to set the value specified.
func SetValE(tptoken uint64, errstr *BufferT, value, varname string, subary []string) error {
var dbkey KeyT
var dbvalue BufferT
var maxsublen, sublen, i uint32
var err error
var cbuft *C.ydb_buffer_t
printEntry("SetValE()")
if 1 != atomic.LoadUint32(&ydbInitialized) {
initializeYottaDB()
}
defer dbkey.Free()
defer dbvalue.Free()
subcnt := uint32(len(subary))
maxsublen = 0
for i = 0; i < subcnt; i++ {
// Find maximum length of subscript so know how much to allocate
sublen = uint32(len(subary[i]))
if sublen > maxsublen {
maxsublen = sublen
}
}
dbkey.Alloc(uint32(len(varname)), subcnt, maxsublen)
dbkey.Varnm.SetValStr(tptoken, errstr, varname)
if nil != err {
panic(fmt.Sprintf("YDB: Unexpected error with SetValStr(): %s", err))
}
// Load subscripts into KeyT (if any)
for i = 0; i < subcnt; i++ {
err = dbkey.Subary.SetValStr(tptoken, errstr, i, subary[i])
if nil != err {
panic(fmt.Sprintf("YDB: Unexpected error with SetValStr(): %s", err))
}
}
err = dbkey.Subary.SetElemUsed(tptoken, errstr, subcnt)
if nil != err {
panic(fmt.Sprintf("YDB: Unexpected error with SetUsed(): %s", err))
}
dbvalue.Alloc(uint32(len(value)))
err = dbvalue.SetValStr(tptoken, errstr, value)
if nil != err {
panic(fmt.Sprintf("YDB: Unexpected error with SetValStr(): %s", err))
}
if nil != errstr {
cbuft = errstr.getCPtr()
}
vargobuft := dbkey.Varnm.getCPtr()
subbuftary := dbkey.Subary.getCPtr()
rc := C.ydb_set_st(C.uint64_t(tptoken), cbuft, vargobuft, C.int(dbkey.Subary.ElemUsed()), subbuftary, dbvalue.getCPtr())
if YDB_OK != rc {
err := NewError(tptoken, errstr, int(rc))
return err
}
runtime.KeepAlive(dbkey) // Make sure dbkey and dbvalue stays intact through the call into YDB
runtime.KeepAlive(dbvalue)
runtime.KeepAlive(errstr)
return nil
}
// SubNextE is a STAPI function to return the next subscript at the current subscript level.
//
// Matching SubNextST(), SubNextE() wraps ydb_subscript_next_st() to facilitate breadth-first traversal of a
// local or global variable sub-tree.
//
// At the level of the last subscript, if there is a next subscript with a node and/or a subtree, it returns that subscript.
// If there is no next node or subtree at that level of the subtree, the function returns the NODEEND error.
//
// In the special case where subary is the null array, SubNextE() returns the name of the next global or local
// variable, and the NODEEND error if varname is the last global or local variable.
func SubNextE(tptoken uint64, errstr *BufferT, varname string, subary []string) (string, error) {
var dbkey KeyT
var dbsub BufferT
var err error
var subscrSize uint32
printEntry("SubNextE()")
defer dbkey.Free()
defer dbsub.Free()
initkey(tptoken, errstr, &dbkey, varname, subary)
subscrSize = easyAPIDefaultSubscrSize
dbsub.Alloc(subscrSize)
// Attempt to fetch the value multiple times. We do not know how big the incoming record is
// so loop till it fits.
for {
// dbsub is allocated with current best-guess size of returning data
err = dbkey.SubNextST(tptoken, errstr, &dbsub)
if nil != err {
// Check if we had an INVSTRLEN error (too small an output buffer)
errorcode := ErrorCode(err)
if int(YDB_ERR_INVSTRLEN) == errorcode {
// This is INVSTRLEN - reallocate the size we need
subscrSize = uint32(dbsub.getCPtr().len_used)
dbsub.Free()
dbsub.Alloc(subscrSize)
continue
}
// Otherwise something badder-er happened
return "", err
}
break // No error so success and we are done!
}
retval, err := dbsub.ValStr(tptoken, errstr)
if nil != err {
panic(fmt.Sprintf("YDB: Unexpected error with ValStr(): %s", err))
}
return retval, nil
}
// SubPrevE is a STAPI function to return the previous subscript at the current subscript level.
//
// Matching SubPrevST(), SubPrevE() wraps ydb_subscript_previous_st() to facilitate reverse breadth-first
// traversal of a local or global variable sub-tree.
//
// At the level of the last subscript, if there is a previous subscript with a node and/or a subtree, it returns that subscript.
// If there is no previous node or subtree at that level of the subtree, the function returns the NODEEND error.
//
// In the special case where subary is the null array SubNextE() returns the name of the previous global or local
// variable, and the NODEEND error if varname is the first global or local variable.
func SubPrevE(tptoken uint64, errstr *BufferT, varname string, subary []string) (string, error) {
var dbkey KeyT
var dbsub BufferT
var err error
var subscrSize uint32
printEntry("SubPrevE()")
defer dbkey.Free()
defer dbsub.Free()
initkey(tptoken, errstr, &dbkey, varname, subary)
subscrSize = easyAPIDefaultSubscrSize
dbsub.Alloc(subscrSize)
// Attempt to fetch the value multiple times. We do not know how big the incoming record is
// so loop till it fits.
for {
// dbsub is allocated with current best-guess size of returning data
err = dbkey.SubPrevST(tptoken, errstr, &dbsub)
if nil != err {
// Check if we had an INVSTRLEN error (too small an output buffer)
errorcode := ErrorCode(err)
if int(YDB_ERR_INVSTRLEN) == errorcode {
// This is INVSTRLEN - reallocate the size we need
subscrSize = uint32(dbsub.getCPtr().len_used)
dbsub.Free()
dbsub.Alloc(subscrSize)
continue
}
// Otherwise something badder-er happened
return "", err
}
break // No error so success and we are done!
}
retval, err := dbsub.ValStr(tptoken, errstr)
if nil != err {
panic(fmt.Sprintf("YDB: Unexpected error with ValStr(): %s", err))
}
return retval, nil
}
// TpE is a Easy API function to drive transactions.
//
// Using TpST(), TpE() wraps ydb_tp_st() to implement transaction processing.
//
// Parameters:
//
// tptoken - the token used to identify nested transaction; start with yottadb.NOTTP.
// tpfn - the closure which will be run during the transaction. This closure may get invoked multiple times if a
//
// transaction fails for some reason (concurrent changes, for example), so should not change any data outside of
// the database.
//
// transid - See docs for ydb_tp_s() in the MLPG.
// varnames - a list of local YottaDB variables to reset should the transaction be restarted; if this is an array of 1 string
//
// with a value of "*" all YDB local variables get reset after a TP_RESTART.
func TpE(tptoken uint64, errstr *BufferT, tpfn func(uint64, *BufferT) int32, transid string, varnames []string) error {
var vnames BufferTArray
var maxvarnmlen, varnmcnt, varnmlen uint32
var i int
var err error
var varname string
printEntry("TpE()")
defer vnames.Free()
varnmcnt = uint32(len(varnames))
// Find maximum length of varname so know how much to allocate
maxvarnmlen = 0
for _, varname = range varnames {
varnmlen = uint32(len(varname))
if varnmlen > maxvarnmlen {
maxvarnmlen = varnmlen
}
}
vnames.Alloc(varnmcnt, maxvarnmlen)
for i, varname = range varnames {
err = vnames.SetValStr(tptoken, errstr, uint32(i), varname)
if nil != err {
panic(fmt.Sprintf("YDB: Unexpected error with SetValStr(): %s", err))
}
}
// Drive simpleAPI wrapper and return its return code
return vnames.TpST(tptoken, errstr, tpfn, transid)
}
//////////////////////////////////////////////////////////////////
// //
// Copyright (c) 2018-2025 YottaDB LLC and/or its subsidiaries. //
// All rights reserved. //
// //
// This source code contains the intellectual property //
// of its copyright holder(s), and is made available //
// under a license. If you do not know the terms of //
// the license, please stop and do not read further. //
// //
//////////////////////////////////////////////////////////////////
package yottadb
import (
"fmt"
"unsafe"
)
// #include <stdlib.h>
// #include "libyottadb.h"
import "C"
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Define error related structures, methods and functions
//
////////////////////////////////////////////////////////////////////////////////////////////////////
type ydbErrorSeverity uint32
const ( // Set of constant ydbErrorSeverity type values - these values match those used in YDB/sr_unix/errorsp.h
// (WARNING, SUCCESS etc.)
ydbSevWarn ydbErrorSeverity = iota
ydbSevSuccess
ydbSevError
ydbSevInfo
ydbSevSevere
)
type ydbEntryPoint uint32
// ydbGoCaller is a parm passed to validateNotifySignal() to indicate which entry point is calling it
const (
ydbEntryRegisterSigNotify ydbEntryPoint = iota + 1
ydbEntryUnRegisterSigNotify
)
// ydbGoErrEntry is a structure that contains the definition of a YDBGo wrapper-only error
type ydbGoErrEntry struct {
errNum C.uint32_t // Error number for this error
errName string // Severity of the error (single char)
errSev string // Name of the error (e.g. MEMORY)
errText string // Text of the error message (e.g. out of memory)
}
// YDBError is a structure that defines the error message format which includes both the formated $ZSTATUS
// type message and the numeric error value.
type YDBError struct {
errcode int // The error value (e.g. YDB_ERR_DBFILERR, etc)
errmsg string // The error string - generally from $ZSTATUS when available
}
// Error is a method to return the expected error message string.
func (err *YDBError) Error() string {
return err.errmsg
}
// ErrorCode is a function used to find the error return code.
func ErrorCode(err error) int {
yerr, ok := err.(*YDBError)
if ok {
rc := yerr.errcode
return rc
}
return -1
}
// NewError is a function to create a new YDBError and return it. Note that we use ydb_zstatus() instead of
// using (for example) GetE() to fetch $ZSTATUS because ydb_zstatus does not require a tptoken. This means
// that we don't need to pass tptoken to all the data access methods (For example, ValStr()).
func NewError(tptoken uint64, errstr *BufferT, errnum int) error {
var msgptr *C.char
var errmsg string
var err error
// Check for non-error constants for which MessageT cannot provide a message from YottaDB (since they are not errors).
// These also provide shortcuts performance-sensitive return codes.
if YDB_TP_RESTART == errnum {
return &YDBError{errnum, "TPRESTART"}
}
if YDB_ERR_NODEEND == errnum {
return &YDBError{errnum, "NODEEND"}
}
if YDB_TP_ROLLBACK == errnum {
return &YDBError{errnum, "ROLLBACK"}
}
if YDB_LOCK_TIMEOUT == errnum {
return &YDBError{errnum, "YDB_LOCK_TIMEOUT"}
}
if (nil != errstr) && (nil != errstr.getCPtr()) {
errmsg = C.GoString((*C.char)(errstr.getCPtr().buf_addr))
}
if 0 == len(errmsg) {
// No message was supplied for the error - see if we can find one via MessageT()
errmsg, err = MessageT(tptoken, errstr, errnum)
if nil != err {
if YDB_ERR_CALLINAFTERXIT != ErrorCode(err) {
panic(fmt.Sprintf("YDB: Unable to find error text for error code %d with error %s", errnum, err))
}
// We had a CALLINAFTERXIT error from MessageT() - treat it as a nil message
errmsg = ""
}
if 0 == len(errmsg) {
// MessageT() did not return anything interesting so Fetch $ZSTATUS to return as the error string.
// This has the advantage that CALLINAFTERXIT cannot happen but we are pulling from $ZSTATUS which is
// a global value in the YottaDB engine that can be changed by any thread so this is last choice. The
// advantage is that its message would be fully populated with parameter substituions of error codes,
// filenames, etc.
//
// Note that MessageT() would have taken care of making sure the engine is initialized before this call.
msgptr = (*C.char)(allocMem(C.size_t(YDB_MAX_ERRORMSG)))
C.ydb_zstatus(msgptr, C.int(YDB_MAX_ERRORMSG))
errmsg = C.GoString((*C.char)(msgptr))
freeMem(unsafe.Pointer(msgptr), C.size_t(YDB_MAX_ERRORMSG))
if 0 == len(errmsg) {
// We couldn't find text to do with this message so at least make sure we know what the error
// code is.
errmsg = fmt.Sprintf("YDB-E-UNKNOWNMSG Unknown error message code: %d", errnum)
}
}
}
return &YDBError{errnum, errmsg}
}
// getWrapperErrorMsg fetches returns a message string containing a formatted-as-error local message given its error number
// If the error is not found in the local cache, an empty string is returned.
func getWrapperErrorMsg(errNum int) string {
var i int
var errMsg string
if 0 > errNum { // Cheap absolute value (rather than float64 convert to/from)
errNum = -errNum
}
// Serial lookup of the error message number
for i = range ydbGoErrors {
if errNum == int(ydbGoErrors[i].errNum) {
errMsg = "%YDB-" + ydbGoErrors[i].errSev + "-" + ydbGoErrors[i].errName + ", " + ydbGoErrors[i].errText
break
}
}
return errMsg
}
//////////////////////////////////////////////////////////////////
// //
// Copyright (c) 2020-2025 YottaDB LLC and/or its subsidiaries. //
// All rights reserved. //
// //
// This source code contains the intellectual property //
// of its copyright holder(s), and is made available //
// under a license. If you do not know the terms of //
// the license, please stop and do not read further. //
// //
//////////////////////////////////////////////////////////////////
package yottadb
import (
"fmt"
"os"
"os/signal"
"runtime"
"strconv"
"strings"
"sync"
"sync/atomic"
"syscall"
"time"
)
// #include "libyottadb.h"
// extern void *ydb_get_gowrapper_panic_callback_funcvp(void);
import "C"
type ydbHndlrWhen int
const (
beforeYDBHandler ydbHndlrWhen = iota + 1
afterYDBHandler
)
// Table of signals that the wrapper handles and passes to YottaDB
var ydbSignalList = []syscall.Signal{
// Signal Index in array
syscall.SIGABRT, // [0]
syscall.SIGALRM, // [1]
syscall.SIGBUS, // [2]
syscall.SIGCONT, // [3]
syscall.SIGFPE, // [4]
syscall.SIGHUP, // [5]
syscall.SIGILL, // [6]
syscall.SIGINT, // [7]
// syscall.SIGIO - this is a duplicate of SIGURG
// syscall.SIGIOT - this is a duplicate of SIGABRT
syscall.SIGQUIT, // [8]
syscall.SIGSEGV, // [9]
syscall.SIGTERM, // [10]
syscall.SIGTRAP, // [11]
syscall.SIGTSTP, // [12]
syscall.SIGTTIN, // [13]
syscall.SIGTTOU, // [14]
syscall.SIGURG, // [15]
syscall.SIGUSR1, // [16]
}
// signalsHandled is the count of the signals the wrapper gets notified of and passes on to YottaDB. This matches with
// the number of signals (and thus also the number of goroutines launched running waitForAndProcessSignal() as well as
// being the dimension of the ydbShutdownChannel array below that has one slot for each signal handled.
var signalsHandled = len(ydbSignalList)
// This is a map value element used to look up user signal notification channel and flags (one handler per signal)
type sigNotificationMapEntry struct {
notifyChan chan bool // Channel for user to be notified of a signal being received.
ackChan chan bool // Channel used to acknowledge that processing for a signal has completed.
notifyWhen YDBHandlerFlag // When/if YDB handler is driven in relation to user notification
}
var sigNotificationMap map[syscall.Signal]sigNotificationMapEntry
var wgSigInit sync.WaitGroup // Used to make sure signals are setup before initializeYottaDB() exits
var wgSigShutdown sync.WaitGroup // Used to wait for all signal threads to shutdown
var ydbInitMutex sync.Mutex // Mutex for access to initialization
var ydbShutdownChannel []chan int // Array of channels used to shutdown signal handling goroutines
var ydbShutdownCheck chan int // Channel used to check if all signal routines have been shutdown
var shutdownSigGortns bool // We have been through shutdownSignalGoroutines()
var shutdownSigGortnsMutex sync.Mutex // Serialize access to shutdownSignalGoroutines()
var sigNotificationMapMutex sync.Mutex // Mutex for access to user sig handler map
var ydbSignalActive []uint32 // Array of indicators indicating indexed signal handlers are active
var ydbShutdownComplete []uint32 // Array of flags that indexed signal goroutine shutdown is complete
// validateNotifySignal verifies that the specified signal is valid for RegisterSignalNotify()/UnregisterSignalNotify()
func validateNotifySignal(sig syscall.Signal, entryPoint ydbEntryPoint) error {
// Verify the supplied signal is one that we support with this function. Note this list contains all of the signals
// that the wrapper traps as seen below in the initializeYottaDB() function except those signals that cause
// problems if handlers other than YottaDB's handler is driven (e.g. SIGTSTP, SIGTTIN, etc). It is up to the user
// to know which signals are duplicates of others so if separate handlers are set for say SIGABRT and SIGIOT, whichever
// handler was set last is the one that gets both signals as they are the same signal.
switch sig { // Validate the signal chosen
case syscall.SIGABRT: // Same as SIGIOT
case syscall.SIGALRM:
case syscall.SIGBUS:
case syscall.SIGCONT:
case syscall.SIGFPE:
case syscall.SIGHUP:
case syscall.SIGILL:
case syscall.SIGINT:
// case syscall.SIGIO: // Same as SIGPOLL & SIGURG
// case syscall.SIGIOT: // Same as SIGABRT (both can't exist as cases in a switch)
case syscall.SIGQUIT:
case syscall.SIGSEGV:
case syscall.SIGTERM:
case syscall.SIGTRAP:
// case syscall.SIGTSTP: // Trying to handle this signal just hangs
// case syscall.SIGTTIN: // Trying to handle this signal just hangs
// case syscall.SIGTTOU: // Trying to handle this signal just hangs
case syscall.SIGURG: // Same as SIGPOLL and SIGIO - happens almost constantly so be careful if used
case syscall.SIGUSR1:
default:
entryPoint := selectString((ydbEntryRegisterSigNotify == entryPoint), "yottadb.RegisterSignalNotify()",
"yottadb.UnRegisterSignalNotify()")
panic(fmt.Sprintf("YDB: The specified signal (%v) is not supported for signal notification by %s",
sig, entryPoint))
}
// Note no error is currently returned but will when YDB#790 is complete.
return nil
}
// RegisterSignalNotify is a function to request notification of a signal occurring on a supplied channel. Additionally,
// the user should respond to the same channel when they are done. To make sure this happens, the first step in the
// routine listening for the signal should be a defer statement that sends an acknowledgement back that handling is
// complete.
func RegisterSignalNotify(sig syscall.Signal, notifyChan, ackChan chan bool, notifyWhen YDBHandlerFlag) error {
// Although this routine itself does not interact with the YottaDB runtime, use of this routine has an expectation that
// the runtime is going to handle signals so make sure it gets initialized.
if 1 != atomic.LoadUint32(&ydbInitialized) {
initializeYottaDB()
}
err := validateNotifySignal(sig, ydbEntryRegisterSigNotify)
if nil != err {
panic(fmt.Sprintf("YDB: %v", err))
}
sigNotificationMapMutex.Lock()
if nil == sigNotificationMap {
sigNotificationMap = make(map[syscall.Signal]sigNotificationMapEntry)
}
sigNotificationMap[sig] = sigNotificationMapEntry{notifyChan, ackChan, notifyWhen}
sigNotificationMapMutex.Unlock()
// Note there is no error return right now but one will be added for YDB#790 in the future so defining it now.
return nil
}
// UnRegisterSignalNotify removes a notification request for the given signal. No error is raised if the signal did not already
// have a notification request in effect.
func UnRegisterSignalNotify(sig syscall.Signal) error {
err := validateNotifySignal(sig, ydbEntryUnRegisterSigNotify)
if nil != err {
panic(fmt.Sprintf("YDB: %v", err))
}
sigNotificationMapMutex.Lock()
delete(sigNotificationMap, sig)
sigNotificationMapMutex.Unlock()
// There is no error to return yet but there will be when YDB#790 is complee
return nil
}
// shutdownSignalGoroutines is a function to stop the signal handling goroutines used to tell the YDB engine what signals
// have occurred. No signals are recognized by the Go wrapper or YottaDB once this is done. All signal handling reverts to
// Go standard handling.
func shutdownSignalGoroutines() {
printEntry("shutdownSignalGoroutines")
shutdownSigGortnsMutex.Lock()
if shutdownSigGortns { // Nothing to do if already done
shutdownSigGortnsMutex.Unlock()
if dbgSigHandling {
fmt.Println("YDB: shutdownSignalGoroutines: Bypass shutdownSignalGoroutines as it has already run")
}
return
}
for i := 0; signalsHandled > i; i++ {
close(ydbShutdownChannel[i]) // Will wakeup signal goroutine and make it exit
}
// Wait for the signal goroutines to exit but with a timeout
done := make(chan struct{})
go func() {
// Loop handling channel notifications as goroutines shutdown. If we are currently handling a fatal signal
// like a SIGQUIT, that channel is active but is busy so will not respond to a shutdown request. For this
// reason, we treat active goroutines the same as successfully shutdown goroutines so we don't delay
// shutdown. No need to wait for something that is likely to not occur (The YottaDB handlers for fatal signals
// drive a process-ending panic and never return).
sigRtnScanStopped := false
for !sigRtnScanStopped {
select {
case _ = <-ydbShutdownCheck: // A goroutine finished - check if all are shutdown or otherwise busy
var i int
for i = 0; signalsHandled > i; i++ {
lclShutdownComplete := (1 == atomic.LoadUint32(&ydbShutdownComplete[i]))
lclSignalActive := (1 == atomic.LoadUint32(&ydbSignalActive[i]))
if !lclShutdownComplete && !lclSignalActive {
// A goroutine is not shutdown and is not active - need to wait for more
// goroutine(s) to complete so break out of this scan loop
break
}
}
if signalsHandled == i { // We made it all the way through the loop satisfactorily
close(done) // Notify select loop below that this is complete
sigRtnScanStopped = true // Escape the sig gortn scan loop
}
}
}
}()
select {
case _ = <-done: // All signal monitoring goroutines are shutdown or are busy!
if dbgSigHandling {
fmt.Fprintln(os.Stderr, "YDB: shutdownSignalGoroutines: All signal goroutines successfully closed or active")
}
case <-time.After(time.Duration(MaximumSigShutDownWait) * time.Second):
// Notify syslog that this timeout happened
if dbgSigHandling {
fmt.Fprintln(os.Stderr, "YDB: shutdownSignalGoroutines: Timeout! Some signal threads did not shutdown")
}
errstr := getWrapperErrorMsg(YDB_ERR_SIGGORTNTIMEOUT)
syslogEntry(errstr)
}
shutdownSigGortns = true
shutdownSigGortnsMutex.Unlock()
// All signal routines should be finished or otherwise occupied
if dbgSigHandling {
fmt.Fprintln(os.Stderr, "YDB: shutdownSignalGoroutines: Channel closings complete")
}
}
// YDBWrapperPanic is a function called from C code. The C code routine address is passed to YottaDB via the ydb_main_lang_init()
// call in the below initializeYottaDB() call and is called by YottaDB when it has completed processing a deferred fatal signal
// and needs to exit in a "Go-ish" manner. The parameter determines the type of panic that gets raised.
//
//export YDBWrapperPanic
func YDBWrapperPanic(sigNum C.int) {
var sig syscall.Signal
printEntry("YDBWrapperPanic()")
atomic.StoreUint32(&ydbSigPanicCalled, 1) // Need "atomic" usage to avoid read/write DATA RACE issues
shutdownSignalGoroutines() // Close the goroutines down with their signal notification channels
sig = syscall.Signal(sigNum) // Convert numeric signal number to Signal type for use in panic() messagee
panic(fmt.Sprintf("YDB: Fatal signal %d (%v) occurred", sig, sig))
}
// ForceInit may be called to tell YDBGo that initialization has already occurred.
// This is for use only by users who are mixing v1 and v2 in one application and have already done the initialization with YDBGo v2.
// See v2 README.md for more information.
func ForceInit() {
atomic.StoreUint32(&ydbInitialized, 1) // YottaDB wrapper is now initialized
}
// initializeYottaDB is a function to initialize the YottaDB engine. This is an atypical method of doing simple API
// initialization as usually, we just make the needed calls and initialization is automatically run. But the Go
// wrapper needs to do its initialization differently due to the need to setup signal handling differently. Usually,
// YottaDB sets up its signal handling but to work well with Go, Go itself needs to do the signal handling and forward
// it as needed to the YottaDB engine.
func initializeYottaDB() {
var errStr BufferT
var err error
var releaseNumberStr, releaseMajorStr, releaseMinorStr string
printEntry("initializeYottaDB()")
ydbInitMutex.Lock()
// Verify we need to be initialized
if 1 == atomic.LoadUint32(&ydbInitialized) {
ydbInitMutex.Unlock() // Already initialized - nothing to see here
return
}
// Drive initialization of the YottaDB engine/runtime
rc := C.ydb_main_lang_init(C.YDB_MAIN_LANG_GO, C.ydb_get_gowrapper_panic_callback_funcvp())
if YDB_OK != rc {
panic(fmt.Sprintf("YDB: YottaDB initialization failed with return code %d", rc))
}
// Make a call to see what YottaDB version we are dealing with to verify this version of the wrapper works
// with this version of YottaDB. Note this operation sets the flag inInit for the duration of the call to
// ValE()/ValST() so it does not report a nested SimpleAPI call.
atomic.StoreUint32(&inInit, 1)
defer func() { atomic.StoreUint32(&inInit, 0) }() // Turn the flag back off when we leave if we haven't already done so
defer errStr.Free()
errStr.Alloc(YDB_MAX_ERRORMSG)
releaseInfoString, err := ValE(NOTTP, &errStr, "$ZYRELEASE", []string{})
if nil != err {
panic(fmt.Sprintf("YDB: YottaDB fetch of $ZYRELEASE failed wih return code %s", err))
}
// The returned output should have the YottaDB version as the 2nd token in the form rxyy[y] where:
// - 'r' is a fixed character
// - x is a numeric digit specifying the major version number
// - yy[y] are basically the remaining digits and specify the minor release number.
releaseInfoTokens := strings.Fields(releaseInfoString)
releaseNumberStr = releaseInfoTokens[1] // Fetch second token
if "r" != releaseNumberStr[:1] { // Better start with 'r'
panic(fmt.Sprintf("YDB: Unexpected output from fetch of $ZYRELEASE: %s", releaseInfoString))
}
releaseNumberStr = releaseNumberStr[1:] // Remove starting 'r' in the release number
dotIndex := strings.Index(releaseNumberStr, ".") // Look for the decimal point that separates major/minor values
if 0 <= dotIndex { // Decimal point found
releaseMajorStr = string(releaseNumberStr[:dotIndex])
releaseMinorStr = string(releaseNumberStr[dotIndex+1:])
} else {
releaseMajorStr = releaseNumberStr // Isolate the major version number
releaseMinorStr = "00"
}
// Note it is possible for either the major or minor release values to have a single letter suffix that is primarily
// for use in a development environment (no production releases have character suffixes). If we get an error, try
// removing a char off the end and retry.
runningYDBReleaseMajor, err := strconv.Atoi(releaseMajorStr)
if nil != err {
releaseMajorStr = releaseMajorStr[:len(releaseMajorStr)-1]
runningYDBReleaseMajor, err = strconv.Atoi(releaseMajorStr)
if nil != err {
panic(fmt.Sprintf("YDB: Failure trying to convert major release to int: %s", err))
}
}
runningYDBReleaseMinor, err := strconv.Atoi(releaseMinorStr)
if nil != err { // Strip off last char and try again
releaseMinorStr = releaseMinorStr[:len(releaseMinorStr)-1]
runningYDBReleaseMinor, err = strconv.Atoi(releaseMinorStr)
if nil != err {
panic(fmt.Sprintf("YDB: Failure trying to convert minor release to int: %s", err))
}
}
// Verify we are running with the minimum YottaDB version or later
if (MinimumYDBReleaseMajor > runningYDBReleaseMajor) ||
((MinimumYDBReleaseMajor == runningYDBReleaseMajor) && (MinimumYDBReleaseMinor > runningYDBReleaseMinor)) {
panic(fmt.Sprintf("YDB: Not running with at least minimum YottaDB release. Needed: %s Have: r%d.%d",
MinimumYDBRelease, runningYDBReleaseMajor, runningYDBReleaseMinor))
}
// Create the shutdown channel array now that we know (at run time) how many we need. See the ydbSignalList defined
// above for the list of signals. Also create the ydbSignalActive array (same size and index).
ydbShutdownChannel = make([]chan int, signalsHandled) // Array of channels for each signal go routine for shutdown notification
ydbShutdownCheck = make(chan int) // Channel used to notify to check if goroutine shutdown is complete
ydbSignalActive = make([]uint32, signalsHandled) // Array of flags that signal handling is active
ydbShutdownComplete = make([]uint32, signalsHandled) // Array of flags that goroutine shutdown is complete
// Start up a goroutine for each signal we want to be notified of. This is so that if one signal is in process,
// we can still catch a different signal and deliver it appropriately (probably to the same thread). For each signal,
// bump our wait group counter so we don't proceed until all of these goroutines are initialized. Add or remove any
// signals to be handled from the ydbSignalsList array up top of this module.
for indx := 0; indx < signalsHandled; indx++ {
wgSigInit.Add(1) // Indicate this signal goroutine is not yet initialized
go waitForAndProcessSignal(indx)
}
// Now wait for the goroutine to initialize and get signals all set up. When that is done, we can return
wgSigInit.Wait()
atomic.StoreUint32(&ydbInitialized, 1) // YottaDB wrapper is now initialized
ydbInitMutex.Unlock()
}
// notifyUserSignalChannel drives a user signal routine associated with a given signal
func notifyUserSignalChannel(sigHndlrEntry sigNotificationMapEntry, whenCalled ydbHndlrWhen) {
// Notify user code via the supplied channel
if dbgSigHandling {
fmt.Fprintln(os.Stderr, "YDB: goroutine-sighandler: Sending 'true' to notify",
"channel", selectString(beforeYDBHandler == whenCalled, "(a)", "(b)"))
}
// Purge the acknowledgement channel of any content by reading the contents if any
for 0 < len(sigHndlrEntry.ackChan) {
if dbgSigHandling {
fmt.Println("YDB: goroutine-sighandler: Flush loop read",
selectString(beforeYDBHandler == whenCalled, "(a)", "(b)"))
}
waitForSignalAckWTimeout(sigHndlrEntry.ackChan,
selectString(beforeYDBHandler == whenCalled, "(flush before)", "(flush after)"))
}
sigHndlrEntry.notifyChan <- true // Notify receiver the signal has been seen
if NotifyAsyncYDBSigHandler != sigHndlrEntry.notifyWhen {
// Wait for acknowledgement that their handling is complete
if dbgSigHandling {
fmt.Fprintln(os.Stderr, "YDB: goroutine-sighandler: Waiting for notify acknowledgement",
selectString(beforeYDBHandler == whenCalled, "(a)", "(b)"))
}
waitForSignalAckWTimeout(sigHndlrEntry.ackChan,
selectString(beforeYDBHandler == whenCalled, "(wait before)", "(wait after)"))
}
}
// waitForSignalAckWTimeout is used to wait for a signal with a timeout value of MaximumSignalAckWait
func waitForSignalAckWTimeout(ackChan chan bool, whatAck string) {
select { // Wait for an acknowledgement but put a timer on it
case _ = <-ackChan:
case <-time.After(time.Duration(MaximumSigAckWait) * time.Second):
syslogEntry(strings.Replace(getWrapperErrorMsg(YDB_ERR_SIGACKTIMEOUT), "!AD", whatAck, 1))
}
}
// waitForAndProcessSignal is used as a goroutine to wait for a signal notification and send it to YottaDB's signal dispatcher.
// This routine will also drive a user handler for the subset of signals we allow to have user handlers if one is defined.
func waitForAndProcessSignal(shutdownChannelIndx int) {
// This Go routine needs its own errstr buffer as it continues running until ydb_exit() is called
var errstr BufferT
var cbuft *C.ydb_buffer_t
var allDone bool
var shutdownChannel *chan int
var sigStatus string
var sig syscall.Signal
var rc C.int
sig = ydbSignalList[shutdownChannelIndx]
shutdownChannel = &ydbShutdownChannel[shutdownChannelIndx]
errstr.Alloc(YDB_MAX_ERRORMSG) // Initialize error string to hold errors
// We only need one of each type of signal so buffer depth is 1, but let it queue one additional
sigchan := make(chan os.Signal, 2)
// Only one message need be passed on shutdown channel so no buffering
*shutdownChannel = make(chan int)
// Tell Go to pass this signal to our channel
signal.Notify(sigchan, sig)
if dbgSigHandling {
fmt.Fprintf(os.Stderr, "YDB: goroutine-sighandler: Signal handler initialized for %v\n", sig)
}
wgSigInit.Done() // Signal parent goroutine that we have completed initializing signal handling
// Although we typically refer to individual signals with their syscall.Signal type, we do need to have them
// in their numeric form too to pass to C when we want to dispatch their YottaDB handler. Unfortunately,
// switching between designations is a bit silly.
csignum := fmt.Sprintf("%d", sig)
signum, err := strconv.Atoi(csignum)
if nil != err {
panic(fmt.Sprintf("YDB: Unexpected error translating signal to numeric: %v", csignum))
}
cbuft = errstr.getCPtr() // Get pointer to ydb_buffer_t embedded in errstr's BufferT we need to pass to C
allDone = false
for !allDone {
select {
case _ = <-sigchan:
// Wait for signal notification
case _ = <-*shutdownChannel:
allDone = true // Done by close of channel
}
if allDone {
break // Got a shutdown request - fall out!
}
func() { // Inline function to provide scope for defer
rc = 0 // In case ydb_sig_dispatch() is bypassed by not running a handler
atomic.StoreUint32(&ydbSignalActive[shutdownChannelIndx], 1)
defer func() {
atomic.StoreUint32(&ydbSignalActive[shutdownChannelIndx], 0) // Shut flag back off when we are done
}()
// See if we have a notification request for this signal
sigNotificationMapMutex.Lock()
sigHndlrEntry, sigHndlrEntryFound := sigNotificationMap[sig]
sigNotificationMapMutex.Unlock()
// If we want to do our user notification before the YDB handler runs, in parallel with the YDB handler,
// or if we don't want to run the YDB handler at all, drive the notification here. The only other case is
// doing the notification after the YDB handler is driven but we'll do that later. For fatal signals, we
// probably won't return from the YDB handler as it will (usually) throw a panic but some fatal signals can
// be deferred under the right conditions (holding crit, interrupts-disabled-state, etc).
if sigHndlrEntryFound {
switch sigHndlrEntry.notifyWhen {
case NotifyBeforeYDBSigHandler:
fallthrough
case NotifyAsyncYDBSigHandler:
fallthrough
case NotifyInsteadOfYDBSigHandler:
// Notify user code via the supplied channel specifying that this message is occurring BEFORE
// the YDB handler has been driven.
notifyUserSignalChannel(sigHndlrEntry, beforeYDBHandler)
case NotifyAfterYDBSigHandler:
}
}
// Now notify the YDB runtime of this signal unless the handler is not supposed to run
if !sigHndlrEntryFound || (NotifyInsteadOfYDBSigHandler != sigHndlrEntry.notifyWhen) {
// Note this call to ydb_sig_dispatch() does not pass a tptoken. The reason for this is that inside
// this routine the tptoken at the time the signal actually pops in unknown. The ydb_sig_dispatch()
// routine itself does not need the tptoken nor does anything it calls but we do still need the
// error buffer in case an error occurs that we need to return.
if dbgSigHandling && (syscall.SIGURG != sig) {
// Note our passage if not SIGURG (happens almost continually so be silent)
fmt.Fprintln(os.Stderr, "YDB: goroutine-sighandler: Go sighandler - sending notification",
"for signal", signum, " to the YottaDB signal dispatcher")
}
rc = C.ydb_sig_dispatch(cbuft, C.int(signum))
switch rc {
case YDB_OK: // Signal handling complete
case YDB_DEFER_HANDLER: // Signal was deferred for some reason
// If CALLINAFTERXIT (positive or negative version) we're done - exit goroutine
case YDB_ERR_CALLINAFTERXIT, -YDB_ERR_CALLINAFTERXIT:
allDone = true
default: // Some sort of error occurred during signal handling
if YDB_OK != rc {
errstring, err := errstr.ValStr(NOTTP, nil)
if "" == errstring {
if nil != err {
errstring = fmt.Sprintf("%s", err)
} else {
errstring = "(unknown error)"
}
}
panic(fmt.Sprintf("YDB: Failure from ydb_sig_dispatch() (rc = %d): %s", rc,
errstring))
}
}
}
// Drive user notification if requested
if sigHndlrEntryFound && (NotifyAfterYDBSigHandler == sigHndlrEntry.notifyWhen) {
notifyUserSignalChannel(sigHndlrEntry, afterYDBHandler)
}
if dbgSigHandling {
sigStatus = "complete"
switch sig {
case syscall.SIGALRM, syscall.SIGCONT, syscall.SIGIO, syscall.SIGIOT, syscall.SIGURG:
// No post processing but SIGALRM is most common signal so check for it first
case syscall.SIGTSTP, syscall.SIGTTIN, syscall.SIGTTOU, syscall.SIGUSR1:
// No post processing here either
case syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM:
// These are the deferrable signals - mark our handling as such if they were deferred
if YDB_DEFER_HANDLER == rc {
sigStatus = "deferred"
}
}
if syscall.SIGURG != sig {
// Note our passage if not SIGURG (SIGURG happens almost continually so be silent)
fmt.Fprintln(os.Stderr, "YDB: goroutine-sighandler: Signal handling for signal", signum,
sigStatus)
}
}
}()
}
signal.Stop(sigchan) // No more signal notification for this signal channel
runtime.KeepAlive(errstr) // Keep errstr (and its internal C buffer) around till sure we are done with it
if dbgSigHandling {
fmt.Fprintln(os.Stderr, "YDB: goroutine-sighandler: Goroutine for signal index", shutdownChannelIndx, "exiting..")
}
atomic.StoreUint32(&ydbShutdownComplete[shutdownChannelIndx], 1) // Indicate this channel is closed
ydbShutdownCheck <- 0 // Notify shutdownSignalGoroutines that it needs to check if all channels closed now
}
//////////////////////////////////////////////////////////////////
// //
// Copyright (c) 2018-2022 YottaDB LLC and/or its subsidiaries. //
// All rights reserved. //
// //
// This source code contains the intellectual property //
// of its copyright holder(s), and is made available //
// under a license. If you do not know the terms of //
// the license, please stop and do not read further. //
// //
//////////////////////////////////////////////////////////////////
package yottadb
import (
"io"
"os"
"runtime"
"sync/atomic"
"unsafe"
)
// #include "libyottadb.h"
import "C"
// KeyT defines a database key including varname and optional subscripts. Because this structure's contents contain
// pointers to C allocated storage, this structure is NOT safe for concurrent access.
type KeyT struct {
Varnm *BufferT
Subary *BufferTArray
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Data manipulation methods for KeyT
//
////////////////////////////////////////////////////////////////////////////////////////////////////
// Alloc is a STAPI method to allocate both pieces of the KeyT according to the supplied parameters.
//
// Invoke Varnm.Alloc(varSiz) and SubAry.Alloc(numSubs, subSiz)
//
// Parameters:
// varSiz - Length of buffer for varname (current var max is 31).
// numSubs - Number of subscripts to supply (current subscript max is 31).
// subSiz - Length of the buffers for subscript values.
func (key *KeyT) Alloc(varSiz, numSubs, subSiz uint32) {
var buffertary BufferTArray
var buffer BufferT
printEntry("KeyT.Alloc()")
if nil == key {
panic("YDB: *KeyT receiver of Alloc() cannot be nil")
}
key.Varnm = &buffer
key.Varnm.Alloc(varSiz)
key.Subary = &buffertary
key.Subary.Alloc(numSubs, subSiz)
}
// Dump is a STAPI method to dump the contents of the KeyT structure.
//
// Invoke Varnm.Dump() and SubAry.Dump().
func (key *KeyT) Dump() {
printEntry("KeyT.Dump()")
if nil == key {
panic("YDB: *KeyT receiver of Dump() cannot be nil")
}
key.DumpToWriter(os.Stdout)
}
// DumpToWriter dumps a textual representation of this key to the writer.
func (key *KeyT) DumpToWriter(writer io.Writer) {
if nil == key {
panic("YDB: *KeyT receiver of DumpWriter() cannot be nil")
}
if nil != key.Varnm {
key.Varnm.DumpToWriter(writer)
}
if nil != key.Subary {
key.Subary.DumpToWriter(writer)
}
}
// Free is a STAPI method to free both pieces of the KeyT structure.
//
// Invoke Varnm.Free() and SubAry.Free().
func (key *KeyT) Free() {
printEntry("KeyT.Free()")
if nil != key { // Ignore if no struct passed
key.Varnm.Free()
key.Subary.Free()
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Simple (Threaded) API methods for KeyT
//
////////////////////////////////////////////////////////////////////////////////////////////////////
// DataST is a STAPI method to determine the status of a given node and its successors.
//
// Matching DataE(), DataST() returns the result of ydb_data_st(). In the event an error is returned, the return value
// is unspecified.
func (key *KeyT) DataST(tptoken uint64, errstr *BufferT) (uint32, error) {
var retval C.uint
var cbuft *C.ydb_buffer_t
printEntry("KeyT.DataST()")
if nil == key {
panic("YDB: *KeyT receiver of DataST() cannot be nil")
}
if 1 != atomic.LoadUint32(&ydbInitialized) {
initializeYottaDB()
}
if nil != errstr {
cbuft = errstr.getCPtr()
}
vargobuft := key.Varnm.getCPtr()
if (nil == vargobuft) || (nil == vargobuft.buf_addr) || (0 == vargobuft.len_used) {
panic("YDB: KeyT varname is not allocated, is nil, or has a 0 length")
}
subgobuftary := key.Subary
if nil == subgobuftary {
panic("YDB: KeyT Subary is nil")
}
subbuftary := subgobuftary.getCPtr()
rc := C.ydb_data_st(C.uint64_t(tptoken), cbuft, vargobuft, C.int(subgobuftary.ElemUsed()), subbuftary, &retval)
if YDB_OK != rc {
err := NewError(tptoken, errstr, int(rc))
return uint32(retval), err
}
runtime.KeepAlive(key) // Make sure key hangs around through the YDB call
runtime.KeepAlive(errstr)
return uint32(retval), nil
}
// DeleteST is a STAPI method to delete a node and perhaps its successors depending on the value of deltype.
//
// Matching DeleteE(), DeleteST() wraps ydb_delete_st() to delete a local or global variable node or (sub)tree, with a value of
// YDB_DEL_NODE for deltype specifying that only the node should be deleted, leaving the (sub)tree untouched, and a value
// of YDB_DEL_TREE specifying that the node as well as the (sub)tree are to be deleted.
func (key *KeyT) DeleteST(tptoken uint64, errstr *BufferT, deltype int) error {
var cbuft *C.ydb_buffer_t
printEntry("KeyT.DeleteST()")
if nil == key {
panic("YDB: *KeyT receiver of DeleteST() cannot be nil")
}
if 1 != atomic.LoadUint32(&ydbInitialized) {
initializeYottaDB()
}
if nil != errstr {
cbuft = errstr.getCPtr()
}
vargobuft := key.Varnm.getCPtr()
if (nil == vargobuft) || (nil == vargobuft.buf_addr) || (0 == vargobuft.len_used) {
panic("YDB: KeyT varname is not allocated, is nil, or has a 0 length")
}
subgobuftary := key.Subary
if nil == subgobuftary {
panic("YDB: KeyT Subary is nil")
}
subbuftary := subgobuftary.getCPtr()
rc := C.ydb_delete_st(C.uint64_t(tptoken), cbuft, vargobuft, C.int(subgobuftary.ElemUsed()), subbuftary,
C.int(deltype))
if YDB_OK != rc {
err := NewError(tptoken, errstr, int(rc))
return err
}
runtime.KeepAlive(key) // Make sure key hangs around through the YDB call
runtime.KeepAlive(errstr)
return nil
}
// ValST is a STAPI method to fetch the given node returning its value in retval.
//
// Matching ValE(), ValST() wraps ydb_get_st() to return the value at the referenced global or local variable node, or
// intrinsic special variable, in the buffer referenced by the BufferT structure referenced by retval.
//
// If ydb_get_st() returns an error such as GVUNDEF, INVSVN, LVUNDEF, the method makes no changes to the structures under retval
// and returns the error. If the length of the data to be returned exceeds retval.getLenAlloc(), the method sets the len_used` of
// the C.ydb_buffer_t referenced by retval to the required length, and returns an INVSTRLEN error. Otherwise, it copies the data
// to the buffer referenced by the retval.buf_addr, and sets retval.lenUsed to its length.
func (key *KeyT) ValST(tptoken uint64, errstr *BufferT, retval *BufferT) error {
var cbuft *C.ydb_buffer_t
printEntry("KeyT.ValST()")
if nil == key {
panic("YDB: *KeyT receiver of ValST() cannot be nil")
}
if (1 != atomic.LoadUint32(&ydbInitialized)) && (1 != atomic.LoadUint32(&inInit)) {
// Run initialization but only if we haven't run it before AND we aren't already in initializeYottaDB()
initializeYottaDB()
}
if nil != errstr {
cbuft = errstr.getCPtr()
}
vargobuft := key.Varnm.getCPtr()
if (nil == vargobuft) || (nil == vargobuft.buf_addr) || (0 == vargobuft.len_used) {
panic("YDB: KeyT varname is not allocated, is nil, or has a 0 length")
}
subgobuftary := key.Subary
if nil == subgobuftary {
panic("YDB: KeyT Subary is nil")
}
subbuftary := subgobuftary.getCPtr()
rc := C.ydb_get_st(C.uint64_t(tptoken), cbuft, vargobuft, C.int(subgobuftary.ElemUsed()), subbuftary,
retval.getCPtr())
if YDB_OK != rc {
err := NewError(tptoken, errstr, int(rc))
return err
}
runtime.KeepAlive(key) // Make sure key hangs around through the YDB call
runtime.KeepAlive(retval)
runtime.KeepAlive(errstr)
return nil
}
// IncrST is a STAPI method to increment a given node and return the new value.
//
// Matching IncrE(), IncrST() wraps ydb_incr_st() to atomically increment the referenced global or local variable node
// coerced to a number, with incr coerced to a number. It stores the result in the node and also returns it through
// the BufferT structure referenced by retval.
//
// If ydb_incr_st() returns an error such as NUMOFLOW, INVSTRLEN, the method makes no changes to the structures under retval and
// returns the error. If the length of the data to be returned exceeds retval.lenAlloc, the method sets the len_used
// of the C.ydb_buffer_t referenced by retval to the required length, and returns an INVSTRLEN error.
// Otherwise, it copies the data to the buffer referenced by the retval.buf_addr, sets retval.lenUsed to its length.
//
// With a nil value for incr, the default increment is 1. Note that the value of the empty string coerced to an integer is zero.
func (key *KeyT) IncrST(tptoken uint64, errstr *BufferT, incr, retval *BufferT) error {
var cbuft, incrcbuft *C.ydb_buffer_t
printEntry("KeyT.IncrST()")
if nil == key {
panic("YDB: *KeyT receiver of IncrST() cannot be nil")
}
if 1 != atomic.LoadUint32(&ydbInitialized) {
initializeYottaDB()
}
if nil != errstr {
cbuft = errstr.getCPtr()
}
vargobuft := key.Varnm.getCPtr()
if (nil == vargobuft) || (nil == vargobuft.buf_addr) || (0 == vargobuft.len_used) {
panic("YDB: KeyT varname is not allocated, is nil, or has a 0 length")
}
subgobuftary := key.Subary
if nil == subgobuftary {
panic("YDB: KeyT Subary is nil")
}
subbuftary := subgobuftary.getCPtr()
if nil != incr {
incrcbuft = incr.getCPtr()
}
rc := C.ydb_incr_st(C.uint64_t(tptoken), cbuft, vargobuft, C.int(subgobuftary.ElemUsed()), subbuftary, incrcbuft,
retval.getCPtr())
if YDB_OK != rc {
err := NewError(tptoken, errstr, int(rc))
return err
}
runtime.KeepAlive(key) // Make sure key hangs around through the YDB call
runtime.KeepAlive(retval)
runtime.KeepAlive(incr)
runtime.KeepAlive(errstr)
return nil
}
// LockDecrST is a STAPI method to decrement the lock-count of a given lock node.
//
// Matching LockDecrE(), LockDecrST() wraps ydb_lock_decr_st() to decrement the count of the lock name
// referenced, releasing it if the count goes to zero or ignoring the invocation if the process does not hold the lock.
func (key *KeyT) LockDecrST(tptoken uint64, errstr *BufferT) error {
var cbuft *C.ydb_buffer_t
printEntry("KeyT.LockDecrST()")
if nil == key {
panic("YDB: *KeyT receiver of LockDecrST() cannot be nil")
}
if 1 != atomic.LoadUint32(&ydbInitialized) {
initializeYottaDB()
}
if nil != errstr {
cbuft = errstr.getCPtr()
}
vargobuft := key.Varnm.getCPtr()
if (nil == vargobuft) || (nil == vargobuft.buf_addr) || (0 == vargobuft.len_used) {
panic("YDB: KeyT varname is not allocated, is nil, or has a 0 length")
}
subgobuftary := key.Subary
if nil == subgobuftary {
panic("YDB: KeyT Subary is nil")
}
subbuftary := subgobuftary.getCPtr()
rc := C.ydb_lock_decr_st(C.uint64_t(tptoken), cbuft, vargobuft, C.int(subgobuftary.ElemUsed()), subbuftary)
if YDB_OK != rc {
err := NewError(tptoken, errstr, int(rc))
return err
}
runtime.KeepAlive(key) // Make sure key hangs around through the YDB call
runtime.KeepAlive(errstr)
return nil
}
// LockIncrST is a STAPI method to increment the lock-count of a given node lock with the given timeout in nano-seconds.
//
// Matching LockIncrE(), LockIncrST() wraps ydb_lock_incr_st() to attempt to acquire the referenced lock
// resource name without releasing any locks the process already holds.
//
// If the process already holds the named lock resource, the method increments its count and returns.
// If timeoutNsec exceeds YDB_MAX_TIME_NSEC, the method returns with an error return TIME2LONG.
// If it is able to aquire the lock resource within timeoutNsec nanoseconds, it returns holding the lock, otherwise it returns
// LOCK_TIMEOUT. If timeoutNsec is zero, the method makes exactly one attempt to acquire the lock.
func (key *KeyT) LockIncrST(tptoken uint64, errstr *BufferT, timeoutNsec uint64) error {
var cbuft *C.ydb_buffer_t
printEntry("KeyT.LockIncrST()")
if nil == key {
panic("YDB: *KeyT receiver of LockIncrST() cannot be nil")
}
if 1 != atomic.LoadUint32(&ydbInitialized) {
initializeYottaDB()
}
if nil != errstr {
cbuft = errstr.getCPtr()
}
vargobuft := key.Varnm.getCPtr()
if (nil == vargobuft) || (nil == vargobuft.buf_addr) || (0 == vargobuft.len_used) {
panic("YDB: KeyT varname is not allocated, is nil, or has a 0 length")
}
subgobuftary := key.Subary
if nil == subgobuftary {
panic("YDB: KeyT Subary is nil")
}
subbuftary := subgobuftary.getCPtr()
rc := C.ydb_lock_incr_st(C.uint64_t(tptoken), cbuft, C.ulonglong(timeoutNsec), vargobuft,
C.int(subgobuftary.ElemUsed()), subbuftary)
if YDB_OK != rc {
err := NewError(tptoken, errstr, int(rc))
return err
}
runtime.KeepAlive(key) // Make sure key hangs around through the YDB call
runtime.KeepAlive(errstr)
return nil
}
// NodeNextST is a STAPI method to return the next subscripted node for the given global - the node logically following the
// specified node (returns *BufferTArray).
//
// Matching NodeNextE(), NodeNextST() wraps ydb_node_next_st() to facilitate depth first traversal of a local or global variable tree.
//
// If there is a next node:
//
// If the number of subscripts of that next node exceeds next.elemAlloc, the method sets next.elemUsed to
// the number of subscripts required, and returns an INSUFFSUBS error. In this case the elemUsed is greater than elemAlloc.
// If one of the C.ydb_buffer_t structures referenced by next (call the first or only element n) has insufficient space for
// the corresponding subscript, the method sets next.elemUsed to n, and the len_alloc of that C.ydb_buffer_t structure to the actual space
// required. The method returns an INVSTRLEN error. In this case the len_used of that structure is greater than its len_alloc.
// Otherwise, it sets the structure next to reference the subscripts of that next node, and next.elemUsed to the number of subscripts.
//
// If the node is the last in the tree, the method returns the NODEEND error, making no changes to the structures below next.
func (key *KeyT) NodeNextST(tptoken uint64, errstr *BufferT, next *BufferTArray) error {
var nextElemPtr *uint32
var nextElemUsed, dummyElemUsed uint32
var nextSubaryPtr, cbuft *C.ydb_buffer_t
printEntry("KeyT.NodeNextST()")
if nil == key {
panic("YDB: *KeyT receiver of NodeNextST() cannot be nil")
}
if 1 != atomic.LoadUint32(&ydbInitialized) {
initializeYottaDB()
}
if nil != errstr {
cbuft = errstr.getCPtr()
}
vargobuft := key.Varnm.getCPtr()
if (nil == vargobuft) || (nil == vargobuft.buf_addr) || (0 == vargobuft.len_used) {
panic("YDB: KeyT varname is not allocated, is nil, or has a 0 length")
}
subgobuftary := key.Subary
if nil == subgobuftary {
panic("YDB: KeyT Subary is nil")
}
// The output buffer does not need to be allocated at this point though it may error in ydb_node_next_s() if not.
if nil != next {
nextElemUsed = next.ElemAlloc() // Set all elements of output array available for output
nextElemPtr = &nextElemUsed
nextSubaryPtr = next.getCPtr()
} else {
nextElemPtr = &dummyElemUsed
nextSubaryPtr = nil
}
subbuftary := subgobuftary.getCPtr()
rc := C.ydb_node_next_st(C.uint64_t(tptoken), cbuft, vargobuft, C.int(subgobuftary.ElemUsed()), subbuftary,
(*C.int)(unsafe.Pointer(nextElemPtr)), nextSubaryPtr)
if nil != next { // If return area supplied, set the subscript count in the output array (always)
next.cbuftary.elemUsed = nextElemUsed
}
if YDB_OK != rc {
err := NewError(tptoken, errstr, int(rc))
return err
}
runtime.KeepAlive(key) // Make sure key hangs around through the YDB call
runtime.KeepAlive(next)
runtime.KeepAlive(errstr)
return nil
}
// NodePrevST is a STAPI method to return the previous subscripted node for the given global - the node logically previous
// to the specified node (returns *BufferTArray).
//
// Matching NodePrevE(), NodePrevST() wraps ydb_node_previous_st() to facilitate reverse depth first traversal of a local or global variable tree.
//
// If there is a previous node:
//
// If the number of subscripts of that previous node exceeds prev.elemAlloc, the method sets prev.elemUsed to
// the number of subscripts required, and returns an INSUFFSUBS error. In this case the elemUsed is greater than elemAlloc.
// If one of the C.ydb_buffer_t structures referenced by prev (call the first or only element n) has insufficient space for
// the corresponding subscript, the method sets prev.elemUsed to n, and the len_alloc of that C.ydb_buffer_t structure to the actual space
// required. The method returns an INVSTRLEN error. In this case the len_used of that structure is greater than its len_alloc.
// Otherwise, it sets the structure prev to reference the subscripts of that prev node, and prev.elemUsed to the number of subscripts.
//
// If the node is the first in the tree, the method returns the NODEEND error making no changes to the structures below prev.
func (key *KeyT) NodePrevST(tptoken uint64, errstr *BufferT, prev *BufferTArray) error {
var prevElemPtr *uint32
var prevElemUsed, dummyElemUsed uint32
var prevSubaryPtr, cbuft *C.ydb_buffer_t
printEntry("KeyT.NodePrevST()")
if nil == key {
panic("YDB: *KeyT receiver of NodePrevST() cannot be nil")
}
if 1 != atomic.LoadUint32(&ydbInitialized) {
initializeYottaDB()
}
if nil != errstr {
cbuft = errstr.getCPtr()
}
vargobuft := key.Varnm.getCPtr()
if (nil == vargobuft) || (nil == vargobuft.buf_addr) || (0 == vargobuft.len_used) {
panic("YDB: KeyT varname is not allocated, is nil, or has a 0 length")
}
subgobuftary := key.Subary
if nil == subgobuftary {
panic("YDB: KeyT Subary is nil")
}
// The output buffer does not need to be allocated at this point though it may error in ydb_node_previous_s() if not.
if nil != prev {
prevElemUsed = prev.ElemAlloc() // Set all elements of output array available for output
prevElemPtr = &prevElemUsed
prevSubaryPtr = prev.getCPtr()
} else {
prevElemPtr = &dummyElemUsed
prevSubaryPtr = nil
}
subbuftary := subgobuftary.getCPtr()
rc := C.ydb_node_previous_st(C.uint64_t(tptoken), cbuft, vargobuft, C.int(subgobuftary.ElemUsed()),
subbuftary, (*C.int)(unsafe.Pointer(prevElemPtr)), prevSubaryPtr)
if nil != prev { // If return area supplied, set the subscript count in the output array (always)
prev.cbuftary.elemUsed = prevElemUsed
}
if YDB_OK != rc {
err := NewError(tptoken, errstr, int(rc))
return err
}
runtime.KeepAlive(key) // Make sure key hangs around through the YDB call
runtime.KeepAlive(prev)
runtime.KeepAlive(errstr)
return nil
}
// SetValST is a STAPI method to set the given value into the given node (glvn or SVN).
//
// Matching SetE(), at the referenced local or global variable node, or the intrinsic special variable, SetValST() wraps
// ydb_set_st() to set the value specified by val.
func (key *KeyT) SetValST(tptoken uint64, errstr *BufferT, value *BufferT) error {
var cbuft *C.ydb_buffer_t
printEntry("KeyT.SetValST()")
if nil == key {
panic("YDB: *KeyT receiver of SetValST() cannot be nil")
}
if 1 != atomic.LoadUint32(&ydbInitialized) {
initializeYottaDB()
}
if nil != errstr {
cbuft = errstr.getCPtr()
}
vargobuft := key.Varnm.getCPtr()
if (nil == vargobuft) || (nil == vargobuft.buf_addr) || (0 == vargobuft.len_used) {
panic("YDB: KeyT varname is not allocated, is nil, or has a 0 length")
}
subgobuftary := key.Subary
if nil == subgobuftary {
panic("YDB: KeyT Subary is nil")
}
cbuftary := subgobuftary.getCPtr()
rc := C.ydb_set_st(C.uint64_t(tptoken), cbuft, vargobuft, C.int(subgobuftary.ElemUsed()), cbuftary, value.getCPtr())
if YDB_OK != rc {
err := NewError(tptoken, errstr, int(rc))
return err
}
runtime.KeepAlive(key) // Make sure key hangs around through the YDB call
runtime.KeepAlive(value)
runtime.KeepAlive(errstr)
return nil
}
// SubNextST is a STAPI method to return the next subscript following the specified node.
//
// Matching SubNextE(), SubNextST() wraps ydb_subscript_next_st() to facilitate breadth-first traversal of a
// local or global variable sub-tree.
//
// At the level of the last subscript, if there is a next subscript with a node and/or a subtree:
//
// If the length of that next subscript exceeds sub.len_alloc, the method sets sub.len_used to the
// actual length of that subscript, and returns an INVSTRLEN error. In this case sub.len_used is greater than
// sub.len_alloc. Otherwise, it copies that subscript to the buffer referenced by
// sub.buf_addr, and sets sub.len_used to its length.
//
// If there is no next node or subtree at that level of the subtree, the method returns the NODEEND error.
func (key *KeyT) SubNextST(tptoken uint64, errstr *BufferT, retval *BufferT) error {
var cbuft *C.ydb_buffer_t
printEntry("KeyT.SubNextST()")
if nil == key {
panic("YDB: *KeyT receiver of SubNextST() cannot be nil")
}
if 1 != atomic.LoadUint32(&ydbInitialized) {
initializeYottaDB()
}
if nil != errstr {
cbuft = errstr.getCPtr()
}
vargobuft := key.Varnm.getCPtr()
if (nil == vargobuft) || (nil == vargobuft.buf_addr) || (0 == vargobuft.len_used) {
panic("YDB: KeyT varname is not allocated, is nil, or has a 0 length")
}
subgobuftary := key.Subary
if nil == subgobuftary {
panic("YDB: KeyT Subary is nil")
}
subbuftary := subgobuftary.getCPtr()
rc := C.ydb_subscript_next_st(C.uint64_t(tptoken), cbuft, vargobuft, C.int(subgobuftary.ElemUsed()),
subbuftary, retval.getCPtr())
if YDB_OK != rc {
err := NewError(tptoken, errstr, int(rc))
return err
}
runtime.KeepAlive(key) // Make sure key hangs around through the YDB call
runtime.KeepAlive(retval)
runtime.KeepAlive(errstr)
return nil
}
// SubPrevST is a STAPI method to return the previous subscript following the specified node.
//
// SubPrevST() wraps ydb_subscript_previous_st() to facilitate reverse breadth-first traversal of a local or global variable sub-tree.
//
// At the level of the last subscript, if there is a previous subscript with a node and/or a subtree:
//
// If the length of that previous subscript exceeds sub.len_alloc, the method sets sub.len_used to the
// actual length of that subscript, and returns an INVSTRLEN error. In this case sub.len_used is greater than
// sub.len_alloc. Otherwise, it copies that subscript to the buffer referenced by sub.buf_addr, and sets buf.len_used to its length.
//
// If there is no previous node or subtree at that level of the subtree, the method returns the NODEEND error.
func (key *KeyT) SubPrevST(tptoken uint64, errstr *BufferT, retval *BufferT) error {
var cbuft *C.ydb_buffer_t
printEntry("KeyT.SubPrevST()")
if nil == key {
panic("YDB: *KeyT receiver of SubPrevST() cannot be nil")
}
if 1 != atomic.LoadUint32(&ydbInitialized) {
initializeYottaDB()
}
if nil != errstr {
cbuft = errstr.getCPtr()
}
vargobuft := key.Varnm.getCPtr()
if (nil == vargobuft) || (nil == vargobuft.buf_addr) || (0 == vargobuft.len_used) {
panic("YDB: KeyT varname is not allocated, is nil, or has a 0 length")
}
subgobuftary := key.Subary
if nil == subgobuftary {
panic("YDB: KeyT Subary is nil")
}
subbuftary := subgobuftary.getCPtr()
rc := C.ydb_subscript_previous_st(C.uint64_t(tptoken), cbuft, vargobuft, C.int(subgobuftary.ElemUsed()),
subbuftary, retval.getCPtr())
if YDB_OK != rc {
err := NewError(tptoken, errstr, int(rc))
return err
}
runtime.KeepAlive(key) // Make sure key hangs around through the YDB call
runtime.KeepAlive(retval)
runtime.KeepAlive(errstr)
return nil
}
//////////////////////////////////////////////////////////////////
// //
// Copyright (c) 2018-2025 YottaDB LLC and/or its subsidiaries. //
// All rights reserved. //
// //
// This source code contains the intellectual property //
// of its copyright holder(s), and is made available //
// under a license. If you do not know the terms of //
// the license, please stop and do not read further. //
// //
//////////////////////////////////////////////////////////////////
package yottadb
import (
"fmt"
"log/syslog"
"os"
"runtime"
"strings"
"sync"
"sync/atomic"
"time"
"unsafe"
)
// #include "libyottadb.h"
import "C"
var wgexit sync.WaitGroup
var mtxInExit sync.Mutex
var exitRun bool
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Miscellaneous functions
//
////////////////////////////////////////////////////////////////////////////////////////////////////
// max is a function to provide max integer value between two given values.
func max(x int, y int) int {
if x >= y {
return x
}
return y
}
// printEntry is a function to print the entry point of the function, when entered, if the printEPHdrs flag is enabled.
func printEntry(funcName string) {
if dbgPrintEPHdrs {
_, file, line, ok := runtime.Caller(2)
if ok {
fmt.Println("Entered ", funcName, " from ", file, " at line ", line)
} else {
fmt.Println("Entered ", funcName)
}
}
}
// initkey is a function to initialize a provided key with the provided varname and subscript array in string form.
func initkey(tptoken uint64, errstr *BufferT, dbkey *KeyT, varname string, subary []string) {
var maxsublen, sublen, i uint32
var err error
subcnt := uint32(len(subary))
maxsublen = 0
for i = 0; i < subcnt; i++ {
// Find maximum length of subscript so know how much to allocate
sublen = uint32(len(subary[i]))
if sublen > maxsublen {
maxsublen = sublen
}
}
dbkey.Alloc(uint32(len(varname)), subcnt, maxsublen)
dbkey.Varnm.SetValStr(tptoken, errstr, varname)
if nil != err {
panic(fmt.Sprintf("YDB: Unexpected error with SetValStr(): %s", err))
}
// Load subscripts into KeyT (if any)
for i = 0; i < subcnt; i++ {
err = dbkey.Subary.SetValStr(tptoken, errstr, i, subary[i])
if nil != err {
panic(fmt.Sprintf("YDB: Unexpected error with SetValStr(): %s", err))
}
}
err = dbkey.Subary.SetElemUsed(tptoken, errstr, subcnt)
if nil != err {
panic(fmt.Sprintf("YDB: Unexpected error with SetUsed(): %s", err))
}
}
// allocMem is a function to allocate memory optionally initializing it in various ways. This can be a future
// point where storage management sanity code can be added.
func allocMem(size C.size_t) unsafe.Pointer {
// This initial call must be to calloc() to get initialized (cleared) storage. We cannot allocate it and then
// do another call to initialize it as that means uninitialized memory is traversing the cgo boundary which
// is what triggers the cgo bug mentioned in the cgo docs (https://golang.org/cmd/cgo/#hdr-Passing_pointers).
mem := C.calloc(1, size)
if dbgInitMalloc && (0x00 != dbgInitMallocChar) { // Want to initialize to something other than nulls
_ = C.memset(mem, dbgInitMallocChar, size)
}
return mem
}
// freeMem is a function to return memory allocated with allocMem() or C.calloc().
func freeMem(mem unsafe.Pointer, size C.size_t) {
if dbgInitFree {
_ = C.memset(mem, dbgInitFreeChar, size)
}
C.free(mem)
}
// errorFormat is a function to replace the FAO codes in YDB error messages with meaningful data. This is normally
// handled by YDB itself but when this Go wrapper raises the same errors, no substitution is done. This routine can
// provide that substitution. It takes set of FAO-code and value pairs performing those substitutions on the error
// message in the order specified. Care must be taken to specify them in the order they appear in the message or
// unexpected substitutions may occur.
func errorFormat(errmsg string, subparms ...string) string {
if 0 != (uint32(len(subparms)) & 1) {
panic("YDB: Odd number of substitution parms - invalid FAO code and substitution value pairing")
}
for i := 0; i < len(subparms); i = i + 2 {
errmsg = strings.Replace(errmsg, subparms[i], subparms[i+1], 1)
}
return errmsg
}
// formatINVSTRLEN is a function to do the fetching and formatting of the INVSTRLEN error with both of its
// substitutable parms filled in.
func formatINVSTRLEN(tptoken uint64, errstr *BufferT, lenalloc, lenused C.uint) string {
errmsg, err := MessageT(tptoken, errstr, (int)(YDB_ERR_INVSTRLEN))
if nil != err {
panic(fmt.Sprintf("YDB: Error fetching INVSTRLEN: %s", err))
}
errmsg = errorFormat(errmsg, "!UL", fmt.Sprintf("%d", lenused), "!UL", fmt.Sprintf("%d", lenalloc)) // Substitute parms
return errmsg
}
// syslogEntry records the given message in the syslog. Since these are rare or one-time per process type errors
// that get recorded here, we open a new syslog handle each time to reduce complexity of single threading access
// across goroutines.
func syslogEntry(logMsg string) {
syslogr, err := syslog.New(syslog.LOG_INFO+syslog.LOG_USER, "[YottaDB-Go-Wrapper]")
if nil != err {
panic(fmt.Sprintf("syslog.New() failed unexpectedly with error: %s", err))
}
err = syslogr.Info(logMsg)
if nil != err {
panic(fmt.Sprintf("syslogr.Info() failed unexpectedly with error: %s", err))
}
err = syslogr.Close()
if nil != err {
panic(fmt.Sprintf("syslogr.Close() failed unexpectedly with error: %s", err))
}
}
// selectString returns the first string parm if the expression is true and the second if it is false
func selectString(boolVal bool, trueString, falseString string) string {
if boolVal {
return trueString
}
return falseString
}
// IsLittleEndian is a function to determine endianness. Exposed in case anyone else wants to know.
func IsLittleEndian() bool {
var bittest = 0x01
if 0x01 == *(*byte)(unsafe.Pointer(&bittest)) {
return true
}
return false
}
// Init is a function to drive the initialization for this process. This is part wrapper initialization and part YottaDB
// runtime initialization. This routine is the exterior face of initialization.
func Init() {
if 1 != atomic.LoadUint32(&ydbInitialized) {
initializeYottaDB()
}
}
// Exit invokes YottaDB's exit handler ydb_exit() to shut down the database properly.
// It MUST be called prior to process termination by any application that modifies the database.
// This is necessary particularly in Go because Go does not call the C atexit() handler (unless building with certain test options),
// so YottaDB itself cannot automatically ensure correct rundown of the database.
//
// If Exit() is not called prior to process termination, steps must be taken to ensure database integrity as documented in [Database Integrity]
// and unreleased locks may cause small subsequent delays (see [relevant LKE documentation]).
//
// Recommended behaviour is for your main routine to defer yottadb.Exit() early in the main routine's initialization, and then for the main routine
// to confirm that all goroutines have stopped or have completely finished accessing the database before returning.
// - If Go routines that access the database are spawned, it is the main routine's responsibility to ensure that all such threads have
// finished using the database before it calls yottadb.Exit().
// - The application must not call Go's os.Exit() function which is a very low-level function that bypasses any defers.
// - Care must be taken with any signal notifications (see [Go Using Signals]) to prevent them from causing premature exit.
// - Note that Go *will* run defers on panic, but not on fatal signals such as SIGSEGV.
//
// Exit() may be called multiple times by different threads during an application shutdown.
//
// [Database Integrity]: https://docs.yottadb.com/MultiLangProgGuide/goprogram.html#database-integrity
// [relevant LKE documentation]: https://docs.yottadb.com/AdminOpsGuide/mlocks.html#introduction
// [Go Using Signals]: https://docs.yottadb.com/MultiLangProgGuide/goprogram.html#go-using-signals
//
// [exceptions]: https://github.com/golang/go/issues/20713#issuecomment-1518197679
func Exit() error {
// Note this function is guarded with a mutex and has a "exitRun" flag indicating it has been run. This is because we have seen
// the Exit() routine being run multiple times by multiple goroutines which causes hangs. So it is now controlled with a mutex and
// the already-been-here global flag "exitRun". Once this routine calls C.ydb_exit(), even if it is blocked by the YottaDB engine lock,
// the goroutine that called Exit is still active, so if the engine lock becomes available prior to process demise, this routine will
// wake up and complete the rundown.
var errstr string
var errNum int
if 1 != atomic.LoadUint32(&ydbInitialized) {
return nil // If never initialized, nothing to do
}
mtxInExit.Lock() // One thread at a time through here else we can get DATA-RACE warnings accessing wgexit wait group
defer mtxInExit.Unlock() // Release lock when we leave this routine
if exitRun {
return nil // If exit has already run, no use in running it again
}
defer func() { exitRun = true }() // Set flag we have run Exit()
if dbgSigHandling {
fmt.Fprintln(os.Stderr, "YDB: Exit(): YDB Engine shutdown started")
}
// When we run ydb_exit(), set up a timer that will pop if ydb_exit() gets stuck in a deadlock or whatever. We could
// be running after some fatal error has occurred so things could potentially be fairly screwed up and ydb_exit() may
// not be able to get the lock. We'll give it the given amount of time to finish before we give up and just exit.
exitdone := make(chan struct{})
wgexit.Add(1)
go func() {
_ = C.ydb_exit()
wgexit.Done()
}()
wgexit.Add(1) // And run our signal goroutine cleanup in parallel
go func() {
shutdownSignalGoroutines()
wgexit.Done()
}()
// And now, set up our channel notification for when those both ydb_exit() and signal goroutine shutdown finish
go func() {
wgexit.Wait()
close(exitdone)
}()
// Wait for either ydb_exit to complete or the timeout to expire but how long we wait depends on how we are ending.
// If a signal drove a panic, we have a much shorter wait as it is highly likely the YDB engine lock is held and
// ydb_exit() won't be able to grab it causing a hang. The timeout is to prevent the hang from becoming permanent.
// This is not a real issue because the signal handler would have driven the exit handler to clean things up already.
// On the other hand, if this is a normal exit, we need to be able to wait a reasonably long time in case there is
// a significant amount of data to flush.
exitWait := MaximumNormalExitWait
if 0 != atomic.LoadUint32(&ydbSigPanicCalled) { // Need "atomic" usage to avoid read/write DATA RACE issues
exitWait = MaximumPanicExitWait
}
select {
case _ = <-exitdone:
// We don't really care at this point what the return code is as we're just trying to run things down the
// best we can as this is the end of using the YottaDB engine in this process.
case <-time.After(time.Duration(exitWait) * time.Second):
if dbgSigHandling {
fmt.Fprintln(os.Stderr, "YDB: Exit(): Wait for ydb_exit() expired")
}
errstr = getWrapperErrorMsg(YDB_ERR_DBRNDWNBYPASS)
errNum = YDB_ERR_DBRNDWNBYPASS
if 0 == atomic.LoadUint32(&ydbSigPanicCalled) { // Need "atomic" usage to avoid read/write DATA RACE issues
// If we panic'd due to a signal, we definitely have run the exit handler as it runs before the panic is
// driven so we can bypass this message in that case.
syslogEntry(errstr)
}
}
// Note - the temptation here is to unset ydbInitialized but things work better if we do not do that (we don't have
// multiple goroutines trying to re-initialize the engine) so we bypass/ re-doing the initialization call later and
// just go straight to getting the CALLINAFTERXIT error when an actual call is attempted. We now handle CALLINAFTERXIT
// in the places it matters.
if "" != errstr {
return &YDBError{errNum, errstr}
}
return nil
}
//////////////////////////////////////////////////////////////////
// //
// Copyright (c) 2018-2022 YottaDB LLC and/or its subsidiaries. //
// All rights reserved. //
// //
// This source code contains the intellectual property //
// of its copyright holder(s), and is made available //
// under a license. If you do not know the terms of //
// the license, please stop and do not read further. //
// //
//////////////////////////////////////////////////////////////////
package yottadb
import (
"fmt"
"runtime"
"strings"
"sync/atomic"
"unsafe"
)
// #include "libyottadb.h"
// /* C routine to get around the cgo issue and its lack of support for variadic plist routines */
// void *ydb_get_lockst_funcvp(void);
// void *ydb_get_lockst_funcvp(void)
// {
// return (void *)&ydb_lock_st;
// }
import "C"
//
// This file contains the only Simple API routine that is not a method. The rest of the threaded SimpleAPI method functions
// are defined in buffer_t.go, bufer_t_array.go, and key_t.go with utilities being defined in util.go.
//
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Simple (Threaded) API function(s)
//
////////////////////////////////////////////////////////////////////////////////////////////////////
// LockST is a STAPI function that releases all existing locks then locks the supplied variadic list of lock keys.
func LockST(tptoken uint64, errstr *BufferT, timeoutNsec uint64, lockname ...*KeyT) error {
var vplist variadicPlist
var lockcnt, namecnt int
var parmIndx uint32
var err error
var cbuft *C.ydb_buffer_t
printEntry("KeyT.SubNextST()")
if 1 != atomic.LoadUint32(&ydbInitialized) {
initializeYottaDB()
}
defer vplist.free()
vplist.alloc()
// First two parms are the tptoken and the contents of the errstr BufferT (not the BufferT itself).
err = vplist.setVPlistParam64Bit(tptoken, errstr, &parmIndx, tptoken) // Takes care of bumping parmIndx
if nil != err {
panic(fmt.Sprintf("YDB: Unknown error with varidicPlist.setVPlistParam64Bit(): %s", err))
}
if nil != errstr {
cbuft = errstr.getCPtr()
}
err = vplist.setVPlistParam(tptoken, errstr, parmIndx, uintptr(unsafe.Pointer(cbuft)))
if nil != err {
panic(fmt.Sprintf("YDB: Unknown error with varidicPlist.setVPlistParam(): %s", err))
}
parmIndx++
// Put the timeout parameter into the plist
err = vplist.setVPlistParam64Bit(tptoken, errstr, &parmIndx, timeoutNsec) // Takes care of bumping parmIndx
if nil != err {
panic(fmt.Sprintf("YDB: Unknown error with varidicPlist.setVPlistParam(): %s", err))
}
// Add the lock count parameter
lockcnt = len(lockname)
namecnt = lockcnt
err = vplist.setVPlistParam(tptoken, errstr, parmIndx, uintptr(namecnt))
if nil != err {
panic(fmt.Sprintf("YDB: Unknown error with varidicPlist.setVPlistParam(): %s", err))
}
parmIndx++
if 0 != lockcnt {
parmsleft := C.MAX_GPARAM_LIST_ARGS - parmIndx // We've already slotted 4 parms in up to 6 slots
parmsleftorig := parmsleft // Save for error below just-in-case
lockindx := 0 // The next lockname index to be read
// Load the lockname parameters into the plist
for 0 < lockcnt {
// Make sure enough room for another set of 3 parms
if 3 > parmsleft {
errmsg, err := MessageT(tptoken, nil, (int)(YDB_ERR_NAMECOUNT2HI))
if nil != err {
panic(fmt.Sprintf("YDB: Error fetching NAMECOUNT2HI: %s", err))
}
// Do some error message substitution
errmsg = strings.Replace(errmsg, "!AD", "LockST()", 1)
errmsg = strings.Replace(errmsg, "!UL", fmt.Sprintf("%d", namecnt), 1)
errmsg = strings.Replace(errmsg, "!UL", fmt.Sprintf("%d", parmsleftorig/3), 1)
return &YDBError{(int)(YDB_ERR_NAMECOUNT2HI), errmsg}
}
// Set the 3 parameters for this lockname
err = vplist.setVPlistParam(tptoken, errstr,
parmIndx, uintptr(unsafe.Pointer(lockname[lockindx].Varnm.getCPtr())))
if nil != err {
panic(fmt.Sprintf("YDB: Unknown error with varidicPlist.setVPlistParam(): %s", err))
}
parmIndx++
parmsleft--
err = vplist.setVPlistParam(tptoken, errstr, parmIndx, uintptr(lockname[lockindx].Subary.ElemUsed()))
if nil != err {
panic(fmt.Sprintf("YDB: Unknown error with varidicPlist.setVPlistParam(): %s", err))
}
parmIndx++
parmsleft--
subgobuftary := lockname[lockindx].Subary
subbuftary := unsafe.Pointer(subgobuftary.getCPtr())
err = vplist.setVPlistParam(tptoken, errstr, parmIndx, uintptr(subbuftary))
if nil != err {
panic(fmt.Sprintf("YDB: Unknown error with varidicPlist.setVPlistParam(): %s", err))
}
parmIndx++
parmsleft--
// Housekeeping
lockindx++
lockcnt--
}
}
err = vplist.setUsed(tptoken, errstr, parmIndx)
if nil != err {
panic(fmt.Sprintf("YDB: Unknown error with varidicPlist.setUsed(): %s", err))
}
// At this point, vplist now contains the plist we want to send to ydb_lock_s(). However, Go/cgo does not permit
// either the call or even creating a function pointer to ydb_lock_s(). So instead of driving vplist.CallVariadicPlistFuncST()
// which is what we would normally do here, we're going to call a C helper function (defined in the cgo preamble at the
// top of this routine) to do the call that callVariadicPlistFuncST() would have done.
rc := vplist.callVariadicPlistFunc(C.ydb_get_lockst_funcvp()) // Drive ydb_lock_st()
if YDB_OK != rc {
err := NewError(tptoken, errstr, int(rc))
return err
}
runtime.KeepAlive(lockname) // Make sure these structures hangs around through the YDB call
runtime.KeepAlive(errstr)
runtime.KeepAlive(vplist)
return nil
}
//////////////////////////////////////////////////////////////////
// //
// Copyright (c) 2018-2022 YottaDB LLC and/or its subsidiaries. //
// All rights reserved. //
// //
// This source code contains the intellectual property //
// of its copyright holder(s), and is made available //
// under a license. If you do not know the terms of //
// the license, please stop and do not read further. //
// //
//////////////////////////////////////////////////////////////////
package yottadb
import (
"fmt"
"runtime"
"sync/atomic"
"unsafe"
)
// #include "libyottadb.h"
// /* C routine to get around the cgo issue and its lack of support for variadic plist routines */
// void *ydb_get_cipt_funcvp(void);
// void *ydb_get_cipt_funcvp(void)
// {
// return (void *)&ydb_cip_t;
// }
import "C"
// CallMDesc is a struct that (ultimately) serves as an anchor point for the C call-in routine descriptor
// used by CallMDescT() that provides for less call-overhead than CallMT() as the descriptor contains fastpath
// information filled in by YottaDB after the first call so subsequent calls have minimal overhead. Because
// this structure's contents contain pointers to C allocated storage, this structure is NOT safe for
// concurrent access.
type CallMDesc struct {
cmdesc *internalCallMDesc
}
type internalCallMDesc struct {
rtnname string // Copy of rtn name in descriptor - easier for use in panic error msgs
filledin bool // Indicates call has been made to fill-in parmtyps struct
cmdesc *C.ci_name_descriptor // Descriptor for M routine with fastpath for calls after first
parmtyps *C.ci_parm_type // Where we hang the information we receive about the entry point
}
// CallMTable is a struct that defines a call table (see
// https://docs.yottadb.com/ProgrammersGuide/extrout.html#calls-from-external-routines-call-ins).
// The methods associated with this struct allow call tables to be opened and to switch between them
// to give access to routines in multiple call tables.
type CallMTable struct {
handle uintptr // Handle used to access the call table
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Utility methods
//
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// First up - methods for CallMDesc struct.
// Free is a method to release both the routine name buffer and the descriptor block associated with
// the CallMDesc block.
func (mdesc *CallMDesc) Free() {
if nil == mdesc {
return
}
mdesc.cmdesc.Free()
mdesc.cmdesc = nil
}
// Call freeMem() on any C memory owned by this internalCallMDesc
func (imdesc *internalCallMDesc) Free() {
printEntry("internalCallMDesc.Free()")
if nil == imdesc {
return
}
cindPtr := imdesc.cmdesc
if nil != cindPtr {
if nil != cindPtr.rtn_name.address {
freeMem(unsafe.Pointer(cindPtr.rtn_name.address), C.size_t(cindPtr.rtn_name.length))
cindPtr.rtn_name.address = nil
}
freeMem(unsafe.Pointer(cindPtr), C.size_t(C.sizeof_ci_name_descriptor))
// The below keeps imdesc around long enough to get rid of this block's C memory. No KeepAlive() necessary.
imdesc.cmdesc = nil
}
ctypPtr := imdesc.parmtyps
if nil != ctypPtr {
freeMem(unsafe.Pointer(ctypPtr), C.size_t(C.sizeof_ci_parm_type))
imdesc.parmtyps = nil
}
}
// SetRtnName is a method for CallMDesc that sets the routine name into the descriptor.
func (mdesc *CallMDesc) SetRtnName(rtnname string) {
var cindPtr *C.ci_name_descriptor
var ctypPtr *C.ci_parm_type
printEntry("CallMDesc.SetRtnName()")
if nil == mdesc {
panic("YDB: *CallMDesc receiver of SetRtnName() cannot be nil")
}
rtnnamelen := len(rtnname)
if 0 == rtnnamelen {
panic("YDB: Routine name string for SetRtnName() cannot be null")
}
// If this is a previously allocated critter, free the CString memory but don't reallocate the
// ci_name_descriptor or set the finalizer (which is already setup).
if nil != mdesc.cmdesc {
if nil == mdesc.cmdesc.cmdesc {
panic("YDB: Inner cmdesc structure allocated but has no C memory allocated")
}
cindPtr = mdesc.cmdesc.cmdesc
if nil == cindPtr.rtn_name.address {
panic("YDB: Routine name address is nil - out of design situation")
}
freeMem(unsafe.Pointer(cindPtr.rtn_name.address), C.size_t(cindPtr.rtn_name.length))
cindPtr.rtn_name.address = nil
} else {
cindPtr = (*C.ci_name_descriptor)(allocMem(C.size_t(C.sizeof_ci_name_descriptor)))
ctypPtr = (*C.ci_parm_type)(allocMem(C.size_t(C.sizeof_ci_parm_type)))
mdesc.cmdesc = &internalCallMDesc{rtnname, false, cindPtr, ctypPtr}
// Set a finalizer so this block is released when garbage collected
runtime.SetFinalizer(mdesc.cmdesc, func(o *internalCallMDesc) { o.Free() })
}
cindPtr.rtn_name.address = C.CString(rtnname) // Allocates new memory we need to release when done (done by finalizer)
cindPtr.rtn_name.length = C.ulong(rtnnamelen)
cindPtr.handle = nil
mdesc.cmdesc.filledin = false // Mark parm type struct as having been NOT filled in yet
runtime.KeepAlive(mdesc)
}
// CallMDescT allows calls to M with string arguments and an optional string return value if the called function returns one
// and a return value is described in the call-in definition. Else return is nil.
func (mdesc *CallMDesc) CallMDescT(tptoken uint64, errstr *BufferT, retvallen uint32, rtnargs ...interface{}) (string, error) {
var vplist variadicPlist
var parmIndx, inmask, outmask, imask, omask uint32
var err error
var retvalPtr *C.ydb_string_t
var cbuft *C.ydb_buffer_t
var i int
var strparm, retval string
var strPtr *string
var intPtr *int
var int32Ptr *int32
var int64Ptr *int64
var uintPtr *uint
var uint32Ptr *uint32
var uint64Ptr *uint64
var boolPtr *bool
var float32Ptr *float32
var float64Ptr *float64
var cmplx64Ptr *complex64
var cmplx128Ptr *complex128
var parmOK bool
printEntry("CallMDesc.CallMDescT()")
if nil == mdesc {
panic("YDB: *CallMDesc receiver of CallMDescT() cannot be nil")
}
if (nil == mdesc.cmdesc) || (nil == (mdesc.cmdesc.cmdesc)) || (nil == mdesc.cmdesc.parmtyps) {
panic("YDB: SetRtnName() method has not been invoked on this descriptor")
}
if 1 != atomic.LoadUint32(&ydbInitialized) {
initializeYottaDB()
}
// If we haven't already fetched the call description from YDB, do that now
if !mdesc.cmdesc.filledin {
if nil != errstr {
cbuft = errstr.getCPtr()
}
rc := C.ydb_ci_get_info_t(C.uint64_t(tptoken), cbuft, mdesc.cmdesc.cmdesc.rtn_name.address, mdesc.cmdesc.parmtyps)
if YDB_OK != rc {
err := NewError(tptoken, errstr, int(rc))
return "", err
}
mdesc.cmdesc.filledin = true
}
defer vplist.free() // Initialize variadic plist we need to use to call ydb_cip_helper()
vplist.alloc()
// First two parms are the tptoken and the contents of the BufferT (not the BufferT itself).
err = vplist.setVPlistParam64Bit(tptoken, errstr, &parmIndx, tptoken) // Takes care of bumping parmIndx
if nil != err {
panic(fmt.Sprintf("YDB: Unknown error with varidicPlist64Bit.setVPlistParam(): %s", err))
}
if nil != errstr {
cbuft = errstr.getCPtr()
}
err = vplist.setVPlistParam(tptoken, errstr, parmIndx, uintptr(unsafe.Pointer(cbuft)))
if nil != err {
panic(fmt.Sprintf("YDB: Unknown error with varidicPlist.setVPlistParam(): %s", err))
}
parmIndx++
// Third parm for ydb_cip_t() is the descriptor address so add that now
err = vplist.setVPlistParam(tptoken, errstr, parmIndx, uintptr(unsafe.Pointer(mdesc.cmdesc.cmdesc)))
if nil != err {
panic(fmt.Sprintf("YDB: Unknown error with varidicPlist.setVPlistParam(): %s", err))
}
parmIndx++
// Setup return value if any (first variable parm)
if 0 != retvallen {
retvalPtr = (*C.ydb_string_t)(allocMem(C.size_t(C.sizeof_ydb_string_t)))
defer freeMem(unsafe.Pointer(retvalPtr), C.size_t(C.sizeof_ydb_string_t)) // Free this when we are done
retvalPtr.address = (*C.char)(allocMem(C.size_t(retvallen)))
defer freeMem(unsafe.Pointer(retvalPtr.address), C.size_t(retvallen))
retvalPtr.length = (C.ulong)(retvallen)
err = vplist.setVPlistParam(tptoken, errstr, parmIndx, uintptr(unsafe.Pointer(retvalPtr)))
if nil != err {
return "", err
}
parmIndx++
}
// Now process each parameter into the variadic parm list. To reduce the number of mallocs/frees for this
// step, we allocate an array of ydb_string_t structs needed to pass these parms into C however due to cgo
// limitations, we cannot copy string args directly to C memory so just let Go allocate the memory for the
// input strings.
//
// Parameters can be various types supported by external calls. They are all converted to strings for now as
// Go does not have access to the call descriptor that defines argument types.
parmcnt := len(rtnargs)
if parmcnt > int(C.YDB_MAX_PARMS) {
panic(fmt.Sprintf("YDB: Parm count of %d exceeds maximum parm count of %d", parmcnt, int(C.YDB_MAX_PARMS)))
}
allocLen := C.size_t(C.sizeof_ydb_string_t * parmcnt)
parmblkPtr := (*C.ydb_string_t)(allocMem(allocLen))
defer freeMem(unsafe.Pointer(parmblkPtr), allocLen)
parmPtr := parmblkPtr
inmask = uint32(mdesc.cmdesc.parmtyps.input_mask)
outmask = uint32(mdesc.cmdesc.parmtyps.output_mask)
// Turn each parameter into a ydb_string_t buffer descriptor and load it into our variadic plist
for i, imask, omask = 0, inmask, outmask; i < parmcnt; i, imask, omask = (i + 1), (imask >> 1), (omask >> 1) {
// If this parm is an input parm (versus output only), rebuffer it in C memory, else just allocate the buffer
// without copying anything. For input parms, we take special care with pass-by-reference types since they
// need to be de-referenced to get to the value so each type must be handled separately.
if 1 == (1 & imask) { // This is an input parameter (note this includes IO parms
if 0 == (1 & omask) { // The only input parms that can also currently be output parms are *string
parmOK = true
} else {
parmOK = false
}
// The rtnargs[i] array of parameters is an interface array because it is not a homogeneous type with
// each parm possibly a different type. Also, rtnargs[i] cannot be dereferenced without using a
// type-assersion (the stmt above the fmt.Sprintf in each block) to convert the interface value to
// the proper type. But to know what type to convert to, we first need to use the big type-switch
// below to specifically test for values passed as addresses so they can be dereferenced. All of the
// basic Go types are represented here.
switch rtnargs[i].(type) {
case *string:
parmOK = true
strPtr = rtnargs[i].(*string)
strparm = fmt.Sprintf("%v", *strPtr)
case *int:
intPtr = rtnargs[i].(*int)
strparm = fmt.Sprintf("%v", *intPtr)
case *int32:
int32Ptr = rtnargs[i].(*int32)
strparm = fmt.Sprintf("%v", *int32Ptr)
case *int64:
int64Ptr = rtnargs[i].(*int64)
strparm = fmt.Sprintf("%v", *int64Ptr)
case *uint:
uintPtr = rtnargs[i].(*uint)
strparm = fmt.Sprintf("%v", *uintPtr)
case *uint32:
uint32Ptr = rtnargs[i].(*uint32)
strparm = fmt.Sprintf("%v", *uint32Ptr)
case *uint64:
uint64Ptr = rtnargs[i].(*uint64)
strparm = fmt.Sprintf("%v", *uint64Ptr)
case *bool:
boolPtr = rtnargs[i].(*bool)
strparm = fmt.Sprintf("%v", *boolPtr)
case *float32:
float32Ptr = rtnargs[i].(*float32)
strparm = fmt.Sprintf("%v", *float32Ptr)
case *float64:
float64Ptr = rtnargs[i].(*float64)
strparm = fmt.Sprintf("%v", *float64Ptr)
case *complex64:
cmplx64Ptr = rtnargs[i].(*complex64)
strparm = fmt.Sprintf("%v", *cmplx64Ptr)
case *complex128:
cmplx128Ptr = rtnargs[i].(*complex128)
strparm = fmt.Sprintf("%v", *cmplx128Ptr)
default:
// Assume passed by value - this generic string conversion suffices
strparm = fmt.Sprintf("%v", rtnargs[i])
}
if !parmOK {
panic(fmt.Sprintf("YDB: Call-in routine %s parm %d is an output parm and must be *string but is not",
mdesc.cmdesc.rtnname, i+1))
}
// Initialize our ydb_string_t (parmPtr) that describes the parm
parmPtr.length = C.ulong(len(strparm))
if 0 < parmPtr.length {
// Check if parm is pass-by-value or pass-by-reference by checking ci info
parmPtr.address = C.CString(strparm)
defer freeMem(unsafe.Pointer(parmPtr.address), C.size_t(parmPtr.length))
} else {
parmPtr.address = nil
}
} else { // Otherwise, this is an output-only parameter - allocate a C buffer for it but otherwise leave it alone
// Note this parm is always passed by reference (verify it).
if 0 == (1 & omask) { // Check for unexpected parameter
panic(fmt.Sprintf("YDB: Call-in routine %s parm %d is not a parameter defined in the call-in table",
mdesc.cmdesc.rtnname, i+1))
}
switch rtnargs[i].(type) {
case *string: // passed-by-reference string parameter
parmOK = true
default:
parmOK = false
}
if !parmOK {
panic(fmt.Sprintf("YDB: Call-in routine %s parm %d is an output parm and must be *string but is not",
mdesc.cmdesc.rtnname, i+1))
}
pval := rtnargs[i].(*string)
// Setup ydb_string_t (parmPtr) to point to our string
parmPtr.length = C.ulong(len(*pval))
if 0 < parmPtr.length {
parmPtr.address = (*C.char)(allocMem(C.size_t(parmPtr.length)))
defer freeMem(unsafe.Pointer(parmPtr.address), C.size_t(parmPtr.length))
} else {
parmPtr.address = nil
}
}
// Now add parmPtr to the variadic plist
err = vplist.setVPlistParam(tptoken, errstr, parmIndx, uintptr(unsafe.Pointer(parmPtr)))
if nil != err {
return "", err
}
// Increment parmPtr to next ydb_buffer_t and the variadic list to its next slot
parmPtr = (*C.ydb_string_t)(unsafe.Pointer(uintptr(unsafe.Pointer(parmPtr)) + uintptr(C.sizeof_ydb_string_t)))
parmIndx++
}
err = vplist.setUsed(tptoken, errstr, uint32(parmIndx))
if nil != err {
panic(fmt.Sprintf("YDB: Unknown error with varidicPlist.setUsed(): %s", err))
}
// Now drive the variadic plist call - we have to drive the C glue routine defined at the top of this file in
// the cgo header in order to drive ydb_cip_t().
rc := vplist.callVariadicPlistFunc(C.ydb_get_cipt_funcvp()) // Drive ydb_cip_t()
if YDB_OK != rc {
err = NewError(tptoken, errstr, int(rc))
return "", err
}
if 0 != retvallen { // If we have a return value
// Build a string of the length of the return value
retval = C.GoStringN(retvalPtr.address, C.int(retvalPtr.length))
} else {
retval = ""
}
// Go through the parameters again to locate the output parameters and copy their values back into Go space
parmPtr = parmblkPtr
for i, omask = 0, outmask; i < parmcnt; i, omask = i+1, omask>>1 {
if 1 == (1 & omask) { // This is an output parameter
rtnargPtr := rtnargs[i].(*string)
*rtnargPtr = C.GoStringN(parmPtr.address, C.int(parmPtr.length))
}
// Increment parmPtr to next ydb_buffer_t and the variadic list to its next slot
parmPtr = (*C.ydb_string_t)(unsafe.Pointer(uintptr(unsafe.Pointer(parmPtr)) + uintptr(C.sizeof_ydb_string_t)))
}
runtime.KeepAlive(mdesc) // Make sure mdesc hangs around through the YDB call
runtime.KeepAlive(vplist)
runtime.KeepAlive(rtnargs)
runtime.KeepAlive(errstr)
return retval, nil
}
// Methods for CallMTable struct
// CallMTableSwitchT method switches whatever the current call table is (only one active at a time) with the supplied
// call table and returns the call table that was in effect (or nil if none).
func (newcmtable *CallMTable) CallMTableSwitchT(tptoken uint64, errstr *BufferT) (*CallMTable, error) {
var cbuft *C.ydb_buffer_t
var callmtabret CallMTable
if nil == newcmtable {
panic("YDB: Non-nil CallMTable structure must be specified")
}
if 1 != atomic.LoadUint32(&ydbInitialized) {
initializeYottaDB()
}
if nil != errstr {
cbuft = errstr.getCPtr()
}
rc := C.ydb_ci_tab_switch_t(C.uint64_t(tptoken), cbuft, C.uintptr_t(newcmtable.handle),
(*C.uintptr_t)(unsafe.Pointer(&callmtabret.handle)))
if YDB_OK != rc {
err := NewError(tptoken, errstr, int(rc))
return nil, err
}
runtime.KeepAlive(errstr)
return &callmtabret, nil
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Utility functions
//
////////////////////////////////////////////////////////////////////////////////////////////////////
// CallMT allows calls to M with string arguments and an optional string return value if the called function returns one
// and a return value is described in the call-in definition. Else return is nil. This function differs from CallMDescT()
// in that the name of the routine is specified here and must always be looked up in the routine list. To avoid having
// two routines nearly identical, this routine is written to invoke CallMDescT().
func CallMT(tptoken uint64, errstr *BufferT, retvallen uint32, rtnname string, rtnargs ...interface{}) (string, error) {
var mdesc CallMDesc
printEntry("CallMDesc.CallMT()")
if "" == rtnname {
panic("YDB: Name of routine to call cannot be null string")
}
mdesc.SetRtnName(rtnname)
return mdesc.CallMDescT(tptoken, errstr, retvallen, rtnargs...)
}
// CallMTableOpenT function opens a new call table or one for which the process had no handle and returns a
// CallMTable for it.
func CallMTableOpenT(tptoken uint64, errstr *BufferT, tablename string) (*CallMTable, error) {
var callmtab CallMTable
var cbuft *C.ydb_buffer_t
if 1 != atomic.LoadUint32(&ydbInitialized) {
initializeYottaDB()
}
cstr := C.CString(tablename)
defer C.free(unsafe.Pointer(cstr))
if nil != errstr {
cbuft = errstr.getCPtr()
}
rc := C.ydb_ci_tab_open_t(C.uint64_t(tptoken), cbuft, cstr, (*C.uintptr_t)(unsafe.Pointer(&callmtab.handle)))
if YDB_OK != rc {
err := NewError(tptoken, errstr, int(rc))
return nil, err
}
runtime.KeepAlive(errstr)
return &callmtab, nil
}
// MessageT is a STAPI utility function to return the error message (sans argument substitution) of a given error number.
func MessageT(tptoken uint64, errstr *BufferT, status int) (string, error) {
var msgval BufferT
var cbuft *C.ydb_buffer_t
var err error
var errorMsg string
printEntry("MessageT()")
if 1 != atomic.LoadUint32(&ydbInitialized) {
initializeYottaDB()
}
statusOriginal := status
if 0 > status { // Get absolute value of status so we can extract facility bits correctly
status = -status
}
// First check the "facility" id buried in the error number (status)
facility := (uint32(status) >> 16) & 0x7ff // See error format in sr_port/error.h of YottaDB - isolate the 11 bit facility
switch facility {
case 246: // GT.M facility (use C.ydb_message_t())
fallthrough
case 256: // YDB facility (use C.ydb_message_t())
// Check for a couple of special cases first. First, if the error is YDB_ERR_THREADEDAPINOTALLOWED, the same error
// will prevent the below call into ydb_message_t() from working so create a hard-return error message for that
// case before attempting the call.
switch statusOriginal {
case YDB_ERR_THREADEDAPINOTALLOWED:
return "%YDB-E-THREADEDAPINOTALLOWED, Process cannot switch to using threaded Simple API while " +
"already using Simple API", nil
case YDB_ERR_CALLINAFTERXIT:
// The engine is shut down so calling ydb_message_t will fail if we attempt it so just hard-code this
// error return value.
return "%YDB-E-CALLINAFTERXIT, After a ydb_exit(), a process cannot create a valid YottaDB context", nil
}
defer msgval.Free()
msgval.Alloc(uint32(YDB_MAX_ERRORMSG))
if nil != errstr {
cbuft = errstr.getCPtr()
}
rc := C.ydb_message_t(C.uint64_t(tptoken), cbuft, C.int(status), msgval.getCPtr())
if YDB_OK != rc {
panic(fmt.Sprintf("YDB: Error calling ydb_message_t() for argument %d: %d", status, int(rc)))
}
// Returned string should be snug in the retval buffer. Pick it out so can return it as a string
errorMsg, err = msgval.ValStr(tptoken, errstr)
if nil != err {
panic(fmt.Sprintf("YDB: Unexpected error with ValStr(): %s", err))
}
case 264: // Facility id for YDBGo wrapper errors
errorMsg = getWrapperErrorMsg(statusOriginal)
if "" == errorMsg {
panic(fmt.Sprintf("YDB: Wrapper error message %d not found", statusOriginal))
}
default:
panic(fmt.Sprintf("YDB: Unknown message facility: %d from error id %d", facility, statusOriginal))
}
runtime.KeepAlive(errstr)
runtime.KeepAlive(msgval)
return errorMsg, err
}
// ReleaseT is a STAPI utility function to return release information for the current underlying YottaDB version
func ReleaseT(tptoken uint64, errstr *BufferT) (string, error) {
printEntry("ReleaseT()")
zyrel, err := ValE(tptoken, errstr, "$ZYRELEASE", []string{})
if nil != err {
return "", err
}
retval := fmt.Sprintf("gowr %s %s", WrapperRelease, zyrel)
return retval, nil
}
//////////////////////////////////////////////////////////////////
// //
// Copyright (c) 2018-2022 YottaDB LLC and/or its subsidiaries. //
// All rights reserved. //
// //
// This source code contains the intellectual property //
// of its copyright holder(s), and is made available //
// under a license. If you do not know the terms of //
// the license, please stop and do not read further. //
// //
//////////////////////////////////////////////////////////////////
package yottadb
import (
"fmt"
"runtime"
"strconv"
"sync/atomic"
"unsafe"
)
// #include <stdlib.h> /* For uint64_t definition on Linux */
// #include "libyottadb.h"
import "C"
const maxARM32RegParms uint32 = 4 // Max number of parms passed in registers in ARM32 (affects passing of 64 bit parms)
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Variadic plist support for C (despite Go [via cgo] not supporting it).
//
////////////////////////////////////////////////////////////////////////////////////////////////////
// variadicPlist structure is used to anchor the C parameter list used to call callg_nc() via
// ydb_call_variadic_list_func_st(). Because this structure's contents contain pointers to C
// allocated storage, this structure is NOT safe for concurrent access unless those accesses are
// setting different array elements and not affecting the overall structure.
type variadicPlist struct { // Variadic plist support (not exported) needed by LockS() function
cvplist *C.gparam_list
}
// alloc is a variadicPlist method to allocate the variable plist C structure anchored in variadicPlist.
func (vplist *variadicPlist) alloc() {
printEntry("variadicPlist.alloc()")
if nil == vplist {
panic("YDB: *variadicPlist receiver of alloc() cannot be nil")
}
if nil != vplist.cvplist {
// Already allocated
return
}
vplist.cvplist = (*C.gparam_list)(allocMem(C.size_t(C.sizeof_gparam_list)))
}
// callVariadicPlistFunc is a variadicPlist method to drive a variadic plist function with the given "vplist".
// The function pointer "vpfunc" must be to a C routine (ensured by caller) as cgo does not allow Go function
// pointers to be passed to C. Hence the unsafe pointer type usage below. Note this routine assumes that "vplist"
// is already initialized by a set-up call (a call to alloc() and one or more calls to setVPlistParam()).
func (vplist *variadicPlist) callVariadicPlistFunc(vpfunc unsafe.Pointer) int {
printEntry("variadicPlist.callVariadicPlistFunc()")
if nil == vplist {
panic("YDB: *variadicPlist receiver of callVariadicPlistFunc() cannot be nil")
}
if 1 != atomic.LoadUint32(&ydbInitialized) {
initializeYottaDB()
}
retval := int(C.ydb_call_variadic_plist_func((C.ydb_vplist_func)(vpfunc),
vplist.cvplist))
runtime.KeepAlive(vplist)
return retval
}
// free is a variadicPlist method to release the allocated C buffer in this structure.
func (vplist *variadicPlist) free() {
printEntry("variadicPlist.free()")
if (nil != vplist) && (nil != vplist.cvplist) {
freeMem(unsafe.Pointer(vplist.cvplist), C.size_t(C.sizeof_gparam_list))
vplist.cvplist = nil
}
}
// dump is a variadicPlist method to dump a variadic plist block for debugging purposes.
func (vplist *variadicPlist) dump(tptoken uint64, errstr *BufferT) {
printEntry("variadicPlist.dump()")
if nil == vplist {
panic("YDB: *variadicPlist receiver of dump() cannot be nil")
}
cvplist := vplist.cvplist
if nil == cvplist {
// Create an error to return
errmsg, err := MessageT(tptoken, errstr, (int)(YDB_ERR_STRUCTUNALLOCD))
if nil != err {
panic(fmt.Sprintf("YDB: Error fetching STRUCTUNALLOCD: %s", err))
}
fmt.Printf("YDB: Error fetching STRUCTUNALLOCD: %s\n", errmsg)
return
}
elemcnt := cvplist.n
fmt.Printf(" Total of %d (0x%x) elements in this variadic plist\n", elemcnt, elemcnt)
argbase := unsafe.Pointer(uintptr(unsafe.Pointer(cvplist)) + unsafe.Sizeof(cvplist))
for i := 0; i < int(elemcnt); i++ {
elemptr := unsafe.Pointer(uintptr(argbase) + (uintptr(i) * uintptr(unsafe.Sizeof(cvplist))))
fmt.Printf(" Elem %d (%p) Value: %d (0x%x)\n", i, elemptr, *((*uintptr)(elemptr)), *((*uintptr)(elemptr)))
}
runtime.KeepAlive(vplist)
}
// setUsed is a variadicPlist method to set the number of used elements in the variadic plist array.
func (vplist *variadicPlist) setUsed(tptoken uint64, errstr *BufferT, newUsed uint32) error {
printEntry("variadicPlist.setUsed")
if nil == vplist {
panic("YDB: *variadicPlist receiver of setUsed() cannot be nil")
}
cvplist := vplist.cvplist
if nil == cvplist {
// Create an error to return
errmsg, err := MessageT(tptoken, nil, (int)(YDB_ERR_STRUCTUNALLOCD))
if nil != err {
panic(fmt.Sprintf("YDB: Error fetching STRUCTUNALLOCD: %s", err))
}
return &YDBError{(int)(YDB_ERR_STRUCTUNALLOCD), errmsg}
}
if C.MAX_GPARAM_LIST_ARGS < newUsed {
panic(fmt.Sprintf("YDB: setUsed item count %d exceeds maximum count of %d", newUsed, C.MAX_GPARAM_LIST_ARGS))
}
cvplist.n = C.intptr_t(newUsed)
runtime.KeepAlive(vplist)
return nil
}
// setVPlistParam is a variadicPlist method to set an entry to the variable plist - note any addresses being passed in
// here MUST point to C allocated memory and NOT Go allocated memory or cgo will cause a panic. Note parameter
// indexes are 0 based.
func (vplist *variadicPlist) setVPlistParam(tptoken uint64, errstr *BufferT, paramindx uint32, paramaddr uintptr) error {
printEntry("variadicPlist.setVPlistParm")
if nil == vplist {
panic("YDB: *variadicPlist receiver of setVPlistParam() cannot be nil")
}
cvplist := vplist.cvplist
if nil == cvplist {
// Create an error to return
errmsg, err := MessageT(tptoken, errstr, (int)(YDB_ERR_STRUCTUNALLOCD))
if nil != err {
panic(fmt.Sprintf("YDB: Error fetching STRUCTUNALLOCD: %s", err))
}
return &YDBError{(int)(YDB_ERR_STRUCTUNALLOCD), errmsg}
}
if C.MAX_GPARAM_LIST_ARGS <= paramindx {
panic(fmt.Sprintf("YDB: setVPlistParam item count %d exceeds maximum count of %d", paramindx, C.MAX_GPARAM_LIST_ARGS))
}
// Compute address of indexed element
elemptr := (*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&cvplist.arg[0])) +
uintptr(paramindx*uint32(unsafe.Sizeof(paramaddr)))))
*elemptr = paramaddr
runtime.KeepAlive(vplist)
return nil
}
// setVPlistParam64Bit pushes the supplied 64 bit parameter onto the supplied variadicPlist at the supplied index. This is for
// parms that are ALWAYS 64 bits regardless of platform address mode. This is interesting only when using a 32 bit address
// mode which means the 64 bit parm must span 2 slots. In that case, the index is bumped by 2. Note that paramindx is incremented
// by this function since the caller does not know whether to bump it once or twice.
func (vplist *variadicPlist) setVPlistParam64Bit(tptoken uint64, errstr *BufferT, paramindx *uint32, parm64bit uint64) error {
var err error
printEntry("variadicPlist.setVPlistParm64Bit")
if nil == vplist {
panic("YDB: *variadicPlist receiver of setVPlistParam64Bit() cannot be nil")
}
cvplist := vplist.cvplist
if nil == cvplist {
// Create an error to return
errmsg, err := MessageT(tptoken, nil, (int)(YDB_ERR_STRUCTUNALLOCD))
if nil != err {
panic(fmt.Sprintf("YDB: Error fetching STRUCTUNALLOCD: %s", err))
}
return &YDBError{(int)(YDB_ERR_STRUCTUNALLOCD), errmsg}
}
if C.MAX_GPARAM_LIST_ARGS <= *paramindx {
panic(fmt.Sprintf("YDB: setVPlistParam64Bit item count %d exceeds maximum count of %d", paramindx,
C.MAX_GPARAM_LIST_ARGS))
}
// Compute address of indexed element(s)
if 64 == strconv.IntSize {
err = vplist.setVPlistParam(tptoken, errstr, *paramindx, uintptr(parm64bit))
if nil != err {
panic(fmt.Sprintf("YDB: Unknown error with varidicPlist.setVPlistParam(): %s", err))
}
} else {
// Have 32 bit addressing - 64 bit parm needs to take 2 slots (in the correct order).
if IsLittleEndian() {
if "arm" == runtime.GOARCH {
// If this is 32 bit ARM, there is a rule about the first 4 parms that go into registers. If
// there is a mix of 32 bit and 64 bit parameters, the 64 bit parm must always go into an
// even/odd pair of registers. If the next index is odd (meaning we could be loading into an
// odd/even pair, then that register is skipped and left unused. This only applies to parms
// loaded into registers and not to parms pushed on the stack.
if (1 == (*paramindx & 0x1)) && (maxARM32RegParms > *paramindx) {
// Our index is odd so skip a spot leaving garbage contents in that slot rather than
// take the time to clear them.
(*paramindx)++
}
if C.MAX_GPARAM_LIST_ARGS <= *paramindx {
panic(fmt.Sprintf("YDB: setVPlistParam64Bit item count %d exceeds maximum count of %d",
paramindx, C.MAX_GPARAM_LIST_ARGS))
}
}
err = vplist.setVPlistParam(tptoken, errstr, *paramindx, uintptr(parm64bit&0xffffffff))
if nil != err {
panic(fmt.Sprintf("YDB: Unknown error with varidicPlist.setVPlistParam(): %s", err))
}
(*paramindx)++
if C.MAX_GPARAM_LIST_ARGS <= *paramindx {
panic(fmt.Sprintf("YDB: setVPlistParam64Bit item count %d exceeds maximum count of %d",
paramindx, C.MAX_GPARAM_LIST_ARGS))
}
err = vplist.setVPlistParam(tptoken, errstr, *paramindx, uintptr(parm64bit>>32))
if nil != err {
panic(fmt.Sprintf("YDB: Unknown error with varidicPlist.setVPlistParam(): %s", err))
}
} else {
err = vplist.setVPlistParam(tptoken, errstr, *paramindx, uintptr(parm64bit>>32))
if nil != err {
panic(fmt.Sprintf("YDB: Unknown error with varidicPlist.setVPlistParam(): %s", err))
}
(*paramindx)++
if C.MAX_GPARAM_LIST_ARGS <= *paramindx {
panic(fmt.Sprintf("YDB: setVPlistParam64Bit item count %d exceeds maximum count of %d",
paramindx, C.MAX_GPARAM_LIST_ARGS))
}
err = vplist.setVPlistParam(tptoken, errstr, *paramindx, uintptr(parm64bit&0xffffffff))
if nil != err {
panic(fmt.Sprintf("YDB: Unknown error with varidicPlist.setVPlistParam(): %s", err))
}
}
}
(*paramindx)++
runtime.KeepAlive(vplist)
return nil
}