Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

64bit key support with bigints #145

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/cursor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ NAN_METHOD(CursorWrap::ctor) {
if (dw->keyType == NodeLmdbKeyType::Uint32Key && keyType != NodeLmdbKeyType::Uint32Key) {
return Nan::ThrowError("You specified keyIsUint32 on the Dbi, so you can't use other key types with it.");
}
#if NODE_LMDB_HAS_BIGINT
else if (dw->keyType == NodeLmdbKeyType::Uint64Key && keyType != NodeLmdbKeyType::Uint64Key) {
return Nan::ThrowError("You specified keyIsUint64 on the Dbi, so you can't use other key types with it.");
}
#endif

// Open the cursor
MDB_cursor *cursor;
Expand Down
6 changes: 6 additions & 0 deletions src/dbi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,15 @@ NAN_METHOD(DbiWrap::ctor) {
return;
}

#if NODE_LMDB_HAS_BIGINT
if (keyType == NodeLmdbKeyType::Uint32Key || keyType == NodeLmdbKeyType::Uint64Key) {
flags |= MDB_INTEGERKEY;
}
#else
if (keyType == NodeLmdbKeyType::Uint32Key) {
flags |= MDB_INTEGERKEY;
}
#endif

// Set flags for txn used to open database
Local<Value> create = options->Get(Nan::New<String>("create").ToLocalChecked());
Expand Down
94 changes: 82 additions & 12 deletions src/misc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,30 @@
#include <stdio.h>

static thread_local uint32_t currentUint32Key = 0;
#if NODE_LMDB_HAS_BIGINT
static thread_local uint64_t currentUint64Key = 0;

inline bool get_uint64_from_bigint(const Local<Value> &val, uint64_t *ret = nullptr) {
bool lossless = true;

Local<Context> context = v8::Isolate::GetCurrent()->GetCurrentContext();
Local<BigInt> bigint_val;
if (!val->ToBigInt(context).ToLocal(&bigint_val)) {
return false;
}
if(ret) {
*ret = bigint_val->Uint64Value(&lossless);
} else {
bigint_val->Uint64Value(&lossless);
}
return lossless;
}

inline Local<Value> uint64_blob_to_bigint(void* data) {
//TODO use nan when it supports bigint
return v8::BigInt::NewFromUnsigned(v8::Isolate::GetCurrent(), *((uint64_t*)data));
}
#endif

void setupExportMisc(Handle<Object> exports) {
Local<Object> versionObj = Nan::New<Object>();
Expand Down Expand Up @@ -62,32 +86,49 @@ NodeLmdbKeyType keyTypeFromOptions(const Local<Value> &val, NodeLmdbKeyType defa
int keyIsUint32 = 0;
int keyIsBuffer = 0;
int keyIsString = 0;
#if NODE_LMDB_HAS_BIGINT
int keyIsUint64 = 0;
#endif

setFlagFromValue(&keyIsUint32, 1, "keyIsUint32", false, obj);
setFlagFromValue(&keyIsString, 1, "keyIsString", false, obj);
setFlagFromValue(&keyIsBuffer, 1, "keyIsBuffer", false, obj);

const char *keySpecificationErrorText = "You can't specify multiple key types at once. Either set keyIsUint32, or keyIsBuffer or keyIsString (default).";
#if NODE_LMDB_HAS_BIGINT
setFlagFromValue(&keyIsUint64, 1, "keyIsUint64", false, obj);
#endif

int key_spec_count = keyIsUint32 + keyIsBuffer + keyIsString;
#if NODE_LMDB_HAS_BIGINT
key_spec_count += keyIsUint64;
#endif

if (key_spec_count == 0) {
return keyType;
} else if (key_spec_count != 1) {
#if NODE_LMDB_HAS_BIGINT
const char *keySpecificationErrorText = "You can't specify multiple key types at once. Either set keyIsUint32, or keyIsUint64, or keyIsBuffer, or keyIsString (default).";
#else
const char *keySpecificationErrorText = "You can't specify multiple key types at once. Either set keyIsUint32, or keyIsBuffer, or keyIsString (default).";
#endif
Nan::ThrowError(keySpecificationErrorText);
return NodeLmdbKeyType::InvalidKey;
}


if (keyIsUint32) {
keyType = NodeLmdbKeyType::Uint32Key;

if (keyIsBuffer || keyIsString) {
Nan::ThrowError(keySpecificationErrorText);
return NodeLmdbKeyType::InvalidKey;
}
}
else if (keyIsBuffer) {
keyType = NodeLmdbKeyType::BinaryKey;

if (keyIsUint32 || keyIsString) {
Nan::ThrowError(keySpecificationErrorText);
return NodeLmdbKeyType::InvalidKey;
}
}
else if (keyIsString) {
keyType = NodeLmdbKeyType::StringKey;
}
#if NODE_LMDB_HAS_BIGINT
else if (keyIsUint64) {
keyType = NodeLmdbKeyType::Uint64Key;
}
#endif

return keyType;
}
Expand All @@ -99,6 +140,11 @@ NodeLmdbKeyType inferKeyType(const Local<Value> &val) {
if (val->IsUint32()) {
return NodeLmdbKeyType::Uint32Key;
}
#if NODE_LMDB_HAS_BIGINT
if (val->IsBigInt() && get_uint64_from_bigint(val)) {
return NodeLmdbKeyType::Uint64Key;
}
#endif
if (node::Buffer::HasInstance(val)) {
return NodeLmdbKeyType::BinaryKey;
}
Expand All @@ -122,6 +168,12 @@ NodeLmdbKeyType inferAndValidateKeyType(const Local<Value> &key, const Local<Val
Nan::ThrowError("You specified keyIsUint32 on the Dbi, so you can't use other key types with it.");
return NodeLmdbKeyType::InvalidKey;
}
#if NODE_LMDB_HAS_BIGINT
if (dbiKeyType == NodeLmdbKeyType::Uint64Key && keyType != NodeLmdbKeyType::Uint64Key) {
Nan::ThrowError("You specified keyIsUint64 on the Dbi, so you can't use other key types with it.");
return NodeLmdbKeyType::InvalidKey;
}
#endif

isValid = true;
return keyType;
Expand Down Expand Up @@ -167,6 +219,20 @@ argtokey_callback_t argToKey(const Local<Value> &val, MDB_val &key, NodeLmdbKeyT

return nullptr;
}
#if NODE_LMDB_HAS_BIGINT
else if (keyType == NodeLmdbKeyType::Uint64Key) {
if (!val->IsBigInt() || !get_uint64_from_bigint(val, &currentUint64Key)) {
Nan::ThrowError("Invalid key. Should be an unsigned 64-bit integer. (Specified with env.openDbi)");
return nullptr;
}

isValid = true;
key.mv_size = sizeof(uint64_t);
key.mv_data = &currentUint64Key;

return nullptr;
}
#endif
else if (keyType == NodeLmdbKeyType::InvalidKey) {
Nan::ThrowError("Invalid key type. This might be a bug in node-lmdb.");
}
Expand All @@ -185,6 +251,10 @@ Local<Value> keyToHandle(MDB_val &key, NodeLmdbKeyType keyType) {
return valToBinary(key);
case NodeLmdbKeyType::StringKey:
return valToString(key);
#if NODE_LMDB_HAS_BIGINT
case NodeLmdbKeyType::Uint64Key:
return uint64_blob_to_bigint(key.mv_data);
#endif
default:
Nan::ThrowError("Unknown key type. This is a bug in node-lmdb.");
return Nan::Undefined();
Expand Down
11 changes: 11 additions & 0 deletions src/node-lmdb.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@
#include <uv.h>
#include "lmdb.h"

//Bigint added in version 10.4
#ifndef NODE_LMDB_HAS_BIGINT
#define NODE_LMDB_HAS_BIGINT (NODE_MAJOR_VERSION > 10 || NODE_MAJOR_VERSION == 10 && NODE_MINOR_VERSION >= 4)
#endif

using namespace v8;
using namespace node;

Expand All @@ -53,6 +58,11 @@ enum class NodeLmdbKeyType {
// LMDB default key format - Appears to V8 as node::Buffer
BinaryKey = 3,

#if NODE_LMDB_HAS_BIGINT
// LMDB fixed size integer key with 32 bit keys - Appearts to V8 as a Bigint which
// is then attempted to be interpreted as aUint64
Uint64Key = 4,
#endif
};

// Exports misc stuff to the module
Expand Down Expand Up @@ -192,6 +202,7 @@ class EnvWrap : public Nan::ObjectWrap {
* name: the name of the database (or null to use the unnamed database)
* create: if true, the database will be created if it doesn't exist
* keyIsUint32: if true, keys are treated as 32-bit unsigned integers
* keyIsUint64: (only in node 10.4.0 or later) if true, keys are treated as 64-bit unsigned integers
* dupSort: if true, the database can hold multiple items with the same key
* reverseKey: keys are strings to be compared in reverse order
* dupFixed: if dupSort is true, indicates that the data items are all the same size
Expand Down
62 changes: 62 additions & 0 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1245,5 +1245,67 @@ describe('Node.js LMDB Bindings', function() {
}, 100);
});
});
describe('Multiple transactions (64-bit keys)', function() {
this.timeout(10000);
var env;
var dbi;
before(function() {
env = new lmdb.Env();
env.open({
path: testDirPath,
maxDbs: 10
});
dbi = env.openDbi({
name: 'mydb6',
create: true,
keyIsUint64: true
});
var txn = env.beginTxn();
//9223372036854776000n is 2^63 and rounded in a regular js Number object:
//ie:
//>9223372036854776001 === 9223372036854776002
//true
//>9223372036854776001n === 9223372036854776002n
//false
txn.putString(dbi, 9223372036854776001n, 'Hello9223372036854776001');
txn.putString(dbi, 9223372036854776002n, 'Hello9223372036854776002');
txn.commit();
});
after(function() {
dbi.close();
env.close();
});
it('readonly transaction should not see uncommited changes', function() {
var readTxn = env.beginTxn({readOnly: true});
var data = readTxn.getString(dbi, 9223372036854776001n);
should.equal(data, 'Hello9223372036854776001');

var writeTxn = env.beginTxn();
writeTxn.putString(dbi, 9223372036854776001n, 'Ha ha ha');

var data2 = writeTxn.getString(dbi, 9223372036854776001n);
data2.should.equal('Ha ha ha');

var data3 = readTxn.getString(dbi, 9223372036854776001n);
should.equal(data3, 'Hello9223372036854776001');

writeTxn.commit();
var data4 = readTxn.getString(dbi, 9223372036854776001n);
should.equal(data4, 'Hello9223372036854776001');

readTxn.reset();
readTxn.renew();
var data5 = readTxn.getString(dbi, 9223372036854776001n);
should.equal(data5, 'Ha ha ha');
readTxn.abort();
});
it('readonly transaction will throw if tries to write', function() {
var readTxn = env.beginTxn({readOnly: true});
(function() {
readTxn.putString(dbi, 9223372036854776002n, 'hööhh');
}).should.throw('Permission denied');
readTxn.abort();
});
});

});