Skip to content

Commit 081a741

Browse files
committed
Implement has() and hasMany()
Adds support of two methods: ```js await db.put('love', 'u') await db.has('love') // true await db.hasMany(['love', 'hate']) // [true, false] ``` Ref: Level/community#142 Category: addition
1 parent 15eb289 commit 081a741

File tree

2 files changed

+186
-1
lines changed

2 files changed

+186
-1
lines changed

binding.cc

Lines changed: 152 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -882,7 +882,7 @@ struct BaseIterator {
882882
if (reverse_ ? cmp > 0 : cmp < 0) {
883883
Next();
884884
}
885-
} else {
885+
} else { // TODO: can we skip this code path if not in reverse?
886886
SeekToFirst();
887887
if (dbIterator_->Valid()) {
888888
int cmp = dbIterator_->key().compare(target);
@@ -893,6 +893,15 @@ struct BaseIterator {
893893
}
894894
}
895895

896+
/**
897+
* Seek to an exact key.
898+
*/
899+
bool SeekExact (leveldb::Slice& target) {
900+
didSeek_ = true;
901+
dbIterator_->Seek(target);
902+
return dbIterator_->Valid() && dbIterator_->key() == target;
903+
}
904+
896905
void CloseIterator () {
897906
if (!hasClosed_) {
898907
hasClosed_ = true;
@@ -1376,6 +1385,74 @@ NAPI_METHOD(db_get) {
13761385
return promise;
13771386
}
13781387

1388+
/**
1389+
* Worker class for db.has().
1390+
*/
1391+
struct HasWorker final : public PriorityWorker {
1392+
HasWorker(
1393+
napi_env env,
1394+
Database* database,
1395+
napi_deferred deferred,
1396+
leveldb::Slice key,
1397+
const bool fillCache,
1398+
ExplicitSnapshot* snapshot
1399+
) : PriorityWorker(env, database, deferred, "classic_level.db.has"),
1400+
key_(key) {
1401+
iterator_ = new BaseIterator(
1402+
database,
1403+
// Range options (not relevant)
1404+
false, NULL, NULL, NULL, NULL, -1,
1405+
fillCache,
1406+
snapshot
1407+
);
1408+
}
1409+
1410+
~HasWorker () {
1411+
DisposeSliceBuffer(key_);
1412+
delete iterator_;
1413+
}
1414+
1415+
void DoExecute () override {
1416+
// LevelDB has no Has() method so use an iterator
1417+
result_ = iterator_->SeekExact(key_);
1418+
SetStatus(iterator_->Status());
1419+
iterator_->CloseIterator();
1420+
}
1421+
1422+
void HandleOKCallback (napi_env env, napi_deferred deferred) override {
1423+
napi_value resultBoolean;
1424+
napi_get_boolean(env, result_, &resultBoolean);
1425+
napi_resolve_deferred(env, deferred, resultBoolean);
1426+
}
1427+
1428+
private:
1429+
leveldb::Slice key_;
1430+
BaseIterator* iterator_;
1431+
bool result_;
1432+
};
1433+
1434+
/**
1435+
* Check if the database has an entry with the given key.
1436+
*/
1437+
NAPI_METHOD(db_has) {
1438+
NAPI_ARGV(4);
1439+
NAPI_DB_CONTEXT();
1440+
NAPI_PROMISE();
1441+
1442+
leveldb::Slice key = ToSlice(env, argv[1]);
1443+
const bool fillCache = BooleanValue(env, argv[2], true);
1444+
1445+
ExplicitSnapshot* snapshot = NULL;
1446+
napi_get_value_external(env, argv[3], (void**)&snapshot);
1447+
1448+
HasWorker* worker = new HasWorker(
1449+
env, database, deferred, key, fillCache, snapshot
1450+
);
1451+
1452+
worker->Queue(env);
1453+
return promise;
1454+
}
1455+
13791456
/**
13801457
* Worker class for getting many values.
13811458
*/
@@ -1481,6 +1558,78 @@ NAPI_METHOD(db_get_many) {
14811558
return promise;
14821559
}
14831560

1561+
/**
1562+
* Worker class for db.hasMany().
1563+
*/
1564+
struct HasManyWorker final : public PriorityWorker {
1565+
HasManyWorker(
1566+
napi_env env,
1567+
Database* database,
1568+
std::vector<std::string> keys,
1569+
napi_deferred deferred,
1570+
uint32_t* bitset,
1571+
const bool fillCache,
1572+
ExplicitSnapshot* snapshot
1573+
) : PriorityWorker(env, database, deferred, "classic_level.has.many"),
1574+
keys_(std::move(keys)),
1575+
bitset_(bitset) {
1576+
iterator_ = new BaseIterator(
1577+
database,
1578+
// Range options (not relevant)
1579+
false, NULL, NULL, NULL, NULL, -1,
1580+
fillCache,
1581+
snapshot
1582+
);
1583+
}
1584+
1585+
~HasManyWorker () {
1586+
delete iterator_;
1587+
}
1588+
1589+
void DoExecute () override {
1590+
for (size_t i = 0; i != keys_.size(); i++) {
1591+
leveldb::Slice target = leveldb::Slice(keys_[i]);
1592+
1593+
if (iterator_->SeekExact(target)) {
1594+
bitset_[i >> 5] |= 1 << (i & 31); // Set bit
1595+
}
1596+
}
1597+
1598+
SetStatus(iterator_->Status());
1599+
iterator_->CloseIterator();
1600+
}
1601+
1602+
private:
1603+
const std::vector<std::string> keys_;
1604+
uint32_t* bitset_;
1605+
BaseIterator* iterator_;
1606+
};
1607+
1608+
/**
1609+
* Check if the database has entries with the given keys.
1610+
*/
1611+
NAPI_METHOD(db_has_many) {
1612+
NAPI_ARGV(5);
1613+
NAPI_DB_CONTEXT();
1614+
NAPI_PROMISE();
1615+
1616+
const auto keys = KeyArray(env, argv[1]);
1617+
const bool fillCache = BooleanValue(env, argv[2], true);
1618+
1619+
ExplicitSnapshot* snapshot = NULL;
1620+
napi_get_value_external(env, argv[3], (void**)&snapshot);
1621+
1622+
uint32_t* bitset = NULL;
1623+
NAPI_STATUS_THROWS(napi_get_arraybuffer_info(env, argv[4], (void**)&bitset, NULL));
1624+
1625+
HasManyWorker* worker = new HasManyWorker(
1626+
env, database, keys, deferred, bitset, fillCache, snapshot
1627+
);
1628+
1629+
worker->Queue(env);
1630+
return promise;
1631+
}
1632+
14841633
/**
14851634
* Worker class for deleting a value from a database.
14861635
*/
@@ -2280,6 +2429,8 @@ NAPI_INIT() {
22802429
NAPI_EXPORT_FUNCTION(db_put);
22812430
NAPI_EXPORT_FUNCTION(db_get);
22822431
NAPI_EXPORT_FUNCTION(db_get_many);
2432+
NAPI_EXPORT_FUNCTION(db_has);
2433+
NAPI_EXPORT_FUNCTION(db_has_many);
22832434
NAPI_EXPORT_FUNCTION(db_del);
22842435
NAPI_EXPORT_FUNCTION(db_clear);
22852436
NAPI_EXPORT_FUNCTION(db_approximate_size);

index.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class ClassicLevel extends AbstractLevel {
2222
utf8: true,
2323
view: true
2424
},
25+
has: true,
2526
createIfMissing: true,
2627
errorIfExists: true,
2728
explicitSnapshots: true,
@@ -77,6 +78,39 @@ class ClassicLevel extends AbstractLevel {
7778
)
7879
}
7980

81+
async _has (key, options) {
82+
return binding.db_has(
83+
this[kContext],
84+
key,
85+
options.fillCache,
86+
options.snapshot?.[kContext]
87+
)
88+
}
89+
90+
async _hasMany (keys, options) {
91+
// Use a space-efficient bitset (with 32-bit words) to contain found keys
92+
const wordCount = (keys.length + 32) >>> 5
93+
const buffer = new ArrayBuffer(wordCount * 4)
94+
const bitset = new Uint32Array(buffer)
95+
96+
await binding.db_has_many(
97+
this[kContext],
98+
keys,
99+
options.fillCache,
100+
options.snapshot?.[kContext],
101+
buffer
102+
)
103+
104+
const values = new Array(keys.length)
105+
106+
for (let i = 0; i < values.length; i++) {
107+
// Check if bit is set
108+
values[i] = (bitset[i >>> 5] & (1 << (i & 31))) !== 0
109+
}
110+
111+
return values
112+
}
113+
80114
async _del (key, options) {
81115
return binding.db_del(this[kContext], key, options)
82116
}

0 commit comments

Comments
 (0)