Skip to content

Commit 3f5700a

Browse files
committed
Add db.getSync() method
Also optimizes `db.get()` to avoid a copy of the key if it's a buffer. Depends on Level/abstract-level#114. Ref: Level/community#144 Category: addition
1 parent 91b2995 commit 3f5700a

File tree

3 files changed

+287
-29
lines changed

3 files changed

+287
-29
lines changed

binding.cc

Lines changed: 210 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <vector>
1414
#include <mutex>
1515
#include <atomic>
16+
#include <optional>
1617

1718
/**
1819
* Forward declarations.
@@ -105,6 +106,8 @@ static std::map<std::string, LevelDbHandle> db_handles;
105106
} \
106107
}
107108

109+
#define undefined NULL
110+
108111
/**
109112
* Bit fields.
110113
*/
@@ -162,6 +165,25 @@ static napi_value CreateCodeError (napi_env env, const char* code, const char* m
162165
return error;
163166
}
164167

168+
static void ThrowError(napi_env env, leveldb::Status status) {
169+
if (status.IsCorruption()) {
170+
napi_throw_error(env, "LEVEL_CORRUPTION", status.ToString().c_str());
171+
} else if (!status.ok()) {
172+
napi_throw_error(env, NULL, status.ToString().c_str());
173+
} else {
174+
napi_throw_error(env, NULL, "Operation failed");
175+
}
176+
}
177+
178+
static napi_value ErrorOrNotFound(napi_env env, leveldb::Status status) {
179+
if (status.IsNotFound()) {
180+
return undefined;
181+
} else {
182+
ThrowError(env, status);
183+
return undefined;
184+
}
185+
}
186+
165187
/**
166188
* Returns true if 'obj' has a property 'key'.
167189
*/
@@ -354,6 +376,14 @@ static std::vector<std::string> KeyArray (napi_env env, napi_value arr) {
354376
return result;
355377
}
356378

379+
// TODO: use in more places
380+
enum Flags : uint32_t {
381+
FILL_CACHE = 1,
382+
KEY_AS_BUFFER = 2,
383+
VALUE_AS_BUFFER = 4,
384+
SHARED_KEY = 8
385+
};
386+
357387
/**
358388
* Whether to yield entries, keys or values.
359389
*/
@@ -538,11 +568,13 @@ struct BaseWorker {
538568
struct Database {
539569
Database ()
540570
: db_(NULL),
571+
sharedBuffer_(NULL),
541572
blockCache_(NULL),
542573
filterPolicy_(leveldb::NewBloomFilterPolicy(10)),
543574
resourceSequence_(0),
544575
pendingCloseWorker_(NULL),
545576
ref_(NULL),
577+
sharedBufferRef_(NULL),
546578
priorityWork_(0) {}
547579

548580
~Database () {
@@ -635,7 +667,40 @@ struct Database {
635667
return priorityWork_ > 0;
636668
}
637669

670+
bool SetSharedBuffer (napi_env env, napi_value value) {
671+
// Delete reference to previous buffer if any
672+
if (sharedBufferRef_) {
673+
napi_delete_reference(env, sharedBufferRef_);
674+
sharedBufferRef_ = NULL;
675+
}
676+
677+
// Get underlying data (length is separately communicated, on use)
678+
if (napi_get_buffer_info(env, value, (void**)&sharedBuffer_, NULL) != napi_ok) {
679+
sharedBuffer_ = NULL;
680+
return false;
681+
}
682+
683+
// Create reference in order to keep buffer alive
684+
if (napi_create_reference(env, value, 1, &sharedBufferRef_) != napi_ok) {
685+
sharedBufferRef_ = NULL;
686+
return false;
687+
}
688+
689+
return true;
690+
}
691+
692+
void ReleaseReferences (napi_env env) {
693+
if (ref_ != NULL) napi_reference_unref(env, ref_, NULL);
694+
if (sharedBufferRef_ != NULL) napi_reference_unref(env, sharedBufferRef_, NULL);
695+
}
696+
697+
void DeleteReferences (napi_env env) {
698+
if (ref_ != NULL) napi_delete_reference(env, ref_);
699+
if (sharedBufferRef_ != NULL) napi_delete_reference(env, sharedBufferRef_);
700+
}
701+
638702
leveldb::DB* db_;
703+
char* sharedBuffer_;
639704
leveldb::Cache* blockCache_;
640705
const leveldb::FilterPolicy* filterPolicy_;
641706
uint32_t resourceSequence_;
@@ -644,6 +709,7 @@ struct Database {
644709
napi_ref ref_;
645710

646711
private:
712+
napi_ref sharedBufferRef_;
647713
std::atomic<uint32_t> priorityWork_;
648714
std::string location_;
649715

@@ -1098,7 +1164,7 @@ static void FinalizeDatabase (napi_env env, void* data, void* hint) {
10981164
if (data) {
10991165
Database* database = (Database*)data;
11001166
napi_remove_env_cleanup_hook(env, env_cleanup_hook, database);
1101-
if (database->ref_ != NULL) napi_delete_reference(env, database->ref_);
1167+
database->DeleteReferences(env);
11021168
delete database;
11031169
}
11041170
}
@@ -1224,7 +1290,7 @@ struct CloseWorker final : public BaseWorker {
12241290
}
12251291

12261292
void DoFinally (napi_env env) override {
1227-
napi_reference_unref(env, database_->ref_, NULL);
1293+
database_->ReleaseReferences(env);
12281294
BaseWorker::DoFinally(env);
12291295
}
12301296
};
@@ -1309,14 +1375,15 @@ struct GetWorker final : public PriorityWorker {
13091375
GetWorker (napi_env env,
13101376
Database* database,
13111377
napi_deferred deferred,
1378+
uint32_t flags,
13121379
leveldb::Slice key,
1313-
const Encoding encoding,
1314-
const bool fillCache,
1380+
napi_ref keyRef,
13151381
ExplicitSnapshot* snapshot)
13161382
: PriorityWorker(env, database, deferred, "classic_level.db.get"),
1383+
flags_(flags),
13171384
key_(key),
1318-
encoding_(encoding) {
1319-
options_.fill_cache = fillCache;
1385+
keyRef_(keyRef) {
1386+
options_.fill_cache = (flags & Flags::FILL_CACHE) != 0;
13201387

13211388
if (snapshot == NULL) {
13221389
implicitSnapshot_ = database->NewSnapshot();
@@ -1328,7 +1395,7 @@ struct GetWorker final : public PriorityWorker {
13281395
}
13291396

13301397
~GetWorker () {
1331-
DisposeSliceBuffer(key_);
1398+
if (!keyRef_) DisposeSliceBuffer(key_);
13321399
}
13331400

13341401
void DoExecute () override {
@@ -1340,43 +1407,166 @@ struct GetWorker final : public PriorityWorker {
13401407
}
13411408
}
13421409

1410+
void DoFinally (napi_env env) override {
1411+
if (keyRef_) napi_delete_reference(env, keyRef_);
1412+
PriorityWorker::DoFinally(env);
1413+
}
1414+
13431415
void HandleOKCallback (napi_env env, napi_deferred deferred) override {
13441416
napi_value argv;
1345-
Entry::Convert(env, &value_, encoding_, argv);
1417+
1418+
if ((flags_ & Flags::VALUE_AS_BUFFER) != 0) {
1419+
napi_create_buffer_copy(env, value_.size(), value_.data(), NULL, &argv);
1420+
} else {
1421+
napi_create_string_utf8(env, value_.data(), value_.size(), &argv);
1422+
}
1423+
13461424
napi_resolve_deferred(env, deferred, argv);
13471425
}
13481426

13491427
private:
13501428
leveldb::ReadOptions options_;
1429+
uint32_t flags_;
13511430
leveldb::Slice key_;
1431+
napi_ref keyRef_;
13521432
std::string value_;
1353-
const Encoding encoding_;
13541433
const leveldb::Snapshot* implicitSnapshot_;
13551434
};
13561435

13571436
/**
13581437
* Gets a value from a database.
13591438
*/
1360-
NAPI_METHOD(db_get) {
1361-
NAPI_ARGV(5);
1439+
NAPI_METHOD(db_get) {
1440+
NAPI_ARGV(4);
13621441
NAPI_DB_CONTEXT();
13631442
NAPI_PROMISE();
13641443

1365-
leveldb::Slice key = ToSlice(env, argv[1]);
1366-
const Encoding encoding = GetEncoding(env, argv[2]);
1367-
const bool fillCache = BooleanValue(env, argv[3], true);
1444+
uint32_t flags;
1445+
NAPI_STATUS_THROWS(napi_get_value_uint32(env, argv[1], &flags));
13681446

13691447
ExplicitSnapshot* snapshot = NULL;
1370-
napi_get_value_external(env, argv[4], (void**)&snapshot);
1448+
napi_get_value_external(env, argv[3], (void**)&snapshot);
13711449

1372-
GetWorker* worker = new GetWorker(
1373-
env, database, deferred, key, encoding, fillCache, snapshot
1374-
);
1450+
char* keyBuffer;
1451+
size_t keySize;
1452+
GetWorker* worker;
1453+
1454+
if ((flags & Flags::KEY_AS_BUFFER) != 0) {
1455+
// Instead of copying the memory, create a reference so that it stays valid
1456+
napi_ref keyRef;
1457+
NAPI_STATUS_THROWS(napi_create_reference(env, argv[2], 1, &keyRef));
1458+
NAPI_STATUS_THROWS(napi_get_typedarray_info(env, argv[2], NULL, &keySize, (void**)&keyBuffer, NULL, NULL));
1459+
leveldb::Slice keySlice(keyBuffer, keySize);
1460+
worker = new GetWorker(env, database, deferred, flags, keySlice, keyRef, snapshot);
1461+
} else {
1462+
NAPI_STATUS_THROWS(napi_get_value_string_utf8(env, argv[2], NULL, 0, &keySize));
1463+
keyBuffer = new char[keySize + 1];
1464+
NAPI_STATUS_THROWS(napi_get_value_string_utf8(env, argv[2], keyBuffer, keySize + 1, NULL));
1465+
keyBuffer[keySize] = '\0';
1466+
leveldb::Slice keySlice(keyBuffer, keySize);
1467+
1468+
// A null keyRef implies that keyBuffer needs to be deleted after the read
1469+
// TODO: solve in a more obvious way like a subclass
1470+
worker = new GetWorker(env, database, deferred, flags, keySlice, NULL, snapshot);
1471+
}
13751472

13761473
worker->Queue(env);
13771474
return promise;
13781475
}
13791476

1477+
struct NapiValueSink : public leveldb::ValueSink {
1478+
NapiValueSink (napi_env env)
1479+
: leveldb::ValueSink(),
1480+
result(NULL),
1481+
env_(env),
1482+
status_(napi_generic_failure) {}
1483+
1484+
virtual ~NapiValueSink() = default;
1485+
1486+
const bool valid() {
1487+
return status_ == napi_ok;
1488+
}
1489+
1490+
napi_value result;
1491+
1492+
protected:
1493+
napi_env env_;
1494+
napi_status status_;
1495+
};
1496+
1497+
struct NapiStringValueSink : public NapiValueSink {
1498+
NapiStringValueSink (napi_env env)
1499+
: NapiValueSink(env) {}
1500+
1501+
public:
1502+
void assign(const char* data, size_t size) override {
1503+
status_ = napi_create_string_utf8(env_, data, size, &result);
1504+
}
1505+
};
1506+
1507+
struct NapiBufferValueSink : public NapiValueSink {
1508+
NapiBufferValueSink (napi_env env)
1509+
: NapiValueSink(env) {}
1510+
1511+
public:
1512+
void assign(const char* data, size_t size) override {
1513+
status_ = napi_create_buffer_copy(env_, size, data, NULL, &result);
1514+
}
1515+
};
1516+
1517+
/**
1518+
* Get a value from a database synchronously.
1519+
*/
1520+
NAPI_METHOD(db_get_sync) {
1521+
NAPI_ARGV(4);
1522+
NAPI_DB_CONTEXT();
1523+
1524+
uint32_t flags;
1525+
NAPI_STATUS_THROWS(napi_get_value_uint32(env, argv[1], &flags));
1526+
1527+
std::optional<leveldb::Slice> keySlice;
1528+
1529+
if ((flags & Flags::SHARED_KEY) != 0) {
1530+
uint32_t keySize;
1531+
NAPI_STATUS_THROWS(napi_get_value_uint32(env, argv[2], &keySize));
1532+
keySlice.emplace(database->sharedBuffer_, keySize);
1533+
} else {
1534+
char* keyBuffer;
1535+
size_t keySize;
1536+
NAPI_STATUS_THROWS(napi_get_typedarray_info(env, argv[2], NULL, &keySize, (void**)&keyBuffer, NULL, NULL));
1537+
keySlice.emplace(keyBuffer, keySize);
1538+
}
1539+
1540+
ExplicitSnapshot* snapshot = NULL;
1541+
napi_get_value_external(env, argv[3], (void**)&snapshot);
1542+
1543+
leveldb::ReadOptions options;
1544+
options.fill_cache = (flags & Flags::FILL_CACHE) != 0;
1545+
options.snapshot = snapshot != NULL ? snapshot->nut : NULL;
1546+
1547+
if ((flags & Flags::VALUE_AS_BUFFER) != 0) {
1548+
NapiBufferValueSink valueSink(env);
1549+
leveldb::Status status = database->Get(options, *keySlice, valueSink);
1550+
return status.ok() && valueSink.valid() ? valueSink.result : ErrorOrNotFound(env, status);
1551+
} else {
1552+
NapiStringValueSink valueSink(env);
1553+
leveldb::Status status = database->Get(options, *keySlice, valueSink);
1554+
return status.ok() && valueSink.valid() ? valueSink.result : ErrorOrNotFound(env, status);
1555+
}
1556+
}
1557+
1558+
NAPI_METHOD(db_set_shared_buffer) {
1559+
NAPI_ARGV(2);
1560+
NAPI_DB_CONTEXT();
1561+
1562+
if (!database->SetSharedBuffer(env, argv[1])) {
1563+
napi_throw_error(env, NULL, "SetSharedBuffer failed");
1564+
return undefined;
1565+
}
1566+
1567+
return undefined;
1568+
}
1569+
13801570
/**
13811571
* Worker class for getting many values.
13821572
*/
@@ -2277,10 +2467,12 @@ NAPI_METHOD(snapshot_close) {
22772467
*/
22782468
NAPI_INIT() {
22792469
NAPI_EXPORT_FUNCTION(db_init);
2470+
NAPI_EXPORT_FUNCTION(db_set_shared_buffer)
22802471
NAPI_EXPORT_FUNCTION(db_open);
22812472
NAPI_EXPORT_FUNCTION(db_close);
22822473
NAPI_EXPORT_FUNCTION(db_put);
22832474
NAPI_EXPORT_FUNCTION(db_get);
2475+
NAPI_EXPORT_FUNCTION(db_get_sync);
22842476
NAPI_EXPORT_FUNCTION(db_get_many);
22852477
NAPI_EXPORT_FUNCTION(db_del);
22862478
NAPI_EXPORT_FUNCTION(db_clear);

0 commit comments

Comments
 (0)