Skip to content

Commit 6aeb739

Browse files
authored
Implement has() and hasMany() (#111)
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 e310ffd commit 6aeb739

File tree

3 files changed

+212
-4
lines changed

3 files changed

+212
-4
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.d.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import {
44
AbstractOpenOptions,
55
AbstractGetOptions,
66
AbstractGetManyOptions,
7+
AbstractHasOptions,
8+
AbstractHasManyOptions,
79
AbstractPutOptions,
810
AbstractDelOptions,
911
AbstractBatchOperation,
@@ -16,6 +18,7 @@ import {
1618
AbstractKeyIteratorOptions,
1719
AbstractValueIterator,
1820
AbstractValueIteratorOptions,
21+
AbstractSnapshot,
1922
Transcoder
2023
} from 'abstract-level'
2124

@@ -54,6 +57,12 @@ declare class ClassicLevel<KDefault = string, VDefault = string>
5457
getMany (keys: KDefault[]): Promise<(VDefault | undefined)[]>
5558
getMany<K = KDefault, V = VDefault> (keys: K[], options: GetManyOptions<K, V>): Promise<(V | undefined)[]>
5659

60+
has (key: KDefault): Promise<boolean>
61+
has<K = KDefault> (key: K, options: HasOptions<K>): Promise<boolean>
62+
63+
hasMany (keys: KDefault[]): Promise<boolean[]>
64+
hasMany<K = KDefault> (keys: K[], options: HasManyOptions<K>): Promise<boolean[]>
65+
5766
put (key: KDefault, value: VDefault): Promise<void>
5867
put<K = KDefault, V = VDefault> (key: K, value: V, options: PutOptions<K, V>): Promise<void>
5968

@@ -73,6 +82,8 @@ declare class ClassicLevel<KDefault = string, VDefault = string>
7382
values (): ValueIterator<typeof this, KDefault, VDefault>
7483
values<K = KDefault, V = VDefault> (options: ValueIteratorOptions<K, V>): ValueIterator<typeof this, K, V>
7584

85+
snapshot (options?: any | undefined): Snapshot
86+
7687
/**
7788
* Get the approximate number of bytes of file system space used by the range
7889
* `[start..end)`.
@@ -198,15 +209,15 @@ export interface OpenOptions extends AbstractOpenOptions {
198209
/**
199210
* Allows multi-threaded access to a single DB instance for sharing a DB
200211
* across multiple worker threads within the same process.
201-
*
212+
*
202213
* @defaultValue `false`
203214
*/
204215
multithreading?: boolean | undefined
205216
}
206217

207218
/**
208-
* Additional options for the {@link ClassicLevel.get} and {@link ClassicLevel.getMany}
209-
* methods.
219+
* Additional options for the {@link ClassicLevel.get}, {@link ClassicLevel.getMany},
220+
* {@link ClassicLevel.has} and {@link ClassicLevel.hasMany} methods.
210221
*/
211222
declare interface ReadOptions {
212223
/**
@@ -229,6 +240,16 @@ export interface GetOptions<K, V> extends AbstractGetOptions<K, V>, ReadOptions
229240
*/
230241
export interface GetManyOptions<K, V> extends AbstractGetManyOptions<K, V>, ReadOptions {}
231242

243+
/**
244+
* Options for the {@link ClassicLevel.has} method.
245+
*/
246+
export interface HasOptions<K> extends AbstractHasOptions<K>, ReadOptions {}
247+
248+
/**
249+
* Options for the {@link ClassicLevel.hasMany} method.
250+
*/
251+
export interface HasManyOptions<K> extends AbstractHasManyOptions<K>, ReadOptions {}
252+
232253
/**
233254
* Additional options for the {@link ClassicLevel.iterator}, {@link ClassicLevel.keys}
234255
* and {@link ClassicLevel.values} methods.
@@ -315,3 +336,5 @@ export type ValueIterator<TDatabase, K, V> = AbstractValueIterator<TDatabase, K,
315336
export type IteratorOptions<K, V> = AbstractIteratorOptions<K, V> & AdditionalIteratorOptions
316337
export type KeyIteratorOptions<K> = AbstractKeyIteratorOptions<K> & AdditionalIteratorOptions
317338
export type ValueIteratorOptions<K, V> = AbstractValueIteratorOptions<K, V> & AdditionalIteratorOptions
339+
340+
export type Snapshot = AbstractSnapshot

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)