370 lines
10 KiB
Odin
370 lines
10 KiB
Odin
|
|
package rocksdb
|
||
|
|
|
||
|
|
import "core:c"
|
||
|
|
import "core:fmt"
|
||
|
|
|
||
|
|
foreign import rocksdb "system:rocksdb"
|
||
|
|
|
||
|
|
// 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]
|
||
|
|
}
|