Skip to content
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

Implement has() and hasMany() #111

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
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);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be faster to use an iterator (if the LevelDB iterator lazily reads values thus allowing us to skip that; not sure) but it would add some complexity so I'll save that for a future PR (or for anyone who wants to take a stab at that).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Get() copies the value while an iterator would not. So should indeed use an iterator.


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
Loading