2018-01-27 15:08:45 +00:00
|
|
|
# Nim-RocksDB
|
|
|
|
# Copyright 2018 Status Research & Development GmbH
|
|
|
|
# Licensed under either of
|
|
|
|
#
|
|
|
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
|
|
|
# * GPL license, version 2.0, ([LICENSE-GPLv2](LICENSE-GPLv2) or https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
|
|
|
#
|
|
|
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
|
|
|
|
2020-04-17 22:18:04 +00:00
|
|
|
{.push raises: [Defect].}
|
|
|
|
|
2020-04-23 18:55:21 +00:00
|
|
|
import cpuinfo, options, stew/[byteutils, results]
|
2020-04-17 22:18:04 +00:00
|
|
|
|
2023-01-17 16:37:10 +00:00
|
|
|
from system/ansi_c import c_free
|
|
|
|
|
2020-04-17 22:18:04 +00:00
|
|
|
export results
|
2018-06-24 23:13:10 +00:00
|
|
|
|
|
|
|
const useCApi = true
|
|
|
|
|
|
|
|
when useCApi:
|
|
|
|
import rocksdb/librocksdb
|
|
|
|
export librocksdb
|
|
|
|
|
|
|
|
else:
|
|
|
|
{.error: "The C++ API of RocksDB is not supported yet".}
|
|
|
|
|
|
|
|
# The intention of this template is that it will hide the
|
|
|
|
# difference between the C and C++ APIs for objects such
|
|
|
|
# as Read/WriteOptions, which are allocated either on the
|
|
|
|
# stack or the heap.
|
|
|
|
template initResource(resourceName) =
|
|
|
|
var res = resourceName()
|
|
|
|
res
|
|
|
|
|
|
|
|
type
|
|
|
|
RocksDBInstance* = object
|
2022-08-11 08:13:10 +00:00
|
|
|
db*: rocksdb_t
|
2018-07-30 09:12:50 +00:00
|
|
|
backupEngine: rocksdb_backup_engine_t
|
2022-08-11 08:13:10 +00:00
|
|
|
options*: rocksdb_options_t
|
|
|
|
readOptions*: rocksdb_readoptions_t
|
2018-08-16 15:13:13 +00:00
|
|
|
writeOptions: rocksdb_writeoptions_t
|
2023-01-17 16:37:10 +00:00
|
|
|
dbPath: string # needed for clear()
|
2018-06-24 23:13:10 +00:00
|
|
|
|
2020-04-23 18:55:21 +00:00
|
|
|
DataProc* = proc(val: openArray[byte]) {.gcsafe, raises: [Defect].}
|
|
|
|
|
2020-04-17 22:18:04 +00:00
|
|
|
RocksDBResult*[T] = Result[T, string]
|
2018-06-24 23:13:10 +00:00
|
|
|
|
|
|
|
template bailOnErrors {.dirty.} =
|
|
|
|
if not errors.isNil:
|
2020-04-17 22:18:04 +00:00
|
|
|
result.err($errors)
|
2018-08-16 01:55:54 +00:00
|
|
|
rocksdb_free(errors)
|
2018-06-24 23:13:10 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
proc init*(rocks: var RocksDBInstance,
|
|
|
|
dbPath, dbBackupPath: string,
|
2019-04-01 08:00:45 +00:00
|
|
|
readOnly = false,
|
2018-06-24 23:13:10 +00:00
|
|
|
cpus = countProcessors(),
|
2019-07-16 13:51:52 +00:00
|
|
|
createIfMissing = true,
|
|
|
|
maxOpenFiles = -1): RocksDBResult[void] =
|
2018-06-24 23:13:10 +00:00
|
|
|
rocks.options = rocksdb_options_create()
|
2018-08-16 15:13:13 +00:00
|
|
|
rocks.readOptions = rocksdb_readoptions_create()
|
|
|
|
rocks.writeOptions = rocksdb_writeoptions_create()
|
2023-01-17 16:37:10 +00:00
|
|
|
rocks.dbPath = dbPath
|
2018-06-24 23:13:10 +00:00
|
|
|
|
|
|
|
# Optimize RocksDB. This is the easiest way to get RocksDB to perform well:
|
|
|
|
rocksdb_options_increase_parallelism(rocks.options, cpus.int32)
|
2018-08-16 02:08:03 +00:00
|
|
|
# This requires snappy - disabled because rocksdb is not always compiled with
|
|
|
|
# snappy support (for example Fedora 28, certain Ubuntu versions)
|
|
|
|
# rocksdb_options_optimize_level_style_compaction(options, 0);
|
2018-06-24 23:13:10 +00:00
|
|
|
rocksdb_options_set_create_if_missing(rocks.options, uint8(createIfMissing))
|
2019-07-16 13:51:52 +00:00
|
|
|
# default set to keep all files open (-1), allow setting it to a specific
|
|
|
|
# value, e.g. in case the application limit would be reached.
|
|
|
|
rocksdb_options_set_max_open_files(rocks.options, maxOpenFiles.cint)
|
2018-06-24 23:13:10 +00:00
|
|
|
|
2018-08-16 01:55:54 +00:00
|
|
|
var errors: cstring
|
2019-04-01 08:00:45 +00:00
|
|
|
if readOnly:
|
|
|
|
rocks.db = rocksdb_open_for_read_only(rocks.options, dbPath, 0'u8, errors.addr)
|
|
|
|
else:
|
|
|
|
rocks.db = rocksdb_open(rocks.options, dbPath, errors.addr)
|
2018-06-24 23:13:10 +00:00
|
|
|
bailOnErrors()
|
|
|
|
rocks.backupEngine = rocksdb_backup_engine_open(rocks.options,
|
2018-08-16 01:55:54 +00:00
|
|
|
dbBackupPath, errors.addr)
|
2018-06-24 23:13:10 +00:00
|
|
|
bailOnErrors()
|
2020-04-17 22:18:04 +00:00
|
|
|
ok()
|
2018-06-24 23:13:10 +00:00
|
|
|
|
|
|
|
template initRocksDB*(args: varargs[untyped]): Option[RocksDBInstance] =
|
|
|
|
var db: RocksDBInstance
|
|
|
|
if not init(db, args):
|
|
|
|
none(RocksDBInstance)
|
|
|
|
else:
|
|
|
|
some(db)
|
|
|
|
|
2020-04-17 22:18:04 +00:00
|
|
|
template getImpl(T: type) {.dirty.} =
|
|
|
|
if key.len <= 0:
|
|
|
|
return err("rocksdb: key cannot be empty on get")
|
2018-06-24 23:13:10 +00:00
|
|
|
|
|
|
|
var
|
2018-08-16 01:55:54 +00:00
|
|
|
errors: cstring
|
2020-04-17 22:18:04 +00:00
|
|
|
len: csize_t
|
2018-08-16 15:13:13 +00:00
|
|
|
data = rocksdb_get(db.db, db.readOptions,
|
2020-04-17 22:18:04 +00:00
|
|
|
cast[cstring](unsafeAddr key[0]), csize_t(key.len),
|
|
|
|
addr len, addr errors)
|
2018-06-24 23:13:10 +00:00
|
|
|
bailOnErrors()
|
2018-09-07 12:59:36 +00:00
|
|
|
if not data.isNil:
|
2020-04-17 22:18:04 +00:00
|
|
|
result = ok(toOpenArray(data, 0, int(len) - 1).to(T))
|
2018-09-07 12:59:36 +00:00
|
|
|
rocksdb_free(data)
|
2019-01-10 23:39:17 +00:00
|
|
|
else:
|
2020-04-17 22:18:04 +00:00
|
|
|
result = err("")
|
2018-06-24 23:13:10 +00:00
|
|
|
|
2020-04-23 18:55:21 +00:00
|
|
|
proc get*(db: RocksDBInstance, key: openArray[byte], onData: DataProc): RocksDBResult[bool] =
|
|
|
|
if key.len <= 0:
|
|
|
|
return err("rocksdb: key cannot be empty on get")
|
|
|
|
|
|
|
|
var
|
|
|
|
errors: cstring
|
|
|
|
len: csize_t
|
|
|
|
data = rocksdb_get(db.db, db.readOptions,
|
|
|
|
cast[cstring](unsafeAddr key[0]), csize_t(key.len),
|
|
|
|
addr len, addr errors)
|
|
|
|
bailOnErrors()
|
|
|
|
if not data.isNil:
|
|
|
|
# TODO onData may raise a Defect - in theory we could catch it and free the
|
|
|
|
# memory but this has a small overhead - setjmp (C) or RTTI (C++) -
|
|
|
|
# reconsider this once the exception dust settles
|
|
|
|
onData(toOpenArrayByte(data, 0, int(len) - 1))
|
|
|
|
rocksdb_free(data)
|
|
|
|
ok(true)
|
|
|
|
else:
|
|
|
|
ok(false)
|
|
|
|
|
|
|
|
proc get*(db: RocksDBInstance, key: openArray[byte]): RocksDBResult[string] {.deprecated: "DataProc".} =
|
2018-09-07 12:59:36 +00:00
|
|
|
## Get value for `key`. If no value exists, set `result.ok` to `false`,
|
|
|
|
## and result.error to `""`.
|
2020-04-23 18:55:21 +00:00
|
|
|
var res: RocksDBResult[string]
|
|
|
|
proc onData(data: openArray[byte]) =
|
|
|
|
res.ok(string.fromBytes(data))
|
2018-06-24 23:13:10 +00:00
|
|
|
|
2020-04-23 18:55:21 +00:00
|
|
|
if ? db.get(key, onData):
|
|
|
|
res
|
|
|
|
else:
|
|
|
|
ok("")
|
|
|
|
|
|
|
|
proc getBytes*(db: RocksDBInstance, key: openArray[byte]): RocksDBResult[seq[byte]] {.deprecated: "DataProc".} =
|
2018-09-07 12:59:36 +00:00
|
|
|
## Get value for `key`. If no value exists, set `result.ok` to `false`,
|
|
|
|
## and result.error to `""`.
|
2020-04-23 18:55:21 +00:00
|
|
|
var res: RocksDBResult[seq[byte]]
|
|
|
|
proc onData(data: openArray[byte]) =
|
|
|
|
res.ok(@data)
|
|
|
|
|
|
|
|
if ? db.get(key, onData):
|
|
|
|
res
|
|
|
|
else:
|
|
|
|
err("")
|
2018-06-24 23:13:10 +00:00
|
|
|
|
2020-04-23 18:55:21 +00:00
|
|
|
proc put*(db: RocksDBInstance, key, val: openArray[byte]): RocksDBResult[void] =
|
2020-04-17 22:18:04 +00:00
|
|
|
if key.len <= 0:
|
|
|
|
return err("rocksdb: key cannot be empty on put")
|
2018-06-24 23:13:10 +00:00
|
|
|
|
|
|
|
var
|
2018-08-16 01:55:54 +00:00
|
|
|
errors: cstring
|
2018-06-24 23:13:10 +00:00
|
|
|
|
2018-08-16 15:13:13 +00:00
|
|
|
rocksdb_put(db.db, db.writeOptions,
|
2020-04-17 22:18:04 +00:00
|
|
|
cast[cstring](unsafeAddr key[0]), csize_t(key.len),
|
|
|
|
cast[cstring](if val.len > 0: unsafeAddr val[0] else: nil),
|
|
|
|
csize_t(val.len),
|
2018-08-16 01:55:54 +00:00
|
|
|
errors.addr)
|
2018-06-24 23:13:10 +00:00
|
|
|
|
|
|
|
bailOnErrors()
|
2020-04-17 22:18:04 +00:00
|
|
|
ok()
|
2018-06-24 23:13:10 +00:00
|
|
|
|
2020-04-23 18:55:21 +00:00
|
|
|
proc contains*(db: RocksDBInstance, key: openArray[byte]): RocksDBResult[bool] =
|
2020-04-17 22:18:04 +00:00
|
|
|
if key.len <= 0:
|
|
|
|
return err("rocksdb: key cannot be empty on contains")
|
2018-06-24 23:13:10 +00:00
|
|
|
|
2018-09-07 12:59:36 +00:00
|
|
|
var
|
|
|
|
errors: cstring
|
2020-04-17 22:18:04 +00:00
|
|
|
len: csize_t
|
2018-09-07 12:59:36 +00:00
|
|
|
data = rocksdb_get(db.db, db.readOptions,
|
2020-04-17 22:18:04 +00:00
|
|
|
cast[cstring](unsafeAddr key[0]), csize_t(key.len),
|
2018-09-07 12:59:36 +00:00
|
|
|
addr len, errors.addr)
|
|
|
|
bailOnErrors()
|
|
|
|
if not data.isNil:
|
|
|
|
rocksdb_free(data)
|
2020-04-17 22:18:04 +00:00
|
|
|
ok(true)
|
|
|
|
else:
|
|
|
|
ok(false)
|
2018-06-24 23:13:10 +00:00
|
|
|
|
2023-01-17 16:37:10 +00:00
|
|
|
proc del*(db: RocksDBInstance, key: openArray[byte]): RocksDBResult[bool] =
|
|
|
|
if key.len <= 0:
|
|
|
|
return err("rocksdb: key cannot be empty on del")
|
|
|
|
|
|
|
|
# This seems like a bad idea, but right now I don't want to
|
|
|
|
# get sidetracked by this. --Adam
|
|
|
|
if not db.contains(key).get:
|
|
|
|
return ok(false)
|
|
|
|
|
|
|
|
var errors: cstring
|
|
|
|
rocksdb_delete(db.db, db.writeOptions,
|
|
|
|
cast[cstring](unsafeAddr key[0]), csize_t(key.len),
|
|
|
|
errors.addr)
|
|
|
|
bailOnErrors()
|
|
|
|
ok(true)
|
|
|
|
|
|
|
|
proc clear*(db: var RocksDBInstance): RocksDBResult[bool] =
|
|
|
|
raiseAssert "unimplemented"
|
|
|
|
|
2018-06-24 23:13:10 +00:00
|
|
|
proc backup*(db: RocksDBInstance): RocksDBResult[void] =
|
2018-08-16 01:55:54 +00:00
|
|
|
var errors: cstring
|
|
|
|
rocksdb_backup_engine_create_new_backup(db.backupEngine, db.db, errors.addr)
|
2018-06-24 23:13:10 +00:00
|
|
|
bailOnErrors()
|
2020-04-17 22:18:04 +00:00
|
|
|
ok()
|
2018-06-24 23:13:10 +00:00
|
|
|
|
|
|
|
# XXX: destructors are just too buggy at the moment:
|
|
|
|
# https://github.com/nim-lang/Nim/issues/8112
|
|
|
|
# proc `=destroy`*(db: var RocksDBInstance) =
|
|
|
|
proc close*(db: var RocksDBInstance) =
|
2024-02-13 13:59:41 +00:00
|
|
|
template freeField(name) =
|
|
|
|
type FieldType = typeof db.`name`
|
2018-08-16 15:13:13 +00:00
|
|
|
if db.`name`.isNil:
|
|
|
|
`rocksdb name destroy`(db.`name`)
|
2024-02-13 13:59:41 +00:00
|
|
|
db.`name` = FieldType(nil)
|
|
|
|
template setFieldToNil(name) =
|
|
|
|
type FieldType = typeof db.`name`
|
|
|
|
db.`name` = FieldType(nil)
|
|
|
|
|
2018-08-16 15:13:13 +00:00
|
|
|
freeField(writeOptions)
|
|
|
|
freeField(readOptions)
|
|
|
|
freeField(options)
|
|
|
|
|
2018-07-30 16:34:36 +00:00
|
|
|
if not db.backupEngine.isNil:
|
2018-06-24 23:13:10 +00:00
|
|
|
rocksdb_backup_engine_close(db.backupEngine)
|
2024-02-13 13:59:41 +00:00
|
|
|
setFieldToNil(backupEngine)
|
2018-06-24 23:13:10 +00:00
|
|
|
|
2018-07-30 16:34:36 +00:00
|
|
|
if not db.db.isNil:
|
2018-06-24 23:13:10 +00:00
|
|
|
rocksdb_close(db.db)
|
2024-02-13 13:59:41 +00:00
|
|
|
setFieldToNil(db)
|