Files
jormun-db/rocksdb/rocksdb.odin

373 lines
10 KiB
Odin
Raw Normal View History

2026-02-15 08:55:22 -05:00
package rocksdb
import "core:c"
import "core:fmt"
foreign import rocksdb "system:rocksdb"
2026-02-15 11:17:12 -05:00
// In order to use RocksDB's WAL replication helpers, we need to import the C++ library so we use this shim
//foreign import rocksdb_shim "system:jormun_rocksdb_shim" // I know we'll use in future but because we're not right now, compiler is complaining
2026-02-15 08:55:22 -05:00
// RocksDB C API types
RocksDB_T :: distinct rawptr
RocksDB_Options :: distinct rawptr
RocksDB_WriteOptions :: distinct rawptr
RocksDB_ReadOptions :: distinct rawptr
RocksDB_WriteBatch :: distinct rawptr
RocksDB_Iterator :: distinct rawptr
RocksDB_FlushOptions :: distinct rawptr
// Error type
Error :: enum {
None,
OpenFailed,
WriteFailed,
ReadFailed,
DeleteFailed,
InvalidArgument,
Corruption,
NotFound,
IOError,
Unknown,
}
// Database handle with options
DB :: struct {
handle: RocksDB_T,
options: RocksDB_Options,
write_options: RocksDB_WriteOptions,
read_options: RocksDB_ReadOptions,
}
// Foreign C functions
@(default_calling_convention = "c")
foreign rocksdb {
// Database operations
rocksdb_open :: proc(options: RocksDB_Options, path: cstring, errptr: ^cstring) -> RocksDB_T ---
rocksdb_close :: proc(db: RocksDB_T) ---
// Options
rocksdb_options_create :: proc() -> RocksDB_Options ---
rocksdb_options_destroy :: proc(options: RocksDB_Options) ---
rocksdb_options_set_create_if_missing :: proc(options: RocksDB_Options, val: c.uchar) ---
rocksdb_options_increase_parallelism :: proc(options: RocksDB_Options, total_threads: c.int) ---
rocksdb_options_optimize_level_style_compaction :: proc(options: RocksDB_Options, memtable_memory_budget: c.uint64_t) ---
rocksdb_options_set_compression :: proc(options: RocksDB_Options, compression: c.int) ---
// Write options
rocksdb_writeoptions_create :: proc() -> RocksDB_WriteOptions ---
rocksdb_writeoptions_destroy :: proc(options: RocksDB_WriteOptions) ---
// Read options
rocksdb_readoptions_create :: proc() -> RocksDB_ReadOptions ---
rocksdb_readoptions_destroy :: proc(options: RocksDB_ReadOptions) ---
// Put/Get/Delete
rocksdb_put :: proc(db: RocksDB_T, options: RocksDB_WriteOptions, key: [^]byte, keylen: c.size_t, val: [^]byte, vallen: c.size_t, errptr: ^cstring) ---
rocksdb_get :: proc(db: RocksDB_T, options: RocksDB_ReadOptions, key: [^]byte, keylen: c.size_t, vallen: ^c.size_t, errptr: ^cstring) -> [^]byte ---
rocksdb_delete :: proc(db: RocksDB_T, options: RocksDB_WriteOptions, key: [^]byte, keylen: c.size_t, errptr: ^cstring) ---
// Flush
rocksdb_flushoptions_create :: proc() -> RocksDB_FlushOptions ---
rocksdb_flushoptions_destroy :: proc(options: RocksDB_FlushOptions) ---
rocksdb_flush :: proc(db: RocksDB_T, options: RocksDB_FlushOptions, errptr: ^cstring) ---
// Write batch
rocksdb_writebatch_create :: proc() -> RocksDB_WriteBatch ---
rocksdb_writebatch_destroy :: proc(batch: RocksDB_WriteBatch) ---
rocksdb_writebatch_put :: proc(batch: RocksDB_WriteBatch, key: [^]byte, keylen: c.size_t, val: [^]byte, vallen: c.size_t) ---
rocksdb_writebatch_delete :: proc(batch: RocksDB_WriteBatch, key: [^]byte, keylen: c.size_t) ---
rocksdb_writebatch_clear :: proc(batch: RocksDB_WriteBatch) ---
rocksdb_write :: proc(db: RocksDB_T, options: RocksDB_WriteOptions, batch: RocksDB_WriteBatch, errptr: ^cstring) ---
// Iterator
rocksdb_create_iterator :: proc(db: RocksDB_T, options: RocksDB_ReadOptions) -> RocksDB_Iterator ---
rocksdb_iter_destroy :: proc(iter: RocksDB_Iterator) ---
rocksdb_iter_seek_to_first :: proc(iter: RocksDB_Iterator) ---
rocksdb_iter_seek_to_last :: proc(iter: RocksDB_Iterator) ---
rocksdb_iter_seek :: proc(iter: RocksDB_Iterator, key: [^]byte, keylen: c.size_t) ---
rocksdb_iter_seek_for_prev :: proc(iter: RocksDB_Iterator, key: [^]byte, keylen: c.size_t) ---
rocksdb_iter_valid :: proc(iter: RocksDB_Iterator) -> c.uchar ---
rocksdb_iter_next :: proc(iter: RocksDB_Iterator) ---
rocksdb_iter_prev :: proc(iter: RocksDB_Iterator) ---
rocksdb_iter_key :: proc(iter: RocksDB_Iterator, klen: ^c.size_t) -> [^]byte ---
rocksdb_iter_value :: proc(iter: RocksDB_Iterator, vlen: ^c.size_t) -> [^]byte ---
// Memory management
rocksdb_free :: proc(ptr: rawptr) ---
}
// Compression types
ROCKSDB_NO_COMPRESSION :: 0
ROCKSDB_SNAPPY_COMPRESSION :: 1
ROCKSDB_ZLIB_COMPRESSION :: 2
ROCKSDB_BZIP2_COMPRESSION :: 3
ROCKSDB_LZ4_COMPRESSION :: 4
ROCKSDB_LZ4HC_COMPRESSION :: 5
ROCKSDB_ZSTD_COMPRESSION :: 7
// Open a database
db_open :: proc(path: string, create_if_missing := true) -> (DB, Error) {
options := rocksdb_options_create()
if options == nil {
return {}, .Unknown
}
// Set create if missing
rocksdb_options_set_create_if_missing(options, create_if_missing ? 1 : 0)
// Performance optimizations
rocksdb_options_increase_parallelism(options, 4)
rocksdb_options_optimize_level_style_compaction(options, 512 * 1024 * 1024)
rocksdb_options_set_compression(options, ROCKSDB_LZ4_COMPRESSION)
// Create write and read options
write_options := rocksdb_writeoptions_create()
if write_options == nil {
rocksdb_options_destroy(options)
return {}, .Unknown
}
read_options := rocksdb_readoptions_create()
if read_options == nil {
rocksdb_writeoptions_destroy(write_options)
rocksdb_options_destroy(options)
return {}, .Unknown
}
// Open database
err: cstring
path_cstr := fmt.ctprintf("%s", path)
handle := rocksdb_open(options, path_cstr, &err)
if err != nil {
defer rocksdb_free(rawptr(err)) // Cast it here and now so we don't deal with issues from FFI down the line
rocksdb_readoptions_destroy(read_options)
rocksdb_writeoptions_destroy(write_options)
rocksdb_options_destroy(options)
return {}, .OpenFailed
}
return DB{
handle = handle,
options = options,
write_options = write_options,
read_options = read_options,
}, .None
}
// Close database
db_close :: proc(db: ^DB) {
rocksdb_readoptions_destroy(db.read_options)
rocksdb_writeoptions_destroy(db.write_options)
rocksdb_close(db.handle)
rocksdb_options_destroy(db.options)
}
// Put key-value pair
db_put :: proc(db: ^DB, key: []byte, value: []byte) -> Error {
err: cstring
rocksdb_put(
db.handle,
db.write_options,
raw_data(key),
c.size_t(len(key)),
raw_data(value),
c.size_t(len(value)),
&err,
)
if err != nil {
defer rocksdb_free(rawptr(err)) // Cast it here and now so we don't deal with issues from FFI down the line
return .WriteFailed
}
return .None
}
// Get value by key (returns owned slice - caller must free)
db_get :: proc(db: ^DB, key: []byte) -> (value: []byte, err: Error) {
errptr: cstring
value_len: c.size_t
value_ptr := rocksdb_get(
db.handle,
db.read_options,
raw_data(key),
c.size_t(len(key)),
&value_len,
&errptr,
)
if errptr != nil {
defer rocksdb_free(rawptr(errptr)) // Cast it here and now so we don't deal with issues from FFI down the line
return nil, .ReadFailed
}
if value_ptr == nil {
return nil, .NotFound
}
// Copy the data and free RocksDB's buffer
result := make([]byte, value_len, context.allocator)
copy(result, value_ptr[:value_len])
rocksdb_free(rawptr(value_ptr)) // Cast it here and now so we don't deal with issues from FFI down the line
return result, .None
}
// Delete key
db_delete :: proc(db: ^DB, key: []byte) -> Error {
err: cstring
rocksdb_delete(
db.handle,
db.write_options,
raw_data(key),
c.size_t(len(key)),
&err,
)
if err != nil {
defer rocksdb_free(rawptr(err)) // Cast it here and now so we don't deal with issues from FFI down the line
return .DeleteFailed
}
return .None
}
// Flush database
db_flush :: proc(db: ^DB) -> Error {
flush_opts := rocksdb_flushoptions_create()
if flush_opts == nil {
return .Unknown
}
defer rocksdb_flushoptions_destroy(flush_opts)
err: cstring
rocksdb_flush(db.handle, flush_opts, &err)
if err != nil {
defer rocksdb_free(rawptr(err)) // Cast it here and now so we don't deal with issues from FFI down the line
return .IOError
}
return .None
}
// Write batch
WriteBatch :: struct {
handle: RocksDB_WriteBatch,
}
// Create write batch
batch_create :: proc() -> (WriteBatch, Error) {
handle := rocksdb_writebatch_create()
if handle == nil {
return {}, .Unknown
}
return WriteBatch{handle = handle}, .None
}
// Destroy write batch
batch_destroy :: proc(batch: ^WriteBatch) {
rocksdb_writebatch_destroy(batch.handle)
}
// Add put operation to batch
batch_put :: proc(batch: ^WriteBatch, key: []byte, value: []byte) {
rocksdb_writebatch_put(
batch.handle,
raw_data(key),
c.size_t(len(key)),
raw_data(value),
c.size_t(len(value)),
)
}
// Add delete operation to batch
batch_delete :: proc(batch: ^WriteBatch, key: []byte) {
rocksdb_writebatch_delete(
batch.handle,
raw_data(key),
c.size_t(len(key)),
)
}
// Clear batch
batch_clear :: proc(batch: ^WriteBatch) {
rocksdb_writebatch_clear(batch.handle)
}
// Write batch to database
batch_write :: proc(db: ^DB, batch: ^WriteBatch) -> Error {
err: cstring
rocksdb_write(db.handle, db.write_options, batch.handle, &err)
if err != nil {
defer rocksdb_free(rawptr(err)) // Cast it here and now so we don't deal with issues from FFI down the line
return .WriteFailed
}
return .None
}
// Iterator
Iterator :: struct {
handle: RocksDB_Iterator,
}
// Create iterator
iter_create :: proc(db: ^DB) -> (Iterator, Error) {
handle := rocksdb_create_iterator(db.handle, db.read_options)
if handle == nil {
return {}, .Unknown
}
return Iterator{handle = handle}, .None
}
// Destroy iterator
iter_destroy :: proc(iter: ^Iterator) {
rocksdb_iter_destroy(iter.handle)
}
// Seek to first
iter_seek_to_first :: proc(iter: ^Iterator) {
rocksdb_iter_seek_to_first(iter.handle)
}
// Seek to last
iter_seek_to_last :: proc(iter: ^Iterator) {
rocksdb_iter_seek_to_last(iter.handle)
}
// Seek to key
iter_seek :: proc(iter: ^Iterator, target: []byte) {
rocksdb_iter_seek(iter.handle, raw_data(target), c.size_t(len(target)))
}
// Check if iterator is valid
iter_valid :: proc(iter: ^Iterator) -> bool {
return rocksdb_iter_valid(iter.handle) != 0
}
// Move to next
iter_next :: proc(iter: ^Iterator) {
rocksdb_iter_next(iter.handle)
}
// Move to previous
iter_prev :: proc(iter: ^Iterator) {
rocksdb_iter_prev(iter.handle)
}
// Get current key (returns borrowed slice)
iter_key :: proc(iter: ^Iterator) -> []byte {
klen: c.size_t
key_ptr := rocksdb_iter_key(iter.handle, &klen)
if key_ptr == nil {
return nil
}
return key_ptr[:klen]
}
// Get current value (returns borrowed slice)
iter_value :: proc(iter: ^Iterator) -> []byte {
vlen: c.size_t
value_ptr := rocksdb_iter_value(iter.handle, &vlen)
if value_ptr == nil {
return nil
}
return value_ptr[:vlen]
}