Skip to content

Commit

Permalink
Implement open()
Browse files Browse the repository at this point in the history
  • Loading branch information
ralphtheninja committed Dec 15, 2018
1 parent 11797d8 commit 71e7823
Show file tree
Hide file tree
Showing 5 changed files with 277 additions and 8 deletions.
11 changes: 8 additions & 3 deletions leveldown.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const util = require('util')
const AbstractLevelDOWN = require('abstract-leveldown').AbstractLevelDOWN
const binding = require('bindings')('leveldown').leveldown
const binding = require('node-gyp-build')(__dirname)
const ChainedBatch = require('./chained-batch')
const Iterator = require('./iterator')

Expand All @@ -16,13 +16,18 @@ function LevelDOWN (location) {
AbstractLevelDOWN.call(this)

this.location = location
this.binding = binding(location)
this.dbContext = binding.leveldown()
}

util.inherits(LevelDOWN, AbstractLevelDOWN)

LevelDOWN.prototype._open = function (options, callback) {
this.binding.open(options, callback)
binding.open(
this.dbContext,
this.location,
options,
callback
)
}

LevelDOWN.prototype._close = function (callback) {
Expand Down
249 changes: 244 additions & 5 deletions napi/leveldown.cc
Original file line number Diff line number Diff line change
@@ -1,14 +1,253 @@
#include <napi-macros.h>
#include <node_api.h>
#include "napi.h"
#include <leveldb/db.h>
#include <leveldb/cache.h>
#include <leveldb/filter_policy.h>

// TODO add to napi-macros.h
#define NAPI_RETURN_UNDEFINED() \
return 0;
/**
* Owns the LevelDB storage together with cache and filter
* policy.
*/
struct DbContext {
DbContext (napi_env env)
: env_(env), db_(NULL), blockCache_(NULL), filterPolicy_(NULL) {}

~DbContext () {
if (db_ != NULL) {
delete db_;
db_ = NULL;
}
}

napi_env env_;
leveldb::DB* db_;
leveldb::Cache* blockCache_;
const leveldb::FilterPolicy* filterPolicy_;
};

/**
* Runs when a DbContext is garbage collected.
*/
static void FinalizeDbContext(napi_env env, void* data, void* hint) {
if (data) {
delete (DbContext*)data;
}
}

/**
* Returns a context object for a database.
* E.g. `var dbContext = leveldown()`
*/
NAPI_METHOD(leveldown) {
DbContext* dbContext = new DbContext(env);

napi_value result;
NAPI_STATUS_THROWS(napi_create_external(env, dbContext,
FinalizeDbContext,
NULL, &result));
return result;
}

// TODO BooleanProperty() and Uint32Property() can be refactored

/**
* Returns a boolean property 'key' from 'obj'.
* Returns 'DEFAULT' if the property doesn't exist.
*/
static bool BooleanProperty(napi_env env,
napi_value obj,
const char* key,
bool DEFAULT)
{
bool exists = false;
napi_value _key;
napi_create_string_utf8(env, key, strlen(key), &_key);
napi_has_property(env, obj, _key, &exists);

if (exists) {
napi_value value;
napi_get_property(env, obj, _key, &value);
bool result;
napi_get_value_bool(env, value, &result);
return result;
}

return DEFAULT;
}

/**
* Returns a uint32 property 'key' from 'obj'
* Returns 'DEFAULT' if the property doesn't exist.
*/
static uint32_t Uint32Property(napi_env env,
napi_value obj,
const char* key,
uint32_t DEFAULT)
{
bool exists = false;
napi_value _key;
napi_create_string_utf8(env, key, strlen(key), &_key);
napi_has_property(env, obj, _key, &exists);

if (exists) {
napi_value value;
napi_get_property(env, obj, _key, &value);
uint32_t result;
napi_get_value_uint32(env, value, &result);
return result;
}

return DEFAULT;
}

/**
* Worker class for opening the database
*/
struct OpenWorker {
OpenWorker(napi_env env,
DbContext* dbContext,
char* location,
bool createIfMissing,
bool errorIfExists,
bool compression,
uint32_t writeBufferSize,
uint32_t blockSize,
uint32_t maxOpenFiles,
uint32_t blockRestartInterval,
uint32_t maxFileSize,
napi_value callback)
: env_(env), dbContext_(dbContext), location_(location) {
options_.block_cache = dbContext->blockCache_;
options_.filter_policy = dbContext->filterPolicy_;
options_.create_if_missing = createIfMissing;
options_.error_if_exists = errorIfExists;
options_.compression = compression
? leveldb::kSnappyCompression
: leveldb::kNoCompression;
options_.write_buffer_size = writeBufferSize;
options_.block_size = blockSize;
options_.max_open_files = maxOpenFiles;
options_.block_restart_interval = blockRestartInterval;
options_.max_file_size = maxFileSize;

// Create reference to callback with ref count set to one.
// TODO move to base class constructor
napi_create_reference(env_, callback, 1, &callbackRef_);
napi_value asyncResourceName;
napi_create_string_utf8(env_, "leveldown::open",
NAPI_AUTO_LENGTH,
&asyncResourceName);
napi_create_async_work(env_, callback,
asyncResourceName,
OpenWorker::Execute,
OpenWorker::Complete,
this,
&asyncWork_);
}

~OpenWorker() {
free(location_);
// TODO move to base class destructor
napi_delete_reference(env_, callbackRef_);
napi_delete_async_work(env_, asyncWork_);
}

// TODO move to base class
void Queue() {
napi_queue_async_work(env_, asyncWork_);
}

static void Execute(napi_env env, void* data) {
OpenWorker* self = (OpenWorker*)data;
DbContext* dbContext = self->dbContext_;
self->status_ = leveldb::DB::Open(self->options_,
self->location_,
&dbContext->db_);
}

static void Complete(napi_env env, napi_status status, void* data) {
OpenWorker* self = (OpenWorker*)data;

// TODO most of the things below can be moved to a
// base class, all operations either calling back with
// NULL or an error have identical logic.

const int argc = 1;
napi_value argv[argc];

napi_value global;
napi_get_global(env, &global);
napi_value callback;
napi_get_reference_value(env, self->callbackRef_, &callback);

if (self->status_.ok()) {
napi_get_null(env, &argv[0]);
} else {
const char* str = self->status_.ToString().c_str();
napi_value msg;
napi_create_string_utf8(env, str, strlen(str), &msg);
napi_create_error(env, NULL, msg, &argv[0]);
}

napi_call_function(env, global, callback, argc, argv, NULL);

delete self;
}

// TODO move to base class
napi_env env_;
napi_ref callbackRef_;
napi_async_work asyncWork_;
DbContext* dbContext_;
leveldb::Status status_;

leveldb::Options options_;
char* location_;
};

/**
* Opens a database.
*/
NAPI_METHOD(open) {
NAPI_ARGV(4);
NAPI_DB_CONTEXT();
NAPI_ARGV_UTF8_MALLOC(location, 1);

// Options object and properties.
napi_value options = argv[2];
bool createIfMissing = BooleanProperty(env, options, "createIfMissing", true);
bool errorIfExists = BooleanProperty(env, options, "errorIfExists", false);
bool compression = BooleanProperty(env, options, "compression", true);

uint32_t cacheSize = Uint32Property(env, options, "cacheSize", 8 << 20);
uint32_t writeBufferSize = Uint32Property(env, options , "writeBufferSize" , 4 << 20);
uint32_t blockSize = Uint32Property(env, options, "blockSize", 4096);
uint32_t maxOpenFiles = Uint32Property(env, options, "maxOpenFiles", 1000);
uint32_t blockRestartInterval = Uint32Property(env, options,
"blockRestartInterval", 16);
uint32_t maxFileSize = Uint32Property(env, options, "maxFileSize", 2 << 20);

// TODO clean these up in close()
dbContext->blockCache_ = leveldb::NewLRUCache(cacheSize);
dbContext->filterPolicy_ = leveldb::NewBloomFilterPolicy(10);

napi_value callback = argv[3];
OpenWorker* worker = new OpenWorker(env,
dbContext,
location,
createIfMissing,
errorIfExists,
compression,
writeBufferSize,
blockSize,
maxOpenFiles,
blockRestartInterval,
maxFileSize,
callback);
worker->Queue();
NAPI_RETURN_UNDEFINED();
}

NAPI_INIT() {
NAPI_EXPORT_FUNCTION(leveldown);
NAPI_EXPORT_FUNCTION(open);
}
10 changes: 10 additions & 0 deletions napi/napi.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#include <napi-macros.h>
#include <node_api.h>

#define NAPI_DB_CONTEXT() \
DbContext* dbContext = NULL; \
NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&dbContext));

// TODO move to napi-macros.h
#define NAPI_RETURN_UNDEFINED() \
return 0;
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"scripts": {
"install": "node-gyp-build",
"test": "standard && verify-travis-appveyor && nyc tape test/*-test.js && prebuild-ci",
"test2": "node test",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"rebuild": "prebuild --compile",
"prebuild": "prebuild --all --strip --verbose",
Expand Down
14 changes: 14 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
require('./leveldown-test')
require('./abstract-leveldown-test')
// require('./approximate-size-test')
// require('./cleanup-hanging-iterators-test')
// require('./compact-range-test')
// require('./compression-test')
// require('./destroy-test')
// require('./getproperty-test')
// require('./iterator-gc-test')
// require('./iterator-recursion-test')
// require('./iterator-test')
// require('./port-libuv-fix-test')
// require('./repair-test')
// require('./segfault-test')

0 comments on commit 71e7823

Please sign in to comment.