Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions cpp/react-native-leveldb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ using namespace facebook;
// TODO(savv): consider re-using unique_ptrs, if they are empty.
std::vector<std::unique_ptr<leveldb::DB>> dbs;
std::vector<std::unique_ptr<leveldb::Iterator>> iterators;
std::vector<std::unique_ptr<leveldb::WriteBatch>> batches;

// Returns false if the passed value is not a string or an ArrayBuffer.
bool valueToString(jsi::Runtime& runtime, const jsi::Value& value, std::string* str) {
Expand Down Expand Up @@ -63,6 +64,24 @@ leveldb::Iterator* valueToIterator(const jsi::Value& value) {
return iterators[idx].get();
}

leveldb::WriteBatch* valueToWriteBatch(const jsi::Value& value, std::string* err) {
if (!value.isNumber()) {
*err = "valueToWriteBatch/param-not-a-number";
return nullptr;
}
int idx = (int)value.getNumber();
if (idx < 0 || idx >= batches.size()) {
*err = "valueToWriteBatch/idx-out-of-range";
return nullptr;
}
if (!batches[idx].get()) {
*err = "valueToWriteBatch/null";
return nullptr;
}

return batches[idx].get();
}

void installLeveldb(jsi::Runtime& jsiRuntime, std::string documentDir) {
if (documentDir[documentDir.length() - 1] != '/') {
documentDir += '/';
Expand Down Expand Up @@ -363,6 +382,108 @@ void installLeveldb(jsi::Runtime& jsiRuntime, std::string documentDir) {
);
jsiRuntime.global().setProperty(jsiRuntime, "leveldbIteratorValueStr", std::move(leveldbIteratorValueStr));

auto leveldbNewWriteBatch = jsi::Function::createFromHostFunction(
jsiRuntime,
jsi::PropNameID::forAscii(jsiRuntime, "leveldbNewWriteBatch"),
0,
[](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, size_t count) -> jsi::Value {
batches.push_back(std::unique_ptr<leveldb::WriteBatch>(new leveldb::WriteBatch()));
return jsi::Value((int)batches.size() - 1);
}
);
jsiRuntime.global().setProperty(jsiRuntime, "leveldbNewWriteBatch", std::move(leveldbNewWriteBatch));

auto leveldbWriteWriteBatch = jsi::Function::createFromHostFunction(
jsiRuntime,
jsi::PropNameID::forAscii(jsiRuntime, "leveldbWriteWriteBatch"),
2, // dbs index, batches index
[](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, size_t count) -> jsi::Value {
std::string dbErr;
leveldb::DB* db = valueToDb(arguments[0], &dbErr);
if (!db) {
throw jsi::JSError(runtime, "leveldbWriteWriteBatch/" + dbErr);
}

std::string batchErr;
leveldb::WriteBatch* batch = valueToWriteBatch(arguments[1], &batchErr);
if (!batch) {
throw jsi::JSError(runtime, "leveldbWriteBatchPut/" + batchErr);
}

db->Write(leveldb::WriteOptions(), batch);

return nullptr;
}
);
jsiRuntime.global().setProperty(jsiRuntime, "leveldbWriteWriteBatch", std::move(leveldbWriteWriteBatch));

auto leveldbWriteBatchPut = jsi::Function::createFromHostFunction(
jsiRuntime,
jsi::PropNameID::forAscii(jsiRuntime, "leveldbWriteBatchPut"),
3, // batches index, key, value
[](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, size_t count) -> jsi::Value {
std::string key, value;
std::string batchErr;
leveldb::WriteBatch* batch = valueToWriteBatch(arguments[0], &batchErr);
if (!batch) {
throw jsi::JSError(runtime, "leveldbWriteBatchPut/" + batchErr);
}
if (!valueToString(runtime, arguments[1], &key) || !valueToString(runtime, arguments[2], &value)) {
throw jsi::JSError(runtime, "leveldbWriteBatchPut/invalid-params");
}

batch->Put(key, value);

return nullptr;
}
);
jsiRuntime.global().setProperty(jsiRuntime, "leveldbWriteBatchPut", std::move(leveldbWriteBatchPut));

auto leveldbWriteBatchDelete = jsi::Function::createFromHostFunction(
jsiRuntime,
jsi::PropNameID::forAscii(jsiRuntime, "leveldbWriteBatchDelete"),
2, // batches index, key
[](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, size_t count) -> jsi::Value {
std::string key, value;
std::string batchErr;
leveldb::WriteBatch* batch = valueToWriteBatch(arguments[0], &batchErr);
if (!batch) {
throw jsi::JSError(runtime, "leveldbWriteBatchDelete/" + batchErr);
}
if (!valueToString(runtime, arguments[1], &key)) {
throw jsi::JSError(runtime, "leveldbWriteBatchDelete/invalid-params");
}

batch->Delete(key);

return nullptr;
}
);
jsiRuntime.global().setProperty(jsiRuntime, "leveldbWriteBatchDelete", std::move(leveldbWriteBatchDelete));

auto leveldbWriteBatchClose = jsi::Function::createFromHostFunction(
jsiRuntime,
jsi::PropNameID::forAscii(jsiRuntime, "leveldbWriteBatchClose"),
1, // batches index
[](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, size_t count) -> jsi::Value {
std::string batchErr;
leveldb::WriteBatch* batch = valueToWriteBatch(arguments[0], &batchErr);
if (!batch) {
throw jsi::JSError(runtime, "leveldbWriteBatchClose/" + batchErr);
}

int idx = (int)arguments[0].getNumber();
if (idx < 0 || idx >= batches.size() || !batches[idx].get()) {
throw jsi::JSError(runtime, "leveldbWriteBatchClose/idx-out-of-bounds");
}

batches[idx].reset();

return nullptr;
}
);
jsiRuntime.global().setProperty(jsiRuntime, "leveldbWriteBatchClose", std::move(leveldbWriteBatchClose));

auto leveldbGetStr = jsi::Function::createFromHostFunction(
jsiRuntime,
jsi::PropNameID::forAscii(jsiRuntime, "leveldbGetStr"),
Expand Down
69 changes: 68 additions & 1 deletion example/src/example.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {LevelDB} from "react-native-leveldb";
import {LevelDB, LevelDBWriteBatch} from "react-native-leveldb";
import {bufEquals, getRandomString} from "./test-util";

export function leveldbExample(): boolean {
Expand Down Expand Up @@ -80,8 +80,75 @@ export function leveldbTestMerge(batchMerge: boolean) {
return errors;
}

function leveldbTestWriteBatch() {
let nameDst = getRandomString(32) + '.db';
console.info('leveldbTestWriteBatch: Opening DB', nameDst);
const dbDst = new LevelDB(nameDst, true, true);

const writeBatch = new LevelDBWriteBatch();
const key1 = new Uint8Array([1, 2, 3]);
const value1 = new Uint8Array([4, 5, 6]);
const key2 = new Uint8Array([3, 2, 1]);
const value2 = new Uint8Array([6, 5, 4]);

writeBatch.put(key1.buffer, value1.buffer);
writeBatch.put(key2.buffer, value2.buffer);
dbDst.write(writeBatch);
writeBatch.close();

const errors: string[] = []
if (!bufEquals(dbDst.getBuf(key1.buffer)!, value1)) {
errors.push(`leveldbTestWriteBatch: key1 didn't have expected value: ${new Uint8Array(dbDst.getBuf(key1.buffer)!)}`);
}
if (!bufEquals(dbDst.getBuf(key2.buffer)!, value2)) {
errors.push(`leveldbTestWriteBatch: key2 didn't have expected value: ${new Uint8Array(dbDst.getBuf(key2.buffer)!)}`);
}

try {
writeBatch.put(key2.buffer, value2.buffer);
errors.push(`leveldbTestWriteBatch: FAILED! No exception after close.`);
} catch(e: any) {
// Expected an exception
}

const writeBatchDelete = new LevelDBWriteBatch();
writeBatchDelete.delete(key1.buffer);
writeBatchDelete.delete(key2.buffer);
dbDst.write(writeBatchDelete);
writeBatchDelete.close();

if (dbDst.getBuf(key1.buffer) !== null) {
errors.push(`leveldbTestWriteBatch: key1 didn't have expected value: ${new Uint8Array(dbDst.getBuf(key1.buffer)!)}`);
}
if (dbDst.getBuf(key2.buffer) !== null) {
errors.push(`leveldbTestWriteBatch: key2 didn't have expected value: ${new Uint8Array(dbDst.getBuf(key2.buffer)!)}`);
}

try {
writeBatch.delete(key2.buffer);
errors.push(`leveldbTestWriteBatch: FAILED! No exception after close.`);
} catch(e: any) {
// Expected an exception
}

return errors
}


export function leveldbTests() {
let s: string[] = [];

try {
const res = leveldbTestWriteBatch();
if (res.length) {
s.push('leveldbTestWriteBatch() failed with:' + res.join('; '));
} else {
s.push('leveldbTestWriteBatch() succeeded');
}
} catch (e: any) {
s.push('leveldbTestWriteBatch() threw: ' + e.message);
}

try {
(global as any).leveldbTestException();
s.push('leveldbTestException: FAILED! No exception.');
Expand Down
37 changes: 37 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,18 @@ export interface LevelDBIteratorI {
compareKey(target: ArrayBuffer | string): number;
}

export interface LevelDBWriteBatchI {
// Set the database entry for "k" to "v".
put(k: ArrayBuffer | string, v: ArrayBuffer | string): void;

// Remove the database entry (if any) for "key".
// It is not an error if "key" did not exist in the database.
delete(k: ArrayBuffer | string): void;

// Drops the reference to this WriteBatch.
close(): void;
}

export interface LevelDBI {
// Close this ref to LevelDB.
close(): void;
Expand Down Expand Up @@ -97,6 +109,27 @@ export interface LevelDBI {
newIterator(): LevelDBIteratorI;
}

export class LevelDBWriteBatch implements LevelDBWriteBatchI {
ref: number;

constructor() {
this.ref = g.leveldbNewWriteBatch();
}

put(k: ArrayBuffer | string, v: ArrayBuffer | string) {
g.leveldbWriteBatchPut(this.ref, k, v);
}

delete(k: ArrayBuffer | string) {
g.leveldbWriteBatchDelete(this.ref, k);
}

close() {
g.leveldbWriteBatchClose(this.ref);
this.ref = -1;
}
}

export class LevelDBIterator implements LevelDBIteratorI {
private ref: number;

Expand Down Expand Up @@ -212,6 +245,10 @@ export class LevelDB implements LevelDBI {
return new LevelDBIterator(this.ref);
}

write(batch: LevelDBWriteBatch) {
g.leveldbWriteWriteBatch(this.ref, batch.ref);
}

// Merges the data from another LevelDB into this one. All keys from src will be written into this LevelDB,
// overwriting any existing values.
// batchMerge=true will write all values from src in one transaction, thus ensuring that the dst DB is not left
Expand Down