From 563925edcf4691f1cc55658b5d0ea4b56388550c Mon Sep 17 00:00:00 2001 From: Adam Retter <adam.retter@googlemail.com> Date: Sat, 7 Jul 2018 09:34:04 +0530 Subject: [PATCH] [rocksdb] Added support for RocksDB Java API (#1052) --- bin/bindings.properties | 1 + bin/ycsb | 1 + distribution/pom.xml | 5 + pom.xml | 4 +- rocksdb/README.md | 45 ++ rocksdb/pom.xml | 68 +++ .../yahoo/ycsb/db/rocksdb/RocksDBClient.java | 395 ++++++++++++++++++ .../yahoo/ycsb/db/rocksdb/package-info.java | 22 + .../ycsb/db/rocksdb/RocksDBClientTest.java | 121 ++++++ 9 files changed, 661 insertions(+), 1 deletion(-) create mode 100644 rocksdb/README.md create mode 100644 rocksdb/pom.xml create mode 100644 rocksdb/src/main/java/com/yahoo/ycsb/db/rocksdb/RocksDBClient.java create mode 100644 rocksdb/src/main/java/com/yahoo/ycsb/db/rocksdb/package-info.java create mode 100644 rocksdb/src/test/java/com/yahoo/ycsb/db/rocksdb/RocksDBClientTest.java diff --git a/bin/bindings.properties b/bin/bindings.properties index 332a6318..a4e84720 100644 --- a/bin/bindings.properties +++ b/bin/bindings.properties @@ -68,6 +68,7 @@ rados:com.yahoo.ycsb.db.RadosClient redis:com.yahoo.ycsb.db.RedisClient rest:com.yahoo.ycsb.webservice.rest.RestClient riak:com.yahoo.ycsb.db.riak.RiakKVClient +rocksdb:com.yahoo.ycsb.db.rocksdb.RocksDBClient s3:com.yahoo.ycsb.db.S3Client solr:com.yahoo.ycsb.db.solr.SolrClient solr6:com.yahoo.ycsb.db.solr6.SolrClient diff --git a/bin/ycsb b/bin/ycsb index b0242f42..9f075f34 100755 --- a/bin/ycsb +++ b/bin/ycsb @@ -95,6 +95,7 @@ DATABASES = { "redis" : "com.yahoo.ycsb.db.RedisClient", "rest" : "com.yahoo.ycsb.webservice.rest.RestClient", "riak" : "com.yahoo.ycsb.db.riak.RiakKVClient", + "rocksdb" : "com.yahoo.ycsb.db.rocksdb.RocksDBClient", "s3" : "com.yahoo.ycsb.db.S3Client", "solr" : "com.yahoo.ycsb.db.solr.SolrClient", "solr6" : "com.yahoo.ycsb.db.solr6.SolrClient", diff --git a/distribution/pom.xml b/distribution/pom.xml index 6403cbfb..58cbf86c 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -229,6 +229,11 @@ LICENSE file. <artifactId>riak-binding</artifactId> <version>${project.version}</version> </dependency> + <dependency> + <groupId>com.yahoo.ycsb</groupId> + <artifactId>rocksdb-binding</artifactId> + <version>${project.version}</version> + </dependency> <dependency> <groupId>com.yahoo.ycsb</groupId> <artifactId>s3-binding</artifactId> diff --git a/pom.xml b/pom.xml index 3db8251a..62dc6aea 100644 --- a/pom.xml +++ b/pom.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- -Copyright (c) 2012 - 2017 YCSB contributors. All rights reserved. +Copyright (c) 2012 - 2018 YCSB contributors. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You @@ -99,6 +99,7 @@ LICENSE file. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <redis.version>2.9.0</redis.version> <riak.version>2.0.5</riak.version> + <rocksdb.version>5.11.3</rocksdb.version> <s3.version>1.10.20</s3.version> <solr.version>5.5.3</solr.version> <solr6.version>6.4.1</solr6.version> @@ -152,6 +153,7 @@ LICENSE file. <module>redis</module> <module>rest</module> <module>riak</module> + <module>rocksdb</module> <module>s3</module> <module>solr</module> <module>solr6</module> diff --git a/rocksdb/README.md b/rocksdb/README.md new file mode 100644 index 00000000..1a0bcde4 --- /dev/null +++ b/rocksdb/README.md @@ -0,0 +1,45 @@ +<!-- +Copyright (c) 2012 - 2018 YCSB contributors. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); you +may not use this file except in compliance with the License. You +may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. See accompanying +LICENSE file. +--> + +## Quick Start + +This section describes how to run YCSB on RocksDB running locally (within the same JVM). +NOTE: RocksDB is an embedded database and so articles like [How to run in parallel](https://github.com/brianfrankcooper/YCSB/wiki/Running-a-Workload-in-Parallel) are not applicable here. + +### 1. Set Up YCSB + +Clone the YCSB git repository and compile: + + git clone https://github.com/brianfrankcooper/YCSB.git + cd YCSB + mvn clean package + +### 2. Run YCSB + +Now you are ready to run! First, load the data: + + ./bin/ycsb load rocksdb -s -P workloads/workloada -p rocksdb.dir=/tmp/ycsb-rocksdb-data + +Then, run the workload: + + ./bin/ycsb run rocksdb -s -P workloads/workloada -p rocksdb.dir=/tmp/ycsb-rocksdb-data + +## RocksDB Configuration Parameters + +* ```rocksdb.dir``` - (required) A path to a folder to hold the RocksDB data files. + * EX. ```/tmp/ycsb-rocksdb-data``` + diff --git a/rocksdb/pom.xml b/rocksdb/pom.xml new file mode 100644 index 00000000..73c1a946 --- /dev/null +++ b/rocksdb/pom.xml @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +Copyright (c) 2017 YCSB contributors. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); you +may not use this file except in compliance with the License. You +may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. See accompanying +LICENSE file. +--> + +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>com.yahoo.ycsb</groupId> + <artifactId>binding-parent</artifactId> + <version>0.15.0-SNAPSHOT</version> + <relativePath>../binding-parent</relativePath> + </parent> + + <artifactId>rocksdb-binding</artifactId> + <name>RocksDB Java Binding</name> + <packaging>jar</packaging> + + <dependencies> + <dependency> + <groupId>org.rocksdb</groupId> + <artifactId>rocksdbjni</artifactId> + <version>${rocksdb.version}</version> + </dependency> + <dependency> + <groupId>com.yahoo.ycsb</groupId> + <artifactId>core</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>net.jcip</groupId> + <artifactId>jcip-annotations</artifactId> + <version>1.0</version> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <version>1.7.25</version> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> + <version>1.7.25</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.12</version> + <scope>test</scope> + </dependency> + + </dependencies> +</project> diff --git a/rocksdb/src/main/java/com/yahoo/ycsb/db/rocksdb/RocksDBClient.java b/rocksdb/src/main/java/com/yahoo/ycsb/db/rocksdb/RocksDBClient.java new file mode 100644 index 00000000..9790734c --- /dev/null +++ b/rocksdb/src/main/java/com/yahoo/ycsb/db/rocksdb/RocksDBClient.java @@ -0,0 +1,395 @@ +/* + * Copyright (c) 2018 YCSB contributors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you + * may not use this file except in compliance with the License. You + * may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. See accompanying + * LICENSE file. + */ + +package com.yahoo.ycsb.db.rocksdb; + +import com.yahoo.ycsb.*; +import com.yahoo.ycsb.Status; +import net.jcip.annotations.GuardedBy; +import org.rocksdb.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.file.*; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * RocksDB binding for <a href="http://rocksdb.org/">RocksDB</a>. + * + * See {@code rocksdb/README.md} for details. + */ +public class RocksDBClient extends DB { + + static final String PROPERTY_ROCKSDB_DIR = "rocksdb.dir"; + private static final String COLUMN_FAMILY_NAMES_FILENAME = "CF_NAMES"; + + private static final Logger LOGGER = LoggerFactory.getLogger(RocksDBClient.class); + + @GuardedBy("RocksDBClient.class") private static Path rocksDbDir = null; + @GuardedBy("RocksDBClient.class") private static RocksObject dbOptions = null; + @GuardedBy("RocksDBClient.class") private static RocksDB rocksDb = null; + @GuardedBy("RocksDBClient.class") private static int references = 0; + + private static final ConcurrentMap<String, ColumnFamily> COLUMN_FAMILIES = new ConcurrentHashMap<>(); + private static final ConcurrentMap<String, Lock> COLUMN_FAMILY_LOCKS = new ConcurrentHashMap<>(); + + @Override + public void init() throws DBException { + synchronized(RocksDBClient.class) { + if(rocksDb == null) { + rocksDbDir = Paths.get(getProperties().getProperty(PROPERTY_ROCKSDB_DIR)); + LOGGER.info("RocksDB data dir: " + rocksDbDir); + + try { + rocksDb = initRocksDB(); + } catch (final IOException | RocksDBException e) { + throw new DBException(e); + } + } + + references++; + } + } + + /** + * Initializes and opens the RocksDB database. + * + * Should only be called with a {@code synchronized(RocksDBClient.class)` block}. + * + * @return The initialized and open RocksDB instance. + */ + private RocksDB initRocksDB() throws IOException, RocksDBException { + if(!Files.exists(rocksDbDir)) { + Files.createDirectories(rocksDbDir); + } + + final List<String> cfNames = loadColumnFamilyNames(); + final List<ColumnFamilyOptions> cfOptionss = new ArrayList<>(); + final List<ColumnFamilyDescriptor> cfDescriptors = new ArrayList<>(); + + for(final String cfName : cfNames) { + final ColumnFamilyOptions cfOptions = new ColumnFamilyOptions() + .optimizeLevelStyleCompaction(); + final ColumnFamilyDescriptor cfDescriptor = new ColumnFamilyDescriptor( + cfName.getBytes(UTF_8), + cfOptions + ); + cfOptionss.add(cfOptions); + cfDescriptors.add(cfDescriptor); + } + + final int rocksThreads = Runtime.getRuntime().availableProcessors() * 2; + + if(cfDescriptors.isEmpty()) { + final Options options = new Options() + .optimizeLevelStyleCompaction() + .setCreateIfMissing(true) + .setCreateMissingColumnFamilies(true) + .setIncreaseParallelism(rocksThreads) + .setMaxBackgroundCompactions(rocksThreads) + .setInfoLogLevel(InfoLogLevel.INFO_LEVEL); + dbOptions = options; + return RocksDB.open(options, rocksDbDir.toAbsolutePath().toString()); + } else { + final DBOptions options = new DBOptions() + .setCreateIfMissing(true) + .setCreateMissingColumnFamilies(true) + .setIncreaseParallelism(rocksThreads) + .setMaxBackgroundCompactions(rocksThreads) + .setInfoLogLevel(InfoLogLevel.INFO_LEVEL); + dbOptions = options; + + final List<ColumnFamilyHandle> cfHandles = new ArrayList<>(); + final RocksDB db = RocksDB.open(options, rocksDbDir.toAbsolutePath().toString(), cfDescriptors, cfHandles); + for(int i = 0; i < cfNames.size(); i++) { + COLUMN_FAMILIES.put(cfNames.get(i), new ColumnFamily(cfHandles.get(i), cfOptionss.get(i))); + } + return db; + } + } + + @Override + public void cleanup() throws DBException { + super.cleanup(); + + synchronized (RocksDBClient.class) { + try { + if (references == 1) { + for (final ColumnFamily cf : COLUMN_FAMILIES.values()) { + cf.getHandle().close(); + } + + rocksDb.close(); + rocksDb = null; + + dbOptions.close(); + dbOptions = null; + + for (final ColumnFamily cf : COLUMN_FAMILIES.values()) { + cf.getOptions().close(); + } + saveColumnFamilyNames(); + COLUMN_FAMILIES.clear(); + + rocksDbDir = null; + } + + } catch (final IOException e) { + throw new DBException(e); + } finally { + references--; + } + } + } + + @Override + public Status read(final String table, final String key, final Set<String> fields, + final Map<String, ByteIterator> result) { + try { + if (!COLUMN_FAMILIES.containsKey(table)) { + createColumnFamily(table); + } + + final ColumnFamilyHandle cf = COLUMN_FAMILIES.get(table).getHandle(); + final byte[] values = rocksDb.get(cf, key.getBytes(UTF_8)); + if(values == null) { + return Status.NOT_FOUND; + } + deserializeValues(values, fields, result); + return Status.OK; + } catch(final RocksDBException e) { + LOGGER.error(e.getMessage(), e); + return Status.ERROR; + } + } + + @Override + public Status scan(final String table, final String startkey, final int recordcount, final Set<String> fields, + final Vector<HashMap<String, ByteIterator>> result) { + try { + if (!COLUMN_FAMILIES.containsKey(table)) { + createColumnFamily(table); + } + + final ColumnFamilyHandle cf = COLUMN_FAMILIES.get(table).getHandle(); + try(final RocksIterator iterator = rocksDb.newIterator(cf)) { + int iterations = 0; + for (iterator.seek(startkey.getBytes(UTF_8)); iterator.isValid() && iterations < recordcount; + iterator.next()) { + final HashMap<String, ByteIterator> values = new HashMap<>(); + deserializeValues(iterator.value(), fields, values); + result.add(values); + iterations++; + } + } + + return Status.OK; + } catch(final RocksDBException e) { + LOGGER.error(e.getMessage(), e); + return Status.ERROR; + } + } + + @Override + public Status update(final String table, final String key, final Map<String, ByteIterator> values) { + //TODO(AR) consider if this would be faster with merge operator + + try { + if (!COLUMN_FAMILIES.containsKey(table)) { + createColumnFamily(table); + } + + final ColumnFamilyHandle cf = COLUMN_FAMILIES.get(table).getHandle(); + final Map<String, ByteIterator> result = new HashMap<>(); + final byte[] currentValues = rocksDb.get(cf, key.getBytes(UTF_8)); + if(currentValues == null) { + return Status.NOT_FOUND; + } + deserializeValues(currentValues, null, result); + + //update + result.putAll(values); + + //store + rocksDb.put(cf, key.getBytes(UTF_8), serializeValues(result)); + + return Status.OK; + + } catch(final RocksDBException | IOException e) { + LOGGER.error(e.getMessage(), e); + return Status.ERROR; + } + } + + @Override + public Status insert(final String table, final String key, final Map<String, ByteIterator> values) { + try { + if (!COLUMN_FAMILIES.containsKey(table)) { + createColumnFamily(table); + } + + final ColumnFamilyHandle cf = COLUMN_FAMILIES.get(table).getHandle(); + rocksDb.put(cf, key.getBytes(UTF_8), serializeValues(values)); + + return Status.OK; + } catch(final RocksDBException | IOException e) { + LOGGER.error(e.getMessage(), e); + return Status.ERROR; + } + } + + @Override + public Status delete(final String table, final String key) { + try { + if (!COLUMN_FAMILIES.containsKey(table)) { + createColumnFamily(table); + } + + final ColumnFamilyHandle cf = COLUMN_FAMILIES.get(table).getHandle(); + rocksDb.delete(cf, key.getBytes(UTF_8)); + + return Status.OK; + } catch(final RocksDBException e) { + LOGGER.error(e.getMessage(), e); + return Status.ERROR; + } + } + + private void saveColumnFamilyNames() throws IOException { + final Path file = rocksDbDir.resolve(COLUMN_FAMILY_NAMES_FILENAME); + try(final PrintWriter writer = new PrintWriter(Files.newBufferedWriter(file, UTF_8))) { + writer.println(new String(RocksDB.DEFAULT_COLUMN_FAMILY, UTF_8)); + for(final String cfName : COLUMN_FAMILIES.keySet()) { + writer.println(cfName); + } + } + } + + private List<String> loadColumnFamilyNames() throws IOException { + final List<String> cfNames = new ArrayList<>(); + final Path file = rocksDbDir.resolve(COLUMN_FAMILY_NAMES_FILENAME); + if(Files.exists(file)) { + try (final LineNumberReader reader = + new LineNumberReader(Files.newBufferedReader(file, UTF_8))) { + String line = null; + while ((line = reader.readLine()) != null) { + cfNames.add(line); + } + } + } + return cfNames; + } + + private Map<String, ByteIterator> deserializeValues(final byte[] values, final Set<String> fields, + final Map<String, ByteIterator> result) { + final ByteBuffer buf = ByteBuffer.allocate(4); + + int offset = 0; + while(offset < values.length) { + buf.put(values, offset, 4); + buf.flip(); + final int keyLen = buf.getInt(); + buf.clear(); + offset += 4; + + final String key = new String(values, offset, keyLen); + offset += keyLen; + + buf.put(values, offset, 4); + buf.flip(); + final int valueLen = buf.getInt(); + buf.clear(); + offset += 4; + + if(fields == null || fields.contains(key)) { + result.put(key, new ByteArrayByteIterator(values, offset, valueLen)); + } + + offset += valueLen; + } + + return result; + } + + private byte[] serializeValues(final Map<String, ByteIterator> values) throws IOException { + try(final ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + final ByteBuffer buf = ByteBuffer.allocate(4); + + for(final Map.Entry<String, ByteIterator> value : values.entrySet()) { + final byte[] keyBytes = value.getKey().getBytes(UTF_8); + final byte[] valueBytes = value.getValue().toArray(); + + buf.putInt(keyBytes.length); + baos.write(buf.array()); + baos.write(keyBytes); + + buf.clear(); + + buf.putInt(valueBytes.length); + baos.write(buf.array()); + baos.write(valueBytes); + + buf.clear(); + } + return baos.toByteArray(); + } + } + + private void createColumnFamily(final String name) throws RocksDBException { + COLUMN_FAMILY_LOCKS.putIfAbsent(name, new ReentrantLock()); + + final Lock l = COLUMN_FAMILY_LOCKS.get(name); + l.lock(); + try { + if(!COLUMN_FAMILIES.containsKey(name)) { + final ColumnFamilyOptions cfOptions = new ColumnFamilyOptions().optimizeLevelStyleCompaction(); + final ColumnFamilyHandle cfHandle = rocksDb.createColumnFamily( + new ColumnFamilyDescriptor(name.getBytes(UTF_8), cfOptions) + ); + COLUMN_FAMILIES.put(name, new ColumnFamily(cfHandle, cfOptions)); + } + } finally { + l.unlock(); + } + } + + private static final class ColumnFamily { + private final ColumnFamilyHandle handle; + private final ColumnFamilyOptions options; + + private ColumnFamily(final ColumnFamilyHandle handle, final ColumnFamilyOptions options) { + this.handle = handle; + this.options = options; + } + + public ColumnFamilyHandle getHandle() { + return handle; + } + + public ColumnFamilyOptions getOptions() { + return options; + } + } +} diff --git a/rocksdb/src/main/java/com/yahoo/ycsb/db/rocksdb/package-info.java b/rocksdb/src/main/java/com/yahoo/ycsb/db/rocksdb/package-info.java new file mode 100644 index 00000000..b2ebcea4 --- /dev/null +++ b/rocksdb/src/main/java/com/yahoo/ycsb/db/rocksdb/package-info.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2018 YCSB contributors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you + * may not use this file except in compliance with the License. You + * may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. See accompanying + * LICENSE file. + */ + +/** + * The RocksDB Java binding for <a href="http://rocksdb.org/">RocksDB</a>. + */ +package com.yahoo.ycsb.db.rocksdb; + diff --git a/rocksdb/src/test/java/com/yahoo/ycsb/db/rocksdb/RocksDBClientTest.java b/rocksdb/src/test/java/com/yahoo/ycsb/db/rocksdb/RocksDBClientTest.java new file mode 100644 index 00000000..49e58f9c --- /dev/null +++ b/rocksdb/src/test/java/com/yahoo/ycsb/db/rocksdb/RocksDBClientTest.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2018 YCSB contributors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you + * may not use this file except in compliance with the License. You + * may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. See accompanying + * LICENSE file. + */ + +package com.yahoo.ycsb.db.rocksdb; + +import com.yahoo.ycsb.ByteIterator; +import com.yahoo.ycsb.Status; +import com.yahoo.ycsb.StringByteIterator; +import org.junit.*; +import org.junit.rules.TemporaryFolder; + +import java.util.*; + +import static org.junit.Assert.assertEquals; + +public class RocksDBClientTest { + + @Rule + public TemporaryFolder tmpFolder = new TemporaryFolder(); + + private static final String MOCK_TABLE = "ycsb"; + private static final String MOCK_KEY0 = "0"; + private static final String MOCK_KEY1 = "1"; + private static final String MOCK_KEY2 = "2"; + private static final String MOCK_KEY3 = "3"; + private static final int NUM_RECORDS = 10; + + private static final Map<String, ByteIterator> MOCK_DATA; + static { + MOCK_DATA = new HashMap<>(NUM_RECORDS); + for (int i = 0; i < NUM_RECORDS; i++) { + MOCK_DATA.put("field" + i, new StringByteIterator("value" + i)); + } + } + + private RocksDBClient instance; + + @Before + public void setup() throws Exception { + instance = new RocksDBClient(); + + final Properties properties = new Properties(); + properties.setProperty(RocksDBClient.PROPERTY_ROCKSDB_DIR, tmpFolder.getRoot().getAbsolutePath()); + instance.setProperties(properties); + + instance.init(); + } + + @After + public void tearDown() throws Exception { + instance.cleanup(); + } + + @Test + public void insertAndRead() throws Exception { + final Status insertResult = instance.insert(MOCK_TABLE, MOCK_KEY0, MOCK_DATA); + assertEquals(Status.OK, insertResult); + + final Set<String> fields = MOCK_DATA.keySet(); + final Map<String, ByteIterator> resultParam = new HashMap<>(NUM_RECORDS); + final Status readResult = instance.read(MOCK_TABLE, MOCK_KEY0, fields, resultParam); + assertEquals(Status.OK, readResult); + } + + @Test + public void insertAndDelete() throws Exception { + final Status insertResult = instance.insert(MOCK_TABLE, MOCK_KEY1, MOCK_DATA); + assertEquals(Status.OK, insertResult); + + final Status result = instance.delete(MOCK_TABLE, MOCK_KEY1); + assertEquals(Status.OK, result); + } + + @Test + public void insertUpdateAndRead() throws Exception { + final Map<String, ByteIterator> newValues = new HashMap<>(NUM_RECORDS); + + final Status insertResult = instance.insert(MOCK_TABLE, MOCK_KEY2, MOCK_DATA); + assertEquals(Status.OK, insertResult); + + for (int i = 0; i < NUM_RECORDS; i++) { + newValues.put("field" + i, new StringByteIterator("newvalue" + i)); + } + + final Status result = instance.update(MOCK_TABLE, MOCK_KEY2, newValues); + assertEquals(Status.OK, result); + + //validate that the values changed + final Map<String, ByteIterator> resultParam = new HashMap<>(NUM_RECORDS); + instance.read(MOCK_TABLE, MOCK_KEY2, MOCK_DATA.keySet(), resultParam); + + for (int i = 0; i < NUM_RECORDS; i++) { + assertEquals("newvalue" + i, resultParam.get("field" + i).toString()); + } + } + + @Test + public void insertAndScan() throws Exception { + final Status insertResult = instance.insert(MOCK_TABLE, MOCK_KEY3, MOCK_DATA); + assertEquals(Status.OK, insertResult); + + final Set<String> fields = MOCK_DATA.keySet(); + final Vector<HashMap<String, ByteIterator>> resultParam = new Vector<>(NUM_RECORDS); + final Status result = instance.scan(MOCK_TABLE, MOCK_KEY3, NUM_RECORDS, fields, resultParam); + assertEquals(Status.OK, result); + } +} -- GitLab