Son CV dans un terminal web en Javascript!
https://terminal-cv.gregandev.fr
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
471 lines
17 KiB
471 lines
17 KiB
|
|
// This file is part of node-lmdb, the Node.js binding for lmdb
|
|
// Copyright (c) 2013-2017 Timur Kristóf
|
|
// Copyright (c) 2021 Kristopher Tate
|
|
// Licensed to you under the terms of the MIT license
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
|
|
#include "node-lmdb.h"
|
|
#include <cstdio>
|
|
|
|
using namespace v8;
|
|
using namespace node;
|
|
|
|
void setFlagFromValue(int *flags, int flag, const char *name, bool defaultValue, Local<Object> options);
|
|
|
|
DbiWrap::DbiWrap(MDB_env *env, MDB_dbi dbi) {
|
|
this->env = env;
|
|
this->dbi = dbi;
|
|
this->keyType = NodeLmdbKeyType::DefaultKey;
|
|
this->compression = nullptr;
|
|
this->isOpen = false;
|
|
this->getFast = false;
|
|
this->keysUse32LE = false;
|
|
this->valuesUse32LE = false;
|
|
this->ew = nullptr;
|
|
}
|
|
|
|
DbiWrap::~DbiWrap() {
|
|
// Imagine the following JS:
|
|
// ------------------------
|
|
// var dbi1 = env.openDbi({ name: "hello" });
|
|
// var dbi2 = env.openDbi({ name: "hello" });
|
|
// dbi1.close();
|
|
// txn.putString(dbi2, "world");
|
|
// -----
|
|
// The above DbiWrap objects would both wrap the same MDB_dbi, and if closing the first one called mdb_dbi_close,
|
|
// that'd also render the second DbiWrap instance unusable.
|
|
//
|
|
// For this reason, we will never call mdb_dbi_close
|
|
// NOTE: according to LMDB authors, it is perfectly fine if mdb_dbi_close is never called on an MDB_dbi
|
|
|
|
if (this->ew) {
|
|
this->ew->Unref();
|
|
}
|
|
if (this->compression)
|
|
this->compression->Unref();
|
|
}
|
|
|
|
void DbiWrap::setUnsafeBuffer(char* unsafePtr, const Persistent<Object> &unsafeBuffer) {
|
|
if (lastUnsafePtr != unsafePtr) {
|
|
(void)handle()->Set(Nan::GetCurrentContext(), Nan::New<String>("unsafeBuffer").ToLocalChecked(),
|
|
unsafeBuffer.Get(Isolate::GetCurrent()));
|
|
lastUnsafePtr = unsafePtr;
|
|
}
|
|
}
|
|
|
|
|
|
NAN_METHOD(DbiWrap::ctor) {
|
|
Nan::HandleScope scope;
|
|
|
|
MDB_dbi dbi;
|
|
MDB_txn *txn;
|
|
int rc;
|
|
int flags = 0;
|
|
int txnFlags = 0;
|
|
Local<String> name;
|
|
bool nameIsNull = false;
|
|
NodeLmdbKeyType keyType = NodeLmdbKeyType::DefaultKey;
|
|
bool needsTransaction = true;
|
|
bool isOpen = false;
|
|
bool hasVersions = false;
|
|
bool keysUse32LE = false;
|
|
bool valuesUse32LE = false;
|
|
|
|
EnvWrap *ew = Nan::ObjectWrap::Unwrap<EnvWrap>(Local<Object>::Cast(info[0]));
|
|
Compression* compression = ew->compression;
|
|
|
|
if (info[1]->IsObject()) {
|
|
Local<Object> options = Local<Object>::Cast(info[1]);
|
|
nameIsNull = options->Get(Nan::GetCurrentContext(), Nan::New<String>("name").ToLocalChecked()).ToLocalChecked()->IsNull();
|
|
name = Local<String>::Cast(options->Get(Nan::GetCurrentContext(), Nan::New<String>("name").ToLocalChecked()).ToLocalChecked());
|
|
|
|
// Get flags from options
|
|
|
|
// NOTE: mdb_set_relfunc is not exposed because MDB_FIXEDMAP is "highly experimental"
|
|
// NOTE: mdb_set_relctx is not exposed because MDB_FIXEDMAP is "highly experimental"
|
|
setFlagFromValue(&flags, MDB_REVERSEKEY, "reverseKey", false, options);
|
|
setFlagFromValue(&flags, MDB_DUPSORT, "dupSort", false, options);
|
|
setFlagFromValue(&flags, MDB_DUPFIXED, "dupFixed", false, options);
|
|
setFlagFromValue(&flags, MDB_INTEGERDUP, "integerDup", false, options);
|
|
setFlagFromValue(&flags, MDB_REVERSEDUP, "reverseDup", false, options);
|
|
setFlagFromValue(&flags, MDB_CREATE, "create", false, options);
|
|
|
|
// TODO: wrap mdb_set_compare
|
|
// TODO: wrap mdb_set_dupsort
|
|
|
|
keyType = keyTypeFromOptions(options);
|
|
if (keyType == NodeLmdbKeyType::InvalidKey) {
|
|
// NOTE: Error has already been thrown inside keyTypeFromOptions
|
|
return;
|
|
}
|
|
|
|
if (keyType == NodeLmdbKeyType::Uint32Key) {
|
|
flags |= MDB_INTEGERKEY;
|
|
}
|
|
Local<Value> compressionOption = options->Get(Nan::GetCurrentContext(), Nan::New<String>("compression").ToLocalChecked()).ToLocalChecked();
|
|
if (compressionOption->IsObject()) {
|
|
compression = Nan::ObjectWrap::Unwrap<Compression>(Nan::To<v8::Object>(compressionOption).ToLocalChecked());
|
|
}
|
|
|
|
// Set flags for txn used to open database
|
|
Local<Value> create = options->Get(Nan::GetCurrentContext(), Nan::New<String>("create").ToLocalChecked()).ToLocalChecked();
|
|
#if NODE_VERSION_AT_LEAST(12,0,0)
|
|
if (create->IsBoolean() ? !create->BooleanValue(Isolate::GetCurrent()) : true) {
|
|
#else
|
|
if (create->IsBoolean() ? !create->BooleanValue(Nan::GetCurrentContext()).FromJust() : true) {
|
|
#endif
|
|
txnFlags |= MDB_RDONLY;
|
|
}
|
|
Local<Value> hasVersionsLocal = options->Get(Nan::GetCurrentContext(), Nan::New<String>("useVersions").ToLocalChecked()).ToLocalChecked();
|
|
hasVersions = hasVersionsLocal->IsTrue();
|
|
|
|
auto txnObj = options->Get(Nan::GetCurrentContext(), Nan::New<String>("txn").ToLocalChecked()).ToLocalChecked();
|
|
if (!txnObj->IsNull() && !txnObj->IsUndefined() && txnObj->IsObject()) {
|
|
TxnWrap *tw = Nan::ObjectWrap::Unwrap<TxnWrap>(Local<Object>::Cast(txnObj));
|
|
needsTransaction = false;
|
|
txn = tw->txn;
|
|
}
|
|
if (options->Get(Nan::GetCurrentContext(), Nan::New<String>("keysUse32LE").ToLocalChecked()).ToLocalChecked()->IsTrue()) {
|
|
keysUse32LE = true;
|
|
}
|
|
if (options->Get(Nan::GetCurrentContext(), Nan::New<String>("valuesUse32LE").ToLocalChecked()).ToLocalChecked()->IsTrue()) {
|
|
valuesUse32LE = true;
|
|
}
|
|
}
|
|
else {
|
|
return Nan::ThrowError("Invalid parameters.");
|
|
}
|
|
|
|
if (needsTransaction) {
|
|
// Open transaction
|
|
rc = mdb_txn_begin(ew->env, nullptr, txnFlags, &txn);
|
|
if (rc != 0) {
|
|
// No need to call mdb_txn_abort, because mdb_txn_begin already cleans up after itself
|
|
return throwLmdbError(rc);
|
|
}
|
|
}
|
|
|
|
// Open database
|
|
// NOTE: nullptr in place of the name means using the unnamed database.
|
|
#if NODE_VERSION_AT_LEAST(12,0,0)
|
|
rc = mdb_dbi_open(txn, nameIsNull ? nullptr : *String::Utf8Value(Isolate::GetCurrent(), name), flags, &dbi);
|
|
#else
|
|
rc = mdb_dbi_open(txn, nameIsNull ? nullptr : *String::Utf8Value(name), flags, &dbi);
|
|
#endif
|
|
if (rc != 0) {
|
|
if (needsTransaction) {
|
|
mdb_txn_abort(txn);
|
|
}
|
|
return throwLmdbError(rc);
|
|
}
|
|
else {
|
|
isOpen = true;
|
|
}
|
|
// Create wrapper
|
|
DbiWrap* dw = new DbiWrap(ew->env, dbi);
|
|
if (isOpen) {
|
|
dw->ew = ew;
|
|
dw->ew->Ref();
|
|
}
|
|
if (keysUse32LE) {
|
|
dw->keysUse32LE = true;
|
|
mdb_set_compare(txn, dbi, compare32LE);
|
|
}
|
|
if (valuesUse32LE) {
|
|
dw->valuesUse32LE = true;
|
|
mdb_set_dupsort(txn, dbi, compare32LE);
|
|
}
|
|
if (needsTransaction) {
|
|
// Commit transaction
|
|
rc = mdb_txn_commit(txn);
|
|
if (rc != 0) {
|
|
return throwLmdbError(rc);
|
|
}
|
|
}
|
|
|
|
dw->keyType = keyType;
|
|
dw->flags = flags;
|
|
dw->isOpen = isOpen;
|
|
if (compression)
|
|
compression->Ref();
|
|
dw->compression = compression;
|
|
dw->hasVersions = hasVersions;
|
|
dw->Wrap(info.This());
|
|
|
|
return info.GetReturnValue().Set(info.This());
|
|
}
|
|
|
|
NAN_METHOD(DbiWrap::close) {
|
|
Nan::HandleScope scope;
|
|
|
|
DbiWrap *dw = Nan::ObjectWrap::Unwrap<DbiWrap>(info.This());
|
|
if (dw->isOpen) {
|
|
mdb_dbi_close(dw->env, dw->dbi);
|
|
dw->isOpen = false;
|
|
dw->ew->Unref();
|
|
dw->ew = nullptr;
|
|
}
|
|
else {
|
|
return Nan::ThrowError("The Dbi is not open, you can't close it.");
|
|
}
|
|
}
|
|
|
|
class DropWorker : public Nan::AsyncWorker {
|
|
public:
|
|
DropWorker(MDB_dbi dbi, MDB_env* env, int del, Nan::Callback *callback)
|
|
: Nan::AsyncWorker(callback), dbi(dbi), env(env), del(del) {
|
|
}
|
|
void Execute() {
|
|
MDB_txn *txn;
|
|
int rc = mdb_txn_begin(env, nullptr, 0, &txn);
|
|
if (!rc)
|
|
rc = mdb_drop(txn, dbi, del);
|
|
if (rc)
|
|
mdb_txn_abort(txn);
|
|
else
|
|
rc = mdb_txn_commit(txn);
|
|
if (rc)
|
|
SetErrorMessage(mdb_strerror(rc));
|
|
}
|
|
|
|
void HandleOKCallback() {
|
|
Nan::HandleScope scope;
|
|
Local<v8::Value> argv[] = {
|
|
Nan::Null()
|
|
};
|
|
callback->Call(1, argv, async_resource);
|
|
}
|
|
private:
|
|
MDB_dbi dbi;
|
|
MDB_env* env;
|
|
int del;
|
|
};
|
|
|
|
NAN_METHOD(DbiWrap::dropAsync) {
|
|
DbiWrap *dw = Nan::ObjectWrap::Unwrap<DbiWrap>(info.This());
|
|
int del = info[0]->IsTrue();
|
|
Nan::Callback* callback = new Nan::Callback(
|
|
Local<v8::Function>::Cast(info[1])
|
|
);
|
|
Nan::AsyncQueueWorker(new DropWorker(dw->dbi, dw->ew->env, del, callback));
|
|
}
|
|
|
|
NAN_METHOD(DbiWrap::drop) {
|
|
Nan::HandleScope scope;
|
|
|
|
DbiWrap *dw = Nan::ObjectWrap::Unwrap<DbiWrap>(info.This());
|
|
int del = 1;
|
|
int rc;
|
|
MDB_txn *txn;
|
|
bool needsTransaction = true;
|
|
|
|
if (!dw->isOpen) {
|
|
return Nan::ThrowError("The Dbi is not open, you can't drop it.");
|
|
}
|
|
|
|
// Check if the database should be deleted
|
|
if (info.Length() == 1 && info[0]->IsObject()) {
|
|
Local<Object> options = Local<Object>::Cast(info[0]);
|
|
|
|
// Just free pages
|
|
Local<Value> opt = options->Get(Nan::GetCurrentContext(), Nan::New<String>("justFreePages").ToLocalChecked()).ToLocalChecked();
|
|
#if NODE_VERSION_AT_LEAST(12,0,0)
|
|
del = opt->IsBoolean() ? !(opt->BooleanValue(Isolate::GetCurrent())) : 1;
|
|
#else
|
|
del = opt->IsBoolean() ? !(opt->BooleanValue(Nan::GetCurrentContext()).FromJust()) : 1;
|
|
#endif
|
|
|
|
// User-supplied txn
|
|
auto txnObj = options->Get(Nan::GetCurrentContext(), Nan::New<String>("txn").ToLocalChecked()).ToLocalChecked();
|
|
if (!txnObj->IsNull() && !txnObj->IsUndefined() && txnObj->IsObject()) {
|
|
TxnWrap *tw = Nan::ObjectWrap::Unwrap<TxnWrap>(Local<Object>::Cast(txnObj));
|
|
needsTransaction = false;
|
|
txn = tw->txn;
|
|
}
|
|
}
|
|
|
|
if (needsTransaction) {
|
|
// Begin transaction
|
|
rc = mdb_txn_begin(dw->env, nullptr, 0, &txn);
|
|
if (rc != 0) {
|
|
return throwLmdbError(rc);
|
|
}
|
|
}
|
|
|
|
// Drop database
|
|
rc = mdb_drop(txn, dw->dbi, del);
|
|
if (rc != 0) {
|
|
if (needsTransaction) {
|
|
mdb_txn_abort(txn);
|
|
}
|
|
return throwLmdbError(rc);
|
|
}
|
|
|
|
if (needsTransaction) {
|
|
// Commit transaction
|
|
rc = mdb_txn_commit(txn);
|
|
if (rc != 0) {
|
|
return throwLmdbError(rc);
|
|
}
|
|
}
|
|
|
|
// Only close database if del == 1
|
|
if (del == 1) {
|
|
dw->isOpen = false;
|
|
dw->ew->Unref();
|
|
dw->ew = nullptr;
|
|
}
|
|
}
|
|
|
|
NAN_METHOD(DbiWrap::stat) {
|
|
Nan::HandleScope scope;
|
|
|
|
DbiWrap *dw = Nan::ObjectWrap::Unwrap<DbiWrap>(info.This());
|
|
|
|
if (info.Length() != 1) {
|
|
return Nan::ThrowError("dbi.stat should be called with a single argument which is a txn.");
|
|
}
|
|
|
|
TxnWrap *txn = Nan::ObjectWrap::Unwrap<TxnWrap>(Local<Object>::Cast(info[0]));
|
|
|
|
MDB_stat stat;
|
|
mdb_stat(txn->txn, dw->dbi, &stat);
|
|
|
|
Local<Context> context = Nan::GetCurrentContext();
|
|
Local<Object> obj = Nan::New<Object>();
|
|
(void)obj->Set(context, Nan::New<String>("pageSize").ToLocalChecked(), Nan::New<Number>(stat.ms_psize));
|
|
(void)obj->Set(context, Nan::New<String>("treeDepth").ToLocalChecked(), Nan::New<Number>(stat.ms_depth));
|
|
(void)obj->Set(context, Nan::New<String>("treeBranchPageCount").ToLocalChecked(), Nan::New<Number>(stat.ms_branch_pages));
|
|
(void)obj->Set(context, Nan::New<String>("treeLeafPageCount").ToLocalChecked(), Nan::New<Number>(stat.ms_leaf_pages));
|
|
(void)obj->Set(context, Nan::New<String>("entryCount").ToLocalChecked(), Nan::New<Number>(stat.ms_entries));
|
|
(void)obj->Set(context, Nan::New<String>("overflowPages").ToLocalChecked(), Nan::New<Number>(stat.ms_overflow_pages));
|
|
|
|
info.GetReturnValue().Set(obj);
|
|
}
|
|
|
|
#if ENABLE_FAST_API && NODE_VERSION_AT_LEAST(16,6,0)
|
|
uint32_t DbiWrap::getByBinaryFast(Local<Object> receiver_obj, uint32_t keySize, FastApiCallbackOptions& options) {
|
|
DbiWrap* dw = static_cast<DbiWrap*>(
|
|
receiver_obj->GetAlignedPointerFromInternalField(0));
|
|
EnvWrap* ew = dw->ew;
|
|
char* keyBuffer = ew->keyBuffer;
|
|
MDB_txn* txn = ew->getReadTxn();
|
|
MDB_val key, data;
|
|
key.mv_size = keySize;
|
|
key.mv_data = (void*) keyBuffer;
|
|
|
|
int result = mdb_get(txn, dw->dbi, &key, &data);
|
|
if (result) {
|
|
if (result == MDB_NOTFOUND)
|
|
return 0xffffffff;
|
|
// let the slow handler handle throwing errors
|
|
options.fallback = true;
|
|
return result;
|
|
}
|
|
dw->getFast = true;
|
|
result = getVersionAndUncompress(data, dw);
|
|
if (result)
|
|
result = valToBinaryFast(data);
|
|
if (!result) {
|
|
// this means an allocation or error needs to be thrown, so we fallback to the slow handler
|
|
// or since we are using signed int32 (so we can return error codes), need special handling for above 2GB entries
|
|
options.fallback = true;
|
|
}
|
|
dw->getFast = false;
|
|
/*
|
|
alternately, if we want to send over the address, which can be used for direct access to the LMDB shared memory, but all benchmarking shows it is slower
|
|
*((size_t*) keyBuffer) = data.mv_size;
|
|
*((uint64_t*) (keyBuffer + 8)) = (uint64_t) data.mv_data;
|
|
return 0;*/
|
|
return data.mv_size;
|
|
}
|
|
#endif
|
|
|
|
void DbiWrap::getByBinary(
|
|
const v8::FunctionCallbackInfo<v8::Value>& info) {
|
|
v8::Local<v8::Object> instance =
|
|
v8::Local<v8::Object>::Cast(info.Holder());
|
|
DbiWrap* dw = Nan::ObjectWrap::Unwrap<DbiWrap>(instance);
|
|
char* keyBuffer = dw->ew->keyBuffer;
|
|
MDB_txn* txn = dw->ew->getReadTxn();
|
|
MDB_val key;
|
|
MDB_val data;
|
|
key.mv_size = info[0]->Uint32Value(Nan::GetCurrentContext()).FromJust();
|
|
key.mv_data = (void*) keyBuffer;
|
|
int rc = mdb_get(txn, dw->dbi, &key, &data);
|
|
if (rc) {
|
|
if (rc == MDB_NOTFOUND)
|
|
return info.GetReturnValue().Set(Nan::New<Number>(0xffffffff));
|
|
else
|
|
return throwLmdbError(rc);
|
|
}
|
|
rc = getVersionAndUncompress(data, dw);
|
|
return info.GetReturnValue().Set(valToBinaryUnsafe(data));
|
|
}
|
|
NAN_METHOD(DbiWrap::getByPrimitive) {
|
|
v8::Local<v8::Object> instance =
|
|
v8::Local<v8::Object>::Cast(info.Holder());
|
|
DbiWrap* dw = Nan::ObjectWrap::Unwrap<DbiWrap>(instance);
|
|
MDB_txn* txn = dw->ew->getReadTxn();
|
|
MDB_val key;
|
|
MDB_val data;
|
|
bool keyIsValid;
|
|
if(argToKey(info[0], key, dw->keyType, keyIsValid)) {
|
|
return Nan::ThrowError("argToKey should not allocate");
|
|
}
|
|
if (!keyIsValid) {
|
|
// argToKey already threw an error
|
|
return;
|
|
}
|
|
int rc = mdb_get(txn, dw->dbi, &key, &data);
|
|
if (rc) {
|
|
if (rc == MDB_NOTFOUND)
|
|
return info.GetReturnValue().Set(Nan::New<Number>(0xffffffff));
|
|
else
|
|
return throwLmdbError(rc);
|
|
}
|
|
rc = getVersionAndUncompress(data, dw);
|
|
return info.GetReturnValue().Set(valToBinaryUnsafe(data));
|
|
}
|
|
|
|
NAN_METHOD(DbiWrap::getStringByPrimitive) {
|
|
v8::Local<v8::Object> instance =
|
|
v8::Local<v8::Object>::Cast(info.Holder());
|
|
DbiWrap* dw = Nan::ObjectWrap::Unwrap<DbiWrap>(instance);
|
|
MDB_txn* txn = dw->ew->getReadTxn();
|
|
MDB_val key;
|
|
MDB_val data;
|
|
bool keyIsValid;
|
|
if(argToKey(info[0], key, dw->keyType, keyIsValid)) {
|
|
return Nan::ThrowError("argToKey should not allocate");
|
|
}
|
|
if (!keyIsValid) {
|
|
// argToKey already threw an error
|
|
return;
|
|
}
|
|
int rc = mdb_get(txn, dw->dbi, &key, &data);
|
|
if (rc) {
|
|
|
|
return throwLmdbError(rc);
|
|
}
|
|
rc = getVersionAndUncompress(data, dw);
|
|
return info.GetReturnValue().Set(valToUtf8(data));
|
|
}
|
|
|