Add support for optimistic transaction db, write batch with index, keyMayExist and empty keys. (#63)
* Add support for optimistic transaction db. * Add keyMayExist to RocksDbRef. * Add support for write batch with index. * Allow empty keys to be used in API.
This commit is contained in:
parent
6b7de5730b
commit
cf1267e845
|
@ -9,9 +9,10 @@
|
|||
|
||||
import
|
||||
./rocksdb/[
|
||||
backup, columnfamily, rocksdb, rocksiterator, sstfilewriter, transactiondb,
|
||||
writebatch,
|
||||
backup, columnfamily, optimistictxdb, rocksdb, rocksiterator, sstfilewriter,
|
||||
transactiondb, writebatch, writebatchwi,
|
||||
]
|
||||
|
||||
export
|
||||
backup, columnfamily, rocksdb, rocksiterator, sstfilewriter, transactiondb, writebatch
|
||||
backup, columnfamily, optimistictxdb, rocksdb, rocksiterator, sstfilewriter,
|
||||
transactiondb, writebatch, writebatchwi
|
||||
|
|
|
@ -38,3 +38,9 @@ template bailOnErrors*(errors: cstring): auto =
|
|||
let res = err($(errors))
|
||||
rocksdb_free(errors)
|
||||
return res
|
||||
|
||||
template unsafeAddrOrNil*(s: openArray[byte]): auto =
|
||||
if s.len > 0:
|
||||
unsafeAddr s[0]
|
||||
else:
|
||||
nil
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
# Nim-RocksDB
|
||||
# Copyright 2024 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.
|
||||
|
||||
## A `OptimisticTxDbRef` can be used to open a connection to the RocksDB database
|
||||
## with support for transactional operations against multiple column families.
|
||||
## To create a new transaction call `beginTransaction` which will return a
|
||||
## `TransactionRef`. To commit or rollback the transaction call `commit` or
|
||||
## `rollback` on the `TransactionRef` type after applying changes to the transaction.
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import
|
||||
std/[sequtils, locks],
|
||||
./lib/librocksdb,
|
||||
./options/[dbopts, readopts, writeopts],
|
||||
./transactions/[transaction, otxopts],
|
||||
./columnfamily/[cfopts, cfdescriptor, cfhandle],
|
||||
./internal/[cftable, utils],
|
||||
./rocksresult
|
||||
|
||||
export dbopts, cfdescriptor, readopts, writeopts, otxopts, transaction, rocksresult
|
||||
|
||||
type
|
||||
OptimisticTxDbPtr* = ptr rocksdb_optimistictransactiondb_t
|
||||
|
||||
OptimisticTxDbRef* = ref object
|
||||
lock: Lock
|
||||
cPtr: OptimisticTxDbPtr
|
||||
path: string
|
||||
dbOpts: DbOptionsRef
|
||||
cfDescriptors: seq[ColFamilyDescriptor]
|
||||
defaultCfHandle: ColFamilyHandleRef
|
||||
cfTable: ColFamilyTableRef
|
||||
|
||||
proc openOptimisticTxDb*(
|
||||
path: string,
|
||||
dbOpts = defaultDbOptions(autoClose = true),
|
||||
columnFamilies: openArray[ColFamilyDescriptor] = [],
|
||||
): RocksDBResult[OptimisticTxDbRef] =
|
||||
## Open a `OptimisticTxDbRef` with the given options and column families.
|
||||
## If no column families are provided the default column family will be used.
|
||||
## If no options are provided the default options will be used.
|
||||
## These default options will be closed when the database is closed.
|
||||
## If any options are provided, they will need to be closed manually.
|
||||
|
||||
var cfs = columnFamilies.toSeq()
|
||||
if DEFAULT_COLUMN_FAMILY_NAME notin columnFamilies.mapIt(it.name()):
|
||||
cfs.add(defaultColFamilyDescriptor(autoClose = true))
|
||||
|
||||
var
|
||||
cfNames = cfs.mapIt(it.name().cstring)
|
||||
cfOpts = cfs.mapIt(it.options.cPtr)
|
||||
cfHandles = newSeq[ColFamilyHandlePtr](cfs.len)
|
||||
errors: cstring
|
||||
|
||||
let txDbPtr = rocksdb_optimistictransactiondb_open_column_families(
|
||||
dbOpts.cPtr,
|
||||
path.cstring,
|
||||
cfNames.len().cint,
|
||||
cast[cstringArray](cfNames[0].addr),
|
||||
cfOpts[0].addr,
|
||||
cfHandles[0].addr,
|
||||
cast[cstringArray](errors.addr),
|
||||
)
|
||||
bailOnErrorsWithCleanup(errors):
|
||||
autoCloseNonNil(dbOpts)
|
||||
autoCloseAll(cfs)
|
||||
|
||||
let
|
||||
cfTable = newColFamilyTable(cfNames.mapIt($it), cfHandles)
|
||||
db = OptimisticTxDbRef(
|
||||
lock: createLock(),
|
||||
cPtr: txDbPtr,
|
||||
path: path,
|
||||
dbOpts: dbOpts,
|
||||
cfDescriptors: cfs,
|
||||
defaultCfHandle: cfTable.get(DEFAULT_COLUMN_FAMILY_NAME),
|
||||
cfTable: cfTable,
|
||||
)
|
||||
ok(db)
|
||||
|
||||
proc getColFamilyHandle*(
|
||||
db: OptimisticTxDbRef, name: string
|
||||
): RocksDBResult[ColFamilyHandleRef] =
|
||||
let cfHandle = db.cfTable.get(name)
|
||||
if cfHandle.isNil():
|
||||
err("rocksdb: unknown column family")
|
||||
else:
|
||||
ok(cfHandle)
|
||||
|
||||
proc isClosed*(db: OptimisticTxDbRef): bool {.inline.} =
|
||||
## Returns `true` if the `OptimisticTxDbRef` has been closed.
|
||||
db.cPtr.isNil()
|
||||
|
||||
proc beginTransaction*(
|
||||
db: OptimisticTxDbRef,
|
||||
readOpts = defaultReadOptions(autoClose = true),
|
||||
writeOpts = defaultWriteOptions(autoClose = true),
|
||||
otxOpts = defaultOptimisticTxOptions(autoClose = true),
|
||||
cfHandle = db.defaultCfHandle,
|
||||
): TransactionRef =
|
||||
## Begin a new transaction against the database. The transaction will default
|
||||
## to using the specified column family. If no column family is specified
|
||||
## then the default column family will be used.
|
||||
doAssert not db.isClosed()
|
||||
|
||||
let txPtr =
|
||||
rocksdb_optimistictransaction_begin(db.cPtr, writeOpts.cPtr, otxOpts.cPtr, nil)
|
||||
|
||||
newTransaction(txPtr, readOpts, writeOpts, nil, otxOpts, cfHandle)
|
||||
|
||||
proc close*(db: OptimisticTxDbRef) =
|
||||
## Close the `OptimisticTxDbRef`.
|
||||
|
||||
withLock(db.lock):
|
||||
if not db.isClosed():
|
||||
# the column families should be closed before the database
|
||||
db.cfTable.close()
|
||||
|
||||
rocksdb_optimistictransactiondb_close(db.cPtr)
|
||||
db.cPtr = nil
|
||||
|
||||
# opts should be closed after the database is closed
|
||||
autoCloseNonNil(db.dbOpts)
|
||||
autoCloseAll(db.cfDescriptors)
|
|
@ -31,11 +31,11 @@ import
|
|||
./options/[dbopts, readopts, writeopts],
|
||||
./columnfamily/[cfopts, cfdescriptor, cfhandle],
|
||||
./internal/[cftable, utils],
|
||||
./rocksiterator,
|
||||
./rocksresult,
|
||||
./writebatch
|
||||
./[rocksiterator, rocksresult, writebatch, writebatchwi]
|
||||
|
||||
export rocksresult, dbopts, readopts, writeopts, cfdescriptor, rocksiterator, writebatch
|
||||
export
|
||||
rocksresult, dbopts, readopts, writeopts, cfdescriptor, cfhandle, rocksiterator,
|
||||
writebatch, writebatchwi
|
||||
|
||||
type
|
||||
RocksDbPtr* = ptr rocksdb_t
|
||||
|
@ -236,9 +236,6 @@ proc get*(
|
|||
## The `onData` callback reduces the number of copies and therefore should be
|
||||
## preferred if performance is required.
|
||||
|
||||
if key.len() == 0:
|
||||
return err("rocksdb: key is empty")
|
||||
|
||||
var
|
||||
len: csize_t
|
||||
errors: cstring
|
||||
|
@ -246,7 +243,7 @@ proc get*(
|
|||
db.cPtr,
|
||||
db.readOpts.cPtr,
|
||||
cfHandle.cPtr,
|
||||
cast[cstring](unsafeAddr key[0]),
|
||||
cast[cstring](key.unsafeAddrOrNil()),
|
||||
csize_t(key.len),
|
||||
len.addr,
|
||||
cast[cstringArray](errors.addr),
|
||||
|
@ -283,21 +280,14 @@ proc put*(
|
|||
): RocksDBResult[void] =
|
||||
## Put the value for the given key into the specified column family.
|
||||
|
||||
if key.len() == 0:
|
||||
return err("rocksdb: key is empty")
|
||||
|
||||
var errors: cstring
|
||||
rocksdb_put_cf(
|
||||
db.cPtr,
|
||||
db.writeOpts.cPtr,
|
||||
cfHandle.cPtr,
|
||||
cast[cstring](unsafeAddr key[0]),
|
||||
cast[cstring](key.unsafeAddrOrNil()),
|
||||
csize_t(key.len),
|
||||
cast[cstring](if val.len > 0:
|
||||
unsafeAddr val[0]
|
||||
else:
|
||||
nil
|
||||
),
|
||||
cast[cstring](val.unsafeAddrOrNil()),
|
||||
csize_t(val.len),
|
||||
cast[cstringArray](errors.addr),
|
||||
)
|
||||
|
@ -305,6 +295,30 @@ proc put*(
|
|||
|
||||
ok()
|
||||
|
||||
proc keyMayExist*(
|
||||
db: RocksDbRef, key: openArray[byte], cfHandle = db.defaultCfHandle
|
||||
): RocksDBResult[bool] =
|
||||
## If the key definitely does not exist in the database, then this method
|
||||
## returns false, otherwise it returns true if the key might exist. That is
|
||||
## to say that this method is probabilistic and may return false positives,
|
||||
## but never a false negative. This check is potentially lighter-weight than
|
||||
## invoking keyExists.
|
||||
|
||||
let keyMayExist = rocksdb_key_may_exist_cf(
|
||||
db.cPtr,
|
||||
db.readOpts.cPtr,
|
||||
cfHandle.cPtr,
|
||||
cast[cstring](key.unsafeAddrOrNil()),
|
||||
csize_t(key.len),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
0,
|
||||
nil,
|
||||
).bool
|
||||
|
||||
ok(keyMayExist)
|
||||
|
||||
proc keyExists*(
|
||||
db: RocksDbRef, key: openArray[byte], cfHandle = db.defaultCfHandle
|
||||
): RocksDBResult[bool] =
|
||||
|
@ -312,9 +326,6 @@ proc keyExists*(
|
|||
## Returns a result containing `true` if the key exists or a result
|
||||
## containing `false` otherwise.
|
||||
|
||||
# TODO: Call rocksdb_key_may_exist_cf to improve performance for the case
|
||||
# when the key does not exist
|
||||
|
||||
db.get(
|
||||
key,
|
||||
proc(data: openArray[byte]) =
|
||||
|
@ -330,15 +341,12 @@ proc delete*(
|
|||
## If the value does not exist, the delete will be a no-op.
|
||||
## To check if the value exists before or after a delete, use `keyExists`.
|
||||
|
||||
if key.len() == 0:
|
||||
return err("rocksdb: key is empty")
|
||||
|
||||
var errors: cstring
|
||||
rocksdb_delete_cf(
|
||||
db.cPtr,
|
||||
db.writeOpts.cPtr,
|
||||
cfHandle.cPtr,
|
||||
cast[cstring](unsafeAddr key[0]),
|
||||
cast[cstring](key.unsafeAddrOrNil()),
|
||||
csize_t(key.len),
|
||||
cast[cstringArray](errors.addr),
|
||||
)
|
||||
|
@ -350,6 +358,7 @@ proc openIterator*(
|
|||
db: RocksDbRef, cfHandle = db.defaultCfHandle
|
||||
): RocksDBResult[RocksIteratorRef] =
|
||||
## Opens an `RocksIteratorRef` for the specified column family.
|
||||
## The iterator should be closed using the `close` method after usage.
|
||||
doAssert not db.isClosed()
|
||||
|
||||
let rocksIterPtr =
|
||||
|
@ -361,10 +370,31 @@ proc openWriteBatch*(
|
|||
db: RocksDbReadWriteRef, cfHandle = db.defaultCfHandle
|
||||
): WriteBatchRef =
|
||||
## Opens a `WriteBatchRef` which defaults to using the specified column family.
|
||||
## The write batch should be closed using the `close` method after usage.
|
||||
doAssert not db.isClosed()
|
||||
|
||||
createWriteBatch(cfHandle)
|
||||
|
||||
proc openWriteBatchWithIndex*(
|
||||
db: RocksDbReadWriteRef,
|
||||
reservedBytes = 0,
|
||||
overwriteKey = false,
|
||||
cfHandle = db.defaultCfHandle,
|
||||
): WriteBatchWIRef =
|
||||
## Opens a `WriteBatchWIRef` which defaults to using the specified column family.
|
||||
## The write batch should be closed using the `close` method after usage.
|
||||
## `WriteBatchWIRef` is similar to `WriteBatchRef` but with a binary searchable
|
||||
## index built for all the keys inserted which allows reading the data which has
|
||||
## been writen to the batch.
|
||||
##
|
||||
## Optionally set the number of bytes to be reserved for the batch by setting
|
||||
## `reservedBytes`. Set `overwriteKey` to true to overwrite the key in the index
|
||||
## when inserting a duplicate key, in this way an iterator will never show two
|
||||
## entries with the same key.
|
||||
doAssert not db.isClosed()
|
||||
|
||||
createWriteBatch(reservedBytes, overwriteKey, db.dbOpts, cfHandle)
|
||||
|
||||
proc write*(db: RocksDbReadWriteRef, updates: WriteBatchRef): RocksDBResult[void] =
|
||||
## Apply the updates in the `WriteBatchRef` to the database.
|
||||
doAssert not db.isClosed()
|
||||
|
@ -377,6 +407,18 @@ proc write*(db: RocksDbReadWriteRef, updates: WriteBatchRef): RocksDBResult[void
|
|||
|
||||
ok()
|
||||
|
||||
proc write*(db: RocksDbReadWriteRef, updates: WriteBatchWIRef): RocksDBResult[void] =
|
||||
## Apply the updates in the `WriteBatchWIRef` to the database.
|
||||
doAssert not db.isClosed()
|
||||
|
||||
var errors: cstring
|
||||
rocksdb_write_writebatch_wi(
|
||||
db.cPtr, db.writeOpts.cPtr, updates.cPtr, cast[cstringArray](errors.addr)
|
||||
)
|
||||
bailOnErrors(errors)
|
||||
|
||||
ok()
|
||||
|
||||
proc ingestExternalFile*(
|
||||
db: RocksDbReadWriteRef, filePath: string, cfHandle = db.defaultCfHandle
|
||||
): RocksDBResult[void] =
|
||||
|
|
|
@ -46,7 +46,7 @@ proc seekToKey*(iter: RocksIteratorRef, key: openArray[byte]) =
|
|||
## invalid.
|
||||
##
|
||||
doAssert not iter.isClosed()
|
||||
rocksdb_iter_seek(iter.cPtr, cast[cstring](unsafeAddr key[0]), csize_t(key.len))
|
||||
rocksdb_iter_seek(iter.cPtr, cast[cstring](key.unsafeAddrOrNil()), csize_t(key.len))
|
||||
|
||||
proc seekToFirst*(iter: RocksIteratorRef) =
|
||||
## Seeks to the first entry in the column family.
|
||||
|
|
|
@ -61,9 +61,9 @@ proc put*(
|
|||
var errors: cstring
|
||||
rocksdb_sstfilewriter_put(
|
||||
writer.cPtr,
|
||||
cast[cstring](unsafeAddr key[0]),
|
||||
cast[cstring](key.unsafeAddrOrNil()),
|
||||
csize_t(key.len),
|
||||
cast[cstring](unsafeAddr val[0]),
|
||||
cast[cstring](val.unsafeAddrOrNil()),
|
||||
csize_t(val.len),
|
||||
cast[cstringArray](errors.addr),
|
||||
)
|
||||
|
@ -77,7 +77,7 @@ proc delete*(writer: SstFileWriterRef, key: openArray[byte]): RocksDBResult[void
|
|||
var errors: cstring
|
||||
rocksdb_sstfilewriter_delete(
|
||||
writer.cPtr,
|
||||
cast[cstring](unsafeAddr key[0]),
|
||||
cast[cstring](key.unsafeAddrOrNil()),
|
||||
csize_t(key.len),
|
||||
cast[cstringArray](errors.addr),
|
||||
)
|
||||
|
|
|
@ -114,13 +114,11 @@ proc beginTransaction*(
|
|||
## Begin a new transaction against the database. The transaction will default
|
||||
## to using the specified column family. If no column family is specified
|
||||
## then the default column family will be used.
|
||||
##
|
||||
##
|
||||
doAssert not db.isClosed()
|
||||
|
||||
let txPtr = rocksdb_transaction_begin(db.cPtr, writeOpts.cPtr, txOpts.cPtr, nil)
|
||||
|
||||
newTransaction(txPtr, readOpts, writeOpts, txOpts, cfHandle)
|
||||
newTransaction(txPtr, readOpts, writeOpts, txOpts, nil, cfHandle)
|
||||
|
||||
proc close*(db: TransactionDbRef) =
|
||||
## Close the `TransactionDbRef`.
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
# Nim-RocksDB
|
||||
# Copyright 2024 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.
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import ../lib/librocksdb
|
||||
|
||||
type
|
||||
OptimisticTxOptionsPtr* = ptr rocksdb_optimistictransaction_options_t
|
||||
|
||||
OptimisticTxOptionsRef* = ref object
|
||||
cPtr: OptimisticTxOptionsPtr
|
||||
autoClose*: bool # if true then close will be called when the transaction is closed
|
||||
|
||||
proc createOptimisticTxOptions*(autoClose = false): OptimisticTxOptionsRef =
|
||||
OptimisticTxOptionsRef(
|
||||
cPtr: rocksdb_optimistictransaction_options_create(), autoClose: autoClose
|
||||
)
|
||||
|
||||
proc isClosed*(txOpts: OptimisticTxOptionsRef): bool {.inline.} =
|
||||
txOpts.cPtr.isNil()
|
||||
|
||||
proc cPtr*(txOpts: OptimisticTxOptionsRef): OptimisticTxOptionsPtr =
|
||||
doAssert not txOpts.isClosed()
|
||||
txOpts.cPtr
|
||||
|
||||
template setOpt(nname, ntyp, ctyp: untyped) =
|
||||
proc `nname=`*(txOpts: OptimisticTxOptionsRef, value: ntyp) =
|
||||
doAssert not txOpts.isClosed()
|
||||
`rocksdb_optimistictransaction_options_set nname`(txOpts.cPtr, value.ctyp)
|
||||
|
||||
setOpt setSnapshot, bool, uint8
|
||||
|
||||
proc defaultOptimisticTxOptions*(autoClose = false): OptimisticTxOptionsRef {.inline.} =
|
||||
let txOpts = createOptimisticTxOptions(autoClose)
|
||||
|
||||
# TODO: set prefered defaults
|
||||
txOpts
|
||||
|
||||
proc close*(txOpts: OptimisticTxOptionsRef) =
|
||||
if not txOpts.isClosed():
|
||||
rocksdb_optimistictransaction_options_destroy(txOpts.cPtr)
|
||||
txOpts.cPtr = nil
|
|
@ -24,7 +24,7 @@ import
|
|||
../options/[readopts, writeopts],
|
||||
../internal/[cftable, utils],
|
||||
../rocksresult,
|
||||
./txopts
|
||||
./[txopts, otxopts]
|
||||
|
||||
export rocksresult
|
||||
|
||||
|
@ -36,6 +36,7 @@ type
|
|||
readOpts: ReadOptionsRef
|
||||
writeOpts: WriteOptionsRef
|
||||
txOpts: TransactionOptionsRef
|
||||
otxOpts: OptimisticTxOptionsRef
|
||||
defaultCfHandle: ColFamilyHandleRef
|
||||
|
||||
proc newTransaction*(
|
||||
|
@ -43,6 +44,7 @@ proc newTransaction*(
|
|||
readOpts: ReadOptionsRef,
|
||||
writeOpts: WriteOptionsRef,
|
||||
txOpts: TransactionOptionsRef,
|
||||
otxOpts: OptimisticTxOptionsRef,
|
||||
defaultCfHandle: ColFamilyHandleRef,
|
||||
): TransactionRef =
|
||||
TransactionRef(
|
||||
|
@ -50,6 +52,7 @@ proc newTransaction*(
|
|||
readOpts: readOpts,
|
||||
writeOpts: writeOpts,
|
||||
txOpts: txOpts,
|
||||
otxOpts: otxOpts,
|
||||
defaultCfHandle: defaultCfHandle,
|
||||
)
|
||||
|
||||
|
@ -66,9 +69,6 @@ proc get*(
|
|||
## Get the value for a given key from the transaction using the provided
|
||||
## `onData` callback.
|
||||
|
||||
if key.len() == 0:
|
||||
return err("rocksdb: key is empty")
|
||||
|
||||
var
|
||||
len: csize_t
|
||||
errors: cstring
|
||||
|
@ -76,7 +76,7 @@ proc get*(
|
|||
tx.cPtr,
|
||||
tx.readOpts.cPtr,
|
||||
cfHandle.cPtr,
|
||||
cast[cstring](unsafeAddr key[0]),
|
||||
cast[cstring](key.unsafeAddrOrNil()),
|
||||
csize_t(key.len),
|
||||
len.addr,
|
||||
cast[cstringArray](errors.addr),
|
||||
|
@ -111,20 +111,13 @@ proc put*(
|
|||
): RocksDBResult[void] =
|
||||
## Put the value for the given key into the transaction.
|
||||
|
||||
if key.len() == 0:
|
||||
return err("rocksdb: key is empty")
|
||||
|
||||
var errors: cstring
|
||||
rocksdb_transaction_put_cf(
|
||||
tx.cPtr,
|
||||
cfHandle.cPtr,
|
||||
cast[cstring](unsafeAddr key[0]),
|
||||
cast[cstring](key.unsafeAddrOrNil()),
|
||||
csize_t(key.len),
|
||||
cast[cstring](if val.len > 0:
|
||||
unsafeAddr val[0]
|
||||
else:
|
||||
nil
|
||||
),
|
||||
cast[cstring](val.unsafeAddrOrNil()),
|
||||
csize_t(val.len),
|
||||
cast[cstringArray](errors.addr),
|
||||
)
|
||||
|
@ -137,14 +130,11 @@ proc delete*(
|
|||
): RocksDBResult[void] =
|
||||
## Delete the value for the given key from the transaction.
|
||||
|
||||
if key.len() == 0:
|
||||
return err("rocksdb: key is empty")
|
||||
|
||||
var errors: cstring
|
||||
rocksdb_transaction_delete_cf(
|
||||
tx.cPtr,
|
||||
cfHandle.cPtr,
|
||||
cast[cstring](unsafeAddr key[0]),
|
||||
cast[cstring](key.unsafeAddrOrNil()),
|
||||
csize_t(key.len),
|
||||
cast[cstringArray](errors.addr),
|
||||
)
|
||||
|
@ -182,3 +172,4 @@ proc close*(tx: TransactionRef) =
|
|||
autoCloseNonNil(tx.readOpts)
|
||||
autoCloseNonNil(tx.writeOpts)
|
||||
autoCloseNonNil(tx.txOpts)
|
||||
autoCloseNonNil(tx.otxOpts)
|
||||
|
|
|
@ -8,10 +8,12 @@
|
|||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
## A `WriteBatchRef` holds a collection of updates to apply atomically to the database.
|
||||
## It depends on resources from an instance of `RocksDbRef' and therefore should be used
|
||||
## and closed before the `RocksDbRef` is closed.
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import ./lib/librocksdb, ./internal/cftable, ./rocksresult
|
||||
import ./lib/librocksdb, ./internal/[cftable, utils], ./rocksresult
|
||||
|
||||
export rocksresult
|
||||
|
||||
|
@ -49,19 +51,12 @@ proc put*(
|
|||
): RocksDBResult[void] =
|
||||
## Add a put operation to the write batch.
|
||||
|
||||
if key.len() == 0:
|
||||
return err("rocksdb: key is empty")
|
||||
|
||||
rocksdb_writebatch_put_cf(
|
||||
batch.cPtr,
|
||||
cfHandle.cPtr,
|
||||
cast[cstring](unsafeAddr key[0]),
|
||||
cast[cstring](key.unsafeAddrOrNil()),
|
||||
csize_t(key.len),
|
||||
cast[cstring](if val.len > 0:
|
||||
unsafeAddr val[0]
|
||||
else:
|
||||
nil
|
||||
),
|
||||
cast[cstring](val.unsafeAddrOrNil()),
|
||||
csize_t(val.len),
|
||||
)
|
||||
|
||||
|
@ -72,11 +67,8 @@ proc delete*(
|
|||
): RocksDBResult[void] =
|
||||
## Add a delete operation to the write batch.
|
||||
|
||||
if key.len() == 0:
|
||||
return err("rocksdb: key is empty")
|
||||
|
||||
rocksdb_writebatch_delete_cf(
|
||||
batch.cPtr, cfHandle.cPtr, cast[cstring](unsafeAddr key[0]), csize_t(key.len)
|
||||
batch.cPtr, cfHandle.cPtr, cast[cstring](key.unsafeAddrOrNil()), csize_t(key.len)
|
||||
)
|
||||
|
||||
ok()
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
# Nim-RocksDB
|
||||
# Copyright 2024 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.
|
||||
|
||||
## A `WriteBatchWIRef` holds a collection of updates to apply atomically to the database.
|
||||
## It depends on resources from an instance of `RocksDbRef' and therefore should be used
|
||||
## and closed before the `RocksDbRef` is closed.
|
||||
##
|
||||
## `WriteBatchWIRef` is similar to `WriteBatchRef` but with a binary searchable index
|
||||
## built for all the keys inserted which allows reading the data which has been writen
|
||||
## to the batch.
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import ./lib/librocksdb, ./internal/[cftable, utils], ./options/dbopts, ./rocksresult
|
||||
|
||||
export rocksresult
|
||||
|
||||
type
|
||||
WriteBatchWIPtr* = ptr rocksdb_writebatch_wi_t
|
||||
|
||||
WriteBatchWIRef* = ref object
|
||||
cPtr: WriteBatchWIPtr
|
||||
dbOpts: DbOptionsRef
|
||||
defaultCfHandle: ColFamilyHandleRef
|
||||
|
||||
proc createWriteBatch*(
|
||||
reservedBytes: int,
|
||||
overwriteKey: bool,
|
||||
dbOpts: DbOptionsRef,
|
||||
defaultCfHandle: ColFamilyHandleRef,
|
||||
): WriteBatchWIRef =
|
||||
WriteBatchWIRef(
|
||||
cPtr: rocksdb_writebatch_wi_create(reservedBytes.csize_t, overwriteKey.uint8),
|
||||
dbOpts: dbOpts,
|
||||
defaultCfHandle: defaultCfHandle,
|
||||
)
|
||||
|
||||
proc isClosed*(batch: WriteBatchWIRef): bool {.inline.} =
|
||||
## Returns `true` if the `WriteBatchWIRef` has been closed and `false` otherwise.
|
||||
batch.cPtr.isNil()
|
||||
|
||||
proc cPtr*(batch: WriteBatchWIRef): WriteBatchWIPtr =
|
||||
## Get the underlying database pointer.
|
||||
doAssert not batch.isClosed()
|
||||
batch.cPtr
|
||||
|
||||
proc clear*(batch: WriteBatchWIRef) =
|
||||
## Clears the write batch.
|
||||
doAssert not batch.isClosed()
|
||||
rocksdb_writebatch_wi_clear(batch.cPtr)
|
||||
|
||||
proc count*(batch: WriteBatchWIRef): int =
|
||||
## Get the number of updates in the write batch.
|
||||
doAssert not batch.isClosed()
|
||||
rocksdb_writebatch_wi_count(batch.cPtr).int
|
||||
|
||||
proc put*(
|
||||
batch: WriteBatchWIRef, key, val: openArray[byte], cfHandle = batch.defaultCfHandle
|
||||
): RocksDBResult[void] =
|
||||
## Add a put operation to the write batch.
|
||||
|
||||
rocksdb_writebatch_wi_put_cf(
|
||||
batch.cPtr,
|
||||
cfHandle.cPtr,
|
||||
cast[cstring](key.unsafeAddrOrNil()),
|
||||
csize_t(key.len),
|
||||
cast[cstring](val.unsafeAddrOrNil()),
|
||||
csize_t(val.len),
|
||||
)
|
||||
|
||||
ok()
|
||||
|
||||
proc delete*(
|
||||
batch: WriteBatchWIRef, key: openArray[byte], cfHandle = batch.defaultCfHandle
|
||||
): RocksDBResult[void] =
|
||||
## Add a delete operation to the write batch.
|
||||
|
||||
rocksdb_writebatch_wi_delete_cf(
|
||||
batch.cPtr, cfHandle.cPtr, cast[cstring](key.unsafeAddrOrNil()), csize_t(key.len)
|
||||
)
|
||||
|
||||
ok()
|
||||
|
||||
proc get*(
|
||||
batch: WriteBatchWIRef,
|
||||
key: openArray[byte],
|
||||
onData: DataProc,
|
||||
cfHandle = batch.defaultCfHandle,
|
||||
): RocksDBResult[bool] =
|
||||
## Get the value for a given key from the batch using the provided
|
||||
## `onData` callback.
|
||||
|
||||
var
|
||||
len: csize_t
|
||||
errors: cstring
|
||||
let data = rocksdb_writebatch_wi_get_from_batch_cf(
|
||||
batch.cPtr,
|
||||
batch.dbOpts.cPtr,
|
||||
cfHandle.cPtr,
|
||||
cast[cstring](key.unsafeAddrOrNil()),
|
||||
csize_t(key.len),
|
||||
len.addr,
|
||||
cast[cstringArray](errors.addr),
|
||||
)
|
||||
bailOnErrors(errors)
|
||||
|
||||
if data.isNil():
|
||||
doAssert len == 0
|
||||
ok(false)
|
||||
else:
|
||||
onData(toOpenArrayByte(data, 0, len.int - 1))
|
||||
rocksdb_free(data)
|
||||
ok(true)
|
||||
|
||||
proc get*(
|
||||
batch: WriteBatchWIRef, key: openArray[byte], cfHandle = batch.defaultCfHandle
|
||||
): RocksDBResult[seq[byte]] =
|
||||
## Get the value for a given key from the batch.
|
||||
|
||||
var dataRes: RocksDBResult[seq[byte]]
|
||||
proc onData(data: openArray[byte]) =
|
||||
dataRes.ok(@data)
|
||||
|
||||
let res = batch.get(key, onData, cfHandle)
|
||||
if res.isOk():
|
||||
return dataRes
|
||||
|
||||
dataRes.err(res.error())
|
||||
|
||||
proc close*(batch: WriteBatchWIRef) =
|
||||
## Close the `WriteBatchWIRef`.
|
||||
if not batch.isClosed():
|
||||
rocksdb_writebatch_wi_destroy(batch.cPtr)
|
||||
batch.cPtr = nil
|
|
@ -18,12 +18,15 @@ import
|
|||
./options/test_readopts,
|
||||
./options/test_tableopts,
|
||||
./options/test_writeopts,
|
||||
./transactions/test_otxopts,
|
||||
./transactions/test_txdbopts,
|
||||
./transactions/test_txopts,
|
||||
./test_backup,
|
||||
./test_columnfamily,
|
||||
./test_optimistictxdb,
|
||||
./test_rocksdb,
|
||||
./test_rocksiterator,
|
||||
./test_sstfilewriter,
|
||||
./test_transactiondb,
|
||||
./test_writebatch
|
||||
./test_writebatch,
|
||||
./test_writebatchwi
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
{.used.}
|
||||
|
||||
import std/sequtils, ../rocksdb/backup, ../rocksdb/rocksdb, ../rocksdb/transactiondb
|
||||
import std/sequtils, ../rocksdb/[backup, rocksdb, transactiondb, optimistictxdb]
|
||||
|
||||
proc initReadWriteDb*(
|
||||
path: string, columnFamilyNames: openArray[string] = @[]
|
||||
|
@ -57,3 +57,17 @@ proc initTransactionDb*(
|
|||
echo res.error()
|
||||
doAssert res.isOk()
|
||||
res.value()
|
||||
|
||||
proc initOptimisticTxDb*(
|
||||
path: string, columnFamilyNames: openArray[string] = @[]
|
||||
): OptimisticTxDbRef =
|
||||
let res = openOptimisticTxDb(
|
||||
path,
|
||||
columnFamilies = columnFamilyNames.mapIt(
|
||||
initColFamilyDescriptor(it, defaultColFamilyOptions(autoClose = true))
|
||||
),
|
||||
)
|
||||
if res.isErr():
|
||||
echo res.error()
|
||||
doAssert res.isOk()
|
||||
res.value()
|
||||
|
|
|
@ -0,0 +1,291 @@
|
|||
# Nim-RocksDB
|
||||
# Copyright 2024 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.
|
||||
|
||||
{.used.}
|
||||
|
||||
import std/os, tempfile, unittest2, ../rocksdb/optimistictxdb, ./test_helper
|
||||
|
||||
suite "OptimisticTxDbRef Tests":
|
||||
const
|
||||
CF_DEFAULT = "default"
|
||||
CF_OTHER = "other"
|
||||
|
||||
let
|
||||
key1 = @[byte(1)]
|
||||
val1 = @[byte(1)]
|
||||
key2 = @[byte(2)]
|
||||
val2 = @[byte(2)]
|
||||
key3 = @[byte(3)]
|
||||
val3 = @[byte(3)]
|
||||
|
||||
setup:
|
||||
let
|
||||
dbPath = mkdtemp() / "data"
|
||||
db = initOptimisticTxDb(dbPath, columnFamilyNames = @[CF_OTHER])
|
||||
defaultCfHandle = db.getColFamilyHandle(CF_DEFAULT).get()
|
||||
otherCfHandle = db.getColFamilyHandle(CF_OTHER).get()
|
||||
|
||||
teardown:
|
||||
db.close()
|
||||
removeDir($dbPath)
|
||||
|
||||
# test multiple transactions
|
||||
test "Test rollback using default column family":
|
||||
var tx = db.beginTransaction()
|
||||
defer:
|
||||
tx.close()
|
||||
check not tx.isClosed()
|
||||
|
||||
check:
|
||||
tx.put(key1, val1).isOk()
|
||||
tx.put(key2, val2).isOk()
|
||||
tx.put(key3, val3).isOk()
|
||||
|
||||
tx.delete(key2).isOk()
|
||||
not tx.isClosed()
|
||||
|
||||
check:
|
||||
tx.get(key1).get() == val1
|
||||
tx.get(key2).error() == ""
|
||||
tx.get(key3).get() == val3
|
||||
|
||||
let res = tx.rollback()
|
||||
check:
|
||||
res.isOk()
|
||||
tx.get(key1).error() == ""
|
||||
tx.get(key2).error() == ""
|
||||
tx.get(key3).error() == ""
|
||||
|
||||
test "Test commit using default column family":
|
||||
var tx = db.beginTransaction()
|
||||
defer:
|
||||
tx.close()
|
||||
check not tx.isClosed()
|
||||
|
||||
check:
|
||||
tx.put(key1, val1).isOk()
|
||||
tx.put(key2, val2).isOk()
|
||||
tx.put(key3, val3).isOk()
|
||||
|
||||
tx.delete(key2).isOk()
|
||||
not tx.isClosed()
|
||||
|
||||
check:
|
||||
tx.get(key1).get() == val1
|
||||
tx.get(key2).error() == ""
|
||||
tx.get(key3).get() == val3
|
||||
|
||||
let res = tx.commit()
|
||||
check:
|
||||
res.isOk()
|
||||
tx.get(key1).get() == val1
|
||||
tx.get(key2).error() == ""
|
||||
tx.get(key3).get() == val3
|
||||
|
||||
test "Test setting column family in beginTransaction":
|
||||
var tx = db.beginTransaction(cfHandle = otherCfHandle)
|
||||
defer:
|
||||
tx.close()
|
||||
check not tx.isClosed()
|
||||
|
||||
check:
|
||||
tx.put(key1, val1).isOk()
|
||||
tx.put(key2, val2).isOk()
|
||||
tx.put(key3, val3).isOk()
|
||||
|
||||
tx.delete(key2).isOk()
|
||||
not tx.isClosed()
|
||||
|
||||
check:
|
||||
tx.get(key1, defaultCfHandle).error() == ""
|
||||
tx.get(key2, defaultCfHandle).error() == ""
|
||||
tx.get(key3, defaultCfHandle).error() == ""
|
||||
tx.get(key1, otherCfHandle).get() == val1
|
||||
tx.get(key2, otherCfHandle).error() == ""
|
||||
tx.get(key3, otherCfHandle).get() == val3
|
||||
|
||||
test "Test rollback and commit with multiple transactions":
|
||||
var tx1 = db.beginTransaction(cfHandle = defaultCfHandle)
|
||||
defer:
|
||||
tx1.close()
|
||||
check not tx1.isClosed()
|
||||
var tx2 = db.beginTransaction(cfHandle = otherCfHandle)
|
||||
defer:
|
||||
tx2.close()
|
||||
check not tx2.isClosed()
|
||||
|
||||
check:
|
||||
tx1.put(key1, val1).isOk()
|
||||
tx1.put(key2, val2).isOk()
|
||||
tx1.put(key3, val3).isOk()
|
||||
tx1.delete(key2).isOk()
|
||||
not tx1.isClosed()
|
||||
tx2.put(key1, val1).isOk()
|
||||
tx2.put(key2, val2).isOk()
|
||||
tx2.put(key3, val3).isOk()
|
||||
tx2.delete(key2).isOk()
|
||||
not tx2.isClosed()
|
||||
|
||||
check:
|
||||
tx1.get(key1, defaultCfHandle).get() == val1
|
||||
tx1.get(key2, defaultCfHandle).error() == ""
|
||||
tx1.get(key3, defaultCfHandle).get() == val3
|
||||
tx1.get(key1, otherCfHandle).error() == ""
|
||||
tx1.get(key2, otherCfHandle).error() == ""
|
||||
tx1.get(key3, otherCfHandle).error() == ""
|
||||
|
||||
tx2.get(key1, defaultCfHandle).error() == ""
|
||||
tx2.get(key2, defaultCfHandle).error() == ""
|
||||
tx2.get(key3, defaultCfHandle).error() == ""
|
||||
tx2.get(key1, otherCfHandle).get() == val1
|
||||
tx2.get(key2, otherCfHandle).error() == ""
|
||||
tx2.get(key3, otherCfHandle).get() == val3
|
||||
|
||||
block:
|
||||
let res = tx1.rollback()
|
||||
check:
|
||||
res.isOk()
|
||||
tx1.get(key1, defaultCfHandle).error() == ""
|
||||
tx1.get(key2, defaultCfHandle).error() == ""
|
||||
tx1.get(key3, defaultCfHandle).error() == ""
|
||||
tx1.get(key1, otherCfHandle).error() == ""
|
||||
tx1.get(key2, otherCfHandle).error() == ""
|
||||
tx1.get(key3, otherCfHandle).error() == ""
|
||||
|
||||
block:
|
||||
let res = tx2.commit()
|
||||
check:
|
||||
res.isOk()
|
||||
tx2.get(key1, defaultCfHandle).error() == ""
|
||||
tx2.get(key2, defaultCfHandle).error() == ""
|
||||
tx2.get(key3, defaultCfHandle).error() == ""
|
||||
tx2.get(key1, otherCfHandle).get() == val1
|
||||
tx2.get(key2, otherCfHandle).error() == ""
|
||||
tx2.get(key3, otherCfHandle).get() == val3
|
||||
|
||||
test "Test close":
|
||||
var tx = db.beginTransaction()
|
||||
|
||||
check not tx.isClosed()
|
||||
tx.close()
|
||||
check tx.isClosed()
|
||||
tx.close()
|
||||
check tx.isClosed()
|
||||
|
||||
check not db.isClosed()
|
||||
db.close()
|
||||
check db.isClosed()
|
||||
db.close()
|
||||
check db.isClosed()
|
||||
|
||||
test "Test close multiple tx":
|
||||
var tx1 = db.beginTransaction()
|
||||
var tx2 = db.beginTransaction()
|
||||
|
||||
check not db.isClosed()
|
||||
check not tx1.isClosed()
|
||||
tx1.close()
|
||||
check tx1.isClosed()
|
||||
tx1.close()
|
||||
check tx1.isClosed()
|
||||
|
||||
check not db.isClosed()
|
||||
check not tx2.isClosed()
|
||||
tx2.close()
|
||||
check tx2.isClosed()
|
||||
tx2.close()
|
||||
check tx2.isClosed()
|
||||
|
||||
test "Test auto close enabled":
|
||||
let
|
||||
dbPath = mkdtemp() / "autoclose-enabled"
|
||||
dbOpts = defaultDbOptions(autoClose = true)
|
||||
columnFamilies =
|
||||
@[
|
||||
initColFamilyDescriptor(CF_DEFAULT, defaultColFamilyOptions(autoClose = true))
|
||||
]
|
||||
db = openOptimisticTxDb(dbPath, dbOpts, columnFamilies).get()
|
||||
|
||||
check:
|
||||
dbOpts.isClosed() == false
|
||||
columnFamilies[0].isClosed() == false
|
||||
db.isClosed() == false
|
||||
|
||||
db.close()
|
||||
|
||||
check:
|
||||
dbOpts.isClosed() == true
|
||||
columnFamilies[0].isClosed() == true
|
||||
db.isClosed() == true
|
||||
|
||||
test "Test auto close enabled":
|
||||
let
|
||||
dbPath = mkdtemp() / "autoclose-disabled"
|
||||
dbOpts = defaultDbOptions(autoClose = false)
|
||||
columnFamilies =
|
||||
@[
|
||||
initColFamilyDescriptor(
|
||||
CF_DEFAULT, defaultColFamilyOptions(autoClose = false)
|
||||
)
|
||||
]
|
||||
db = openOptimisticTxDb(dbPath, dbOpts, columnFamilies).get()
|
||||
|
||||
check:
|
||||
dbOpts.isClosed() == false
|
||||
columnFamilies[0].isClosed() == false
|
||||
db.isClosed() == false
|
||||
|
||||
db.close()
|
||||
|
||||
check:
|
||||
dbOpts.isClosed() == false
|
||||
columnFamilies[0].isClosed() == false
|
||||
db.isClosed() == true
|
||||
|
||||
test "Test auto close tx enabled":
|
||||
let
|
||||
readOpts = defaultReadOptions(autoClose = true)
|
||||
writeOpts = defaultWriteOptions(autoClose = true)
|
||||
otxOpts = defaultOptimisticTxOptions(autoClose = true)
|
||||
tx = db.beginTransaction(readOpts, writeOpts, otxOpts)
|
||||
|
||||
check:
|
||||
readOpts.isClosed() == false
|
||||
writeOpts.isClosed() == false
|
||||
otxOpts.isClosed() == false
|
||||
tx.isClosed() == false
|
||||
|
||||
tx.close()
|
||||
|
||||
check:
|
||||
readOpts.isClosed() == true
|
||||
writeOpts.isClosed() == true
|
||||
otxOpts.isClosed() == true
|
||||
tx.isClosed() == true
|
||||
|
||||
test "Test auto close tx disabled":
|
||||
let
|
||||
readOpts = defaultReadOptions(autoClose = false)
|
||||
writeOpts = defaultWriteOptions(autoClose = false)
|
||||
otxOpts = defaultOptimisticTxOptions(autoClose = false)
|
||||
tx = db.beginTransaction(readOpts, writeOpts, otxOpts)
|
||||
|
||||
check:
|
||||
readOpts.isClosed() == false
|
||||
writeOpts.isClosed() == false
|
||||
otxOpts.isClosed() == false
|
||||
tx.isClosed() == false
|
||||
|
||||
tx.close()
|
||||
|
||||
check:
|
||||
readOpts.isClosed() == false
|
||||
writeOpts.isClosed() == false
|
||||
otxOpts.isClosed() == false
|
||||
tx.isClosed() == true
|
|
@ -324,6 +324,38 @@ suite "RocksDbRef Tests":
|
|||
v.len() == 0
|
||||
db.get(key5).isErr()
|
||||
|
||||
test "Test keyMayExist":
|
||||
let
|
||||
key1 = @[byte(1)] # exists with non empty value
|
||||
val1 = @[byte(1)]
|
||||
key2 = @[byte(2)] # exists with empty seq value
|
||||
val2: seq[byte] = @[]
|
||||
key3 = @[byte(3)] # exists with empty array value
|
||||
val3: array[0, byte] = []
|
||||
key4 = @[byte(4)] # deleted key
|
||||
key5 = @[byte(5)] # key not created
|
||||
|
||||
check:
|
||||
db.put(key1, val1).isOk()
|
||||
db.put(key2, val2).isOk()
|
||||
db.put(key3, val3).isOk()
|
||||
db.delete(key4).isOk()
|
||||
|
||||
db.keyMayExist(key1).isOk()
|
||||
db.keyMayExist(key2).isOk()
|
||||
db.keyMayExist(key3).isOk()
|
||||
db.keyMayExist(key4).get() == false
|
||||
db.keyMayExist(key5).get() == false
|
||||
|
||||
test "Put, get and delete empty key":
|
||||
let empty: seq[byte] = @[]
|
||||
|
||||
check:
|
||||
db.put(empty, val).isOk()
|
||||
db.get(empty).get() == val
|
||||
db.delete(empty).isOk()
|
||||
db.get(empty).isErr()
|
||||
|
||||
test "List column familes":
|
||||
let cfRes1 = listColumnFamilies(dbPath)
|
||||
check:
|
||||
|
|
|
@ -165,6 +165,20 @@ suite "RocksIteratorRef Tests":
|
|||
iter.key() == key2
|
||||
iter.value() == val2
|
||||
|
||||
test "Seek to empty key":
|
||||
let empty: seq[byte] = @[]
|
||||
check db.put(empty, val1).isOk()
|
||||
|
||||
let iter = db.openIterator().get()
|
||||
defer:
|
||||
iter.close()
|
||||
|
||||
iter.seekToKey(empty)
|
||||
check:
|
||||
iter.isValid()
|
||||
iter.key() == empty
|
||||
iter.value() == val1
|
||||
|
||||
test "Empty column family":
|
||||
let res = db.openIterator(emptyCfHandle)
|
||||
check res.isOk()
|
||||
|
|
|
@ -76,6 +76,19 @@ suite "SstFileWriterRef Tests":
|
|||
db.get(key2, otherCfHandle).get() == val2
|
||||
db.get(key3, otherCfHandle).get() == val3
|
||||
|
||||
test "Put, get and delete empty key":
|
||||
let writer = openSstFileWriter(sstFilePath).get()
|
||||
defer:
|
||||
writer.close()
|
||||
|
||||
let empty: seq[byte] = @[]
|
||||
check:
|
||||
writer.put(empty, val1).isOk()
|
||||
writer.finish().isOk()
|
||||
db.ingestExternalFile(sstFilePath).isOk()
|
||||
db.keyExists(empty).get() == true
|
||||
db.get(empty).get() == val1
|
||||
|
||||
test "Test close":
|
||||
let res = openSstFileWriter(sstFilePath)
|
||||
check res.isOk()
|
||||
|
|
|
@ -169,6 +169,18 @@ suite "TransactionDbRef Tests":
|
|||
tx2.get(key2, otherCfHandle).error() == ""
|
||||
tx2.get(key3, otherCfHandle).get() == val3
|
||||
|
||||
test "Put, get and delete empty key":
|
||||
let tx = db.beginTransaction()
|
||||
defer:
|
||||
tx.close()
|
||||
|
||||
let empty: seq[byte] = @[]
|
||||
check:
|
||||
tx.put(empty, val1).isOk()
|
||||
tx.get(empty).get() == val1
|
||||
tx.delete(empty).isOk()
|
||||
tx.get(empty).isErr()
|
||||
|
||||
test "Test close":
|
||||
var tx = db.beginTransaction()
|
||||
|
||||
|
@ -227,7 +239,7 @@ suite "TransactionDbRef Tests":
|
|||
columnFamilies[0].isClosed() == true
|
||||
db.isClosed() == true
|
||||
|
||||
test "Test auto close enabled":
|
||||
test "Test auto close disabled":
|
||||
let
|
||||
dbPath = mkdtemp() / "autoclose-disabled"
|
||||
dbOpts = defaultDbOptions(autoClose = false)
|
||||
|
|
|
@ -36,7 +36,7 @@ suite "WriteBatchRef Tests":
|
|||
removeDir($dbPath)
|
||||
|
||||
test "Test writing batch to the default column family":
|
||||
var batch = db.openWriteBatch()
|
||||
let batch = db.openWriteBatch()
|
||||
defer:
|
||||
batch.close()
|
||||
check not batch.isClosed()
|
||||
|
@ -65,7 +65,7 @@ suite "WriteBatchRef Tests":
|
|||
not batch.isClosed()
|
||||
|
||||
test "Test writing batch to column family":
|
||||
var batch = db.openWriteBatch()
|
||||
let batch = db.openWriteBatch()
|
||||
defer:
|
||||
batch.close()
|
||||
check not batch.isClosed()
|
||||
|
@ -93,7 +93,7 @@ suite "WriteBatchRef Tests":
|
|||
not batch.isClosed()
|
||||
|
||||
test "Test writing to multiple column families in single batch":
|
||||
var batch = db.openWriteBatch()
|
||||
let batch = db.openWriteBatch()
|
||||
defer:
|
||||
batch.close()
|
||||
check not batch.isClosed()
|
||||
|
@ -123,17 +123,16 @@ suite "WriteBatchRef Tests":
|
|||
not batch.isClosed()
|
||||
|
||||
test "Test writing to multiple column families in multiple batches":
|
||||
var batch1 = db.openWriteBatch()
|
||||
let
|
||||
batch1 = db.openWriteBatch()
|
||||
batch2 = db.openWriteBatch()
|
||||
defer:
|
||||
batch1.close()
|
||||
check not batch1.isClosed()
|
||||
|
||||
var batch2 = db.openWriteBatch()
|
||||
defer:
|
||||
batch2.close()
|
||||
check not batch2.isClosed()
|
||||
|
||||
check:
|
||||
not batch1.isClosed()
|
||||
not batch2.isClosed()
|
||||
batch1.put(key1, val1).isOk()
|
||||
batch1.delete(key2, otherCfHandle).isOk()
|
||||
batch1.put(key3, val3, otherCfHandle).isOk()
|
||||
|
@ -155,8 +154,28 @@ suite "WriteBatchRef Tests":
|
|||
db.keyExists(key2, otherCfHandle).get() == false
|
||||
db.get(key3, otherCfHandle).get() == val3
|
||||
|
||||
# Write batch is unchanged after write
|
||||
batch1.count() == 3
|
||||
batch2.count() == 3
|
||||
not batch1.isClosed()
|
||||
not batch2.isClosed()
|
||||
|
||||
test "Put, get and delete empty key":
|
||||
let batch = db.openWriteBatch()
|
||||
defer:
|
||||
batch.close()
|
||||
|
||||
let empty: seq[byte] = @[]
|
||||
check:
|
||||
batch.put(empty, val1).isOk()
|
||||
db.write(batch).isOk()
|
||||
db.get(empty).get() == val1
|
||||
batch.delete(empty).isOk()
|
||||
db.write(batch).isOk()
|
||||
db.get(empty).isErr()
|
||||
|
||||
test "Test write empty batch":
|
||||
var batch = db.openWriteBatch()
|
||||
let batch = db.openWriteBatch()
|
||||
defer:
|
||||
batch.close()
|
||||
check not batch.isClosed()
|
||||
|
@ -166,9 +185,10 @@ suite "WriteBatchRef Tests":
|
|||
check:
|
||||
res1.isOk()
|
||||
batch.count() == 0
|
||||
not batch.isClosed()
|
||||
|
||||
test "Test close":
|
||||
var batch = db.openWriteBatch()
|
||||
let batch = db.openWriteBatch()
|
||||
|
||||
check not batch.isClosed()
|
||||
batch.close()
|
||||
|
|
|
@ -0,0 +1,235 @@
|
|||
# Nim-RocksDB
|
||||
# Copyright 2024 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.
|
||||
|
||||
{.used.}
|
||||
|
||||
import std/os, tempfile, unittest2, ../rocksdb/[rocksdb, writebatchwi], ./test_helper
|
||||
|
||||
suite "WriteBatchWIRef Tests":
|
||||
const
|
||||
CF_DEFAULT = "default"
|
||||
CF_OTHER = "other"
|
||||
|
||||
let
|
||||
key1 = @[byte(1)]
|
||||
val1 = @[byte(1)]
|
||||
key2 = @[byte(2)]
|
||||
val2 = @[byte(2)]
|
||||
key3 = @[byte(3)]
|
||||
val3 = @[byte(3)]
|
||||
|
||||
setup:
|
||||
let
|
||||
dbPath = mkdtemp() / "data"
|
||||
db = initReadWriteDb(dbPath, columnFamilyNames = @[CF_DEFAULT, CF_OTHER])
|
||||
defaultCfHandle = db.getColFamilyHandle(CF_DEFAULT).get()
|
||||
otherCfHandle = db.getColFamilyHandle(CF_OTHER).get()
|
||||
|
||||
teardown:
|
||||
db.close()
|
||||
removeDir($dbPath)
|
||||
|
||||
test "Test writing batch to the default column family":
|
||||
let batch = db.openWriteBatchWithIndex()
|
||||
defer:
|
||||
batch.close()
|
||||
check not batch.isClosed()
|
||||
|
||||
check:
|
||||
batch.put(key1, val1).isOk()
|
||||
batch.put(key2, val2).isOk()
|
||||
batch.put(key3, val3).isOk()
|
||||
batch.count() == 3
|
||||
|
||||
batch.delete(key2).isOk()
|
||||
batch.count() == 4
|
||||
not batch.isClosed()
|
||||
|
||||
batch.get(key1).get() == val1
|
||||
batch.get(key2).isErr()
|
||||
batch.get(key3).get() == val3
|
||||
|
||||
let res = db.write(batch)
|
||||
check:
|
||||
res.isOk()
|
||||
db.write(batch).isOk() # test that it's idempotent
|
||||
db.get(key1).get() == val1
|
||||
db.keyExists(key2).get() == false
|
||||
db.get(key3).get() == val3
|
||||
|
||||
batch.get(key1).get() == val1
|
||||
batch.get(key2).isErr()
|
||||
batch.get(key3).get() == val3
|
||||
|
||||
batch.clear()
|
||||
check:
|
||||
batch.count() == 0
|
||||
not batch.isClosed()
|
||||
|
||||
test "Test writing batch to column family":
|
||||
let batch = db.openWriteBatchWithIndex()
|
||||
defer:
|
||||
batch.close()
|
||||
check not batch.isClosed()
|
||||
|
||||
check:
|
||||
batch.put(key1, val1, otherCfHandle).isOk()
|
||||
batch.put(key2, val2, otherCfHandle).isOk()
|
||||
batch.put(key3, val3, otherCfHandle).isOk()
|
||||
batch.count() == 3
|
||||
|
||||
batch.delete(key2, otherCfHandle).isOk()
|
||||
batch.count() == 4
|
||||
not batch.isClosed()
|
||||
|
||||
batch.get(key1, otherCfHandle).get() == val1
|
||||
batch.get(key2, otherCfHandle).isErr()
|
||||
batch.get(key3, otherCfHandle).get() == val3
|
||||
|
||||
let res = db.write(batch)
|
||||
check:
|
||||
res.isOk()
|
||||
db.get(key1, otherCfHandle).get() == val1
|
||||
db.keyExists(key2, otherCfHandle).get() == false
|
||||
db.get(key3, otherCfHandle).get() == val3
|
||||
|
||||
batch.get(key1, otherCfHandle).get() == val1
|
||||
batch.get(key2, otherCfHandle).isErr()
|
||||
batch.get(key3, otherCfHandle).get() == val3
|
||||
|
||||
batch.clear()
|
||||
check:
|
||||
batch.count() == 0
|
||||
not batch.isClosed()
|
||||
|
||||
test "Test writing to multiple column families in single batch":
|
||||
let batch = db.openWriteBatchWithIndex()
|
||||
defer:
|
||||
batch.close()
|
||||
check not batch.isClosed()
|
||||
|
||||
check:
|
||||
batch.put(key1, val1, defaultCfHandle).isOk()
|
||||
batch.put(key1, val1, otherCfHandle).isOk()
|
||||
batch.put(key2, val2, otherCfHandle).isOk()
|
||||
batch.put(key3, val3, otherCfHandle).isOk()
|
||||
batch.count() == 4
|
||||
|
||||
batch.delete(key2, otherCfHandle).isOk()
|
||||
batch.count() == 5
|
||||
not batch.isClosed()
|
||||
|
||||
let res = db.write(batch)
|
||||
check:
|
||||
res.isOk()
|
||||
db.get(key1, defaultCfHandle).get() == val1
|
||||
db.get(key1, otherCfHandle).get() == val1
|
||||
db.keyExists(key2, otherCfHandle).get() == false
|
||||
db.get(key3, otherCfHandle).get() == val3
|
||||
|
||||
batch.clear()
|
||||
check:
|
||||
batch.count() == 0
|
||||
not batch.isClosed()
|
||||
|
||||
test "Test writing to multiple column families in multiple batches":
|
||||
let
|
||||
batch1 = db.openWriteBatchWithIndex()
|
||||
batch2 = db.openWriteBatchWithIndex()
|
||||
defer:
|
||||
batch1.close()
|
||||
batch2.close()
|
||||
|
||||
check:
|
||||
not batch1.isClosed()
|
||||
not batch2.isClosed()
|
||||
batch1.put(key1, val1).isOk()
|
||||
batch1.delete(key2, otherCfHandle).isOk()
|
||||
batch1.put(key3, val3, otherCfHandle).isOk()
|
||||
batch2.put(key1, val1, otherCfHandle).isOk()
|
||||
batch2.delete(key1, otherCfHandle).isOk()
|
||||
batch2.put(key3, val3).isOk()
|
||||
batch1.count() == 3
|
||||
batch2.count() == 3
|
||||
|
||||
let res1 = db.write(batch1)
|
||||
let res2 = db.write(batch2)
|
||||
check:
|
||||
res1.isOk()
|
||||
res2.isOk()
|
||||
db.get(key1).get() == val1
|
||||
db.keyExists(key2).get() == false
|
||||
db.get(key3).get() == val3
|
||||
db.keyExists(key1, otherCfHandle).get() == false
|
||||
db.keyExists(key2, otherCfHandle).get() == false
|
||||
db.get(key3, otherCfHandle).get() == val3
|
||||
|
||||
# Write batch is unchanged after write
|
||||
batch1.count() == 3
|
||||
batch2.count() == 3
|
||||
not batch1.isClosed()
|
||||
not batch2.isClosed()
|
||||
|
||||
test "Test write empty batch":
|
||||
let batch = db.openWriteBatchWithIndex()
|
||||
defer:
|
||||
batch.close()
|
||||
check not batch.isClosed()
|
||||
|
||||
check batch.count() == 0
|
||||
let res1 = db.write(batch)
|
||||
check:
|
||||
res1.isOk()
|
||||
batch.count() == 0
|
||||
not batch.isClosed()
|
||||
|
||||
test "Test multiple writes to same key":
|
||||
let
|
||||
batch1 = db.openWriteBatchWithIndex(overwriteKey = false)
|
||||
batch2 = db.openWriteBatchWithIndex(overwriteKey = true)
|
||||
defer:
|
||||
batch1.close()
|
||||
batch2.close()
|
||||
check:
|
||||
not batch1.isClosed()
|
||||
not batch2.isClosed()
|
||||
|
||||
check:
|
||||
batch1.put(key1, val1).isOk()
|
||||
batch1.delete(key1).isOk()
|
||||
batch1.put(key1, val3).isOk()
|
||||
batch1.count() == 3
|
||||
batch1.get(key1).get() == val3
|
||||
|
||||
batch2.put(key1, val3).isOk()
|
||||
batch2.put(key1, val2).isOk()
|
||||
batch2.put(key1, val1).isOk()
|
||||
batch2.count() == 3
|
||||
batch2.get(key1).get() == val1
|
||||
|
||||
test "Put, get and delete empty key":
|
||||
let batch = db.openWriteBatchWithIndex()
|
||||
defer:
|
||||
batch.close()
|
||||
|
||||
let empty: seq[byte] = @[]
|
||||
check:
|
||||
batch.put(empty, val1).isOk()
|
||||
batch.get(empty).get() == val1
|
||||
batch.delete(empty).isOk()
|
||||
batch.get(empty).isErr()
|
||||
|
||||
test "Test close":
|
||||
let batch = db.openWriteBatchWithIndex()
|
||||
|
||||
check not batch.isClosed()
|
||||
batch.close()
|
||||
check batch.isClosed()
|
||||
batch.close()
|
||||
check batch.isClosed()
|
|
@ -0,0 +1,36 @@
|
|||
# Nim-RocksDB
|
||||
# Copyright 2024 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.
|
||||
|
||||
{.used.}
|
||||
|
||||
import unittest2, ../../rocksdb/transactions/otxopts
|
||||
|
||||
suite "OptimisticTxOptionsRef Tests":
|
||||
test "Test createOptimisticTxOptions":
|
||||
let txOpts = createOptimisticTxOptions()
|
||||
|
||||
check not txOpts.cPtr.isNil()
|
||||
|
||||
txOpts.close()
|
||||
|
||||
test "Test defaultTransactionOptions":
|
||||
let txOpts = defaultOptimisticTxOptions()
|
||||
|
||||
check not txOpts.cPtr.isNil()
|
||||
|
||||
txOpts.close()
|
||||
|
||||
test "Test close":
|
||||
let txOpts = defaultOptimisticTxOptions()
|
||||
|
||||
check not txOpts.isClosed()
|
||||
txOpts.close()
|
||||
check txOpts.isClosed()
|
||||
txOpts.close()
|
||||
check txOpts.isClosed()
|
|
@ -13,21 +13,21 @@ import unittest2, ../../rocksdb/transactions/txdbopts
|
|||
|
||||
suite "TransactionDbOptionsRef Tests":
|
||||
test "Test newTransactionDbOptions":
|
||||
var txDbOpts = createTransactionDbOptions()
|
||||
let txDbOpts = createTransactionDbOptions()
|
||||
|
||||
check not txDbOpts.cPtr.isNil()
|
||||
|
||||
txDbOpts.close()
|
||||
|
||||
test "Test defaultTransactionDbOptions":
|
||||
var txDbOpts = defaultTransactionDbOptions()
|
||||
let txDbOpts = defaultTransactionDbOptions()
|
||||
|
||||
check not txDbOpts.cPtr.isNil()
|
||||
|
||||
txDbOpts.close()
|
||||
|
||||
test "Test close":
|
||||
var txDbOpts = defaultTransactionDbOptions()
|
||||
let txDbOpts = defaultTransactionDbOptions()
|
||||
|
||||
check not txDbOpts.isClosed()
|
||||
txDbOpts.close()
|
||||
|
|
|
@ -13,21 +13,21 @@ import unittest2, ../../rocksdb/transactions/txopts
|
|||
|
||||
suite "TransactionOptionsRef Tests":
|
||||
test "Test newTransactionOptions":
|
||||
var txOpts = createTransactionOptions()
|
||||
let txOpts = createTransactionOptions()
|
||||
|
||||
check not txOpts.cPtr.isNil()
|
||||
|
||||
txOpts.close()
|
||||
|
||||
test "Test defaultTransactionOptions":
|
||||
var txOpts = defaultTransactionOptions()
|
||||
let txOpts = defaultTransactionOptions()
|
||||
|
||||
check not txOpts.cPtr.isNil()
|
||||
|
||||
txOpts.close()
|
||||
|
||||
test "Test close":
|
||||
var txOpts = defaultTransactionOptions()
|
||||
let txOpts = defaultTransactionOptions()
|
||||
|
||||
check not txOpts.isClosed()
|
||||
txOpts.close()
|
||||
|
|
Loading…
Reference in New Issue