summaryrefslogtreecommitdiffstats
path: root/src/rocksdb/tools/rdb
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/rocksdb/tools/rdb/.gitignore1
-rw-r--r--src/rocksdb/tools/rdb/API.md178
-rw-r--r--src/rocksdb/tools/rdb/README.md40
-rw-r--r--src/rocksdb/tools/rdb/binding.gyp25
-rw-r--r--src/rocksdb/tools/rdb/db_wrapper.cc526
-rw-r--r--src/rocksdb/tools/rdb/db_wrapper.h60
-rwxr-xr-xsrc/rocksdb/tools/rdb/rdb3
-rw-r--r--src/rocksdb/tools/rdb/rdb.cc16
-rw-r--r--src/rocksdb/tools/rdb/unit_test.js125
9 files changed, 974 insertions, 0 deletions
diff --git a/src/rocksdb/tools/rdb/.gitignore b/src/rocksdb/tools/rdb/.gitignore
new file mode 100644
index 000000000..378eac25d
--- /dev/null
+++ b/src/rocksdb/tools/rdb/.gitignore
@@ -0,0 +1 @@
+build
diff --git a/src/rocksdb/tools/rdb/API.md b/src/rocksdb/tools/rdb/API.md
new file mode 100644
index 000000000..e9c2e5925
--- /dev/null
+++ b/src/rocksdb/tools/rdb/API.md
@@ -0,0 +1,178 @@
+# JavaScript API
+
+## DBWrapper
+
+### Constructor
+
+ # Creates a new database wrapper object
+ RDB()
+
+### Open
+
+ # Open a new or existing RocksDB database.
+ #
+ # db_name (string) - Location of the database (inside the
+ # `/tmp` directory).
+ # column_families (string[]) - Names of additional column families
+ # beyond the default. If there are no other
+ # column families, this argument can be
+ # left off.
+ #
+ # Returns true if the database was opened successfully, or false otherwise
+ db_obj.(db_name, column_families = [])
+
+### Get
+
+ # Get the value of a given key.
+ #
+ # key (string) - Which key to get the value of.
+ # column_family (string) - Which column family to check for the key.
+ # This argument can be left off for the default
+ # column family
+ #
+ # Returns the value (string) that is associated with the given key if
+ # one exists, or null otherwise.
+ db_obj.get(key, column_family = { default })
+
+### Put
+
+ # Associate a value with a key.
+ #
+ # key (string) - Which key to associate the value with.
+ # value (string) - The value to associate with the key.
+ # column_family (string) - Which column family to put the key-value pair
+ # in. This argument can be left off for the
+ # default column family.
+ #
+ # Returns true if the key-value pair was successfully stored in the
+ # database, or false otherwise.
+ db_obj.put(key, value, column_family = { default })
+
+### Delete
+
+ # Delete a value associated with a given key.
+ #
+ # key (string) - Which key to delete the value of..
+ # column_family (string) - Which column family to check for the key.
+ # This argument can be left off for the default
+ # column family
+ #
+ # Returns true if an error occurred while trying to delete the key in
+ # the database, or false otherwise. Note that this is NOT the same as
+ # whether a value was deleted; in the case of a specified key not having
+ # a value, this will still return true. Use the `get` method prior to
+ # this method to check if a value existed before the call to `delete`.
+ db_obj.delete(key, column_family = { default })
+
+### Dump
+
+ # Print out all the key-value pairs in a given column family of the
+ # database.
+ #
+ # column_family (string) - Which column family to dump the pairs from.
+ # This argument can be left off for the default
+ # column family.
+ #
+ # Returns true if the keys were successfully read from the database, or
+ # false otherwise.
+ db_obj.dump(column_family = { default })
+
+### WriteBatch
+
+ # Execute an atomic batch of writes (i.e. puts and deletes) to the
+ # database.
+ #
+ # cf_batches (BatchObject[]; see below) - Put and Delete writes grouped
+ # by column family to execute
+ # atomically.
+ #
+ # Returns true if the argument array was well-formed and was
+ # successfully written to the database, or false otherwise.
+ db_obj.writeBatch(cf_batches)
+
+### CreateColumnFamily
+
+ # Create a new column family for the database.
+ #
+ # column_family_name (string) - Name of the new column family.
+ #
+ # Returns true if the new column family was successfully created, or
+ # false otherwise.
+ db_obj.createColumnFamily(column_family_name)
+
+### CompactRange
+
+ # Compact the underlying storage for a given range.
+ #
+ # In addition to the endpoints of the range, the method is overloaded to
+ # accept a non-default column family, a set of options, or both.
+ #
+ # begin (string) - First key in the range to compact.
+ # end (string) - Last key in the range to compact.
+ # options (object) - Contains a subset of the following key-value
+ # pairs:
+ # * 'target_level' => int
+ # * 'target_path_id' => int
+ # column_family (string) - Which column family to compact the range in.
+ db_obj.compactRange(begin, end)
+ db_obj.compactRange(begin, end, options)
+ db_obj.compactRange(begin, end, column_family)
+ db_obj.compactRange(begin, end, options, column_family)
+
+
+
+### Close
+
+ # Close an a database and free the memory associated with it.
+ #
+ # Return null.
+ # db_obj.close()
+
+
+## BatchObject
+
+### Structure
+
+A BatchObject must have at least one of the following key-value pairs:
+
+* 'put' => Array of ['string1', 'string1'] pairs, each of which signifies that
+the key 'string1' should be associated with the value 'string2'
+* 'delete' => Array of strings, each of which is a key whose value should be
+deleted.
+
+The following key-value pair is optional:
+
+* 'column_family' => The name (string) of the column family to apply the
+changes to.
+
+### Examples
+
+ # Writes the key-value pairs 'firstname' => 'Saghm' and
+ # 'lastname' => 'Rossi' atomically to the database.
+ db_obj.writeBatch([
+ {
+ put: [ ['firstname', 'Saghm'], ['lastname', 'Rossi'] ]
+ }
+ ]);
+
+
+ # Deletes the values associated with 'firstname' and 'lastname' in
+ # the default column family and adds the key 'number_of_people' with
+ # with the value '2'. Additionally, adds the key-value pair
+ # 'name' => 'Saghm Rossi' to the column family 'user1' and the pair
+ # 'name' => 'Matt Blaze' to the column family 'user2'. All writes
+ # are done atomically.
+ db_obj.writeBatch([
+ {
+ put: [ ['number_of_people', '2'] ],
+ delete: ['firstname', 'lastname']
+ },
+ {
+ put: [ ['name', 'Saghm Rossi'] ],
+ column_family: 'user1'
+ },
+ {
+ put: [ ['name', Matt Blaze'] ],
+ column_family: 'user2'
+ }
+ ]);
diff --git a/src/rocksdb/tools/rdb/README.md b/src/rocksdb/tools/rdb/README.md
new file mode 100644
index 000000000..f69b3f7b1
--- /dev/null
+++ b/src/rocksdb/tools/rdb/README.md
@@ -0,0 +1,40 @@
+# RDB - RocksDB Shell
+
+RDB is a NodeJS-based shell interface to RocksDB. It can also be used as a
+JavaScript binding for RocksDB within a Node application.
+
+## Setup/Compilation
+
+### Requirements
+
+* static RocksDB library (i.e. librocksdb.a)
+* libsnappy
+* node (tested onv0.10.33, no guarantees on anything else!)
+* node-gyp
+* python2 (for node-gyp; tested with 2.7.8)
+
+### Installation
+
+NOTE: If your default `python` binary is not a version of python2, add
+the arguments `--python /path/to/python2` to the `node-gyp` commands.
+
+1. Make sure you have the static library (i.e. "librocksdb.a") in the root
+directory of your rocksdb installation. If not, `cd` there and run
+`make static_lib`.
+
+2. Run `node-gyp configure` to generate the build.
+
+3. Run `node-gyp build` to compile RDB.
+
+## Usage
+
+### Running the shell
+
+Assuming everything compiled correctly, you can run the `rdb` executable
+located in the root of the `tools/rdb` directory to start the shell. The file is
+just a shell script that runs the node shell and loads the constructor for the
+RDB object into the top-level function `RDB`.
+
+### JavaScript API
+
+See `API.md` for how to use RocksDB from the shell.
diff --git a/src/rocksdb/tools/rdb/binding.gyp b/src/rocksdb/tools/rdb/binding.gyp
new file mode 100644
index 000000000..89145541c
--- /dev/null
+++ b/src/rocksdb/tools/rdb/binding.gyp
@@ -0,0 +1,25 @@
+{
+ "targets": [
+ {
+ "target_name": "rdb",
+ "sources": [
+ "rdb.cc",
+ "db_wrapper.cc",
+ "db_wrapper.h"
+ ],
+ "cflags_cc!": [
+ "-fno-exceptions"
+ ],
+ "cflags_cc+": [
+ "-std=c++11",
+ ],
+ "include_dirs+": [
+ "../../include"
+ ],
+ "libraries": [
+ "../../../librocksdb.a",
+ "-lsnappy"
+ ],
+ }
+ ]
+}
diff --git a/src/rocksdb/tools/rdb/db_wrapper.cc b/src/rocksdb/tools/rdb/db_wrapper.cc
new file mode 100644
index 000000000..632cf05a8
--- /dev/null
+++ b/src/rocksdb/tools/rdb/db_wrapper.cc
@@ -0,0 +1,526 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+#include <iostream>
+#include <memory>
+#include <vector>
+#include <v8.h>
+#include <node.h>
+
+#include "db/_wrapper.h"
+#include "rocksdb/db.h"
+#include "rocksdb/options.h"
+#include "rocksdb/slice.h"
+
+namespace {
+ void printWithBackSlashes(std::string str) {
+ for (std::string::size_type i = 0; i < str.size(); i++) {
+ if (str[i] == '\\' || str[i] == '"') {
+ std::cout << "\\";
+ }
+
+ std::cout << str[i];
+ }
+ }
+
+ bool has_key_for_array(Local<Object> obj, std::string key) {
+ return obj->Has(String::NewSymbol(key.c_str())) &&
+ obj->Get(String::NewSymbol(key.c_str()))->IsArray();
+ }
+}
+
+using namespace v8;
+
+
+Persistent<Function> DBWrapper::constructor;
+
+DBWrapper::DBWrapper() {
+ options_.IncreaseParallelism();
+ options_.OptimizeLevelStyleCompaction();
+ options_.disable_auto_compactions = true;
+ options_.create_if_missing = true;
+}
+
+DBWrapper::~DBWrapper() {
+ delete db_;
+}
+
+bool DBWrapper::HasFamilyNamed(std::string& name, DBWrapper* db) {
+ return db->columnFamilies_.find(name) != db->columnFamilies_.end();
+}
+
+
+void DBWrapper::Init(Handle<Object> exports) {
+ Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
+ tpl->SetClassName(String::NewSymbol("DBWrapper"));
+ tpl->InstanceTemplate()->SetInternalFieldCount(8);
+ tpl->PrototypeTemplate()->Set(String::NewSymbol("open"),
+ FunctionTemplate::New(Open)->GetFunction());
+ tpl->PrototypeTemplate()->Set(String::NewSymbol("get"),
+ FunctionTemplate::New(Get)->GetFunction());
+ tpl->PrototypeTemplate()->Set(String::NewSymbol("put"),
+ FunctionTemplate::New(Put)->GetFunction());
+ tpl->PrototypeTemplate()->Set(String::NewSymbol("delete"),
+ FunctionTemplate::New(Delete)->GetFunction());
+ tpl->PrototypeTemplate()->Set(String::NewSymbol("dump"),
+ FunctionTemplate::New(Dump)->GetFunction());
+ tpl->PrototypeTemplate()->Set(String::NewSymbol("createColumnFamily"),
+ FunctionTemplate::New(CreateColumnFamily)->GetFunction());
+ tpl->PrototypeTemplate()->Set(String::NewSymbol("writeBatch"),
+ FunctionTemplate::New(WriteBatch)->GetFunction());
+ tpl->PrototypeTemplate()->Set(String::NewSymbol("compactRange"),
+ FunctionTemplate::New(CompactRange)->GetFunction());
+
+ constructor = Persistent<Function>::New(tpl->GetFunction());
+ exports->Set(String::NewSymbol("DBWrapper"), constructor);
+}
+
+Handle<Value> DBWrapper::Open(const Arguments& args) {
+ HandleScope scope;
+ DBWrapper* db_wrapper = ObjectWrap::Unwrap<DBWrapper>(args.This());
+
+ if (!(args[0]->IsString() &&
+ (args[1]->IsUndefined() || args[1]->IsArray()))) {
+ return scope.Close(Boolean::New(false));
+ }
+
+ std::string db_file = *v8::String::Utf8Value(args[0]->ToString());
+
+ std::vector<std::string> cfs = {ROCKSDB_NAMESPACE::kDefaultColumnFamilyName};
+
+ if (!args[1]->IsUndefined()) {
+ Handle<Array> array = Handle<Array>::Cast(args[1]);
+ for (uint i = 0; i < array->Length(); i++) {
+ if (!array->Get(i)->IsString()) {
+ return scope.Close(Boolean::New(false));
+ }
+
+ cfs.push_back(*v8::String::Utf8Value(array->Get(i)->ToString()));
+ }
+ }
+
+ if (cfs.size() == 1) {
+ db_wrapper->status_ = ROCKSDB_NAMESPACE::DB::Open(
+ db_wrapper->options_, db_file, &db_wrapper->db_);
+
+ return scope.Close(Boolean::New(db_wrapper->status_.ok()));
+ }
+
+ std::vector<ROCKSDB_NAMESPACE::ColumnFamilyDescriptor> families;
+
+ for (std::vector<int>::size_type i = 0; i < cfs.size(); i++) {
+ families.push_back(ROCKSDB_NAMESPACE::ColumnFamilyDescriptor(
+ cfs[i], ROCKSDB_NAMESPACE::ColumnFamilyOptions()));
+ }
+
+ std::vector<ROCKSDB_NAMESPACE::ColumnFamilyHandle*> handles;
+ db_wrapper->status_ = ROCKSDB_NAMESPACE::DB::Open(
+ db_wrapper->options_, db_file, families, &handles, &db_wrapper->db_);
+
+ if (!db_wrapper->status_.ok()) {
+ return scope.Close(Boolean::New(db_wrapper->status_.ok()));
+ }
+
+ for (std::vector<int>::size_type i = 0; i < handles.size(); i++) {
+ db_wrapper->columnFamilies_[cfs[i]] = handles[i];
+ }
+
+ return scope.Close(Boolean::New(true));
+}
+
+
+Handle<Value> DBWrapper::New(const Arguments& args) {
+ HandleScope scope;
+ Handle<Value> to_return;
+
+ if (args.IsConstructCall()) {
+ DBWrapper* db_wrapper = new DBWrapper();
+ db_wrapper->Wrap(args.This());
+
+ return args.This();
+ }
+
+ const int argc = 0;
+ Local<Value> argv[0] = {};
+
+ return scope.Close(constructor->NewInstance(argc, argv));
+}
+
+Handle<Value> DBWrapper::Get(const Arguments& args) {
+ HandleScope scope;
+
+ if (!(args[0]->IsString() &&
+ (args[1]->IsUndefined() || args[1]->IsString()))) {
+ return scope.Close(Null());
+ }
+
+ DBWrapper* db_wrapper = ObjectWrap::Unwrap<DBWrapper>(args.This());
+ std::string key = *v8::String::Utf8Value(args[0]->ToString());
+ std::string cf = *v8::String::Utf8Value(args[1]->ToString());
+ std::string value;
+
+ if (args[1]->IsUndefined()) {
+ db_wrapper->status_ =
+ db_wrapper->db_->Get(ROCKSDB_NAMESPACE::ReadOptions(), key, &value);
+ } else if (db_wrapper->HasFamilyNamed(cf, db_wrapper)) {
+ db_wrapper->status_ =
+ db_wrapper->db_->Get(ROCKSDB_NAMESPACE::ReadOptions(),
+ db_wrapper->columnFamilies_[cf], key, &value);
+ } else {
+ return scope.Close(Null());
+ }
+
+ Handle<Value> v = db_wrapper->status_.ok() ?
+ String::NewSymbol(value.c_str()) : Null();
+
+ return scope.Close(v);
+}
+
+Handle<Value> DBWrapper::Put(const Arguments& args) {
+ HandleScope scope;
+
+ if (!(args[0]->IsString() && args[1]->IsString() &&
+ (args[2]->IsUndefined() || args[2]->IsString()))) {
+ return scope.Close(Boolean::New(false));
+ }
+
+ DBWrapper* db_wrapper = ObjectWrap::Unwrap<DBWrapper>(args.This());
+ std::string key = *v8::String::Utf8Value(args[0]->ToString());
+ std::string value = *v8::String::Utf8Value(args[1]->ToString());
+ std::string cf = *v8::String::Utf8Value(args[2]->ToString());
+
+ if (args[2]->IsUndefined()) {
+ db_wrapper->status_ =
+ db_wrapper->db_->Put(ROCKSDB_NAMESPACE::WriteOptions(), key, value);
+ } else if (db_wrapper->HasFamilyNamed(cf, db_wrapper)) {
+ db_wrapper->status_ =
+ db_wrapper->db_->Put(ROCKSDB_NAMESPACE::WriteOptions(),
+ db_wrapper->columnFamilies_[cf], key, value);
+ } else {
+ return scope.Close(Boolean::New(false));
+ }
+
+
+ return scope.Close(Boolean::New(db_wrapper->status_.ok()));
+}
+
+Handle<Value> DBWrapper::Delete(const Arguments& args) {
+ HandleScope scope;
+
+ if (!args[0]->IsString()) {
+ return scope.Close(Boolean::New(false));
+ }
+
+ DBWrapper* db_wrapper = ObjectWrap::Unwrap<DBWrapper>(args.This());
+ std::string arg0 = *v8::String::Utf8Value(args[0]->ToString());
+ std::string arg1 = *v8::String::Utf8Value(args[1]->ToString());
+
+ if (args[1]->IsUndefined()) {
+ db_wrapper->status_ =
+ db_wrapper->db_->Delete(ROCKSDB_NAMESPACE::WriteOptions(), arg0);
+ } else {
+ if (!db_wrapper->HasFamilyNamed(arg1, db_wrapper)) {
+ return scope.Close(Boolean::New(false));
+ }
+ db_wrapper->status_ =
+ db_wrapper->db_->Delete(ROCKSDB_NAMESPACE::WriteOptions(),
+ db_wrapper->columnFamilies_[arg1], arg0);
+ }
+
+ return scope.Close(Boolean::New(db_wrapper->status_.ok()));
+}
+
+Handle<Value> DBWrapper::Dump(const Arguments& args) {
+ HandleScope scope;
+ std::unique_ptr<ROCKSDB_NAMESPACE::Iterator> iterator;
+ DBWrapper* db_wrapper = ObjectWrap::Unwrap<DBWrapper>(args.This());
+ std::string arg0 = *v8::String::Utf8Value(args[0]->ToString());
+
+ if (args[0]->IsUndefined()) {
+ iterator.reset(
+ db_wrapper->db_->NewIterator(ROCKSDB_NAMESPACE::ReadOptions()));
+ } else {
+ if (!db_wrapper->HasFamilyNamed(arg0, db_wrapper)) {
+ return scope.Close(Boolean::New(false));
+ }
+
+ iterator.reset(db_wrapper->db_->NewIterator(
+ ROCKSDB_NAMESPACE::ReadOptions(), db_wrapper->columnFamilies_[arg0]));
+ }
+
+ iterator->SeekToFirst();
+
+ while (iterator->Valid()) {
+ std::cout << "\"";
+ printWithBackSlashes(iterator->key().ToString());
+ std::cout << "\" => \"";
+ printWithBackSlashes(iterator->value().ToString());
+ std::cout << "\"\n";
+ iterator->Next();
+ }
+
+ return scope.Close(Boolean::New(true));
+}
+
+Handle<Value> DBWrapper::CreateColumnFamily(const Arguments& args) {
+ HandleScope scope;
+
+ if (!args[0]->IsString()) {
+ return scope.Close(Boolean::New(false));
+ }
+
+ DBWrapper* db_wrapper = ObjectWrap::Unwrap<DBWrapper>(args.This());
+ std::string cf_name = *v8::String::Utf8Value(args[0]->ToString());
+
+ if (db_wrapper->HasFamilyNamed(cf_name, db_wrapper)) {
+ return scope.Close(Boolean::New(false));
+ }
+
+ ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf;
+ db_wrapper->status_ = db_wrapper->db_->CreateColumnFamily(
+ ROCKSDB_NAMESPACE::ColumnFamilyOptions(), cf_name, &cf);
+
+ if (!db_wrapper->status_.ok()) {
+ return scope.Close(Boolean::New(false));
+ }
+
+ db_wrapper->columnFamilies_[cf_name] = cf;
+
+ return scope.Close(Boolean::New(true));
+}
+
+bool DBWrapper::AddToBatch(ROCKSDB_NAMESPACE::WriteBatch& batch, bool del,
+ Handle<Array> array) {
+ Handle<Array> put_pair;
+ for (uint i = 0; i < array->Length(); i++) {
+ if (del) {
+ if (!array->Get(i)->IsString()) {
+ return false;
+ }
+
+ batch.Delete(*v8::String::Utf8Value(array->Get(i)->ToString()));
+ continue;
+ }
+
+ if (!array->Get(i)->IsArray()) {
+ return false;
+ }
+
+ put_pair = Handle<Array>::Cast(array->Get(i));
+
+ if (!put_pair->Get(0)->IsString() || !put_pair->Get(1)->IsString()) {
+ return false;
+ }
+
+ batch.Put(
+ *v8::String::Utf8Value(put_pair->Get(0)->ToString()),
+ *v8::String::Utf8Value(put_pair->Get(1)->ToString()));
+ }
+
+ return true;
+}
+
+bool DBWrapper::AddToBatch(ROCKSDB_NAMESPACE::WriteBatch& batch, bool del,
+ Handle<Array> array, DBWrapper* db_wrapper,
+ std::string cf) {
+ Handle<Array> put_pair;
+ for (uint i = 0; i < array->Length(); i++) {
+ if (del) {
+ if (!array->Get(i)->IsString()) {
+ return false;
+ }
+
+ batch.Delete(
+ db_wrapper->columnFamilies_[cf],
+ *v8::String::Utf8Value(array->Get(i)->ToString()));
+ continue;
+ }
+
+ if (!array->Get(i)->IsArray()) {
+ return false;
+ }
+
+ put_pair = Handle<Array>::Cast(array->Get(i));
+
+ if (!put_pair->Get(0)->IsString() || !put_pair->Get(1)->IsString()) {
+ return false;
+ }
+
+ batch.Put(
+ db_wrapper->columnFamilies_[cf],
+ *v8::String::Utf8Value(put_pair->Get(0)->ToString()),
+ *v8::String::Utf8Value(put_pair->Get(1)->ToString()));
+ }
+
+ return true;
+}
+
+Handle<Value> DBWrapper::WriteBatch(const Arguments& args) {
+ HandleScope scope;
+
+ if (!args[0]->IsArray()) {
+ return scope.Close(Boolean::New(false));
+ }
+
+ DBWrapper* db_wrapper = ObjectWrap::Unwrap<DBWrapper>(args.This());
+ Handle<Array> sub_batches = Handle<Array>::Cast(args[0]);
+ Local<Object> sub_batch;
+ ROCKSDB_NAMESPACE::WriteBatch batch;
+ bool well_formed;
+
+ for (uint i = 0; i < sub_batches->Length(); i++) {
+ if (!sub_batches->Get(i)->IsObject()) {
+ return scope.Close(Boolean::New(false));
+ }
+ sub_batch = sub_batches->Get(i)->ToObject();
+
+ if (sub_batch->Has(String::NewSymbol("column_family"))) {
+ if (!has_key_for_array(sub_batch, "put") &&
+ !has_key_for_array(sub_batch, "delete")) {
+ return scope.Close(Boolean::New(false));
+ }
+
+ well_formed = db_wrapper->AddToBatch(
+ batch, false,
+ Handle<Array>::Cast(sub_batch->Get(String::NewSymbol("put"))),
+ db_wrapper, *v8::String::Utf8Value(sub_batch->Get(
+ String::NewSymbol("column_family"))));
+
+ well_formed = db_wrapper->AddToBatch(
+ batch, true,
+ Handle<Array>::Cast(sub_batch->Get(String::NewSymbol("delete"))),
+ db_wrapper, *v8::String::Utf8Value(sub_batch->Get(
+ String::NewSymbol("column_family"))));
+ } else {
+ well_formed = db_wrapper->AddToBatch(
+ batch, false,
+ Handle<Array>::Cast(sub_batch->Get(String::NewSymbol("put"))));
+ well_formed = db_wrapper->AddToBatch(
+ batch, true,
+ Handle<Array>::Cast(sub_batch->Get(String::NewSymbol("delete"))));
+
+ if (!well_formed) {
+ return scope.Close(Boolean::New(false));
+ }
+ }
+ }
+
+ db_wrapper->status_ =
+ db_wrapper->db_->Write(ROCKSDB_NAMESPACE::WriteOptions(), &batch);
+
+ return scope.Close(Boolean::New(db_wrapper->status_.ok()));
+}
+
+Handle<Value> DBWrapper::CompactRangeDefault(const Arguments& args) {
+ HandleScope scope;
+
+ DBWrapper* db_wrapper = ObjectWrap::Unwrap<DBWrapper>(args.This());
+ ROCKSDB_NAMESPACE::Slice begin = *v8::String::Utf8Value(args[0]->ToString());
+ ROCKSDB_NAMESPACE::Slice end = *v8::String::Utf8Value(args[1]->ToString());
+ db_wrapper->status_ = db_wrapper->db_->CompactRange(&end, &begin);
+
+ return scope.Close(Boolean::New(db_wrapper->status_.ok()));
+}
+
+Handle<Value> DBWrapper::CompactColumnFamily(const Arguments& args) {
+ HandleScope scope;
+
+ DBWrapper* db_wrapper = ObjectWrap::Unwrap<DBWrapper>(args.This());
+ ROCKSDB_NAMESPACE::Slice begin = *v8::String::Utf8Value(args[0]->ToString());
+ ROCKSDB_NAMESPACE::Slice end = *v8::String::Utf8Value(args[1]->ToString());
+ std::string cf = *v8::String::Utf8Value(args[2]->ToString());
+ db_wrapper->status_ = db_wrapper->db_->CompactRange(
+ db_wrapper->columnFamilies_[cf], &begin, &end);
+
+ return scope.Close(Boolean::New(db_wrapper->status_.ok()));
+}
+
+Handle<Value> DBWrapper::CompactOptions(const Arguments& args) {
+ HandleScope scope;
+
+ if (!args[2]->IsObject()) {
+ return scope.Close(Boolean::New(false));
+ }
+
+ DBWrapper* db_wrapper = ObjectWrap::Unwrap<DBWrapper>(args.This());
+ ROCKSDB_NAMESPACE::Slice begin = *v8::String::Utf8Value(args[0]->ToString());
+ ROCKSDB_NAMESPACE::Slice end = *v8::String::Utf8Value(args[1]->ToString());
+ Local<Object> options = args[2]->ToObject();
+ int target_level = -1, target_path_id = 0;
+
+ if (options->Has(String::NewSymbol("target_level")) &&
+ options->Get(String::NewSymbol("target_level"))->IsInt32()) {
+ target_level = (int)(options->Get(
+ String::NewSymbol("target_level"))->ToInt32()->Value());
+
+ if (options->Has(String::NewSymbol("target_path_id")) ||
+ options->Get(String::NewSymbol("target_path_id"))->IsInt32()) {
+ target_path_id = (int)(options->Get(
+ String::NewSymbol("target_path_id"))->ToInt32()->Value());
+ }
+ }
+
+ db_wrapper->status_ = db_wrapper->db_->CompactRange(
+ &begin, &end, true, target_level, target_path_id
+ );
+
+ return scope.Close(Boolean::New(db_wrapper->status_.ok()));
+}
+
+Handle<Value> DBWrapper::CompactAll(const Arguments& args) {
+ HandleScope scope;
+
+ if (!args[2]->IsObject() || !args[3]->IsString()) {
+ return scope.Close(Boolean::New(false));
+ }
+
+ DBWrapper* db_wrapper = ObjectWrap::Unwrap<DBWrapper>(args.This());
+ ROCKSDB_NAMESPACE::Slice begin = *v8::String::Utf8Value(args[0]->ToString());
+ ROCKSDB_NAMESPACE::Slice end = *v8::String::Utf8Value(args[1]->ToString());
+ Local<Object> options = args[2]->ToObject();
+ std::string cf = *v8::String::Utf8Value(args[3]->ToString());
+
+ int target_level = -1, target_path_id = 0;
+
+ if (options->Has(String::NewSymbol("target_level")) &&
+ options->Get(String::NewSymbol("target_level"))->IsInt32()) {
+ target_level = (int)(options->Get(
+ String::NewSymbol("target_level"))->ToInt32()->Value());
+
+ if (options->Has(String::NewSymbol("target_path_id")) ||
+ options->Get(String::NewSymbol("target_path_id"))->IsInt32()) {
+ target_path_id = (int)(options->Get(
+ String::NewSymbol("target_path_id"))->ToInt32()->Value());
+ }
+ }
+
+ db_wrapper->status_ = db_wrapper->db_->CompactRange(
+ db_wrapper->columnFamilies_[cf], &begin, &end, true, target_level,
+ target_path_id);
+
+ return scope.Close(Boolean::New(db_wrapper->status_.ok()));
+}
+
+Handle<Value> DBWrapper::CompactRange(const Arguments& args) {
+ HandleScope scope;
+
+ if (!args[0]->IsString() || !args[1]->IsString()) {
+ return scope.Close(Boolean::New(false));
+ }
+
+ switch(args.Length()) {
+ case 2:
+ return CompactRangeDefault(args);
+ case 3:
+ return args[2]->IsString() ? CompactColumnFamily(args) :
+ CompactOptions(args);
+ default:
+ return CompactAll(args);
+ }
+}
+
+Handle<Value> DBWrapper::Close(const Arguments& args) {
+ HandleScope scope;
+
+ delete ObjectWrap::Unwrap<DBWrapper>(args.This());
+
+ return scope.Close(Null());
+}
diff --git a/src/rocksdb/tools/rdb/db_wrapper.h b/src/rocksdb/tools/rdb/db_wrapper.h
new file mode 100644
index 000000000..4b57320cd
--- /dev/null
+++ b/src/rocksdb/tools/rdb/db_wrapper.h
@@ -0,0 +1,60 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+#ifndef DBWRAPPER_H
+#define DBWRAPPER_H
+
+#include <map>
+#include <node.h>
+
+#include "rocksdb/db.h"
+#include "rocksdb/slice.h"
+#include "rocksdb/options.h"
+
+using namespace v8;
+
+// Used to encapsulate a particular instance of an opened database.
+//
+// This object should not be used directly in C++; it exists solely to provide
+// a mapping from a JavaScript object to a C++ code that can use the RocksDB
+// API.
+class DBWrapper : public node::ObjectWrap {
+ public:
+ static void Init(Handle<Object> exports);
+
+ private:
+ explicit DBWrapper();
+ ~DBWrapper();
+
+ // Helper methods
+ static bool HasFamilyNamed(std::string& name, DBWrapper* db);
+ static bool AddToBatch(ROCKSDB_NAMESPACE::WriteBatch& batch, bool del,
+ Handle<Array> array);
+ static bool AddToBatch(ROCKSDB_NAMESPACE::WriteBatch& batch, bool del,
+ Handle<Array> array, DBWrapper* db_wrapper,
+ std::string cf);
+ static Handle<Value> CompactRangeDefault(const v8::Arguments& args);
+ static Handle<Value> CompactColumnFamily(const Arguments& args);
+ static Handle<Value> CompactOptions(const Arguments& args);
+ static Handle<Value> CompactAll(const Arguments& args);
+
+ // C++ mappings of API methods
+ static Persistent<v8::Function> constructor;
+ static Handle<Value> Open(const Arguments& args);
+ static Handle<Value> New(const Arguments& args);
+ static Handle<Value> Get(const Arguments& args);
+ static Handle<Value> Put(const Arguments& args);
+ static Handle<Value> Delete(const Arguments& args);
+ static Handle<Value> Dump(const Arguments& args);
+ static Handle<Value> WriteBatch(const Arguments& args);
+ static Handle<Value> CreateColumnFamily(const Arguments& args);
+ static Handle<Value> CompactRange(const Arguments& args);
+ static Handle<Value> Close(const Arguments& args);
+
+ // Internal fields
+ ROCKSDB_NAMESPACE::Options options_;
+ ROCKSDB_NAMESPACE::Status status_;
+ ROCKSDB_NAMESPACE::DB* db_;
+ std::unordered_map<std::string, ROCKSDB_NAMESPACE::ColumnFamilyHandle*>
+ columnFamilies_;
+};
+
+#endif
diff --git a/src/rocksdb/tools/rdb/rdb b/src/rocksdb/tools/rdb/rdb
new file mode 100755
index 000000000..05da1158b
--- /dev/null
+++ b/src/rocksdb/tools/rdb/rdb
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+
+node -e "RDB = require('./build/Release/rdb').DBWrapper; console.log('Loaded rocksdb in variable RDB'); repl = require('repl').start('> ');"
diff --git a/src/rocksdb/tools/rdb/rdb.cc b/src/rocksdb/tools/rdb/rdb.cc
new file mode 100644
index 000000000..119fcc410
--- /dev/null
+++ b/src/rocksdb/tools/rdb/rdb.cc
@@ -0,0 +1,16 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+#ifndef BUILDING_NODE_EXTENSION
+#define BUILDING_NODE_EXTENSION
+#endif
+
+#include <node.h>
+#include <v8.h>
+#include "db/_wrapper.h"
+
+using namespace v8;
+
+void InitAll(Handle<Object> exports) {
+ DBWrapper::Init(exports);
+}
+
+NODE_MODULE(rdb, InitAll)
diff --git a/src/rocksdb/tools/rdb/unit_test.js b/src/rocksdb/tools/rdb/unit_test.js
new file mode 100644
index 000000000..d01ae1df8
--- /dev/null
+++ b/src/rocksdb/tools/rdb/unit_test.js
@@ -0,0 +1,125 @@
+// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+assert = require('assert')
+RDB = require('./build/Release/rdb').DBWrapper
+exec = require('child_process').exec
+util = require('util')
+
+DB_NAME = '/tmp/rocksdbtest-' + process.getuid()
+
+a = RDB()
+assert.equal(a.open(DB_NAME, ['b']), false)
+
+exec(
+ util.format(
+ "node -e \"RDB = require('./build/Release/rdb').DBWrapper; \
+ a = RDB('%s'); a.createColumnFamily('b')\"",
+ DB_NAME
+ ).exitCode, null
+)
+
+
+exec(
+ util.format(
+ "node -e \"RDB = require('./build/Release/rdb').DBWrapper; \
+ a = RDB('%s', ['b'])\"",
+ DB_NAME
+ ).exitCode, null
+)
+
+exec('rm -rf ' + DB_NAME)
+
+a = RDB()
+assert.equal(a.open(DB_NAME, ['a']), false)
+assert(a.open(DB_NAME), true)
+assert(a.createColumnFamily('temp'))
+
+b = RDB()
+assert.equal(b.open(DB_NAME), false)
+
+exec('rm -rf ' + DB_NAME)
+
+DB_NAME += 'b'
+
+a = RDB()
+assert(a.open(DB_NAME))
+assert.equal(a.constructor.name, 'DBWrapper')
+assert.equal(a.createColumnFamily(), false)
+assert.equal(a.createColumnFamily(1), false)
+assert.equal(a.createColumnFamily(['']), false)
+assert(a.createColumnFamily('b'))
+assert.equal(a.createColumnFamily('b'), false)
+
+// Get and Put
+assert.equal(a.get(1), null)
+assert.equal(a.get(['a']), null)
+assert.equal(a.get('a', 1), null)
+assert.equal(a.get(1, 'a'), null)
+assert.equal(a.get(1, 1), null)
+
+assert.equal(a.put(1), false)
+assert.equal(a.put(['a']), false)
+assert.equal(a.put('a', 1), false)
+assert.equal(a.put(1, 'a'), false)
+assert.equal(a.put(1, 1), false)
+assert.equal(a.put('a', 'a', 1), false)
+assert.equal(a.put('a', 1, 'a'), false)
+assert.equal(a.put(1, 'a', 'a'), false)
+assert.equal(a.put('a', 1, 1), false)
+assert.equal(a.put(1, 'a', 1), false)
+assert.equal(a.put(1, 1, 'a'), false)
+assert.equal(a.put(1, 1, 1), false)
+
+
+assert.equal(a.get(), null)
+assert.equal(a.get('a'), null)
+assert.equal(a.get('a', 'c'), null)
+assert.equal(a.put(), false)
+assert.equal(a.put('a'), false)
+assert.equal(a.get('a', 'b', 'c'), null)
+
+assert(a.put('a', 'axe'))
+assert(a.put('a', 'first'))
+assert.equal(a.get('a'), 'first')
+assert.equal(a.get('a', 'b'), null)
+assert.equal(a.get('a', 'c'), null)
+
+assert(a.put('a', 'apple', 'b'))
+assert.equal(a.get('a', 'b'), 'apple')
+assert.equal(a.get('a'), 'first')
+assert(a.put('b', 'butter', 'b'), 'butter')
+assert(a.put('b', 'banana', 'b'))
+assert.equal(a.get('b', 'b'), 'banana')
+assert.equal(a.get('b'), null)
+assert.equal(a.get('b', 'c'), null)
+
+// Delete
+assert.equal(a.delete(1), false)
+assert.equal(a.delete('a', 1), false)
+assert.equal(a.delete(1, 'a'), false)
+assert.equal(a.delete(1, 1), false)
+
+assert.equal(a.delete('b'), true)
+assert(a.delete('a'))
+assert.equal(a.get('a'), null)
+assert.equal(a.get('a', 'b'), 'apple')
+assert.equal(a.delete('c', 'c'), false)
+assert.equal(a.delete('c', 'b'), true)
+assert(a.delete('b', 'b'))
+assert.equal(a.get('b', 'b'), null)
+
+// Dump
+console.log("MARKER 1")
+assert(a.dump())
+console.log("Should be no output between 'MARKER 1' and here\n")
+console.log('Next line should be "a" => "apple"')
+assert(a.dump('b'))
+
+console.log("\nMARKER 2")
+assert.equal(a.dump('c'), false)
+console.log("Should be no output between 'MARKER 2' and here\n")
+
+// WriteBatch
+
+
+// Clean up test database
+exec('rm -rf ' + DB_NAME)