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]
```

Depends on a pending `abstract-level` release, and lacks support of
explicit snapshots which should be implemented after #110 lands.

Ref: Level/community#142
  • Loading branch information
vweevers committed Dec 29, 2024
1 parent d142686 commit a28189d
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 1 deletion.
151 changes: 151 additions & 0 deletions binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1306,6 +1306,75 @@ 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
) : PriorityWorker(env, database, deferred, "classic_level.db.has"),
key_(key) {
options_.fill_cache = fillCache;
options_.snapshot = database->NewSnapshot();
}

~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);
}

database_->ReleaseSnapshot(options_.snapshot);
}

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_;
};

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

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

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

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

/**
* Worker class for getting many values.
*/
Expand Down Expand Up @@ -1391,6 +1460,86 @@ 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
) : PriorityWorker(env, database, deferred, "classic_level.has.many"),
keys_(std::move(keys)) {
options_.fill_cache = fillCache;
options_.snapshot = database->NewSnapshot();
}

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;
}
}

database_->ReleaseSnapshot(options_.snapshot);
}

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_;
};

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

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

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

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

/**
* Worker class for deleting a value from a database.
*/
Expand Down Expand Up @@ -2182,6 +2331,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
15 changes: 15 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class ClassicLevel extends AbstractLevel {
view: true
},
seek: true,
has: true,
createIfMissing: true,
errorIfExists: true,
additionalMethods: {
Expand Down Expand Up @@ -71,6 +72,20 @@ class ClassicLevel extends AbstractLevel {
return binding.db_get_many(this[kContext], keys, options)
}

// TODO: snapshots
async _has (key, options) {
return binding.db_has(
this[kContext],
key,
options.fillCache
)
}

// TODO: snapshots
async _hasMany (keys, options) {
return binding.db_has_many(this[kContext], keys, options.fillCache)
}

async _del (key, options) {
return binding.db_del(this[kContext], key, options)
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"prebuild-win32-x64": "prebuildify -t 18.20.4 --napi --strip"
},
"dependencies": {
"abstract-level": "^2.0.0",
"abstract-level": "github:Level/abstract-level",
"module-error": "^1.0.1",
"napi-macros": "^2.2.2",
"node-gyp-build": "^4.3.0"
Expand Down

0 comments on commit a28189d

Please sign in to comment.