Summary: First commit for rdb shell Test Plan: unit_test.js does simple assertions on most of the main functionality; will update with rest of tests Reviewers: igor, rven, lijn, yhciang, sdong Subscribers: dhruba, leveldb Differential Revision: https://reviews.facebook.net/D28749main
parent
9e285d4238
commit
bafce61979
@ -0,0 +1 @@ |
|||||||
|
build |
@ -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' |
||||||
|
} |
||||||
|
]); |
@ -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. |
@ -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" |
||||||
|
], |
||||||
|
} |
||||||
|
] |
||||||
|
} |
@ -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()); |
||||||
|
} |
@ -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 |
@ -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('> ');" |
@ -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) |
@ -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) |
Loading…
Reference in new issue