2171 lines
		
	
	
		
			58 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			2171 lines
		
	
	
		
			58 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
#define NAPI_VERSION 3
 | 
						|
 | 
						|
#include <napi-macros.h>
 | 
						|
#include <node_api.h>
 | 
						|
#include <assert.h>
 | 
						|
 | 
						|
#include <leveldb/db.h>
 | 
						|
#include <leveldb/write_batch.h>
 | 
						|
#include <leveldb/cache.h>
 | 
						|
#include <leveldb/filter_policy.h>
 | 
						|
 | 
						|
#include <map>
 | 
						|
#include <vector>
 | 
						|
#include <mutex>
 | 
						|
 | 
						|
/**
 | 
						|
 * Forward declarations.
 | 
						|
 */
 | 
						|
struct Database;
 | 
						|
struct Iterator;
 | 
						|
static void iterator_close_do (napi_env env, Iterator* iterator, napi_value cb);
 | 
						|
static leveldb::Status threadsafe_open(const leveldb::Options &options,
 | 
						|
                                       bool multithreading,
 | 
						|
                                       Database &db_instance);
 | 
						|
static leveldb::Status threadsafe_close(Database &db_instance);
 | 
						|
 | 
						|
/**
 | 
						|
 * Global declarations for multi-threaded access. These are not context-aware
 | 
						|
 * by definition and is specifically to allow for cross thread access to the
 | 
						|
 * single database handle.
 | 
						|
 */
 | 
						|
struct LevelDbHandle
 | 
						|
{
 | 
						|
  leveldb::DB *db;
 | 
						|
  size_t open_handle_count;
 | 
						|
};
 | 
						|
static std::mutex handles_mutex;
 | 
						|
// only access this when protected by the handles_mutex!
 | 
						|
static std::map<std::string, LevelDbHandle> db_handles;
 | 
						|
 | 
						|
/**
 | 
						|
 * Macros.
 | 
						|
 */
 | 
						|
#define NAPI_DB_CONTEXT() \
 | 
						|
  Database* database = NULL; \
 | 
						|
  NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&database));
 | 
						|
 | 
						|
#define NAPI_ITERATOR_CONTEXT() \
 | 
						|
  Iterator* iterator = NULL; \
 | 
						|
  NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&iterator));
 | 
						|
 | 
						|
#define NAPI_BATCH_CONTEXT() \
 | 
						|
  Batch* batch = NULL; \
 | 
						|
  NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&batch));
 | 
						|
 | 
						|
#define NAPI_RETURN_UNDEFINED() \
 | 
						|
  return 0;
 | 
						|
 | 
						|
#define NAPI_UTF8_NEW(name, val)                \
 | 
						|
  size_t name##_size = 0;                                               \
 | 
						|
  NAPI_STATUS_THROWS(napi_get_value_string_utf8(env, val, NULL, 0, &name##_size)) \
 | 
						|
  char* name = new char[name##_size + 1];                               \
 | 
						|
  NAPI_STATUS_THROWS(napi_get_value_string_utf8(env, val, name, name##_size + 1, &name##_size)) \
 | 
						|
  name[name##_size] = '\0';
 | 
						|
 | 
						|
#define NAPI_ARGV_UTF8_NEW(name, i) \
 | 
						|
  NAPI_UTF8_NEW(name, argv[i])
 | 
						|
 | 
						|
// TODO: consider using encoding options instead of type checking
 | 
						|
#define LD_STRING_OR_BUFFER_TO_COPY(env, from, to)                      \
 | 
						|
  char* to##Ch_ = 0;                                                    \
 | 
						|
  size_t to##Sz_ = 0;                                                   \
 | 
						|
  if (IsString(env, from)) {                                            \
 | 
						|
    napi_get_value_string_utf8(env, from, NULL, 0, &to##Sz_);           \
 | 
						|
    to##Ch_ = new char[to##Sz_ + 1];                                    \
 | 
						|
    napi_get_value_string_utf8(env, from, to##Ch_, to##Sz_ + 1, &to##Sz_); \
 | 
						|
    to##Ch_[to##Sz_] = '\0';                                            \
 | 
						|
  } else if (IsBuffer(env, from)) {                                     \
 | 
						|
    char* buf = 0;                                                      \
 | 
						|
    napi_get_buffer_info(env, from, (void **)&buf, &to##Sz_);           \
 | 
						|
    to##Ch_ = new char[to##Sz_];                                        \
 | 
						|
    memcpy(to##Ch_, buf, to##Sz_);                                      \
 | 
						|
  } else {                                                              \
 | 
						|
    char* buf = 0;                                                      \
 | 
						|
    napi_typedarray_type type;                                          \
 | 
						|
    napi_status status = napi_get_typedarray_info(env, from, &type, &to##Sz_, (void **)&buf, NULL, NULL); \
 | 
						|
    if (status != napi_ok || type != napi_typedarray_type::napi_uint8_array) { \
 | 
						|
      /* TODO: refactor so that we can napi_throw_type_error() here */  \
 | 
						|
      to##Sz_ = 0;                                                      \
 | 
						|
      to##Ch_ = new char[to##Sz_];                                      \
 | 
						|
    } else {                                                            \
 | 
						|
      to##Ch_ = new char[to##Sz_];                                      \
 | 
						|
      memcpy(to##Ch_, buf, to##Sz_);                                    \
 | 
						|
    }                                                                   \
 | 
						|
  }
 | 
						|
 | 
						|
/*********************************************************************
 | 
						|
 * Helpers.
 | 
						|
 ********************************************************************/
 | 
						|
 | 
						|
/**
 | 
						|
 * Returns true if 'value' is a string.
 | 
						|
 */
 | 
						|
static bool IsString (napi_env env, napi_value value) {
 | 
						|
  napi_valuetype type;
 | 
						|
  napi_typeof(env, value, &type);
 | 
						|
  return type == napi_string;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Returns true if 'value' is a buffer.
 | 
						|
 */
 | 
						|
static bool IsBuffer (napi_env env, napi_value value) {
 | 
						|
  bool isBuffer;
 | 
						|
  napi_is_buffer(env, value, &isBuffer);
 | 
						|
  return isBuffer;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Returns true if 'value' is an object.
 | 
						|
 */
 | 
						|
static bool IsObject (napi_env env, napi_value value) {
 | 
						|
  napi_valuetype type;
 | 
						|
  napi_typeof(env, value, &type);
 | 
						|
  return type == napi_object;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Create an error object.
 | 
						|
 */
 | 
						|
static napi_value CreateError (napi_env env, const char* str) {
 | 
						|
  napi_value msg;
 | 
						|
  napi_create_string_utf8(env, str, strlen(str), &msg);
 | 
						|
  napi_value error;
 | 
						|
  napi_create_error(env, NULL, msg, &error);
 | 
						|
  return error;
 | 
						|
}
 | 
						|
 | 
						|
static napi_value CreateCodeError (napi_env env, const char* code, const char* msg) {
 | 
						|
  napi_value codeValue;
 | 
						|
  napi_create_string_utf8(env, code, strlen(code), &codeValue);
 | 
						|
  napi_value msgValue;
 | 
						|
  napi_create_string_utf8(env, msg, strlen(msg), &msgValue);
 | 
						|
  napi_value error;
 | 
						|
  napi_create_error(env, codeValue, msgValue, &error);
 | 
						|
  return error;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Returns true if 'obj' has a property 'key'.
 | 
						|
 */
 | 
						|
static bool HasProperty (napi_env env, napi_value obj, const char* key) {
 | 
						|
  bool has = false;
 | 
						|
  napi_has_named_property(env, obj, key, &has);
 | 
						|
  return has;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Returns a property in napi_value form.
 | 
						|
 */
 | 
						|
static napi_value GetProperty (napi_env env, napi_value obj, const char* key) {
 | 
						|
  napi_value value;
 | 
						|
  napi_get_named_property(env, obj, key, &value);
 | 
						|
  return value;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * 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) {
 | 
						|
  if (HasProperty(env, obj, key)) {
 | 
						|
    napi_value value = GetProperty(env, obj, key);
 | 
						|
    bool result;
 | 
						|
    napi_get_value_bool(env, value, &result);
 | 
						|
    return result;
 | 
						|
  }
 | 
						|
 | 
						|
  return DEFAULT;
 | 
						|
}
 | 
						|
 | 
						|
enum Encoding { buffer, utf8, view };
 | 
						|
 | 
						|
/**
 | 
						|
 * Returns internal Encoding enum matching the given encoding option.
 | 
						|
 */
 | 
						|
static Encoding GetEncoding (napi_env env, napi_value options, const char* option) {
 | 
						|
  napi_value value;
 | 
						|
  size_t copied;
 | 
						|
  char buf[2];
 | 
						|
 | 
						|
  if (napi_get_named_property(env, options, option, &value) == napi_ok &&
 | 
						|
    napi_get_value_string_utf8(env, value, buf, 2, &copied) == napi_ok && copied == 1) {
 | 
						|
    // Value is either "buffer", "utf8" or "view" so we only have to read the first char
 | 
						|
    switch (buf[0]) {
 | 
						|
      case 'b': return Encoding::buffer;
 | 
						|
      case 'v': return Encoding::view;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return Encoding::utf8;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * 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) {
 | 
						|
  if (HasProperty(env, obj, key)) {
 | 
						|
    napi_value value = GetProperty(env, obj, key);
 | 
						|
    uint32_t result;
 | 
						|
    napi_get_value_uint32(env, value, &result);
 | 
						|
    return result;
 | 
						|
  }
 | 
						|
 | 
						|
  return DEFAULT;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Returns a int32 property 'key' from 'obj'.
 | 
						|
 * Returns 'DEFAULT' if the property doesn't exist.
 | 
						|
 */
 | 
						|
static int Int32Property (napi_env env, napi_value obj, const char* key,
 | 
						|
                          int DEFAULT) {
 | 
						|
  if (HasProperty(env, obj, key)) {
 | 
						|
    napi_value value = GetProperty(env, obj, key);
 | 
						|
    int result;
 | 
						|
    napi_get_value_int32(env, value, &result);
 | 
						|
    return result;
 | 
						|
  }
 | 
						|
 | 
						|
  return DEFAULT;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Returns a string property 'key' from 'obj'.
 | 
						|
 * Returns empty string if the property doesn't exist.
 | 
						|
 */
 | 
						|
static std::string StringProperty (napi_env env, napi_value obj, const char* key) {
 | 
						|
  if (HasProperty(env, obj, key)) {
 | 
						|
    napi_value value = GetProperty(env, obj, key);
 | 
						|
    if (IsString(env, value)) {
 | 
						|
      size_t size = 0;
 | 
						|
      napi_get_value_string_utf8(env, value, NULL, 0, &size);
 | 
						|
 | 
						|
      char* buf = new char[size + 1];
 | 
						|
      napi_get_value_string_utf8(env, value, buf, size + 1, &size);
 | 
						|
      buf[size] = '\0';
 | 
						|
 | 
						|
      std::string result = buf;
 | 
						|
      delete [] buf;
 | 
						|
      return result;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return "";
 | 
						|
}
 | 
						|
 | 
						|
static void DisposeSliceBuffer (leveldb::Slice slice) {
 | 
						|
  if (!slice.empty()) delete [] slice.data();
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Convert a napi_value to a leveldb::Slice.
 | 
						|
 */
 | 
						|
static leveldb::Slice ToSlice (napi_env env, napi_value from) {
 | 
						|
  LD_STRING_OR_BUFFER_TO_COPY(env, from, to);
 | 
						|
  return leveldb::Slice(toCh_, toSz_);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Takes a Buffer, string or Uint8Array property 'name' from 'opts'.
 | 
						|
 * Returns null if the property does not exist.
 | 
						|
 */
 | 
						|
static std::string* RangeOption (napi_env env, napi_value opts, const char* name) {
 | 
						|
  if (HasProperty(env, opts, name)) {
 | 
						|
    napi_value value = GetProperty(env, opts, name);
 | 
						|
    // TODO: we can avoid a copy here
 | 
						|
    LD_STRING_OR_BUFFER_TO_COPY(env, value, to);
 | 
						|
    std::string* result = new std::string(toCh_, toSz_);
 | 
						|
    delete [] toCh_;
 | 
						|
    return result;
 | 
						|
  }
 | 
						|
 | 
						|
  return NULL;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Converts an array containing Buffer or string keys to a vector.
 | 
						|
 */
 | 
						|
static std::vector<std::string> KeyArray (napi_env env, napi_value arr) {
 | 
						|
  uint32_t length;
 | 
						|
  std::vector<std::string> result;
 | 
						|
 | 
						|
  if (napi_get_array_length(env, arr, &length) == napi_ok) {
 | 
						|
    result.reserve(length);
 | 
						|
 | 
						|
    for (uint32_t i = 0; i < length; i++) {
 | 
						|
      napi_value element;
 | 
						|
 | 
						|
      if (napi_get_element(env, arr, i, &element) == napi_ok) {
 | 
						|
        LD_STRING_OR_BUFFER_TO_COPY(env, element, to);
 | 
						|
        result.emplace_back(toCh_, toSz_);
 | 
						|
        delete [] toCh_;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return result;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Calls a function.
 | 
						|
 */
 | 
						|
static napi_status CallFunction (napi_env env,
 | 
						|
                                 napi_value callback,
 | 
						|
                                 const int argc,
 | 
						|
                                 napi_value* argv) {
 | 
						|
  napi_value global;
 | 
						|
  napi_get_global(env, &global);
 | 
						|
  return napi_call_function(env, global, callback, argc, argv, NULL);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Whether to yield entries, keys or values.
 | 
						|
 */
 | 
						|
enum Mode {
 | 
						|
  entries,
 | 
						|
  keys,
 | 
						|
  values
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Helper struct for caching and converting a key-value pair to napi_values.
 | 
						|
 */
 | 
						|
struct Entry {
 | 
						|
  Entry (const leveldb::Slice& key, const leveldb::Slice& value)
 | 
						|
    : key_(key.data(), key.size()),
 | 
						|
      value_(value.data(), value.size()) {}
 | 
						|
 | 
						|
  void ConvertByMode (napi_env env, Mode mode, const Encoding keyEncoding, const Encoding valueEncoding, napi_value& result) const {
 | 
						|
    if (mode == Mode::entries) {
 | 
						|
      napi_create_array_with_length(env, 2, &result);
 | 
						|
 | 
						|
      napi_value keyElement;
 | 
						|
      napi_value valueElement;
 | 
						|
 | 
						|
      Convert(env, &key_, keyEncoding, keyElement);
 | 
						|
      Convert(env, &value_, valueEncoding, valueElement);
 | 
						|
 | 
						|
      napi_set_element(env, result, 0, keyElement);
 | 
						|
      napi_set_element(env, result, 1, valueElement);
 | 
						|
    } else if (mode == Mode::keys) {
 | 
						|
      Convert(env, &key_, keyEncoding, result);
 | 
						|
    } else {
 | 
						|
      Convert(env, &value_, valueEncoding, result);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  static void Convert (napi_env env, const std::string* s, const Encoding encoding, napi_value& result) {
 | 
						|
    if (s == NULL) {
 | 
						|
      napi_get_undefined(env, &result);
 | 
						|
    } else if (encoding == Encoding::buffer || encoding == Encoding::view) {
 | 
						|
      napi_create_buffer_copy(env, s->size(), s->data(), NULL, &result);
 | 
						|
    } else {
 | 
						|
      napi_create_string_utf8(env, s->data(), s->size(), &result);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
private:
 | 
						|
  std::string key_;
 | 
						|
  std::string value_;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Base worker class. Handles the async work. Derived classes can override the
 | 
						|
 * following virtual methods (listed in the order in which they're called):
 | 
						|
 *
 | 
						|
 * - DoExecute (abstract, worker pool thread): main work
 | 
						|
 * - HandleOKCallback (main thread): call JS callback on success
 | 
						|
 * - HandleErrorCallback (main thread): call JS callback on error
 | 
						|
 * - DoFinally (main thread): do cleanup regardless of success
 | 
						|
 */
 | 
						|
struct BaseWorker {
 | 
						|
  // Note: storing env is discouraged as we'd end up using it in unsafe places.
 | 
						|
  BaseWorker (napi_env env,
 | 
						|
              Database* database,
 | 
						|
              napi_value callback,
 | 
						|
              const char* resourceName)
 | 
						|
    : database_(database), errMsg_(NULL) {
 | 
						|
    NAPI_STATUS_THROWS_VOID(napi_create_reference(env, callback, 1, &callbackRef_));
 | 
						|
    napi_value asyncResourceName;
 | 
						|
    NAPI_STATUS_THROWS_VOID(napi_create_string_utf8(env, resourceName,
 | 
						|
                                               NAPI_AUTO_LENGTH,
 | 
						|
                                               &asyncResourceName));
 | 
						|
    NAPI_STATUS_THROWS_VOID(napi_create_async_work(env, callback,
 | 
						|
                                              asyncResourceName,
 | 
						|
                                              BaseWorker::Execute,
 | 
						|
                                              BaseWorker::Complete,
 | 
						|
                                              this, &asyncWork_));
 | 
						|
  }
 | 
						|
 | 
						|
  virtual ~BaseWorker () {
 | 
						|
    delete [] errMsg_;
 | 
						|
  }
 | 
						|
 | 
						|
  static void Execute (napi_env env, void* data) {
 | 
						|
    BaseWorker* self = (BaseWorker*)data;
 | 
						|
 | 
						|
    // Don't pass env to DoExecute() because use of Node-API
 | 
						|
    // methods should generally be avoided in async work.
 | 
						|
    self->DoExecute();
 | 
						|
  }
 | 
						|
 | 
						|
  bool SetStatus (leveldb::Status status) {
 | 
						|
    status_ = status;
 | 
						|
    if (!status.ok()) {
 | 
						|
      SetErrorMessage(status.ToString().c_str());
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  void SetErrorMessage(const char *msg) {
 | 
						|
    delete [] errMsg_;
 | 
						|
    size_t size = strlen(msg) + 1;
 | 
						|
    errMsg_ = new char[size];
 | 
						|
    memcpy(errMsg_, msg, size);
 | 
						|
  }
 | 
						|
 | 
						|
  virtual void DoExecute () = 0;
 | 
						|
 | 
						|
  static void Complete (napi_env env, napi_status status, void* data) {
 | 
						|
    BaseWorker* self = (BaseWorker*)data;
 | 
						|
 | 
						|
    self->DoComplete(env);
 | 
						|
    self->DoFinally(env);
 | 
						|
  }
 | 
						|
 | 
						|
  void DoComplete (napi_env env) {
 | 
						|
    napi_value callback;
 | 
						|
    napi_get_reference_value(env, callbackRef_, &callback);
 | 
						|
 | 
						|
    if (status_.ok()) {
 | 
						|
      HandleOKCallback(env, callback);
 | 
						|
    } else {
 | 
						|
      HandleErrorCallback(env, callback);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  virtual void HandleOKCallback (napi_env env, napi_value callback) {
 | 
						|
    napi_value argv;
 | 
						|
    napi_get_null(env, &argv);
 | 
						|
    CallFunction(env, callback, 1, &argv);
 | 
						|
  }
 | 
						|
 | 
						|
  virtual void HandleErrorCallback (napi_env env, napi_value callback) {
 | 
						|
    napi_value argv;
 | 
						|
 | 
						|
    if (status_.IsNotFound()) {
 | 
						|
      argv = CreateCodeError(env, "LEVEL_NOT_FOUND", errMsg_);
 | 
						|
    } else if (status_.IsCorruption()) {
 | 
						|
      argv = CreateCodeError(env, "LEVEL_CORRUPTION", errMsg_);
 | 
						|
    } else if (status_.IsIOError()) {
 | 
						|
      if (strlen(errMsg_) > 15 && strncmp("IO error: lock ", errMsg_, 15) == 0) { // env_posix.cc
 | 
						|
        argv = CreateCodeError(env, "LEVEL_LOCKED", errMsg_);
 | 
						|
      } else if (strlen(errMsg_) > 19 && strncmp("IO error: LockFile ", errMsg_, 19) == 0) { // env_win.cc
 | 
						|
        argv = CreateCodeError(env, "LEVEL_LOCKED", errMsg_);
 | 
						|
      } else {
 | 
						|
        argv = CreateCodeError(env, "LEVEL_IO_ERROR", errMsg_);
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      argv = CreateError(env, errMsg_);
 | 
						|
    }
 | 
						|
 | 
						|
    CallFunction(env, callback, 1, &argv);
 | 
						|
  }
 | 
						|
 | 
						|
  virtual void DoFinally (napi_env env) {
 | 
						|
    napi_delete_reference(env, callbackRef_);
 | 
						|
    napi_delete_async_work(env, asyncWork_);
 | 
						|
 | 
						|
    delete this;
 | 
						|
  }
 | 
						|
 | 
						|
  void Queue (napi_env env) {
 | 
						|
    napi_queue_async_work(env, asyncWork_);
 | 
						|
  }
 | 
						|
 | 
						|
  Database* database_;
 | 
						|
 | 
						|
private:
 | 
						|
  napi_ref callbackRef_;
 | 
						|
  napi_async_work asyncWork_;
 | 
						|
  leveldb::Status status_;
 | 
						|
  char *errMsg_;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Owns the LevelDB storage, cache, filter policy and iterators.
 | 
						|
 */
 | 
						|
struct Database {
 | 
						|
  Database ()
 | 
						|
    : db_(NULL),
 | 
						|
      blockCache_(NULL),
 | 
						|
      filterPolicy_(leveldb::NewBloomFilterPolicy(10)),
 | 
						|
      currentIteratorId_(0),
 | 
						|
      pendingCloseWorker_(NULL),
 | 
						|
      ref_(NULL),
 | 
						|
      priorityWork_(0) {}
 | 
						|
 | 
						|
  ~Database () {
 | 
						|
    if (db_ != NULL) {
 | 
						|
      threadsafe_close(*this);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  leveldb::Status Open (const leveldb::Options& options,
 | 
						|
                        const std::string &location,
 | 
						|
                        bool multithreading) {
 | 
						|
    location_ = location;
 | 
						|
    return threadsafe_open(options, multithreading, *this);
 | 
						|
  }
 | 
						|
 | 
						|
  void CloseDatabase () {
 | 
						|
    if (db_ != NULL) {
 | 
						|
      threadsafe_close(*this);
 | 
						|
    }
 | 
						|
    if (blockCache_) {
 | 
						|
      delete blockCache_;
 | 
						|
      blockCache_ = NULL;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  leveldb::Status Put (const leveldb::WriteOptions& options,
 | 
						|
                       leveldb::Slice key,
 | 
						|
                       leveldb::Slice value) {
 | 
						|
    return db_->Put(options, key, value);
 | 
						|
  }
 | 
						|
 | 
						|
  leveldb::Status Get (const leveldb::ReadOptions& options,
 | 
						|
                       leveldb::Slice key,
 | 
						|
                       std::string& value) {
 | 
						|
    return db_->Get(options, key, &value);
 | 
						|
  }
 | 
						|
 | 
						|
  leveldb::Status Del (const leveldb::WriteOptions& options,
 | 
						|
                       leveldb::Slice key) {
 | 
						|
    return db_->Delete(options, key);
 | 
						|
  }
 | 
						|
 | 
						|
  leveldb::Status WriteBatch (const leveldb::WriteOptions& options,
 | 
						|
                              leveldb::WriteBatch* batch) {
 | 
						|
    return db_->Write(options, batch);
 | 
						|
  }
 | 
						|
 | 
						|
  uint64_t ApproximateSize (const leveldb::Range* range) {
 | 
						|
    uint64_t size = 0;
 | 
						|
    db_->GetApproximateSizes(range, 1, &size);
 | 
						|
    return size;
 | 
						|
  }
 | 
						|
 | 
						|
  void CompactRange (const leveldb::Slice* start,
 | 
						|
                     const leveldb::Slice* end) {
 | 
						|
    db_->CompactRange(start, end);
 | 
						|
  }
 | 
						|
 | 
						|
  void GetProperty (const leveldb::Slice& property, std::string* value) {
 | 
						|
    db_->GetProperty(property, value);
 | 
						|
  }
 | 
						|
 | 
						|
  const leveldb::Snapshot* NewSnapshot () {
 | 
						|
    return db_->GetSnapshot();
 | 
						|
  }
 | 
						|
 | 
						|
  leveldb::Iterator* NewIterator (leveldb::ReadOptions* options) {
 | 
						|
    return db_->NewIterator(*options);
 | 
						|
  }
 | 
						|
 | 
						|
  void ReleaseSnapshot (const leveldb::Snapshot* snapshot) {
 | 
						|
    return db_->ReleaseSnapshot(snapshot);
 | 
						|
  }
 | 
						|
 | 
						|
  void AttachIterator (napi_env env, uint32_t id, Iterator* iterator) {
 | 
						|
    iterators_[id] = iterator;
 | 
						|
    IncrementPriorityWork(env);
 | 
						|
  }
 | 
						|
 | 
						|
  void DetachIterator (napi_env env, uint32_t id) {
 | 
						|
    iterators_.erase(id);
 | 
						|
    DecrementPriorityWork(env);
 | 
						|
  }
 | 
						|
 | 
						|
  void IncrementPriorityWork (napi_env env) {
 | 
						|
    napi_reference_ref(env, ref_, &priorityWork_);
 | 
						|
  }
 | 
						|
 | 
						|
  void DecrementPriorityWork (napi_env env) {
 | 
						|
    napi_reference_unref(env, ref_, &priorityWork_);
 | 
						|
 | 
						|
    if (priorityWork_ == 0 && pendingCloseWorker_ != NULL) {
 | 
						|
      pendingCloseWorker_->Queue(env);
 | 
						|
      pendingCloseWorker_ = NULL;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  bool HasPriorityWork () const {
 | 
						|
    return priorityWork_ > 0;
 | 
						|
  }
 | 
						|
 | 
						|
  leveldb::DB* db_;
 | 
						|
  leveldb::Cache* blockCache_;
 | 
						|
  const leveldb::FilterPolicy* filterPolicy_;
 | 
						|
  uint32_t currentIteratorId_;
 | 
						|
  BaseWorker *pendingCloseWorker_;
 | 
						|
  std::map< uint32_t, Iterator * > iterators_;
 | 
						|
  napi_ref ref_;
 | 
						|
 | 
						|
private:
 | 
						|
  uint32_t priorityWork_;
 | 
						|
  std::string location_;
 | 
						|
 | 
						|
  // for separation of concerns the threadsafe functionality was kept at the global
 | 
						|
  // level and made a friend so it is explict where the threadsafe boundary exists
 | 
						|
  friend leveldb::Status threadsafe_open(const leveldb::Options &options,
 | 
						|
                                         bool multithreading,
 | 
						|
                                         Database &db_instance);
 | 
						|
  friend leveldb::Status threadsafe_close(Database &db_instance);
 | 
						|
};
 | 
						|
 | 
						|
 | 
						|
leveldb::Status threadsafe_open(const leveldb::Options &options,
 | 
						|
                                bool multithreading,
 | 
						|
                                Database &db_instance) {
 | 
						|
  // Bypass lock and handles if multithreading is disabled
 | 
						|
  if (!multithreading) {
 | 
						|
    return leveldb::DB::Open(options, db_instance.location_, &db_instance.db_);
 | 
						|
  }
 | 
						|
 | 
						|
  std::unique_lock<std::mutex> lock(handles_mutex);
 | 
						|
 | 
						|
  auto it = db_handles.find(db_instance.location_);
 | 
						|
  if (it == db_handles.end()) {
 | 
						|
    // Database not opened yet for this location, unless it was with
 | 
						|
    // multithreading disabled, in which case we're expected to fail here.
 | 
						|
    LevelDbHandle handle = {nullptr, 0};
 | 
						|
    leveldb::Status status = leveldb::DB::Open(options, db_instance.location_, &handle.db);
 | 
						|
 | 
						|
    if (status.ok()) {
 | 
						|
      handle.open_handle_count++;
 | 
						|
      db_instance.db_ = handle.db;
 | 
						|
      db_handles[db_instance.location_] = handle;
 | 
						|
    }
 | 
						|
    
 | 
						|
    return status;
 | 
						|
  }
 | 
						|
 | 
						|
  ++(it->second.open_handle_count);
 | 
						|
  db_instance.db_ = it->second.db;
 | 
						|
 | 
						|
  return leveldb::Status::OK();
 | 
						|
}
 | 
						|
 | 
						|
leveldb::Status threadsafe_close(Database &db_instance) {
 | 
						|
  std::unique_lock<std::mutex> lock(handles_mutex);
 | 
						|
 | 
						|
  auto it = db_handles.find(db_instance.location_);
 | 
						|
  if (it == db_handles.end()) {
 | 
						|
    // Was not opened with multithreading enabled
 | 
						|
    delete db_instance.db_;
 | 
						|
  } else if (--(it->second.open_handle_count) == 0) {
 | 
						|
    delete it->second.db;
 | 
						|
    db_handles.erase(it);
 | 
						|
  }
 | 
						|
 | 
						|
  // ensure db_ pointer is nullified in Database instance
 | 
						|
  db_instance.db_ = NULL;
 | 
						|
  return leveldb::Status::OK();
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Base worker class for doing async work that defers closing the database.
 | 
						|
 */
 | 
						|
struct PriorityWorker : public BaseWorker {
 | 
						|
  PriorityWorker (napi_env env, Database* database, napi_value callback, const char* resourceName)
 | 
						|
    : BaseWorker(env, database, callback, resourceName) {
 | 
						|
      database_->IncrementPriorityWork(env);
 | 
						|
  }
 | 
						|
 | 
						|
  virtual ~PriorityWorker () {}
 | 
						|
 | 
						|
  void DoFinally (napi_env env) override {
 | 
						|
    database_->DecrementPriorityWork(env);
 | 
						|
    BaseWorker::DoFinally(env);
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Owns a leveldb iterator.
 | 
						|
 */
 | 
						|
struct BaseIterator {
 | 
						|
  BaseIterator(Database* database,
 | 
						|
               const bool reverse,
 | 
						|
               std::string* lt,
 | 
						|
               std::string* lte,
 | 
						|
               std::string* gt,
 | 
						|
               std::string* gte,
 | 
						|
               const int limit,
 | 
						|
               const bool fillCache)
 | 
						|
    : database_(database),
 | 
						|
      hasClosed_(false),
 | 
						|
      didSeek_(false),
 | 
						|
      reverse_(reverse),
 | 
						|
      lt_(lt),
 | 
						|
      lte_(lte),
 | 
						|
      gt_(gt),
 | 
						|
      gte_(gte),
 | 
						|
      limit_(limit),
 | 
						|
      count_(0) {
 | 
						|
    options_ = new leveldb::ReadOptions();
 | 
						|
    options_->fill_cache = fillCache;
 | 
						|
    options_->snapshot = database->NewSnapshot();
 | 
						|
    dbIterator_ = database_->NewIterator(options_);
 | 
						|
  }
 | 
						|
 | 
						|
  virtual ~BaseIterator () {
 | 
						|
    assert(hasClosed_);
 | 
						|
 | 
						|
    if (lt_ != NULL) delete lt_;
 | 
						|
    if (gt_ != NULL) delete gt_;
 | 
						|
    if (lte_ != NULL) delete lte_;
 | 
						|
    if (gte_ != NULL) delete gte_;
 | 
						|
 | 
						|
    delete options_;
 | 
						|
  }
 | 
						|
 | 
						|
  bool DidSeek () const {
 | 
						|
    return didSeek_;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Seek to the first relevant key based on range options.
 | 
						|
   */
 | 
						|
  void SeekToRange () {
 | 
						|
    didSeek_ = true;
 | 
						|
 | 
						|
    if (!reverse_ && gte_ != NULL) {
 | 
						|
      dbIterator_->Seek(*gte_);
 | 
						|
    } else if (!reverse_ && gt_ != NULL) {
 | 
						|
      dbIterator_->Seek(*gt_);
 | 
						|
 | 
						|
      if (dbIterator_->Valid() && dbIterator_->key().compare(*gt_) == 0) {
 | 
						|
        dbIterator_->Next();
 | 
						|
      }
 | 
						|
    } else if (reverse_ && lte_ != NULL) {
 | 
						|
      dbIterator_->Seek(*lte_);
 | 
						|
 | 
						|
      if (!dbIterator_->Valid()) {
 | 
						|
        dbIterator_->SeekToLast();
 | 
						|
      } else if (dbIterator_->key().compare(*lte_) > 0) {
 | 
						|
        dbIterator_->Prev();
 | 
						|
      }
 | 
						|
    } else if (reverse_ && lt_ != NULL) {
 | 
						|
      dbIterator_->Seek(*lt_);
 | 
						|
 | 
						|
      if (!dbIterator_->Valid()) {
 | 
						|
        dbIterator_->SeekToLast();
 | 
						|
      } else if (dbIterator_->key().compare(*lt_) >= 0) {
 | 
						|
        dbIterator_->Prev();
 | 
						|
      }
 | 
						|
    } else if (reverse_) {
 | 
						|
      dbIterator_->SeekToLast();
 | 
						|
    } else {
 | 
						|
      dbIterator_->SeekToFirst();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Seek manually (during iteration).
 | 
						|
   */
 | 
						|
  void Seek (leveldb::Slice& target) {
 | 
						|
    didSeek_ = true;
 | 
						|
 | 
						|
    if (OutOfRange(target)) {
 | 
						|
      return SeekToEnd();
 | 
						|
    }
 | 
						|
 | 
						|
    dbIterator_->Seek(target);
 | 
						|
 | 
						|
    if (dbIterator_->Valid()) {
 | 
						|
      int cmp = dbIterator_->key().compare(target);
 | 
						|
      if (reverse_ ? cmp > 0 : cmp < 0) {
 | 
						|
        Next();
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      SeekToFirst();
 | 
						|
      if (dbIterator_->Valid()) {
 | 
						|
        int cmp = dbIterator_->key().compare(target);
 | 
						|
        if (reverse_ ? cmp > 0 : cmp < 0) {
 | 
						|
          SeekToEnd();
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  void Close () {
 | 
						|
    if (!hasClosed_) {
 | 
						|
      hasClosed_ = true;
 | 
						|
      delete dbIterator_;
 | 
						|
      dbIterator_ = NULL;
 | 
						|
      database_->ReleaseSnapshot(options_->snapshot);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  bool Valid () const {
 | 
						|
    return dbIterator_->Valid() && !OutOfRange(dbIterator_->key());
 | 
						|
  }
 | 
						|
 | 
						|
  bool Increment () {
 | 
						|
    return limit_ < 0 || ++count_ <= limit_;
 | 
						|
  }
 | 
						|
 | 
						|
  void Next () {
 | 
						|
    if (reverse_) dbIterator_->Prev();
 | 
						|
    else dbIterator_->Next();
 | 
						|
  }
 | 
						|
 | 
						|
  void SeekToFirst () {
 | 
						|
    if (reverse_) dbIterator_->SeekToLast();
 | 
						|
    else dbIterator_->SeekToFirst();
 | 
						|
  }
 | 
						|
 | 
						|
  void SeekToLast () {
 | 
						|
    if (reverse_) dbIterator_->SeekToFirst();
 | 
						|
    else dbIterator_->SeekToLast();
 | 
						|
  }
 | 
						|
 | 
						|
  void SeekToEnd () {
 | 
						|
    SeekToLast();
 | 
						|
    Next();
 | 
						|
  }
 | 
						|
 | 
						|
  leveldb::Slice CurrentKey () const {
 | 
						|
    return dbIterator_->key();
 | 
						|
  }
 | 
						|
 | 
						|
  leveldb::Slice CurrentValue () const {
 | 
						|
    return dbIterator_->value();
 | 
						|
  }
 | 
						|
 | 
						|
  leveldb::Status Status () const {
 | 
						|
    return dbIterator_->status();
 | 
						|
  }
 | 
						|
 | 
						|
  bool OutOfRange (const leveldb::Slice& target) const {
 | 
						|
    // The lte and gte options take precedence over lt and gt respectively
 | 
						|
    if (lte_ != NULL) {
 | 
						|
      if (target.compare(*lte_) > 0) return true;
 | 
						|
    } else if (lt_ != NULL) {
 | 
						|
      if (target.compare(*lt_) >= 0) return true;
 | 
						|
    }
 | 
						|
 | 
						|
    if (gte_ != NULL) {
 | 
						|
      if (target.compare(*gte_) < 0) return true;
 | 
						|
    } else if (gt_ != NULL) {
 | 
						|
      if (target.compare(*gt_) <= 0) return true;
 | 
						|
    }
 | 
						|
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  Database* database_;
 | 
						|
  bool hasClosed_;
 | 
						|
 | 
						|
private:
 | 
						|
  leveldb::Iterator* dbIterator_;
 | 
						|
  bool didSeek_;
 | 
						|
  const bool reverse_;
 | 
						|
  std::string* lt_;
 | 
						|
  std::string* lte_;
 | 
						|
  std::string* gt_;
 | 
						|
  std::string* gte_;
 | 
						|
  const int limit_;
 | 
						|
  int count_;
 | 
						|
  leveldb::ReadOptions* options_;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Extends BaseIterator for reading it from JS land.
 | 
						|
 */
 | 
						|
struct Iterator final : public BaseIterator {
 | 
						|
  Iterator (Database* database,
 | 
						|
            const uint32_t id,
 | 
						|
            const bool reverse,
 | 
						|
            const bool keys,
 | 
						|
            const bool values,
 | 
						|
            const int limit,
 | 
						|
            std::string* lt,
 | 
						|
            std::string* lte,
 | 
						|
            std::string* gt,
 | 
						|
            std::string* gte,
 | 
						|
            const bool fillCache,
 | 
						|
            const Encoding keyEncoding,
 | 
						|
            const Encoding valueEncoding,
 | 
						|
            const uint32_t highWaterMarkBytes)
 | 
						|
    : BaseIterator(database, reverse, lt, lte, gt, gte, limit, fillCache),
 | 
						|
      id_(id),
 | 
						|
      keys_(keys),
 | 
						|
      values_(values),
 | 
						|
      keyEncoding_(keyEncoding),
 | 
						|
      valueEncoding_(valueEncoding),
 | 
						|
      highWaterMarkBytes_(highWaterMarkBytes),
 | 
						|
      first_(true),
 | 
						|
      nexting_(false),
 | 
						|
      isClosing_(false),
 | 
						|
      closeWorker_(NULL),
 | 
						|
      ref_(NULL) {
 | 
						|
  }
 | 
						|
 | 
						|
  ~Iterator () {}
 | 
						|
 | 
						|
  void Attach (napi_env env, napi_value context) {
 | 
						|
    napi_create_reference(env, context, 1, &ref_);
 | 
						|
    database_->AttachIterator(env, id_, this);
 | 
						|
  }
 | 
						|
 | 
						|
  void Detach (napi_env env) {
 | 
						|
    database_->DetachIterator(env, id_);
 | 
						|
    if (ref_ != NULL) napi_delete_reference(env, ref_);
 | 
						|
  }
 | 
						|
 | 
						|
  bool ReadMany (uint32_t size) {
 | 
						|
    cache_.clear();
 | 
						|
    cache_.reserve(size);
 | 
						|
    size_t bytesRead = 0;
 | 
						|
    leveldb::Slice empty;
 | 
						|
 | 
						|
    while (true) {
 | 
						|
      if (!first_) Next();
 | 
						|
      else first_ = false;
 | 
						|
 | 
						|
      if (!Valid() || !Increment()) break;
 | 
						|
 | 
						|
      if (keys_ && values_) {
 | 
						|
        leveldb::Slice k = CurrentKey();
 | 
						|
        leveldb::Slice v = CurrentValue();
 | 
						|
        cache_.emplace_back(k, v);
 | 
						|
        bytesRead += k.size() + v.size();
 | 
						|
      } else if (keys_) {
 | 
						|
        leveldb::Slice k = CurrentKey();
 | 
						|
        cache_.emplace_back(k, empty);
 | 
						|
        bytesRead += k.size();
 | 
						|
      } else if (values_) {
 | 
						|
        leveldb::Slice v = CurrentValue();
 | 
						|
        cache_.emplace_back(empty, v);
 | 
						|
        bytesRead += v.size();
 | 
						|
      }
 | 
						|
 | 
						|
      if (bytesRead > highWaterMarkBytes_ || cache_.size() >= size) {
 | 
						|
        return true;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  const uint32_t id_;
 | 
						|
  const bool keys_;
 | 
						|
  const bool values_;
 | 
						|
  const Encoding keyEncoding_;
 | 
						|
  const Encoding valueEncoding_;
 | 
						|
  const uint32_t highWaterMarkBytes_;
 | 
						|
  bool first_;
 | 
						|
  bool nexting_;
 | 
						|
  bool isClosing_;
 | 
						|
  BaseWorker* closeWorker_;
 | 
						|
  std::vector<Entry> cache_;
 | 
						|
 | 
						|
private:
 | 
						|
  napi_ref ref_;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Hook for when the environment exits. This hook will be called after
 | 
						|
 * already-scheduled napi_async_work items have finished, which gives us
 | 
						|
 * the guarantee that no db operations will be in-flight at this time.
 | 
						|
 */
 | 
						|
static void env_cleanup_hook (void* arg) {
 | 
						|
  Database* database = (Database*)arg;
 | 
						|
 | 
						|
  // Do everything that db_close() does but synchronously. We're expecting that GC
 | 
						|
  // did not (yet) collect the database because that would be a user mistake (not
 | 
						|
  // closing their db) made during the lifetime of the environment. That's different
 | 
						|
  // from an environment being torn down (like the main process or a worker thread)
 | 
						|
  // where it's our responsibility to clean up. Note also, the following code must
 | 
						|
  // be a safe noop if called before db_open() or after db_close().
 | 
						|
  if (database && database->db_ != NULL) {
 | 
						|
    std::map<uint32_t, Iterator*> iterators = database->iterators_;
 | 
						|
    std::map<uint32_t, Iterator*>::iterator it;
 | 
						|
 | 
						|
    // TODO: does not do `napi_delete_reference(env, iterator->ref_)`. Problem?
 | 
						|
    for (it = iterators.begin(); it != iterators.end(); ++it) {
 | 
						|
      it->second->Close();
 | 
						|
    }
 | 
						|
 | 
						|
    // Having closed the iterators (and released snapshots) we can safely close.
 | 
						|
    database->CloseDatabase();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Runs when a Database is garbage collected.
 | 
						|
 */
 | 
						|
static void FinalizeDatabase (napi_env env, void* data, void* hint) {
 | 
						|
  if (data) {
 | 
						|
    Database* database = (Database*)data;
 | 
						|
    napi_remove_env_cleanup_hook(env, env_cleanup_hook, database);
 | 
						|
    if (database->ref_ != NULL) napi_delete_reference(env, database->ref_);
 | 
						|
    delete database;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Returns a context object for a database.
 | 
						|
 */
 | 
						|
NAPI_METHOD(db_init) {
 | 
						|
  Database* database = new Database();
 | 
						|
  napi_add_env_cleanup_hook(env, env_cleanup_hook, database);
 | 
						|
 | 
						|
  napi_value result;
 | 
						|
  NAPI_STATUS_THROWS(napi_create_external(env, database,
 | 
						|
                                          FinalizeDatabase,
 | 
						|
                                          NULL, &result));
 | 
						|
 | 
						|
  // Reference counter to prevent GC of database while priority workers are active
 | 
						|
  NAPI_STATUS_THROWS(napi_create_reference(env, result, 0, &database->ref_));
 | 
						|
 | 
						|
  return result;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Worker class for opening a database.
 | 
						|
 * TODO: shouldn't this be a PriorityWorker?
 | 
						|
 */
 | 
						|
struct OpenWorker final : public BaseWorker {
 | 
						|
  OpenWorker (napi_env env,
 | 
						|
              Database* database,
 | 
						|
              napi_value callback,
 | 
						|
              const std::string& location,
 | 
						|
              const bool createIfMissing,
 | 
						|
              const bool errorIfExists,
 | 
						|
              const bool compression,
 | 
						|
              const bool multithreading,
 | 
						|
              const uint32_t writeBufferSize,
 | 
						|
              const uint32_t blockSize,
 | 
						|
              const uint32_t maxOpenFiles,
 | 
						|
              const uint32_t blockRestartInterval,
 | 
						|
              const uint32_t maxFileSize)
 | 
						|
    : BaseWorker(env, database, callback, "classic_level.db.open"),
 | 
						|
      location_(location),
 | 
						|
      multithreading_(multithreading) {
 | 
						|
    options_.block_cache = database->blockCache_;
 | 
						|
    options_.filter_policy = database->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;
 | 
						|
  }
 | 
						|
 | 
						|
  ~OpenWorker () {}
 | 
						|
 | 
						|
  void DoExecute () override {
 | 
						|
    SetStatus(database_->Open(options_, location_, multithreading_));
 | 
						|
  }
 | 
						|
 | 
						|
  leveldb::Options options_;
 | 
						|
  std::string location_;
 | 
						|
  bool multithreading_;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Open a database.
 | 
						|
 */
 | 
						|
NAPI_METHOD(db_open) {
 | 
						|
  NAPI_ARGV(4);
 | 
						|
  NAPI_DB_CONTEXT();
 | 
						|
  NAPI_ARGV_UTF8_NEW(location, 1);
 | 
						|
 | 
						|
  napi_value options = argv[2];
 | 
						|
  const bool createIfMissing = BooleanProperty(env, options, "createIfMissing", true);
 | 
						|
  const bool errorIfExists = BooleanProperty(env, options, "errorIfExists", false);
 | 
						|
  const bool compression = BooleanProperty(env, options, "compression", true);
 | 
						|
  const bool multithreading = BooleanProperty(env, options, "multithreading", false);
 | 
						|
 | 
						|
  const uint32_t cacheSize = Uint32Property(env, options, "cacheSize", 8 << 20);
 | 
						|
  const uint32_t writeBufferSize = Uint32Property(env, options , "writeBufferSize" , 4 << 20);
 | 
						|
  const uint32_t blockSize = Uint32Property(env, options, "blockSize", 4096);
 | 
						|
  const uint32_t maxOpenFiles = Uint32Property(env, options, "maxOpenFiles", 1000);
 | 
						|
  const uint32_t blockRestartInterval = Uint32Property(env, options,
 | 
						|
                                                 "blockRestartInterval", 16);
 | 
						|
  const uint32_t maxFileSize = Uint32Property(env, options, "maxFileSize", 2 << 20);
 | 
						|
 | 
						|
  database->blockCache_ = leveldb::NewLRUCache(cacheSize);
 | 
						|
 | 
						|
  napi_value callback = argv[3];
 | 
						|
  OpenWorker* worker = new OpenWorker(env, database, callback, location,
 | 
						|
                                      createIfMissing, errorIfExists,
 | 
						|
                                      compression, multithreading,
 | 
						|
                                      writeBufferSize, blockSize,
 | 
						|
                                      maxOpenFiles, blockRestartInterval,
 | 
						|
                                      maxFileSize);
 | 
						|
  worker->Queue(env);
 | 
						|
  delete [] location;
 | 
						|
 | 
						|
  NAPI_RETURN_UNDEFINED();
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Worker class for closing a database
 | 
						|
 */
 | 
						|
struct CloseWorker final : public BaseWorker {
 | 
						|
  CloseWorker (napi_env env,
 | 
						|
               Database* database,
 | 
						|
               napi_value callback)
 | 
						|
    : BaseWorker(env, database, callback, "classic_level.db.close") {}
 | 
						|
 | 
						|
  ~CloseWorker () {}
 | 
						|
 | 
						|
  void DoExecute () override {
 | 
						|
    database_->CloseDatabase();
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
napi_value noop_callback (napi_env env, napi_callback_info info) {
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Close a database.
 | 
						|
 */
 | 
						|
NAPI_METHOD(db_close) {
 | 
						|
  NAPI_ARGV(2);
 | 
						|
  NAPI_DB_CONTEXT();
 | 
						|
 | 
						|
  napi_value callback = argv[1];
 | 
						|
  CloseWorker* worker = new CloseWorker(env, database, callback);
 | 
						|
 | 
						|
  if (!database->HasPriorityWork()) {
 | 
						|
    worker->Queue(env);
 | 
						|
    NAPI_RETURN_UNDEFINED();
 | 
						|
  }
 | 
						|
 | 
						|
  database->pendingCloseWorker_ = worker;
 | 
						|
 | 
						|
  napi_value noop;
 | 
						|
  napi_create_function(env, NULL, 0, noop_callback, NULL, &noop);
 | 
						|
 | 
						|
  std::map<uint32_t, Iterator*> iterators = database->iterators_;
 | 
						|
  std::map<uint32_t, Iterator*>::iterator it;
 | 
						|
 | 
						|
  for (it = iterators.begin(); it != iterators.end(); ++it) {
 | 
						|
    iterator_close_do(env, it->second, noop);
 | 
						|
  }
 | 
						|
 | 
						|
  NAPI_RETURN_UNDEFINED();
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Worker class for putting key/value to the database
 | 
						|
 */
 | 
						|
struct PutWorker final : public PriorityWorker {
 | 
						|
  PutWorker (napi_env env,
 | 
						|
             Database* database,
 | 
						|
             napi_value callback,
 | 
						|
             leveldb::Slice key,
 | 
						|
             leveldb::Slice value,
 | 
						|
             bool sync)
 | 
						|
    : PriorityWorker(env, database, callback, "classic_level.db.put"),
 | 
						|
      key_(key), value_(value) {
 | 
						|
    options_.sync = sync;
 | 
						|
  }
 | 
						|
 | 
						|
  ~PutWorker () {
 | 
						|
    DisposeSliceBuffer(key_);
 | 
						|
    DisposeSliceBuffer(value_);
 | 
						|
  }
 | 
						|
 | 
						|
  void DoExecute () override {
 | 
						|
    SetStatus(database_->Put(options_, key_, value_));
 | 
						|
  }
 | 
						|
 | 
						|
  leveldb::WriteOptions options_;
 | 
						|
  leveldb::Slice key_;
 | 
						|
  leveldb::Slice value_;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Puts a key and a value to a database.
 | 
						|
 */
 | 
						|
NAPI_METHOD(db_put) {
 | 
						|
  NAPI_ARGV(5);
 | 
						|
  NAPI_DB_CONTEXT();
 | 
						|
 | 
						|
  leveldb::Slice key = ToSlice(env, argv[1]);
 | 
						|
  leveldb::Slice value = ToSlice(env, argv[2]);
 | 
						|
  bool sync = BooleanProperty(env, argv[3], "sync", false);
 | 
						|
  napi_value callback = argv[4];
 | 
						|
 | 
						|
  PutWorker* worker = new PutWorker(env, database, callback, key, value, sync);
 | 
						|
  worker->Queue(env);
 | 
						|
 | 
						|
  NAPI_RETURN_UNDEFINED();
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Worker class for getting a value from a database.
 | 
						|
 */
 | 
						|
struct GetWorker final : public PriorityWorker {
 | 
						|
  GetWorker (napi_env env,
 | 
						|
             Database* database,
 | 
						|
             napi_value callback,
 | 
						|
             leveldb::Slice key,
 | 
						|
             const Encoding encoding,
 | 
						|
             const bool fillCache)
 | 
						|
    : PriorityWorker(env, database, callback, "classic_level.db.get"),
 | 
						|
      key_(key),
 | 
						|
      encoding_(encoding) {
 | 
						|
    options_.fill_cache = fillCache;
 | 
						|
  }
 | 
						|
 | 
						|
  ~GetWorker () {
 | 
						|
    DisposeSliceBuffer(key_);
 | 
						|
  }
 | 
						|
 | 
						|
  void DoExecute () override {
 | 
						|
    SetStatus(database_->Get(options_, key_, value_));
 | 
						|
  }
 | 
						|
 | 
						|
  void HandleOKCallback (napi_env env, napi_value callback) override {
 | 
						|
    napi_value argv[2];
 | 
						|
    napi_get_null(env, &argv[0]);
 | 
						|
    Entry::Convert(env, &value_, encoding_, argv[1]);
 | 
						|
    CallFunction(env, callback, 2, argv);
 | 
						|
  }
 | 
						|
 | 
						|
private:
 | 
						|
  leveldb::ReadOptions options_;
 | 
						|
  leveldb::Slice key_;
 | 
						|
  std::string value_;
 | 
						|
  const Encoding encoding_;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Gets a value from a database.
 | 
						|
 */
 | 
						|
NAPI_METHOD(db_get) {
 | 
						|
  NAPI_ARGV(4);
 | 
						|
  NAPI_DB_CONTEXT();
 | 
						|
 | 
						|
  leveldb::Slice key = ToSlice(env, argv[1]);
 | 
						|
  napi_value options = argv[2];
 | 
						|
  const Encoding encoding = GetEncoding(env, options, "valueEncoding");
 | 
						|
  const bool fillCache = BooleanProperty(env, options, "fillCache", true);
 | 
						|
  napi_value callback = argv[3];
 | 
						|
 | 
						|
  GetWorker* worker = new GetWorker(env, database, callback, key, encoding,
 | 
						|
                                    fillCache);
 | 
						|
  worker->Queue(env);
 | 
						|
 | 
						|
  NAPI_RETURN_UNDEFINED();
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Worker class for getting many values.
 | 
						|
 */
 | 
						|
struct GetManyWorker final : public PriorityWorker {
 | 
						|
  GetManyWorker (napi_env env,
 | 
						|
                 Database* database,
 | 
						|
                 std::vector<std::string> keys,
 | 
						|
                 napi_value callback,
 | 
						|
                 const Encoding valueEncoding,
 | 
						|
                 const bool fillCache)
 | 
						|
    : PriorityWorker(env, database, callback, "classic_level.get.many"),
 | 
						|
      keys_(std::move(keys)), valueEncoding_(valueEncoding) {
 | 
						|
      options_.fill_cache = fillCache;
 | 
						|
      options_.snapshot = database->NewSnapshot();
 | 
						|
    }
 | 
						|
 | 
						|
  void DoExecute () override {
 | 
						|
    cache_.reserve(keys_.size());
 | 
						|
 | 
						|
    for (const std::string& key: keys_) {
 | 
						|
      std::string* value = new std::string();
 | 
						|
      leveldb::Status status = database_->Get(options_, key, *value);
 | 
						|
 | 
						|
      if (status.ok()) {
 | 
						|
        cache_.push_back(value);
 | 
						|
      } else if (status.IsNotFound()) {
 | 
						|
        delete value;
 | 
						|
        cache_.push_back(NULL);
 | 
						|
      } else {
 | 
						|
        delete value;
 | 
						|
        for (const std::string* value: cache_) {
 | 
						|
          if (value != NULL) delete value;
 | 
						|
        }
 | 
						|
        SetStatus(status);
 | 
						|
        break;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    database_->ReleaseSnapshot(options_.snapshot);
 | 
						|
  }
 | 
						|
 | 
						|
  void HandleOKCallback (napi_env env, napi_value callback) override {
 | 
						|
    size_t size = cache_.size();
 | 
						|
    napi_value array;
 | 
						|
    napi_create_array_with_length(env, size, &array);
 | 
						|
 | 
						|
    for (size_t idx = 0; idx < size; idx++) {
 | 
						|
      std::string* value = cache_[idx];
 | 
						|
      napi_value element;
 | 
						|
      Entry::Convert(env, value, valueEncoding_, element);
 | 
						|
      napi_set_element(env, array, static_cast<uint32_t>(idx), element);
 | 
						|
      if (value != NULL) delete value;
 | 
						|
    }
 | 
						|
 | 
						|
    napi_value argv[2];
 | 
						|
    napi_get_null(env, &argv[0]);
 | 
						|
    argv[1] = array;
 | 
						|
    CallFunction(env, callback, 2, argv);
 | 
						|
  }
 | 
						|
 | 
						|
private:
 | 
						|
  leveldb::ReadOptions options_;
 | 
						|
  const std::vector<std::string> keys_;
 | 
						|
  const Encoding valueEncoding_;
 | 
						|
  std::vector<std::string*> cache_;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Gets many values from a database.
 | 
						|
 */
 | 
						|
NAPI_METHOD(db_get_many) {
 | 
						|
  NAPI_ARGV(4);
 | 
						|
  NAPI_DB_CONTEXT();
 | 
						|
 | 
						|
  const auto keys = KeyArray(env, argv[1]);
 | 
						|
  napi_value options = argv[2];
 | 
						|
  const Encoding valueEncoding = GetEncoding(env, options, "valueEncoding");
 | 
						|
  const bool fillCache = BooleanProperty(env, options, "fillCache", true);
 | 
						|
  napi_value callback = argv[3];
 | 
						|
 | 
						|
  GetManyWorker* worker = new GetManyWorker(
 | 
						|
    env, database, keys, callback, valueEncoding, fillCache
 | 
						|
  );
 | 
						|
 | 
						|
  worker->Queue(env);
 | 
						|
  NAPI_RETURN_UNDEFINED();
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Worker class for deleting a value from a database.
 | 
						|
 */
 | 
						|
struct DelWorker final : public PriorityWorker {
 | 
						|
  DelWorker (napi_env env,
 | 
						|
             Database* database,
 | 
						|
             napi_value callback,
 | 
						|
             leveldb::Slice key,
 | 
						|
             bool sync)
 | 
						|
    : PriorityWorker(env, database, callback, "classic_level.db.del"),
 | 
						|
      key_(key) {
 | 
						|
    options_.sync = sync;
 | 
						|
  }
 | 
						|
 | 
						|
  ~DelWorker () {
 | 
						|
    DisposeSliceBuffer(key_);
 | 
						|
  }
 | 
						|
 | 
						|
  void DoExecute () override {
 | 
						|
    SetStatus(database_->Del(options_, key_));
 | 
						|
  }
 | 
						|
 | 
						|
  leveldb::WriteOptions options_;
 | 
						|
  leveldb::Slice key_;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Delete a value from a database.
 | 
						|
 */
 | 
						|
NAPI_METHOD(db_del) {
 | 
						|
  NAPI_ARGV(4);
 | 
						|
  NAPI_DB_CONTEXT();
 | 
						|
 | 
						|
  leveldb::Slice key = ToSlice(env, argv[1]);
 | 
						|
  bool sync = BooleanProperty(env, argv[2], "sync", false);
 | 
						|
  napi_value callback = argv[3];
 | 
						|
 | 
						|
  DelWorker* worker = new DelWorker(env, database, callback, key, sync);
 | 
						|
  worker->Queue(env);
 | 
						|
 | 
						|
  NAPI_RETURN_UNDEFINED();
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Worker class for deleting a range from a database.
 | 
						|
 */
 | 
						|
struct ClearWorker final : public PriorityWorker {
 | 
						|
  ClearWorker (napi_env env,
 | 
						|
               Database* database,
 | 
						|
               napi_value callback,
 | 
						|
               const bool reverse,
 | 
						|
               const int limit,
 | 
						|
               std::string* lt,
 | 
						|
               std::string* lte,
 | 
						|
               std::string* gt,
 | 
						|
               std::string* gte)
 | 
						|
    : PriorityWorker(env, database, callback, "classic_level.db.clear") {
 | 
						|
    iterator_ = new BaseIterator(database, reverse, lt, lte, gt, gte, limit, false);
 | 
						|
    writeOptions_ = new leveldb::WriteOptions();
 | 
						|
    writeOptions_->sync = false;
 | 
						|
  }
 | 
						|
 | 
						|
  ~ClearWorker () {
 | 
						|
    delete iterator_;
 | 
						|
    delete writeOptions_;
 | 
						|
  }
 | 
						|
 | 
						|
  void DoExecute () override {
 | 
						|
    iterator_->SeekToRange();
 | 
						|
 | 
						|
    // TODO: add option
 | 
						|
    uint32_t hwm = 16 * 1024;
 | 
						|
    leveldb::WriteBatch batch;
 | 
						|
 | 
						|
    while (true) {
 | 
						|
      size_t bytesRead = 0;
 | 
						|
 | 
						|
      while (bytesRead <= hwm && iterator_->Valid() && iterator_->Increment()) {
 | 
						|
        leveldb::Slice key = iterator_->CurrentKey();
 | 
						|
        batch.Delete(key);
 | 
						|
        bytesRead += key.size();
 | 
						|
        iterator_->Next();
 | 
						|
      }
 | 
						|
 | 
						|
      if (!SetStatus(iterator_->Status()) || bytesRead == 0) {
 | 
						|
        break;
 | 
						|
      }
 | 
						|
 | 
						|
      if (!SetStatus(database_->WriteBatch(*writeOptions_, &batch))) {
 | 
						|
        break;
 | 
						|
      }
 | 
						|
 | 
						|
      batch.Clear();
 | 
						|
    }
 | 
						|
 | 
						|
    iterator_->Close();
 | 
						|
  }
 | 
						|
 | 
						|
private:
 | 
						|
  BaseIterator* iterator_;
 | 
						|
  leveldb::WriteOptions* writeOptions_;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Delete a range from a database.
 | 
						|
 */
 | 
						|
NAPI_METHOD(db_clear) {
 | 
						|
  NAPI_ARGV(3);
 | 
						|
  NAPI_DB_CONTEXT();
 | 
						|
 | 
						|
  napi_value options = argv[1];
 | 
						|
  napi_value callback = argv[2];
 | 
						|
 | 
						|
  const bool reverse = BooleanProperty(env, options, "reverse", false);
 | 
						|
  const int limit = Int32Property(env, options, "limit", -1);
 | 
						|
 | 
						|
  std::string* lt = RangeOption(env, options, "lt");
 | 
						|
  std::string* lte = RangeOption(env, options, "lte");
 | 
						|
  std::string* gt = RangeOption(env, options, "gt");
 | 
						|
  std::string* gte = RangeOption(env, options, "gte");
 | 
						|
 | 
						|
  ClearWorker* worker = new ClearWorker(env, database, callback, reverse, limit, lt, lte, gt, gte);
 | 
						|
  worker->Queue(env);
 | 
						|
 | 
						|
  NAPI_RETURN_UNDEFINED();
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Worker class for calculating the size of a range.
 | 
						|
 */
 | 
						|
struct ApproximateSizeWorker final : public PriorityWorker {
 | 
						|
  ApproximateSizeWorker (napi_env env,
 | 
						|
                         Database* database,
 | 
						|
                         napi_value callback,
 | 
						|
                         leveldb::Slice start,
 | 
						|
                         leveldb::Slice end)
 | 
						|
    : PriorityWorker(env, database, callback, "classic_level.db.approximate_size"),
 | 
						|
      start_(start), end_(end) {}
 | 
						|
 | 
						|
  ~ApproximateSizeWorker () {
 | 
						|
    DisposeSliceBuffer(start_);
 | 
						|
    DisposeSliceBuffer(end_);
 | 
						|
  }
 | 
						|
 | 
						|
  void DoExecute () override {
 | 
						|
    leveldb::Range range(start_, end_);
 | 
						|
    size_ = database_->ApproximateSize(&range);
 | 
						|
  }
 | 
						|
 | 
						|
  void HandleOKCallback (napi_env env, napi_value callback) override {
 | 
						|
    napi_value argv[2];
 | 
						|
    napi_get_null(env, &argv[0]);
 | 
						|
    napi_create_int64(env, (uint64_t)size_, &argv[1]);
 | 
						|
    CallFunction(env, callback, 2, argv);
 | 
						|
  }
 | 
						|
 | 
						|
  leveldb::Slice start_;
 | 
						|
  leveldb::Slice end_;
 | 
						|
  uint64_t size_;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Calculates the approximate size of a range in a database.
 | 
						|
 */
 | 
						|
NAPI_METHOD(db_approximate_size) {
 | 
						|
  NAPI_ARGV(4);
 | 
						|
  NAPI_DB_CONTEXT();
 | 
						|
 | 
						|
  leveldb::Slice start = ToSlice(env, argv[1]);
 | 
						|
  leveldb::Slice end = ToSlice(env, argv[2]);
 | 
						|
 | 
						|
  napi_value callback = argv[3];
 | 
						|
 | 
						|
  ApproximateSizeWorker* worker  = new ApproximateSizeWorker(env, database,
 | 
						|
                                                             callback, start,
 | 
						|
                                                             end);
 | 
						|
  worker->Queue(env);
 | 
						|
 | 
						|
  NAPI_RETURN_UNDEFINED();
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Worker class for compacting a range in a database.
 | 
						|
 */
 | 
						|
struct CompactRangeWorker final : public PriorityWorker {
 | 
						|
  CompactRangeWorker (napi_env env,
 | 
						|
                      Database* database,
 | 
						|
                      napi_value callback,
 | 
						|
                      leveldb::Slice start,
 | 
						|
                      leveldb::Slice end)
 | 
						|
    : PriorityWorker(env, database, callback, "classic_level.db.compact_range"),
 | 
						|
      start_(start), end_(end) {}
 | 
						|
 | 
						|
  ~CompactRangeWorker () {
 | 
						|
    DisposeSliceBuffer(start_);
 | 
						|
    DisposeSliceBuffer(end_);
 | 
						|
  }
 | 
						|
 | 
						|
  void DoExecute () override {
 | 
						|
    database_->CompactRange(&start_, &end_);
 | 
						|
  }
 | 
						|
 | 
						|
  leveldb::Slice start_;
 | 
						|
  leveldb::Slice end_;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Compacts a range in a database.
 | 
						|
 */
 | 
						|
NAPI_METHOD(db_compact_range) {
 | 
						|
  NAPI_ARGV(4);
 | 
						|
  NAPI_DB_CONTEXT();
 | 
						|
 | 
						|
  leveldb::Slice start = ToSlice(env, argv[1]);
 | 
						|
  leveldb::Slice end = ToSlice(env, argv[2]);
 | 
						|
  napi_value callback = argv[3];
 | 
						|
 | 
						|
  CompactRangeWorker* worker  = new CompactRangeWorker(env, database, callback,
 | 
						|
                                                       start, end);
 | 
						|
  worker->Queue(env);
 | 
						|
 | 
						|
  NAPI_RETURN_UNDEFINED();
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Get a property from a database.
 | 
						|
 */
 | 
						|
NAPI_METHOD(db_get_property) {
 | 
						|
  NAPI_ARGV(2);
 | 
						|
  NAPI_DB_CONTEXT();
 | 
						|
 | 
						|
  leveldb::Slice property = ToSlice(env, argv[1]);
 | 
						|
 | 
						|
  std::string value;
 | 
						|
  database->GetProperty(property, &value);
 | 
						|
 | 
						|
  napi_value result;
 | 
						|
  napi_create_string_utf8(env, value.data(), value.size(), &result);
 | 
						|
 | 
						|
  DisposeSliceBuffer(property);
 | 
						|
 | 
						|
  return result;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Worker class for destroying a database.
 | 
						|
 */
 | 
						|
struct DestroyWorker final : public BaseWorker {
 | 
						|
  DestroyWorker (napi_env env,
 | 
						|
                 const std::string& location,
 | 
						|
                 napi_value callback)
 | 
						|
    : BaseWorker(env, NULL, callback, "classic_level.destroy_db"),
 | 
						|
      location_(location) {}
 | 
						|
 | 
						|
  ~DestroyWorker () {}
 | 
						|
 | 
						|
  void DoExecute () override {
 | 
						|
    leveldb::Options options;
 | 
						|
    SetStatus(leveldb::DestroyDB(location_, options));
 | 
						|
  }
 | 
						|
 | 
						|
  std::string location_;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Destroys a database.
 | 
						|
 */
 | 
						|
NAPI_METHOD(destroy_db) {
 | 
						|
  NAPI_ARGV(2);
 | 
						|
  NAPI_ARGV_UTF8_NEW(location, 0);
 | 
						|
  napi_value callback = argv[1];
 | 
						|
 | 
						|
  DestroyWorker* worker = new DestroyWorker(env, location, callback);
 | 
						|
  worker->Queue(env);
 | 
						|
 | 
						|
  delete [] location;
 | 
						|
 | 
						|
  NAPI_RETURN_UNDEFINED();
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Worker class for repairing a database.
 | 
						|
 */
 | 
						|
struct RepairWorker final : public BaseWorker {
 | 
						|
  RepairWorker (napi_env env,
 | 
						|
                const std::string& location,
 | 
						|
                napi_value callback)
 | 
						|
    : BaseWorker(env, NULL, callback, "classic_level.repair_db"),
 | 
						|
      location_(location) {}
 | 
						|
 | 
						|
  ~RepairWorker () {}
 | 
						|
 | 
						|
  void DoExecute () override {
 | 
						|
    leveldb::Options options;
 | 
						|
    SetStatus(leveldb::RepairDB(location_, options));
 | 
						|
  }
 | 
						|
 | 
						|
  std::string location_;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Repairs a database.
 | 
						|
 */
 | 
						|
NAPI_METHOD(repair_db) {
 | 
						|
  NAPI_ARGV(2);
 | 
						|
  NAPI_ARGV_UTF8_NEW(location, 0);
 | 
						|
  napi_value callback = argv[1];
 | 
						|
 | 
						|
  RepairWorker* worker = new RepairWorker(env, location, callback);
 | 
						|
  worker->Queue(env);
 | 
						|
 | 
						|
  delete [] location;
 | 
						|
 | 
						|
  NAPI_RETURN_UNDEFINED();
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Runs when an Iterator is garbage collected.
 | 
						|
 */
 | 
						|
static void FinalizeIterator (napi_env env, void* data, void* hint) {
 | 
						|
  if (data) {
 | 
						|
    delete (Iterator*)data;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Create an iterator.
 | 
						|
 */
 | 
						|
NAPI_METHOD(iterator_init) {
 | 
						|
  NAPI_ARGV(2);
 | 
						|
  NAPI_DB_CONTEXT();
 | 
						|
 | 
						|
  napi_value options = argv[1];
 | 
						|
  const bool reverse = BooleanProperty(env, options, "reverse", false);
 | 
						|
  const bool keys = BooleanProperty(env, options, "keys", true);
 | 
						|
  const bool values = BooleanProperty(env, options, "values", true);
 | 
						|
  const bool fillCache = BooleanProperty(env, options, "fillCache", false);
 | 
						|
  const Encoding keyEncoding = GetEncoding(env, options, "keyEncoding");
 | 
						|
  const Encoding valueEncoding = GetEncoding(env, options, "valueEncoding");
 | 
						|
  const int limit = Int32Property(env, options, "limit", -1);
 | 
						|
  const uint32_t highWaterMarkBytes = Uint32Property(env, options, "highWaterMarkBytes", 16 * 1024);
 | 
						|
 | 
						|
  std::string* lt = RangeOption(env, options, "lt");
 | 
						|
  std::string* lte = RangeOption(env, options, "lte");
 | 
						|
  std::string* gt = RangeOption(env, options, "gt");
 | 
						|
  std::string* gte = RangeOption(env, options, "gte");
 | 
						|
 | 
						|
  const uint32_t id = database->currentIteratorId_++;
 | 
						|
  Iterator* iterator = new Iterator(database, id, reverse, keys,
 | 
						|
                                    values, limit, lt, lte, gt, gte, fillCache,
 | 
						|
                                    keyEncoding, valueEncoding, highWaterMarkBytes);
 | 
						|
  napi_value result;
 | 
						|
 | 
						|
  NAPI_STATUS_THROWS(napi_create_external(env, iterator,
 | 
						|
                                          FinalizeIterator,
 | 
						|
                                          NULL, &result));
 | 
						|
 | 
						|
  // Prevent GC of JS object before the iterator is closed (explicitly or on
 | 
						|
  // db close) and keep track of non-closed iterators to close them on db close.
 | 
						|
  iterator->Attach(env, result);
 | 
						|
 | 
						|
  return result;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Seeks an iterator.
 | 
						|
 */
 | 
						|
NAPI_METHOD(iterator_seek) {
 | 
						|
  NAPI_ARGV(2);
 | 
						|
  NAPI_ITERATOR_CONTEXT();
 | 
						|
 | 
						|
  if (iterator->isClosing_ || iterator->hasClosed_) {
 | 
						|
    NAPI_RETURN_UNDEFINED();
 | 
						|
  }
 | 
						|
 | 
						|
  leveldb::Slice target = ToSlice(env, argv[1]);
 | 
						|
  iterator->first_ = true;
 | 
						|
  iterator->Seek(target);
 | 
						|
 | 
						|
  DisposeSliceBuffer(target);
 | 
						|
  NAPI_RETURN_UNDEFINED();
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Worker class for closing an iterator
 | 
						|
 */
 | 
						|
struct CloseIteratorWorker final : public BaseWorker {
 | 
						|
  CloseIteratorWorker (napi_env env,
 | 
						|
             Iterator* iterator,
 | 
						|
             napi_value callback)
 | 
						|
    : BaseWorker(env, iterator->database_, callback, "classic_level.iterator.close"),
 | 
						|
      iterator_(iterator) {}
 | 
						|
 | 
						|
  ~CloseIteratorWorker () {}
 | 
						|
 | 
						|
  void DoExecute () override {
 | 
						|
    iterator_->Close();
 | 
						|
  }
 | 
						|
 | 
						|
  void DoFinally (napi_env env) override {
 | 
						|
    iterator_->Detach(env);
 | 
						|
    BaseWorker::DoFinally(env);
 | 
						|
  }
 | 
						|
 | 
						|
private:
 | 
						|
  Iterator* iterator_;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Called by NAPI_METHOD(iterator_close) and also when closing
 | 
						|
 * open iterators during NAPI_METHOD(db_close).
 | 
						|
 */
 | 
						|
static void iterator_close_do (napi_env env, Iterator* iterator, napi_value cb) {
 | 
						|
  if (!iterator->isClosing_ && !iterator->hasClosed_) {
 | 
						|
    CloseIteratorWorker* worker = new CloseIteratorWorker(env, iterator, cb);
 | 
						|
    iterator->isClosing_ = true;
 | 
						|
 | 
						|
    if (iterator->nexting_) {
 | 
						|
      iterator->closeWorker_ = worker;
 | 
						|
    } else {
 | 
						|
      worker->Queue(env);
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Closes an iterator.
 | 
						|
 */
 | 
						|
NAPI_METHOD(iterator_close) {
 | 
						|
  NAPI_ARGV(2);
 | 
						|
  NAPI_ITERATOR_CONTEXT();
 | 
						|
 | 
						|
  iterator_close_do(env, iterator, argv[1]);
 | 
						|
 | 
						|
  NAPI_RETURN_UNDEFINED();
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Worker class for nexting an iterator.
 | 
						|
 */
 | 
						|
struct NextWorker final : public BaseWorker {
 | 
						|
  NextWorker (napi_env env,
 | 
						|
              Iterator* iterator,
 | 
						|
              uint32_t size,
 | 
						|
              napi_value callback)
 | 
						|
    : BaseWorker(env, iterator->database_, callback,
 | 
						|
                 "classic_level.iterator.next"),
 | 
						|
      iterator_(iterator), size_(size), ok_() {}
 | 
						|
 | 
						|
  ~NextWorker () {}
 | 
						|
 | 
						|
  void DoExecute () override {
 | 
						|
    if (!iterator_->DidSeek()) {
 | 
						|
      iterator_->SeekToRange();
 | 
						|
    }
 | 
						|
 | 
						|
    ok_ = iterator_->ReadMany(size_);
 | 
						|
 | 
						|
    if (!ok_) {
 | 
						|
      SetStatus(iterator_->Status());
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  void HandleOKCallback (napi_env env, napi_value callback) override {
 | 
						|
    size_t size = iterator_->cache_.size();
 | 
						|
    napi_value jsArray;
 | 
						|
    napi_create_array_with_length(env, size, &jsArray);
 | 
						|
 | 
						|
    const Encoding ke = iterator_->keyEncoding_;
 | 
						|
    const Encoding ve = iterator_->valueEncoding_;
 | 
						|
 | 
						|
    for (uint32_t idx = 0; idx < size; idx++) {
 | 
						|
      napi_value element;
 | 
						|
      iterator_->cache_[idx].ConvertByMode(env, Mode::entries, ke, ve, element);
 | 
						|
      napi_set_element(env, jsArray, idx, element);
 | 
						|
    }
 | 
						|
 | 
						|
    napi_value argv[3];
 | 
						|
    napi_get_null(env, &argv[0]);
 | 
						|
    argv[1] = jsArray;
 | 
						|
    napi_get_boolean(env, !ok_, &argv[2]);
 | 
						|
    CallFunction(env, callback, 3, argv);
 | 
						|
  }
 | 
						|
 | 
						|
  void DoFinally (napi_env env) override {
 | 
						|
    // clean up & handle the next/close state
 | 
						|
    iterator_->nexting_ = false;
 | 
						|
 | 
						|
    if (iterator_->closeWorker_ != NULL) {
 | 
						|
      iterator_->closeWorker_->Queue(env);
 | 
						|
      iterator_->closeWorker_ = NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    BaseWorker::DoFinally(env);
 | 
						|
  }
 | 
						|
 | 
						|
private:
 | 
						|
  Iterator* iterator_;
 | 
						|
  uint32_t size_;
 | 
						|
  bool ok_;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Advance repeatedly and get multiple entries at once.
 | 
						|
 */
 | 
						|
NAPI_METHOD(iterator_nextv) {
 | 
						|
  NAPI_ARGV(3);
 | 
						|
  NAPI_ITERATOR_CONTEXT();
 | 
						|
 | 
						|
  uint32_t size;
 | 
						|
  NAPI_STATUS_THROWS(napi_get_value_uint32(env, argv[1], &size));
 | 
						|
  if (size == 0) size = 1;
 | 
						|
 | 
						|
  napi_value callback = argv[2];
 | 
						|
 | 
						|
  if (iterator->isClosing_ || iterator->hasClosed_) {
 | 
						|
    napi_value argv = CreateCodeError(env, "LEVEL_ITERATOR_NOT_OPEN", "Iterator is not open");
 | 
						|
    NAPI_STATUS_THROWS(CallFunction(env, callback, 1, &argv));
 | 
						|
    NAPI_RETURN_UNDEFINED();
 | 
						|
  }
 | 
						|
 | 
						|
  NextWorker* worker = new NextWorker(env, iterator, size, callback);
 | 
						|
  iterator->nexting_ = true;
 | 
						|
  worker->Queue(env);
 | 
						|
 | 
						|
  NAPI_RETURN_UNDEFINED();
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Worker class for batch write operation.
 | 
						|
 */
 | 
						|
struct BatchWorker final : public PriorityWorker {
 | 
						|
  BatchWorker (napi_env env,
 | 
						|
               Database* database,
 | 
						|
               napi_value callback,
 | 
						|
               leveldb::WriteBatch* batch,
 | 
						|
               const bool sync,
 | 
						|
               const bool hasData)
 | 
						|
    : PriorityWorker(env, database, callback, "classic_level.batch.do"),
 | 
						|
      batch_(batch), hasData_(hasData) {
 | 
						|
    options_.sync = sync;
 | 
						|
  }
 | 
						|
 | 
						|
  ~BatchWorker () {
 | 
						|
    delete batch_;
 | 
						|
  }
 | 
						|
 | 
						|
  void DoExecute () override {
 | 
						|
    if (hasData_) {
 | 
						|
      SetStatus(database_->WriteBatch(options_, batch_));
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
private:
 | 
						|
  leveldb::WriteOptions options_;
 | 
						|
  leveldb::WriteBatch* batch_;
 | 
						|
  const bool hasData_;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Does a batch write operation on a database.
 | 
						|
 */
 | 
						|
NAPI_METHOD(batch_do) {
 | 
						|
  NAPI_ARGV(4);
 | 
						|
  NAPI_DB_CONTEXT();
 | 
						|
 | 
						|
  napi_value array = argv[1];
 | 
						|
  const bool sync = BooleanProperty(env, argv[2], "sync", false);
 | 
						|
  napi_value callback = argv[3];
 | 
						|
 | 
						|
  uint32_t length;
 | 
						|
  napi_get_array_length(env, array, &length);
 | 
						|
 | 
						|
  leveldb::WriteBatch* batch = new leveldb::WriteBatch();
 | 
						|
  bool hasData = false;
 | 
						|
 | 
						|
  for (uint32_t i = 0; i < length; i++) {
 | 
						|
    napi_value element;
 | 
						|
    napi_get_element(env, array, i, &element);
 | 
						|
 | 
						|
    if (!IsObject(env, element)) continue;
 | 
						|
 | 
						|
    std::string type = StringProperty(env, element, "type");
 | 
						|
 | 
						|
    if (type == "del") {
 | 
						|
      if (!HasProperty(env, element, "key")) continue;
 | 
						|
      leveldb::Slice key = ToSlice(env, GetProperty(env, element, "key"));
 | 
						|
 | 
						|
      batch->Delete(key);
 | 
						|
      if (!hasData) hasData = true;
 | 
						|
 | 
						|
      DisposeSliceBuffer(key);
 | 
						|
    } else if (type == "put") {
 | 
						|
      if (!HasProperty(env, element, "key")) continue;
 | 
						|
      if (!HasProperty(env, element, "value")) continue;
 | 
						|
 | 
						|
      leveldb::Slice key = ToSlice(env, GetProperty(env, element, "key"));
 | 
						|
      leveldb::Slice value = ToSlice(env, GetProperty(env, element, "value"));
 | 
						|
 | 
						|
      batch->Put(key, value);
 | 
						|
      if (!hasData) hasData = true;
 | 
						|
 | 
						|
      DisposeSliceBuffer(key);
 | 
						|
      DisposeSliceBuffer(value);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  BatchWorker* worker = new BatchWorker(env, database, callback, batch, sync, hasData);
 | 
						|
  worker->Queue(env);
 | 
						|
 | 
						|
  NAPI_RETURN_UNDEFINED();
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Owns a WriteBatch.
 | 
						|
 */
 | 
						|
struct Batch {
 | 
						|
  Batch (Database* database)
 | 
						|
    : database_(database),
 | 
						|
      batch_(new leveldb::WriteBatch()),
 | 
						|
      hasData_(false) {}
 | 
						|
 | 
						|
  ~Batch () {
 | 
						|
    delete batch_;
 | 
						|
  }
 | 
						|
 | 
						|
  void Put (leveldb::Slice key, leveldb::Slice value) {
 | 
						|
    batch_->Put(key, value);
 | 
						|
    hasData_ = true;
 | 
						|
  }
 | 
						|
 | 
						|
  void Del (leveldb::Slice key) {
 | 
						|
    batch_->Delete(key);
 | 
						|
    hasData_ = true;
 | 
						|
  }
 | 
						|
 | 
						|
  void Clear () {
 | 
						|
    batch_->Clear();
 | 
						|
    hasData_ = false;
 | 
						|
  }
 | 
						|
 | 
						|
  leveldb::Status Write (bool sync) {
 | 
						|
    leveldb::WriteOptions options;
 | 
						|
    options.sync = sync;
 | 
						|
    return database_->WriteBatch(options, batch_);
 | 
						|
  }
 | 
						|
 | 
						|
  Database* database_;
 | 
						|
  leveldb::WriteBatch* batch_;
 | 
						|
  bool hasData_;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Runs when a Batch is garbage collected.
 | 
						|
 */
 | 
						|
static void FinalizeBatch (napi_env env, void* data, void* hint) {
 | 
						|
  if (data) {
 | 
						|
    delete (Batch*)data;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Return a batch object.
 | 
						|
 */
 | 
						|
NAPI_METHOD(batch_init) {
 | 
						|
  NAPI_ARGV(1);
 | 
						|
  NAPI_DB_CONTEXT();
 | 
						|
 | 
						|
  Batch* batch = new Batch(database);
 | 
						|
 | 
						|
  napi_value result;
 | 
						|
  NAPI_STATUS_THROWS(napi_create_external(env, batch,
 | 
						|
                                          FinalizeBatch,
 | 
						|
                                          NULL, &result));
 | 
						|
  return result;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Adds a put instruction to a batch object.
 | 
						|
 */
 | 
						|
NAPI_METHOD(batch_put) {
 | 
						|
  NAPI_ARGV(3);
 | 
						|
  NAPI_BATCH_CONTEXT();
 | 
						|
 | 
						|
  leveldb::Slice key = ToSlice(env, argv[1]);
 | 
						|
  leveldb::Slice value = ToSlice(env, argv[2]);
 | 
						|
  batch->Put(key, value);
 | 
						|
  DisposeSliceBuffer(key);
 | 
						|
  DisposeSliceBuffer(value);
 | 
						|
 | 
						|
  NAPI_RETURN_UNDEFINED();
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Adds a delete instruction to a batch object.
 | 
						|
 */
 | 
						|
NAPI_METHOD(batch_del) {
 | 
						|
  NAPI_ARGV(2);
 | 
						|
  NAPI_BATCH_CONTEXT();
 | 
						|
 | 
						|
  leveldb::Slice key = ToSlice(env, argv[1]);
 | 
						|
  batch->Del(key);
 | 
						|
  DisposeSliceBuffer(key);
 | 
						|
 | 
						|
  NAPI_RETURN_UNDEFINED();
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Clears a batch object.
 | 
						|
 */
 | 
						|
NAPI_METHOD(batch_clear) {
 | 
						|
  NAPI_ARGV(1);
 | 
						|
  NAPI_BATCH_CONTEXT();
 | 
						|
 | 
						|
  batch->Clear();
 | 
						|
 | 
						|
  NAPI_RETURN_UNDEFINED();
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Worker class for batch write operation.
 | 
						|
 */
 | 
						|
struct BatchWriteWorker final : public PriorityWorker {
 | 
						|
  BatchWriteWorker (napi_env env,
 | 
						|
                    napi_value context,
 | 
						|
                    Batch* batch,
 | 
						|
                    napi_value callback,
 | 
						|
                    const bool sync)
 | 
						|
    : PriorityWorker(env, batch->database_, callback, "classic_level.batch.write"),
 | 
						|
      batch_(batch),
 | 
						|
      sync_(sync) {
 | 
						|
        // Prevent GC of batch object before we execute
 | 
						|
        NAPI_STATUS_THROWS_VOID(napi_create_reference(env, context, 1, &contextRef_));
 | 
						|
      }
 | 
						|
 | 
						|
  ~BatchWriteWorker () {}
 | 
						|
 | 
						|
  void DoExecute () override {
 | 
						|
    if (batch_->hasData_) {
 | 
						|
      SetStatus(batch_->Write(sync_));
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  void DoFinally (napi_env env) override {
 | 
						|
    napi_delete_reference(env, contextRef_);
 | 
						|
    PriorityWorker::DoFinally(env);
 | 
						|
  }
 | 
						|
 | 
						|
private:
 | 
						|
  Batch* batch_;
 | 
						|
  const bool sync_;
 | 
						|
  napi_ref contextRef_;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Writes a batch object.
 | 
						|
 */
 | 
						|
NAPI_METHOD(batch_write) {
 | 
						|
  NAPI_ARGV(3);
 | 
						|
  NAPI_BATCH_CONTEXT();
 | 
						|
 | 
						|
  napi_value options = argv[1];
 | 
						|
  const bool sync = BooleanProperty(env, options, "sync", false);
 | 
						|
  napi_value callback = argv[2];
 | 
						|
 | 
						|
  BatchWriteWorker* worker  = new BatchWriteWorker(env, argv[0], batch, callback, sync);
 | 
						|
  worker->Queue(env);
 | 
						|
 | 
						|
  NAPI_RETURN_UNDEFINED();
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * All exported functions.
 | 
						|
 */
 | 
						|
NAPI_INIT() {
 | 
						|
  NAPI_EXPORT_FUNCTION(db_init);
 | 
						|
  NAPI_EXPORT_FUNCTION(db_open);
 | 
						|
  NAPI_EXPORT_FUNCTION(db_close);
 | 
						|
  NAPI_EXPORT_FUNCTION(db_put);
 | 
						|
  NAPI_EXPORT_FUNCTION(db_get);
 | 
						|
  NAPI_EXPORT_FUNCTION(db_get_many);
 | 
						|
  NAPI_EXPORT_FUNCTION(db_del);
 | 
						|
  NAPI_EXPORT_FUNCTION(db_clear);
 | 
						|
  NAPI_EXPORT_FUNCTION(db_approximate_size);
 | 
						|
  NAPI_EXPORT_FUNCTION(db_compact_range);
 | 
						|
  NAPI_EXPORT_FUNCTION(db_get_property);
 | 
						|
 | 
						|
  NAPI_EXPORT_FUNCTION(destroy_db);
 | 
						|
  NAPI_EXPORT_FUNCTION(repair_db);
 | 
						|
 | 
						|
  NAPI_EXPORT_FUNCTION(iterator_init);
 | 
						|
  NAPI_EXPORT_FUNCTION(iterator_seek);
 | 
						|
  NAPI_EXPORT_FUNCTION(iterator_close);
 | 
						|
  NAPI_EXPORT_FUNCTION(iterator_nextv);
 | 
						|
 | 
						|
  NAPI_EXPORT_FUNCTION(batch_do);
 | 
						|
  NAPI_EXPORT_FUNCTION(batch_init);
 | 
						|
  NAPI_EXPORT_FUNCTION(batch_put);
 | 
						|
  NAPI_EXPORT_FUNCTION(batch_del);
 | 
						|
  NAPI_EXPORT_FUNCTION(batch_clear);
 | 
						|
  NAPI_EXPORT_FUNCTION(batch_write);
 | 
						|
}
 |