mirror of https://github.com/ghostfolio/ghostfolio
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.
398 lines
13 KiB
398 lines
13 KiB
#include "lmdb-js.h"
|
|
#include <string.h>
|
|
|
|
using namespace Napi;
|
|
|
|
const int INCLUDE_VALUES = 0x100;
|
|
const int REVERSE = 0x400;
|
|
const int VALUES_FOR_KEY = 0x800;
|
|
const int ONLY_COUNT = 0x1000;
|
|
const int RENEW_CURSOR = 0x2000;
|
|
const int EXACT_MATCH = 0x4000;
|
|
const int INCLUSIVE_END = 0x8000;
|
|
const int EXCLUSIVE_START = 0x10000;
|
|
|
|
CursorWrap::CursorWrap(const CallbackInfo& info) : Napi::ObjectWrap<CursorWrap>(info) {
|
|
this->keyType = LmdbKeyType::StringKey;
|
|
this->freeKey = nullptr;
|
|
this->endKey.mv_size = 0; // indicates no end key (yet)
|
|
if (info.Length() < 1) {
|
|
throwError(info.Env(), "Wrong number of arguments");
|
|
return;
|
|
}
|
|
|
|
DbiWrap *dw;
|
|
napi_unwrap(info.Env(), info[0], (void**)&dw);
|
|
int64_t tw_address = 0;
|
|
napi_get_value_int64(info.Env(), info[1], &tw_address);
|
|
// Open the cursor
|
|
MDB_cursor *cursor;
|
|
MDB_txn *txn = dw->ew->getReadTxn(tw_address);
|
|
int rc = mdb_cursor_open(txn, dw->dbi, &cursor);
|
|
if (rc != 0) {
|
|
throwLmdbError(info.Env(), rc);
|
|
return;
|
|
}
|
|
info.This().As<Object>().Set("address", Number::New(info.Env(), (size_t) this));
|
|
this->cursor = cursor;
|
|
this->dw = dw;
|
|
this->txn = txn;
|
|
this->keyType = keyType;
|
|
}
|
|
|
|
CursorWrap::~CursorWrap() {
|
|
if (this->cursor) {
|
|
// Don't close cursor here, it is possible that the environment may already be closed, which causes it to crash
|
|
//mdb_cursor_close(this->cursor);
|
|
}
|
|
if (this->freeKey) {
|
|
this->freeKey(this->key);
|
|
}
|
|
}
|
|
|
|
Value CursorWrap::close(const CallbackInfo& info) {
|
|
if (this->cursor) {
|
|
mdb_cursor_close(this->cursor);
|
|
this->cursor = nullptr;
|
|
}
|
|
return info.Env().Undefined();
|
|
}
|
|
|
|
Value CursorWrap::del(const CallbackInfo& info) {
|
|
int flags = 0;
|
|
|
|
if (info.Length() == 1) {
|
|
if (!info[0].IsObject()) {
|
|
return throwError(info.Env(), "cursor.del: Invalid options argument. It should be an object.");
|
|
}
|
|
|
|
auto options = info[0].As<Object>();
|
|
setFlagFromValue(&flags, MDB_NODUPDATA, "noDupData", false, options);
|
|
}
|
|
|
|
int rc = mdb_cursor_del(this->cursor, flags);
|
|
if (rc != 0) {
|
|
return throwLmdbError(info.Env(), rc);
|
|
}
|
|
return info.Env().Undefined();
|
|
}
|
|
int CursorWrap::returnEntry(int lastRC, MDB_val &key, MDB_val &data) {
|
|
if (lastRC) {
|
|
if (lastRC == MDB_NOTFOUND)
|
|
return 0;
|
|
else {
|
|
return lastRC > 0 ? -lastRC : lastRC;
|
|
}
|
|
}
|
|
if (endKey.mv_size > 0) {
|
|
int comparison;
|
|
if (flags & VALUES_FOR_KEY)
|
|
comparison = mdb_dcmp(txn, dw->dbi, &endKey, &data);
|
|
else
|
|
comparison = mdb_cmp(txn, dw->dbi, &endKey, &key);
|
|
if ((flags & REVERSE) ? comparison >= 0 : (comparison <= 0)) {
|
|
if (!((flags & INCLUSIVE_END) && comparison == 0))
|
|
return 0;
|
|
}
|
|
}
|
|
char* keyBuffer = dw->ew->keyBuffer;
|
|
if (flags & INCLUDE_VALUES) {
|
|
int result = getVersionAndUncompress(data, dw);
|
|
bool fits = true;
|
|
if (result) {
|
|
fits = valToBinaryFast(data, dw); // it fit in the global/compression-target buffer
|
|
}
|
|
#if ENABLE_V8_API
|
|
if (fits || result == 2 || data.mv_size < SHARED_BUFFER_THRESHOLD) {// if it was decompressed
|
|
#endif
|
|
*((uint32_t*)keyBuffer) = data.mv_size;
|
|
*((uint32_t*)(keyBuffer + 4)) = 0; // buffer id of 0
|
|
#if ENABLE_V8_API
|
|
} else {
|
|
EnvWrap::toSharedBuffer(dw->ew->env, (uint32_t*) dw->ew->keyBuffer, data);
|
|
}
|
|
#endif
|
|
}
|
|
if (!(flags & VALUES_FOR_KEY)) {
|
|
memcpy(keyBuffer + 32, key.mv_data, key.mv_size);
|
|
*(keyBuffer + 32 + key.mv_size) = 0; // make sure it is null terminated for the sake of better ordered-binary performance
|
|
}
|
|
|
|
return key.mv_size;
|
|
}
|
|
|
|
const int START_ADDRESS_POSITION = 4064;
|
|
int32_t CursorWrap::doPosition(uint32_t offset, uint32_t keySize, uint64_t endKeyAddress) {
|
|
//char* keyBuffer = dw->ew->keyBuffer;
|
|
MDB_val key, data;
|
|
int rc;
|
|
if (dw->ew->env == nullptr) {
|
|
return MDB_BAD_TXN;
|
|
}
|
|
if (flags & RENEW_CURSOR) { // TODO: check the txn_id to determine if we need to renew
|
|
rc = mdb_cursor_renew(txn = dw->ew->getReadTxn(), cursor);
|
|
if (rc) {
|
|
if (rc > 0)
|
|
rc = -rc;
|
|
return rc;
|
|
}
|
|
}
|
|
if (endKeyAddress) {
|
|
uint32_t* keyBuffer = (uint32_t*) endKeyAddress;
|
|
endKey.mv_size = *keyBuffer;
|
|
endKey.mv_data = (char*)(keyBuffer + 1);
|
|
} else
|
|
endKey.mv_size = 0;
|
|
iteratingOp = (flags & REVERSE) ?
|
|
(flags & INCLUDE_VALUES) ?
|
|
(flags & VALUES_FOR_KEY) ? MDB_PREV_DUP : MDB_PREV :
|
|
MDB_PREV_NODUP :
|
|
(flags & INCLUDE_VALUES) ?
|
|
(flags & VALUES_FOR_KEY) ? MDB_NEXT_DUP : MDB_NEXT :
|
|
MDB_NEXT_NODUP;
|
|
key.mv_size = keySize;
|
|
key.mv_data = dw->ew->keyBuffer;
|
|
if (keySize == 0) {
|
|
rc = mdb_cursor_get(cursor, &key, &data, flags & REVERSE ? MDB_LAST : MDB_FIRST);
|
|
} else {
|
|
if (flags & VALUES_FOR_KEY) { // only values for this key
|
|
// take the next part of the key buffer as a pointer to starting data
|
|
uint32_t* startValueBuffer = (uint32_t*)(size_t)(*(double*)(dw->ew->keyBuffer + START_ADDRESS_POSITION));
|
|
data.mv_size = endKeyAddress ? *((uint32_t*)startValueBuffer) : 0;
|
|
data.mv_data = startValueBuffer + 1;
|
|
MDB_val startValue;
|
|
if (flags & EXCLUSIVE_START)
|
|
startValue = data; // save it for comparison
|
|
if (flags & REVERSE) {// reverse through values
|
|
startValue = data; // save it for comparison
|
|
rc = mdb_cursor_get(cursor, &key, &data, data.mv_size ? MDB_GET_BOTH_RANGE : MDB_SET_KEY);
|
|
if (rc) {
|
|
if (startValue.mv_size) {
|
|
// value specified, but not found, so find key and go to last item
|
|
rc = mdb_cursor_get(cursor, &key, &data, MDB_SET_KEY);
|
|
if (!rc)
|
|
rc = mdb_cursor_get(cursor, &key, &data, MDB_LAST_DUP);
|
|
} // else just couldn't find the key
|
|
} else { // found entry
|
|
if (startValue.mv_size == 0) // no value specified, so go to last value
|
|
rc = mdb_cursor_get(cursor, &key, &data, MDB_LAST_DUP);
|
|
else if (mdb_dcmp(txn, dw->dbi, &startValue, &data)) // the range found the next value *after* the start
|
|
rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV_DUP);
|
|
}
|
|
} else // forward, just do a get by range
|
|
rc = mdb_cursor_get(cursor, &key, &data, data.mv_size ?
|
|
(flags & EXACT_MATCH) ? MDB_GET_BOTH : MDB_GET_BOTH_RANGE : MDB_SET_KEY);
|
|
|
|
if (rc == MDB_NOTFOUND)
|
|
return 0;
|
|
if (flags & ONLY_COUNT && (!endKeyAddress || (flags & EXACT_MATCH))) {
|
|
size_t count;
|
|
rc = mdb_cursor_count(cursor, &count);
|
|
if (rc)
|
|
return rc > 0 ? -rc : rc;
|
|
return count;
|
|
}
|
|
if (flags & EXCLUSIVE_START) {
|
|
while(!rc) {
|
|
if (mdb_dcmp(txn, dw->dbi, &startValue, &data))
|
|
break;
|
|
rc = mdb_cursor_get(cursor, &key, &data, iteratingOp);
|
|
}
|
|
}
|
|
} else {
|
|
MDB_val firstKey;
|
|
if (flags & EXCLUSIVE_START)
|
|
firstKey = key; // save it for comparison
|
|
if (flags & REVERSE) {// reverse
|
|
firstKey = key; // save it for comparison
|
|
rc = mdb_cursor_get(cursor, &key, &data, MDB_SET_RANGE);
|
|
if (rc)
|
|
rc = mdb_cursor_get(cursor, &key, &data, MDB_LAST);
|
|
else if (mdb_cmp(txn, dw->dbi, &firstKey, &key)) // the range found the next entry *after* the start
|
|
rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV);
|
|
else if (dw->flags & MDB_DUPSORT)
|
|
// we need to go to the last value of this key
|
|
rc = mdb_cursor_get(cursor, &key, &data, MDB_LAST_DUP);
|
|
} else // forward, just do a get by range
|
|
rc = mdb_cursor_get(cursor, &key, &data, (flags & EXACT_MATCH) ? MDB_SET_KEY : MDB_SET_RANGE);
|
|
if (flags & EXCLUSIVE_START) {
|
|
while(!rc) {
|
|
if (mdb_cmp(txn, dw->dbi, &firstKey, &key))
|
|
break;
|
|
rc = mdb_cursor_get(cursor, &key, &data, iteratingOp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
while (offset-- > 0 && !rc) {
|
|
rc = mdb_cursor_get(cursor, &key, &data, iteratingOp);
|
|
}
|
|
if (flags & ONLY_COUNT) {
|
|
uint32_t count = 0;
|
|
bool useCursorCount = false;
|
|
// if we are in a dupsort database, and we are iterating over all entries, we can just count all the values for each key
|
|
if (dw->flags & MDB_DUPSORT) {
|
|
if (iteratingOp == MDB_PREV) {
|
|
iteratingOp = MDB_PREV_NODUP;
|
|
useCursorCount = true;
|
|
}
|
|
if (iteratingOp == MDB_NEXT) {
|
|
iteratingOp = MDB_NEXT_NODUP;
|
|
useCursorCount = true;
|
|
}
|
|
}
|
|
|
|
while (!rc) {
|
|
if (endKey.mv_size > 0) {
|
|
int comparison;
|
|
if (flags & VALUES_FOR_KEY)
|
|
comparison = mdb_dcmp(txn, dw->dbi, &endKey, &data);
|
|
else
|
|
comparison = mdb_cmp(txn, dw->dbi, &endKey, &key);
|
|
if ((flags & REVERSE) ? comparison >= 0 : (comparison <=0)) {
|
|
if (!((flags & INCLUSIVE_END) && comparison == 0))
|
|
return count;
|
|
}
|
|
}
|
|
if (useCursorCount) {
|
|
size_t countForKey;
|
|
rc = mdb_cursor_count(cursor, &countForKey);
|
|
if (rc) {
|
|
if (rc > 0)
|
|
rc = -rc;
|
|
return rc;
|
|
}
|
|
count += countForKey;
|
|
} else
|
|
count++;
|
|
rc = mdb_cursor_get(cursor, &key, &data, iteratingOp);
|
|
}
|
|
return count;
|
|
}
|
|
// TODO: Handle count?
|
|
return returnEntry(rc, key, data);
|
|
}
|
|
NAPI_FUNCTION(position) {
|
|
ARGS(5)
|
|
GET_INT64_ARG(0);
|
|
CursorWrap* cw = (CursorWrap*) i64;
|
|
GET_UINT32_ARG(cw->flags, 1);
|
|
uint32_t offset;
|
|
GET_UINT32_ARG(offset, 2);
|
|
uint32_t keySize;
|
|
GET_UINT32_ARG(keySize, 3);
|
|
napi_get_value_int64(env, args[4], &i64);
|
|
int64_t endKeyAddress = i64;
|
|
int32_t result = cw->doPosition(offset, keySize, endKeyAddress);
|
|
RETURN_INT32(result);
|
|
}
|
|
int32_t positionFFI(double cwPointer, uint32_t flags, uint32_t offset, uint32_t keySize, uint64_t endKeyAddress) {
|
|
CursorWrap* cw = (CursorWrap*) (size_t) cwPointer;
|
|
DbiWrap* dw = cw->dw;
|
|
dw->getFast = true;
|
|
cw->flags = flags;
|
|
return cw->doPosition(offset, keySize, endKeyAddress);
|
|
}
|
|
|
|
NAPI_FUNCTION(iterate) {
|
|
ARGS(1)
|
|
GET_INT64_ARG(0);
|
|
CursorWrap* cw = (CursorWrap*) i64;
|
|
MDB_val key, data;
|
|
int rc;
|
|
if (cw->dw->ew->env == nullptr) rc = MDB_BAD_TXN;
|
|
else
|
|
rc = mdb_cursor_get(cw->cursor, &key, &data, cw->iteratingOp);
|
|
RETURN_INT32(cw->returnEntry(rc, key, data));
|
|
}
|
|
|
|
int32_t iterateFFI(double cwPointer) {
|
|
CursorWrap* cw = (CursorWrap*) (size_t) cwPointer;
|
|
DbiWrap* dw = cw->dw;
|
|
dw->getFast = true;
|
|
MDB_val key, data;
|
|
if (cw->dw->ew->env == nullptr)
|
|
return MDB_BAD_TXN;
|
|
int rc = mdb_cursor_get(cw->cursor, &key, &data, cw->iteratingOp);
|
|
return cw->returnEntry(rc, key, data);
|
|
}
|
|
|
|
|
|
NAPI_FUNCTION(getCurrentValue) {
|
|
ARGS(1)
|
|
GET_INT64_ARG(0);
|
|
CursorWrap* cw = (CursorWrap*) i64;
|
|
MDB_val key, data;
|
|
int rc = mdb_cursor_get(cw->cursor, &key, &data, MDB_GET_CURRENT);
|
|
RETURN_INT32(cw->returnEntry(rc, key, data));
|
|
}
|
|
|
|
napi_finalize noopCursor = [](napi_env, void *, void *) {
|
|
// Data belongs to LMDB, we shouldn't free it here
|
|
};
|
|
NAPI_FUNCTION(getCurrentShared) {
|
|
ARGS(1)
|
|
GET_INT64_ARG(0);
|
|
CursorWrap* cw = (CursorWrap*) i64;
|
|
MDB_val key, data;
|
|
int rc = mdb_cursor_get(cw->cursor, &key, &data, MDB_GET_CURRENT);
|
|
if (rc)
|
|
RETURN_INT32(cw->returnEntry(rc, key, data));
|
|
getVersionAndUncompress(data, cw->dw);
|
|
napi_create_external_buffer(env, data.mv_size,
|
|
(char*) data.mv_data, noopCursor, nullptr, &returnValue);
|
|
return returnValue;
|
|
}
|
|
|
|
NAPI_FUNCTION(renew) {
|
|
ARGS(1)
|
|
GET_INT64_ARG(0);
|
|
CursorWrap* cw = (CursorWrap*) i64;
|
|
mdb_cursor_renew(cw->txn = cw->dw->ew->getReadTxn(), cw->cursor);
|
|
RETURN_UNDEFINED;
|
|
}
|
|
|
|
void CursorWrap::setupExports(Napi::Env env, Object exports) {
|
|
// CursorWrap: Prepare constructor template
|
|
Function CursorClass = DefineClass(env, "Cursor", {
|
|
// CursorWrap: Add functions to the prototype
|
|
CursorWrap::InstanceMethod("close", &CursorWrap::close),
|
|
CursorWrap::InstanceMethod("del", &CursorWrap::del),
|
|
});
|
|
EXPORT_NAPI_FUNCTION("position", position);
|
|
EXPORT_NAPI_FUNCTION("iterate", iterate);
|
|
EXPORT_NAPI_FUNCTION("getCurrentValue", getCurrentValue);
|
|
EXPORT_NAPI_FUNCTION("getCurrentShared", getCurrentShared);
|
|
EXPORT_NAPI_FUNCTION("renew", renew);
|
|
EXPORT_FUNCTION_ADDRESS("positionPtr", positionFFI);
|
|
EXPORT_FUNCTION_ADDRESS("iteratePtr", iterateFFI);
|
|
|
|
exports.Set("Cursor", CursorClass);
|
|
|
|
// cursorTpl->InstanceTemplate()->SetInternalFieldCount(1);
|
|
}
|
|
|
|
// This file contains code from the node-lmdb project
|
|
// 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.
|
|
|
|
|