From 4a06c27145894924a90f650befd910805a682119 Mon Sep 17 00:00:00 2001 From: Chris Savvopoulos Date: Mon, 21 Dec 2020 15:47:20 +0100 Subject: [PATCH] feat: add documentation and final touches --- README.md | 42 +++++++++++++++++++++++++++++++----- cpp/react-native-leveldb.cpp | 37 ++++++++++++++++++++++--------- example/src/App.tsx | 15 ++++++++++--- example/src/example.ts | 37 +++++++++++++++++++++++++++++++ src/index.tsx | 20 +++++++++++------ 5 files changed, 127 insertions(+), 24 deletions(-) create mode 100644 example/src/example.ts diff --git a/README.md b/README.md index 95d37e8..73fcfd3 100644 --- a/README.md +++ b/README.md @@ -5,17 +5,49 @@ Superfast React Native bindings for LevelDB ## Installation ```sh -npm install react-native-leveldb +yarn add react-native-leveldb ``` ## Usage -```js -import Leveldb from "react-native-leveldb"; +```ts +import {LevelDB} from "react-native-leveldb"; -// ... +export function leveldbExample(): boolean { + // Open a potentially new database. + const name = 'example.db'; + const createIfMissing = true; + const errorIfExists = false; + const db = new LevelDB(name, createIfMissing, errorIfExists); -const result = await Leveldb.multiply(3, 7); + // Insert something into the database. Note that the key and the value can either be + // strings or ArrayBuffers. Strings are read & written in utf8. + db.put('key', 'value'); + + // You can also use ArrayBuffers as input, containing binary data. + const key = new Uint8Array([1, 2, 3]); + const value = new Uint32Array([654321]); + db.put(key.buffer, value.buffer); + + // Get values as string or as an ArrayBuffer (useful for binary data). + const readStringValue = db.getStr('key'); + const readBufferValue = new Uint32Array(db.getBuf(key.buffer)); + console.log(readStringValue, readBufferValue); // should log: value [654321] + + // Iterate over a range of values (here, from key "key" to the end.) + let iter = db.newIterator(); + for (iter.seek('key'); iter.valid(); iter.next()) { + // There are also *Buf version to access iterators' keys & values. + console.log(`iterating: "${iter.keyStr()}" / "${iter.valueStr()}"`); + } + + // You need to close iterators when you are done with them. Iterators will throw an error if used after this. + iter.close(); + + db.close(); // Same for databases. + + return readStringValue == 'value' && readBufferValue.length == 1 && readBufferValue[0] == 654321; +} ``` ## Contributing diff --git a/cpp/react-native-leveldb.cpp b/cpp/react-native-leveldb.cpp index 266b6e4..f9a64da 100644 --- a/cpp/react-native-leveldb.cpp +++ b/cpp/react-native-leveldb.cpp @@ -13,9 +13,9 @@ std::vector> dbs; std::vector> iterators; // Returns false if the passed value is not a string or an ArrayBuffer. -bool valueToSlice(jsi::Runtime& runtime, const jsi::Value& value, leveldb::Slice* slice) { +bool valueToString(jsi::Runtime& runtime, const jsi::Value& value, std::string* str) { if (value.isString()) { - *slice = leveldb::Slice(value.asString(runtime).utf8(runtime)); + *str = value.asString(runtime).utf8(runtime); return true; } @@ -25,7 +25,7 @@ bool valueToSlice(jsi::Runtime& runtime, const jsi::Value& value, leveldb::Slice return false; } auto buf = obj.getArrayBuffer(runtime); - *slice = leveldb::Slice((char*)buf.data(runtime), buf.size(runtime)); + *str = std::string((char*)buf.data(runtime), buf.size(runtime)); return true; } @@ -115,9 +115,9 @@ void installLeveldb(jsi::Runtime& jsiRuntime, std::string documentDir) { jsi::PropNameID::forAscii(jsiRuntime, "leveldbPut"), 3, // dbs index, key, value [](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, size_t count) -> jsi::Value { - leveldb::Slice key, value; + std::string key, value; leveldb::DB* db = valueToDb(arguments[0]); - if (!db || !valueToSlice(runtime, arguments[1], &key) || !valueToSlice(runtime, arguments[2], &value)) { + if (!db || !valueToString(runtime, arguments[1], &key) || !valueToString(runtime, arguments[2], &value)) { return jsi::Value(-1); } @@ -187,11 +187,11 @@ void installLeveldb(jsi::Runtime& jsiRuntime, std::string documentDir) { return jsi::Value(-1); } - leveldb::Slice slice; - if (!valueToSlice(runtime, arguments[1], &slice)) { + std::string target; + if (!valueToString(runtime, arguments[1], &target)) { return jsi::Value(-1); } - iterator->Seek(slice); + iterator->Seek(target); return nullptr; } ); @@ -226,6 +226,21 @@ void installLeveldb(jsi::Runtime& jsiRuntime, std::string documentDir) { ); jsiRuntime.global().setProperty(jsiRuntime, "leveldbIteratorNext", std::move(leveldbIteratorNext)); + auto leveldbIteratorDelete = jsi::Function::createFromHostFunction( + jsiRuntime, + jsi::PropNameID::forAscii(jsiRuntime, "leveldbIteratorDelete"), + 1, // iterators index + [](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, size_t count) -> jsi::Value { + leveldb::Iterator* iterator = valueToIterator(arguments[0]); + if (!iterator) { + return jsi::Value(-1); + } + iterators[(int)arguments[0].getNumber()].release(); + return nullptr; + } + ); + jsiRuntime.global().setProperty(jsiRuntime, "leveldbIteratorDelete", std::move(leveldbIteratorDelete)); + auto leveldbIteratorKeyStr = jsi::Function::createFromHostFunction( jsiRuntime, jsi::PropNameID::forAscii(jsiRuntime, "leveldbIteratorKeyStr"), @@ -259,8 +274,9 @@ void installLeveldb(jsi::Runtime& jsiRuntime, std::string documentDir) { jsi::PropNameID::forAscii(jsiRuntime, "leveldbGetStr"), 2, // dbs index, key [](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, size_t count) -> jsi::Value { - leveldb::Slice key; leveldb::DB* db = valueToDb(arguments[0]); + std::string key; + valueToString(runtime, arguments[1], &key); std::string value; auto status = db->Get(leveldb::ReadOptions(), key, &value); @@ -317,8 +333,9 @@ void installLeveldb(jsi::Runtime& jsiRuntime, std::string documentDir) { jsi::PropNameID::forAscii(jsiRuntime, "leveldbGetBuf"), 2, // dbs index, key [](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, size_t count) -> jsi::Value { - leveldb::Slice key; leveldb::DB* db = valueToDb(arguments[0]); + std::string key; + valueToString(runtime, arguments[1], &key); std::string value; auto status = db->Get(leveldb::ReadOptions(), key, &value); diff --git a/example/src/App.tsx b/example/src/App.tsx index 2b4a302..bf3592f 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,9 +1,16 @@ import * as React from 'react'; -import {StyleSheet, View} from 'react-native'; -import {benchmarkAsyncStorage, benchmarkLeveldb, BenchmarkResults, BenchmarkResultsView} from "./benchmark"; +import {StyleSheet, View, Text} from 'react-native'; +import { + benchmarkAsyncStorage, + benchmarkLeveldb, + BenchmarkResults, + BenchmarkResultsView +} from "./benchmark"; +import {leveldbExample} from "./example"; interface BenchmarkState { leveldb?: BenchmarkResults; + leveldbExample?: boolean; asyncStorage?: BenchmarkResults; } @@ -13,7 +20,8 @@ export default class App extends React.Component<{}, BenchmarkState> { componentDidMount() { try { this.setState({ - leveldb: benchmarkLeveldb() + leveldb: benchmarkLeveldb(), + leveldbExample: leveldbExample(), }); benchmarkAsyncStorage().then(res => this.setState({asyncStorage: res})); @@ -25,6 +33,7 @@ export default class App extends React.Component<{}, BenchmarkState> { render() { return ( + Example validity: {this.state.leveldbExample == undefined ? '' : this.state.leveldbExample ? 'passed' : 'failed'} {this.state.leveldb && } {this.state.asyncStorage && } diff --git a/example/src/example.ts b/example/src/example.ts new file mode 100644 index 0000000..4232b8c --- /dev/null +++ b/example/src/example.ts @@ -0,0 +1,37 @@ +import {LevelDB} from "react-native-leveldb"; + +export function leveldbExample(): boolean { + // Open a potentially new database. + const name = 'example.db'; + const createIfMissing = true; + const errorIfExists = false; + const db = new LevelDB(name, createIfMissing, errorIfExists); + + // Insert something into the database. Note that the key and the value can either be + // strings or ArrayBuffers. Strings are read & written in utf8. + db.put('key', 'value'); + + // You can also use ArrayBuffers as input, containing binary data. + const key = new Uint8Array([1, 2, 3]); + const value = new Uint32Array([654321]); + db.put(key.buffer, value.buffer); + + // Get values as string or as an ArrayBuffer (useful for binary data). + const readStringValue = db.getStr('key'); + const readBufferValue = new Uint32Array(db.getBuf(key.buffer)); + console.log(readStringValue, readBufferValue); // should log: value [654321] + + // Iterate over a range of values (here, from key "key" to the end.) + let iter = db.newIterator(); + for (iter.seek('key'); iter.valid(); iter.next()) { + // There are also *Buf version to access iterators' keys & values. + console.log(`iterating: "${iter.keyStr()}" / "${iter.valueStr()}"`); + } + + // You need to close iterators when you are done with them. Iterators will throw an error if used after this. + iter.close(); + + db.close(); // Same for databases. + + return readStringValue == 'value' && readBufferValue.length == 1 && readBufferValue[0] == 654321; +} diff --git a/src/index.tsx b/src/index.tsx index df9688a..05356eb 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -55,6 +55,14 @@ export class LevelDBIterator { } } + close() { + if (isBadResult(g.leveldbIteratorDelete(this.ref))) { + throw new Error('LevelDBIterator error'); + } + + this.ref = -1; + } + keyStr(): string { const res = g.leveldbIteratorKeyStr(this.ref); if (isBadResult(res)) { @@ -147,18 +155,18 @@ export class LevelDB { } } - getStr(k: ArrayBuffer | string, v: ArrayBuffer | string) { - const res = g.leveldbGetStr(this.ref, k, v); + getStr(k: ArrayBuffer | string) { + const res = g.leveldbGetStr(this.ref, k); if (isBadResult(res)) { - throw new Error('LevelDB: unable to get()'); + throw new Error(`LevelDB: unable to getStr(): ${res}`); } return res; } - getBuf(k: ArrayBuffer | string, v: ArrayBuffer | string) { - const res = g.leveldbGetBuf(this.ref, k, v); + getBuf(k: ArrayBuffer | string) { + const res = g.leveldbGetBuf(this.ref, k); if (isBadResult(res)) { - throw new Error('LevelDB: unable to get()'); + throw new Error(`LevelDB: unable to getBuf(): ${res}`); } return res; }