Skip to content

Implement has() and hasMany() #111

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 152 additions & 1 deletion binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -882,7 +882,7 @@ struct BaseIterator {
if (reverse_ ? cmp > 0 : cmp < 0) {
Next();
}
} else {
} else { // TODO: can we skip this code path if not in reverse?
SeekToFirst();
if (dbIterator_->Valid()) {
int cmp = dbIterator_->key().compare(target);
Expand All @@ -893,6 +893,15 @@ struct BaseIterator {
}
}

/**
* Seek to an exact key.
*/
bool SeekExact (leveldb::Slice& target) {
didSeek_ = true;
dbIterator_->Seek(target);
return dbIterator_->Valid() && dbIterator_->key() == target;
}

void CloseIterator () {
if (!hasClosed_) {
hasClosed_ = true;
Expand Down Expand Up @@ -1376,6 +1385,74 @@ NAPI_METHOD(db_get) {
return promise;
}

/**
* Worker class for db.has().
*/
struct HasWorker final : public PriorityWorker {
HasWorker(
napi_env env,
Database* database,
napi_deferred deferred,
leveldb::Slice key,
const bool fillCache,
ExplicitSnapshot* snapshot
) : PriorityWorker(env, database, deferred, "classic_level.db.has"),
key_(key) {
iterator_ = new BaseIterator(
database,
// Range options (not relevant)
false, NULL, NULL, NULL, NULL, -1,
fillCache,
snapshot
);
}

~HasWorker () {
DisposeSliceBuffer(key_);
delete iterator_;
}

void DoExecute () override {
// LevelDB has no Has() method so use an iterator
result_ = iterator_->SeekExact(key_);
SetStatus(iterator_->Status());
iterator_->CloseIterator();
}

void HandleOKCallback (napi_env env, napi_deferred deferred) override {
napi_value resultBoolean;
napi_get_boolean(env, result_, &resultBoolean);
napi_resolve_deferred(env, deferred, resultBoolean);
}

private:
leveldb::Slice key_;
BaseIterator* iterator_;
bool result_;
};

/**
* Check if the database has an entry with the given key.
*/
NAPI_METHOD(db_has) {
NAPI_ARGV(4);
NAPI_DB_CONTEXT();
NAPI_PROMISE();

leveldb::Slice key = ToSlice(env, argv[1]);
const bool fillCache = BooleanValue(env, argv[2], true);

ExplicitSnapshot* snapshot = NULL;
napi_get_value_external(env, argv[3], (void**)&snapshot);

HasWorker* worker = new HasWorker(
env, database, deferred, key, fillCache, snapshot
);

worker->Queue(env);
return promise;
}

/**
* Worker class for getting many values.
*/
Expand Down Expand Up @@ -1481,6 +1558,78 @@ NAPI_METHOD(db_get_many) {
return promise;
}

/**
* Worker class for db.hasMany().
*/
struct HasManyWorker final : public PriorityWorker {
HasManyWorker(
napi_env env,
Database* database,
std::vector<std::string> keys,
napi_deferred deferred,
uint32_t* bitset,
const bool fillCache,
ExplicitSnapshot* snapshot
) : PriorityWorker(env, database, deferred, "classic_level.has.many"),
keys_(std::move(keys)),
bitset_(bitset) {
iterator_ = new BaseIterator(
database,
// Range options (not relevant)
false, NULL, NULL, NULL, NULL, -1,
fillCache,
snapshot
);
}

~HasManyWorker () {
delete iterator_;
}

void DoExecute () override {
for (size_t i = 0; i != keys_.size(); i++) {
leveldb::Slice target = leveldb::Slice(keys_[i]);

if (iterator_->SeekExact(target)) {
bitset_[i >> 5] |= 1 << (i & 31); // Set bit
}
}

SetStatus(iterator_->Status());
iterator_->CloseIterator();
}

private:
const std::vector<std::string> keys_;
uint32_t* bitset_;
BaseIterator* iterator_;
};

/**
* Check if the database has entries with the given keys.
*/
NAPI_METHOD(db_has_many) {
NAPI_ARGV(5);
NAPI_DB_CONTEXT();
NAPI_PROMISE();

const auto keys = KeyArray(env, argv[1]);
const bool fillCache = BooleanValue(env, argv[2], true);

ExplicitSnapshot* snapshot = NULL;
napi_get_value_external(env, argv[3], (void**)&snapshot);

uint32_t* bitset = NULL;
NAPI_STATUS_THROWS(napi_get_arraybuffer_info(env, argv[4], (void**)&bitset, NULL));

HasManyWorker* worker = new HasManyWorker(
env, database, keys, deferred, bitset, fillCache, snapshot
);

worker->Queue(env);
return promise;
}

/**
* Worker class for deleting a value from a database.
*/
Expand Down Expand Up @@ -2280,6 +2429,8 @@ NAPI_INIT() {
NAPI_EXPORT_FUNCTION(db_put);
NAPI_EXPORT_FUNCTION(db_get);
NAPI_EXPORT_FUNCTION(db_get_many);
NAPI_EXPORT_FUNCTION(db_has);
NAPI_EXPORT_FUNCTION(db_has_many);
NAPI_EXPORT_FUNCTION(db_del);
NAPI_EXPORT_FUNCTION(db_clear);
NAPI_EXPORT_FUNCTION(db_approximate_size);
Expand Down
29 changes: 26 additions & 3 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
AbstractOpenOptions,
AbstractGetOptions,
AbstractGetManyOptions,
AbstractHasOptions,
AbstractHasManyOptions,
AbstractPutOptions,
AbstractDelOptions,
AbstractBatchOperation,
Expand All @@ -16,6 +18,7 @@ import {
AbstractKeyIteratorOptions,
AbstractValueIterator,
AbstractValueIteratorOptions,
AbstractSnapshot,
Transcoder
} from 'abstract-level'

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

has (key: KDefault): Promise<boolean>
has<K = KDefault> (key: K, options: HasOptions<K>): Promise<boolean>

hasMany (keys: KDefault[]): Promise<boolean[]>
hasMany<K = KDefault> (keys: K[], options: HasManyOptions<K>): Promise<boolean[]>

put (key: KDefault, value: VDefault): Promise<void>
put<K = KDefault, V = VDefault> (key: K, value: V, options: PutOptions<K, V>): Promise<void>

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

snapshot (options?: any | undefined): Snapshot

/**
* Get the approximate number of bytes of file system space used by the range
* `[start..end)`.
Expand Down Expand Up @@ -198,15 +209,15 @@ export interface OpenOptions extends AbstractOpenOptions {
/**
* Allows multi-threaded access to a single DB instance for sharing a DB
* across multiple worker threads within the same process.
*
*
* @defaultValue `false`
*/
multithreading?: boolean | undefined
}

/**
* Additional options for the {@link ClassicLevel.get} and {@link ClassicLevel.getMany}
* methods.
* Additional options for the {@link ClassicLevel.get}, {@link ClassicLevel.getMany},
* {@link ClassicLevel.has} and {@link ClassicLevel.hasMany} methods.
*/
declare interface ReadOptions {
/**
Expand All @@ -229,6 +240,16 @@ export interface GetOptions<K, V> extends AbstractGetOptions<K, V>, ReadOptions
*/
export interface GetManyOptions<K, V> extends AbstractGetManyOptions<K, V>, ReadOptions {}

/**
* Options for the {@link ClassicLevel.has} method.
*/
export interface HasOptions<K> extends AbstractHasOptions<K>, ReadOptions {}

/**
* Options for the {@link ClassicLevel.hasMany} method.
*/
export interface HasManyOptions<K> extends AbstractHasManyOptions<K>, ReadOptions {}

/**
* Additional options for the {@link ClassicLevel.iterator}, {@link ClassicLevel.keys}
* and {@link ClassicLevel.values} methods.
Expand Down Expand Up @@ -315,3 +336,5 @@ export type ValueIterator<TDatabase, K, V> = AbstractValueIterator<TDatabase, K,
export type IteratorOptions<K, V> = AbstractIteratorOptions<K, V> & AdditionalIteratorOptions
export type KeyIteratorOptions<K> = AbstractKeyIteratorOptions<K> & AdditionalIteratorOptions
export type ValueIteratorOptions<K, V> = AbstractValueIteratorOptions<K, V> & AdditionalIteratorOptions

export type Snapshot = AbstractSnapshot
34 changes: 34 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class ClassicLevel extends AbstractLevel {
utf8: true,
view: true
},
has: true,
createIfMissing: true,
errorIfExists: true,
explicitSnapshots: true,
Expand Down Expand Up @@ -77,6 +78,39 @@ class ClassicLevel extends AbstractLevel {
)
}

async _has (key, options) {
return binding.db_has(
this[kContext],
key,
options.fillCache,
options.snapshot?.[kContext]
)
}

async _hasMany (keys, options) {
// Use a space-efficient bitset (with 32-bit words) to contain found keys
const wordCount = (keys.length + 32) >>> 5
const buffer = new ArrayBuffer(wordCount * 4)
const bitset = new Uint32Array(buffer)

await binding.db_has_many(
this[kContext],
keys,
options.fillCache,
options.snapshot?.[kContext],
buffer
)

const values = new Array(keys.length)

for (let i = 0; i < values.length; i++) {
// Check if bit is set
values[i] = (bitset[i >>> 5] & (1 << (i & 31))) !== 0
}

return values
}

async _del (key, options) {
return binding.db_del(this[kContext], key, options)
}
Expand Down