Skip to content

Commit

Permalink
Implement has() and hasMany()
Browse files Browse the repository at this point in the history
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
  • Loading branch information
vweevers committed Jan 26, 2025
1 parent 15eb289 commit e3a328c
Show file tree
Hide file tree
Showing 2 changed files with 198 additions and 0 deletions.
179 changes: 179 additions & 0 deletions binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1376,6 +1376,89 @@ 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) {
options_.fill_cache = fillCache;

if (snapshot == NULL) {
implicitSnapshot_ = database->NewSnapshot();
options_.snapshot = implicitSnapshot_;
} else {
implicitSnapshot_ = NULL;
options_.snapshot = snapshot->nut;
}
}

~HasWorker () {
DisposeSliceBuffer(key_);
}

void DoExecute () override {
// LevelDB (and our wrapper) has no Has() method
std::string value;
leveldb::Status status = database_->Get(options_, key_, value);

if (status.ok()) {
result_ = true;
SetStatus(status);
} else if (status.IsNotFound()) {
result_ = false;
SetStatus(leveldb::Status::OK());
} else {
SetStatus(status);
}

if (implicitSnapshot_) {
database_->ReleaseSnapshot(implicitSnapshot_);
}
}

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::ReadOptions options_;
leveldb::Slice key_;
bool result_;
const leveldb::Snapshot* implicitSnapshot_;
};

/**
* 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 +1564,100 @@ 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,
const bool fillCache,
ExplicitSnapshot* snapshot
) : PriorityWorker(env, database, deferred, "classic_level.has.many"),
keys_(std::move(keys)) {
options_.fill_cache = fillCache;

if (snapshot == NULL) {
implicitSnapshot_ = database->NewSnapshot();
options_.snapshot = implicitSnapshot_;
} else {
implicitSnapshot_ = NULL;
options_.snapshot = snapshot->nut;
}
}

void DoExecute () override {
cache_.reserve(keys_.size());

for (const std::string& key: keys_) {
std::string value;
leveldb::Status status = database_->Get(options_, key, value);

if (status.ok()) {
cache_.push_back(true);
} else if (status.IsNotFound()) {
cache_.push_back(false);
} else {
SetStatus(status);
break;
}
}

if (implicitSnapshot_) {
database_->ReleaseSnapshot(implicitSnapshot_);
}
}

void HandleOKCallback (napi_env env, napi_deferred deferred) override {
size_t size = cache_.size();

napi_value array;
napi_value booleanTrue;
napi_value booleanFalse;

napi_create_array_with_length(env, size, &array);
napi_get_boolean(env, true, &booleanTrue);
napi_get_boolean(env, false, &booleanFalse);

for (size_t i = 0; i < size; i++) {
auto value = cache_[i] ? booleanTrue : booleanFalse;
napi_set_element(env, array, static_cast<uint32_t>(i), value);
}

napi_resolve_deferred(env, deferred, array);
}

private:
leveldb::ReadOptions options_;
const std::vector<std::string> keys_;
std::vector<bool> cache_;
const leveldb::Snapshot* implicitSnapshot_;
};

/**
* Check if the database has entries with the given keys.
*/
NAPI_METHOD(db_has_many) {
NAPI_ARGV(4);
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);

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

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

/**
* Worker class for deleting a value from a database.
*/
Expand Down Expand Up @@ -2280,6 +2457,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
19 changes: 19 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,24 @@ class ClassicLevel extends AbstractLevel {
)
}

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

async _hasMany (keys, options) {
return binding.db_has_many(
this[kContext],
keys,
options.fillCache,
options.snapshot?.[kContext]
)
}

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

0 comments on commit e3a328c

Please sign in to comment.