Merge pull request #413 from saghmrossi/master

first rdb commit
This commit is contained in:
Igor Canadi 2014-11-24 16:54:44 -05:00
commit 7530c75ab0
9 changed files with 969 additions and 0 deletions

1
tools/rdb/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
build

178
tools/rdb/API.md Normal file
View File

@ -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 occured 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 familiy 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'
}
]);

40
tools/rdb/README.md Normal file
View File

@ -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 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.

25
tools/rdb/binding.gyp Normal file
View File

@ -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"
],
}
]
}

525
tools/rdb/db_wrapper.cc Normal file
View File

@ -0,0 +1,525 @@
#include <iostream>
#include <memory>
#include <vector>
#include <v8.h>
#include <node.h>
#include "db_wrapper.h"
#include "rocksdb/db.h"
#include "rocksdb/slice.h"
#include "rocksdb/options.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::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::DB::Open(
db_wrapper->options_, db_file, &db_wrapper->db_);
return scope.Close(Boolean::New(db_wrapper->status_.ok()));
}
std::vector<rocksdb::ColumnFamilyDescriptor> families;
for (std::vector<int>::size_type i = 0; i < cfs.size(); i++) {
families.push_back(rocksdb::ColumnFamilyDescriptor(
cfs[i], rocksdb::ColumnFamilyOptions()));
}
std::vector<rocksdb::ColumnFamilyHandle*> handles;
db_wrapper->status_ = rocksdb::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::ReadOptions(), key, &value);
} else if (db_wrapper->HasFamilyNamed(cf, db_wrapper)) {
db_wrapper->status_ = db_wrapper->db_->Get(
rocksdb::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::WriteOptions(), key, value
);
} else if (db_wrapper->HasFamilyNamed(cf, db_wrapper)) {
db_wrapper->status_ = db_wrapper->db_->Put(
rocksdb::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::WriteOptions(), arg0);
} else {
if (!db_wrapper->HasFamilyNamed(arg1, db_wrapper)) {
return scope.Close(Boolean::New(false));
}
db_wrapper->status_ = db_wrapper->db_->Delete(
rocksdb::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::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::ReadOptions()));
} else {
if (!db_wrapper->HasFamilyNamed(arg0, db_wrapper)) {
return scope.Close(Boolean::New(false));
}
iterator.reset(db_wrapper->db_->NewIterator(
rocksdb::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::ColumnFamilyHandle* cf;
db_wrapper->status_ = db_wrapper->db_->CreateColumnFamily(
rocksdb::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::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::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::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::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::Slice begin = *v8::String::Utf8Value(args[0]->ToString());
rocksdb::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::Slice begin = *v8::String::Utf8Value(args[0]->ToString());
rocksdb::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::Slice begin = *v8::String::Utf8Value(args[0]->ToString());
rocksdb::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::Slice begin = *v8::String::Utf8Value(args[0]->ToString());
rocksdb::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());
}

58
tools/rdb/db_wrapper.h Normal file
View File

@ -0,0 +1,58 @@
#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::WriteBatch& batch, bool del,
Handle<Array> array);
static bool AddToBatch(rocksdb::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::Options options_;
rocksdb::Status status_;
rocksdb::DB* db_;
std::unordered_map<std::string, rocksdb::ColumnFamilyHandle*>
columnFamilies_;
};
#endif

3
tools/rdb/rdb Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
node -e "RDB = require('./build/Release/rdb').DBWrapper; console.log('Loaded rocksdb in variable RDB'); repl = require('repl').start('> ');"

15
tools/rdb/rdb.cc Normal file
View File

@ -0,0 +1,15 @@
#ifndef BUILDING_NODE_EXTENSION
#define BUILDING_NODE_EXTENSION
#endif
#include <v8.h>
#include <node.h>
#include "db_wrapper.h"
using namespace v8;
void InitAll(Handle<Object> exports) {
DBWrapper::Init(exports);
}
NODE_MODULE(rdb, InitAll)

124
tools/rdb/unit_test.js Normal file
View File

@ -0,0 +1,124 @@
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)