diff --git a/rocksdb/internal/utils.nim b/rocksdb/internal/utils.nim index e4ba78a..a9581ab 100644 --- a/rocksdb/internal/utils.nim +++ b/rocksdb/internal/utils.nim @@ -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 diff --git a/rocksdb/rocksdb.nim b/rocksdb/rocksdb.nim index 638db93..d6117a5 100644 --- a/rocksdb/rocksdb.nim +++ b/rocksdb/rocksdb.nim @@ -243,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), @@ -285,13 +285,9 @@ proc put*( 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), ) @@ -312,7 +308,7 @@ proc keyMayExist*( db.cPtr, db.readOpts.cPtr, cfHandle.cPtr, - cast[cstring](unsafeAddr key[0]), + cast[cstring](key.unsafeAddrOrNil()), csize_t(key.len), nil, nil, @@ -350,7 +346,7 @@ proc delete*( db.cPtr, db.writeOpts.cPtr, cfHandle.cPtr, - cast[cstring](unsafeAddr key[0]), + cast[cstring](key.unsafeAddrOrNil()), csize_t(key.len), cast[cstringArray](errors.addr), ) diff --git a/rocksdb/rocksiterator.nim b/rocksdb/rocksiterator.nim index 9f0371b..2a2129a 100644 --- a/rocksdb/rocksiterator.nim +++ b/rocksdb/rocksiterator.nim @@ -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. diff --git a/rocksdb/sstfilewriter.nim b/rocksdb/sstfilewriter.nim index 107cd48..7d6c6c5 100644 --- a/rocksdb/sstfilewriter.nim +++ b/rocksdb/sstfilewriter.nim @@ -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), ) diff --git a/rocksdb/transactions/transaction.nim b/rocksdb/transactions/transaction.nim index 65cc4c9..e0db707 100644 --- a/rocksdb/transactions/transaction.nim +++ b/rocksdb/transactions/transaction.nim @@ -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), @@ -115,13 +115,9 @@ proc put*( 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), ) @@ -138,7 +134,7 @@ proc delete*( 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), ) diff --git a/rocksdb/writebatch.nim b/rocksdb/writebatch.nim index c065dbd..c302ebd 100644 --- a/rocksdb/writebatch.nim +++ b/rocksdb/writebatch.nim @@ -13,7 +13,7 @@ {.push raises: [].} -import ./lib/librocksdb, ./internal/cftable, ./rocksresult +import ./lib/librocksdb, ./internal/[cftable, utils], ./rocksresult export rocksresult @@ -54,13 +54,9 @@ proc put*( 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,7 +68,7 @@ proc delete*( ## Add a delete operation to the write batch. 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() diff --git a/rocksdb/writebatchwi.nim b/rocksdb/writebatchwi.nim index e251f0f..e4ec721 100644 --- a/rocksdb/writebatchwi.nim +++ b/rocksdb/writebatchwi.nim @@ -68,13 +68,9 @@ proc put*( rocksdb_writebatch_wi_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), ) @@ -86,7 +82,7 @@ proc delete*( ## Add a delete operation to the write batch. rocksdb_writebatch_wi_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() @@ -107,7 +103,7 @@ proc get*( batch.cPtr, batch.dbOpts.cPtr, cfHandle.cPtr, - cast[cstring](unsafeAddr key[0]), + cast[cstring](key.unsafeAddrOrNil()), csize_t(key.len), len.addr, cast[cstringArray](errors.addr), diff --git a/tests/test_rocksdb.nim b/tests/test_rocksdb.nim index 669a29d..9b6094f 100644 --- a/tests/test_rocksdb.nim +++ b/tests/test_rocksdb.nim @@ -347,6 +347,15 @@ suite "RocksDbRef Tests": 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: diff --git a/tests/test_rocksiterator.nim b/tests/test_rocksiterator.nim index b8c2fae..cfacf6c 100644 --- a/tests/test_rocksiterator.nim +++ b/tests/test_rocksiterator.nim @@ -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() diff --git a/tests/test_sstfilewriter.nim b/tests/test_sstfilewriter.nim index 187affe..3cd33a0 100644 --- a/tests/test_sstfilewriter.nim +++ b/tests/test_sstfilewriter.nim @@ -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() diff --git a/tests/test_transactiondb.nim b/tests/test_transactiondb.nim index 2ae0132..76d3d14 100644 --- a/tests/test_transactiondb.nim +++ b/tests/test_transactiondb.nim @@ -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) diff --git a/tests/test_writebatch.nim b/tests/test_writebatch.nim index d0150bb..fc55962 100644 --- a/tests/test_writebatch.nim +++ b/tests/test_writebatch.nim @@ -160,6 +160,20 @@ suite "WriteBatchRef Tests": 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": let batch = db.openWriteBatch() defer: diff --git a/tests/test_writebatchwi.nim b/tests/test_writebatchwi.nim index 09afd52..457d06d 100644 --- a/tests/test_writebatchwi.nim +++ b/tests/test_writebatchwi.nim @@ -213,6 +213,18 @@ suite "WriteBatchWIRef Tests": 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()