Files
zeekayandHanzo Dev fe8d85d663 rename package badger -> zapdb
The module has been github.com/luxfi/zapdb for a while, but the package inside
it still declared itself badger, so every consumer had to write

	zapdb "github.com/luxfi/zapdb" // package badger

and the alias existed only to paper over the mismatch. 56 root files and the
134 qualified references inside the repo now say zapdb.

Backward compatible in both directions: an existing consumer with the alias
keeps compiling (the alias is now merely redundant), and one without it starts
compiling instead of failing on an undefined zapdb.

Directory badger/ and its cmd package are untouched — that is the CLI, and its
binary is named badger.

Build and vet clean; suite green except TestZRestore, which wants a /tmp/zb/src
benchmark fixture and fails identically without this change.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-27 14:24:18 -07:00

409 lines
11 KiB
Go

// Package main exports lux/zapdb functions as a C shared library.
//
// Build:
//
// CGO_ENABLED=1 go build -buildmode=c-shared -o dist/libzapdb.so ./bindings/cabi/
// CGO_ENABLED=1 go build -buildmode=c-shared -o dist/libzapdb.dylib ./bindings/cabi/ # macOS
// install_name_tool -id @rpath/libzapdb.dylib dist/libzapdb.dylib # macOS rpath
//
// This produces libzapdb.{so,dylib,dll} + libzapdb.h
// which Rust (FFI), Python (ctypes), and TypeScript (N-API) can bind to.
//
// All stateful Go objects (DB, Txn) are stored behind opaque uint64 handles.
// A global handle map prevents GC collection.
package main
// #include <string.h>
import "C"
import (
"io"
"os"
"sync"
"sync/atomic"
"unsafe"
zapdb "github.com/luxfi/zapdb"
)
// ═══════════════════════════════════════════════════════════════════════
// Handle map — opaque uint64 IDs prevent Go GC from collecting objects
// ═══════════════════════════════════════════════════════════════════════
var (
handleMu sync.RWMutex
handleMap = make(map[C.ulonglong]any)
nextHandle atomic.Uint64
)
func storeHandle(obj any) C.ulonglong {
h := C.ulonglong(nextHandle.Add(1))
handleMu.Lock()
handleMap[h] = obj
handleMu.Unlock()
return h
}
func loadHandle[T any](h C.ulonglong) (T, bool) {
handleMu.RLock()
obj, ok := handleMap[h]
handleMu.RUnlock()
if !ok {
var zero T
return zero, false
}
val, ok := obj.(T)
return val, ok
}
func dropHandle(h C.ulonglong) {
handleMu.Lock()
delete(handleMap, h)
handleMu.Unlock()
}
// ═══════════════════════════════════════════════════════════════════════
// Status codes
// ═══════════════════════════════════════════════════════════════════════
//
// 0 = success
// -1 = invalid handle
// -2 = key not found
// -3 = operation error
// -4 = buffer too small (valLen updated with required size)
// ═══════════════════════════════════════════════════════════════════════
// Database lifecycle
// ═══════════════════════════════════════════════════════════════════════
//export lux_zapdb_open
func lux_zapdb_open(path *C.char, pathLen C.int, out *C.ulonglong) C.int {
goPath := C.GoStringN(path, pathLen)
opts := zapdb.DefaultOptions(goPath)
opts.Logger = nil // Silence internal logging for FFI use.
db, err := zapdb.Open(opts)
if err != nil {
return -3
}
*out = storeHandle(db)
return 0
}
//export lux_zapdb_close
func lux_zapdb_close(handle C.ulonglong) C.int {
db, ok := loadHandle[*zapdb.DB](handle)
if !ok {
return -1
}
dropHandle(handle)
if err := db.Close(); err != nil {
return -3
}
return 0
}
// ═══════════════════════════════════════════════════════════════════════
// Key/Value operations (implicit read-only transaction)
// ═══════════════════════════════════════════════════════════════════════
//export lux_zapdb_get
func lux_zapdb_get(
handle C.ulonglong,
key *C.char, keyLen C.int,
val *C.char, valLen *C.int,
) C.int {
db, ok := loadHandle[*zapdb.DB](handle)
if !ok {
return -1
}
goKey := C.GoBytes(unsafe.Pointer(key), keyLen)
var goVal []byte
err := db.View(func(txn *zapdb.Txn) error {
item, err := txn.Get(goKey)
if err != nil {
return err
}
goVal, err = item.ValueCopy(nil)
return err
})
if err == zapdb.ErrKeyNotFound {
return -2
}
if err != nil {
return -3
}
// Check output buffer capacity.
if C.int(len(goVal)) > *valLen {
*valLen = C.int(len(goVal))
return -4
}
*valLen = C.int(len(goVal))
if len(goVal) > 0 {
C.memcpy(unsafe.Pointer(val), unsafe.Pointer(&goVal[0]), C.size_t(len(goVal)))
}
return 0
}
//export lux_zapdb_set
func lux_zapdb_set(
handle C.ulonglong,
key *C.char, keyLen C.int,
val *C.char, valLen C.int,
) C.int {
db, ok := loadHandle[*zapdb.DB](handle)
if !ok {
return -1
}
goKey := C.GoBytes(unsafe.Pointer(key), keyLen)
goVal := C.GoBytes(unsafe.Pointer(val), valLen)
err := db.Update(func(txn *zapdb.Txn) error {
return txn.Set(goKey, goVal)
})
if err != nil {
return -3
}
return 0
}
//export lux_zapdb_delete
func lux_zapdb_delete(
handle C.ulonglong,
key *C.char, keyLen C.int,
) C.int {
db, ok := loadHandle[*zapdb.DB](handle)
if !ok {
return -1
}
goKey := C.GoBytes(unsafe.Pointer(key), keyLen)
err := db.Update(func(txn *zapdb.Txn) error {
return txn.Delete(goKey)
})
if err != nil {
return -3
}
return 0
}
// ═══════════════════════════════════════════════════════════════════════
// Iteration
// ═══════════════════════════════════════════════════════════════════════
// IterCallback is the C function pointer type for iteration.
// Returns 0 to continue, non-zero to stop.
type IterCallback = C.int
//export lux_zapdb_iterate
func lux_zapdb_iterate(
handle C.ulonglong,
prefix *C.char, prefixLen C.int,
callback unsafe.Pointer,
) C.int {
db, ok := loadHandle[*zapdb.DB](handle)
if !ok {
return -1
}
var goPrefix []byte
if prefixLen > 0 {
goPrefix = C.GoBytes(unsafe.Pointer(prefix), prefixLen)
}
// The callback signature from C:
// int callback(const char* key, int keyLen, const char* val, int valLen)
type cbFunc = func(k *C.char, kl C.int, v *C.char, vl C.int) C.int
cb := *(*cbFunc)(callback)
err := db.View(func(txn *zapdb.Txn) error {
opts := zapdb.DefaultIteratorOptions
if len(goPrefix) > 0 {
opts.Prefix = goPrefix
}
it := txn.NewIterator(opts)
defer it.Close()
for it.Rewind(); it.Valid(); it.Next() {
item := it.Item()
k := item.Key()
var v []byte
v, err := item.ValueCopy(nil)
if err != nil {
return err
}
var kPtr, vPtr *C.char
if len(k) > 0 {
kPtr = (*C.char)(unsafe.Pointer(&k[0]))
}
if len(v) > 0 {
vPtr = (*C.char)(unsafe.Pointer(&v[0]))
}
ret := cb(kPtr, C.int(len(k)), vPtr, C.int(len(v)))
if ret != 0 {
return nil // Caller requested stop.
}
}
return nil
})
if err != nil {
return -3
}
return 0
}
// ═══════════════════════════════════════════════════════════════════════
// Backup / Load / Sync / Compact
// ═══════════════════════════════════════════════════════════════════════
//export lux_zapdb_backup
func lux_zapdb_backup(handle C.ulonglong, path *C.char, pathLen C.int) C.int {
db, ok := loadHandle[*zapdb.DB](handle)
if !ok {
return -1
}
goPath := C.GoStringN(path, pathLen)
f, err := os.Create(goPath)
if err != nil {
return -3
}
defer f.Close()
if _, err := db.Backup(f, 0); err != nil {
return -3
}
return 0
}
//export lux_zapdb_load
func lux_zapdb_load(handle C.ulonglong, path *C.char, pathLen C.int) C.int {
db, ok := loadHandle[*zapdb.DB](handle)
if !ok {
return -1
}
goPath := C.GoStringN(path, pathLen)
f, err := os.Open(goPath)
if err != nil {
return -3
}
defer f.Close()
if err := db.Load(f, 256); err != nil && err != io.EOF {
return -3
}
return 0
}
//export lux_zapdb_sync
func lux_zapdb_sync(handle C.ulonglong) C.int {
db, ok := loadHandle[*zapdb.DB](handle)
if !ok {
return -1
}
if err := db.Sync(); err != nil {
return -3
}
return 0
}
//export lux_zapdb_compact
func lux_zapdb_compact(handle C.ulonglong) C.int {
db, ok := loadHandle[*zapdb.DB](handle)
if !ok {
return -1
}
if err := db.Flatten(4); err != nil {
return -3
}
return 0
}
// ═══════════════════════════════════════════════════════════════════════
// Transactions
// ═══════════════════════════════════════════════════════════════════════
//export lux_zapdb_new_txn
func lux_zapdb_new_txn(handle C.ulonglong, readOnly C.int, out *C.ulonglong) C.int {
db, ok := loadHandle[*zapdb.DB](handle)
if !ok {
return -1
}
txn := db.NewTransaction(readOnly == 0) // update = !readOnly
*out = storeHandle(txn)
return 0
}
//export lux_zapdb_txn_commit
func lux_zapdb_txn_commit(txnHandle C.ulonglong) C.int {
txn, ok := loadHandle[*zapdb.Txn](txnHandle)
if !ok {
return -1
}
dropHandle(txnHandle)
if err := txn.Commit(); err != nil {
return -3
}
return 0
}
//export lux_zapdb_txn_discard
func lux_zapdb_txn_discard(txnHandle C.ulonglong) {
txn, ok := loadHandle[*zapdb.Txn](txnHandle)
if !ok {
return
}
dropHandle(txnHandle)
txn.Discard()
}
//export lux_zapdb_txn_get
func lux_zapdb_txn_get(
txnHandle C.ulonglong,
key *C.char, keyLen C.int,
val *C.char, valLen *C.int,
) C.int {
txn, ok := loadHandle[*zapdb.Txn](txnHandle)
if !ok {
return -1
}
goKey := C.GoBytes(unsafe.Pointer(key), keyLen)
item, err := txn.Get(goKey)
if err == zapdb.ErrKeyNotFound {
return -2
}
if err != nil {
return -3
}
goVal, err := item.ValueCopy(nil)
if err != nil {
return -3
}
if C.int(len(goVal)) > *valLen {
*valLen = C.int(len(goVal))
return -4
}
*valLen = C.int(len(goVal))
if len(goVal) > 0 {
C.memcpy(unsafe.Pointer(val), unsafe.Pointer(&goVal[0]), C.size_t(len(goVal)))
}
return 0
}
//export lux_zapdb_txn_set
func lux_zapdb_txn_set(
txnHandle C.ulonglong,
key *C.char, keyLen C.int,
val *C.char, valLen C.int,
) C.int {
txn, ok := loadHandle[*zapdb.Txn](txnHandle)
if !ok {
return -1
}
goKey := C.GoBytes(unsafe.Pointer(key), keyLen)
goVal := C.GoBytes(unsafe.Pointer(val), valLen)
if err := txn.Set(goKey, goVal); err != nil {
return -3
}
return 0
}
func main() {}