diff --git a/orientdb/src/main/java/com/yahoo/ycsb/db/OrientDBClient.java b/orientdb/src/main/java/com/yahoo/ycsb/db/OrientDBClient.java index 4e47fbf1..853f9ab3 100644 --- a/orientdb/src/main/java/com/yahoo/ycsb/db/OrientDBClient.java +++ b/orientdb/src/main/java/com/yahoo/ycsb/db/OrientDBClient.java @@ -1,334 +1,264 @@ /** * Copyright (c) 2012 - 2016 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; import com.orientechnologies.common.exception.OException; import com.orientechnologies.orient.client.remote.OEngineRemote; import com.orientechnologies.orient.client.remote.OServerAdmin; import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; import com.orientechnologies.orient.core.db.record.OIdentifiable; import com.orientechnologies.orient.core.dictionary.ODictionary; import com.orientechnologies.orient.core.exception.ODatabaseException; import com.orientechnologies.orient.core.index.OIndexCursor; import com.orientechnologies.orient.core.intent.OIntentMassiveInsert; import com.orientechnologies.orient.core.record.ORecord; import com.orientechnologies.orient.core.record.impl.ODocument; import com.yahoo.ycsb.ByteIterator; import com.yahoo.ycsb.DB; import com.yahoo.ycsb.DBException; import com.yahoo.ycsb.Status; import com.yahoo.ycsb.StringByteIterator; import java.io.IOException; import java.util.HashMap; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.Vector; /** * OrientDB client for YCSB framework. */ public class OrientDBClient extends DB { private static final String CLASS = "usertable"; - protected ODatabaseDocumentTx db; + private ODatabaseDocumentTx db; private ODictionary dictionary; private boolean isRemote = false; private static final String URL_PROPERTY = "orientdb.url"; private static final String USER_PROPERTY = "orientdb.user"; private static final String USER_PROPERTY_DEFAULT = "admin"; private static final String PASSWORD_PROPERTY = "orientdb.password"; private static final String PASSWORD_PROPERTY_DEFAULT = "admin"; private static final String NEWDB_PROPERTY = "orientdb.newdb"; private static final String NEWDB_PROPERTY_DEFAULT = "false"; private static final String STORAGE_TYPE_PROPERTY = "orientdb.remote.storagetype"; private static final String DO_TRANSACTIONS_PROPERTY = "dotransactions"; private static final String DO_TRANSACTIONS_PROPERTY_DEFAULT = "true"; private static final String ORIENTDB_DOCUMENT_TYPE = "document"; - /** - * Initialize any state for this DB. Called once per DB instance; there is one - * DB instance per client thread. - */ + @Override public void init() throws DBException { Properties props = getProperties(); String url = props.getProperty(URL_PROPERTY); String user = props.getProperty(USER_PROPERTY, USER_PROPERTY_DEFAULT); String password = props.getProperty(PASSWORD_PROPERTY, PASSWORD_PROPERTY_DEFAULT); Boolean newdb = Boolean.parseBoolean(props.getProperty(NEWDB_PROPERTY, NEWDB_PROPERTY_DEFAULT)); String remoteStorageType = props.getProperty(STORAGE_TYPE_PROPERTY); - Boolean dotransactions = Boolean.parseBoolean(props.getProperty(DO_TRANSACTIONS_PROPERTY, DO_TRANSACTIONS_PROPERTY_DEFAULT)); + Boolean dotransactions = Boolean.parseBoolean( + props.getProperty(DO_TRANSACTIONS_PROPERTY, DO_TRANSACTIONS_PROPERTY_DEFAULT)); if (url == null) { throw new DBException(String.format("Required property \"%s\" missing for OrientDBClient", URL_PROPERTY)); } System.err.println("OrientDB loading database url = " + url); // If using a remote database, use the OServerAdmin interface to connect if (url.startsWith(OEngineRemote.NAME)) { isRemote = true; if (remoteStorageType == null) { - throw new DBException("When connecting to a remote OrientDB instance, specify a database storage type (plocal or memory) with " + STORAGE_TYPE_PROPERTY); + throw new DBException("When connecting to a remote OrientDB instance, " + + "specify a database storage type (plocal or memory) with " + STORAGE_TYPE_PROPERTY); } try { OServerAdmin server = new OServerAdmin(url).connect(user, password); if (server.existsDatabase()) { if (newdb && !dotransactions) { System.err.println("OrientDB dropping and recreating fresh db on remote server."); server.dropDatabase(remoteStorageType); server.createDatabase(server.getURL(), ORIENTDB_DOCUMENT_TYPE, remoteStorageType); } } else { System.err.println("OrientDB database not found, creating fresh db"); server.createDatabase(server.getURL(), ORIENTDB_DOCUMENT_TYPE, remoteStorageType); } server.close(); db = new ODatabaseDocumentTx(url).open(user, password); } catch (IOException | OException e) { throw new DBException(String.format("Error interfacing with %s", url), e); } } else { try { db = new ODatabaseDocumentTx(url); if (db.exists()) { db.open(user, password); if (newdb && !dotransactions) { System.err.println("OrientDB dropping and recreating fresh db."); db.drop(); db.create(); } } else { System.err.println("OrientDB database not found, creating fresh db"); db.create(); } } catch (ODatabaseException e) { throw new DBException(String.format("Error interfacing with %s", url), e); } } System.err.println("OrientDB connection created with " + url); dictionary = db.getMetadata().getIndexManager().getDictionary(); - if (!db.getMetadata().getSchema().existsClass(CLASS)) + if (!db.getMetadata().getSchema().existsClass(CLASS)) { db.getMetadata().getSchema().createClass(CLASS); + } - // TODO: This is a transparent optimization that should be openned up to the user. db.declareIntent(new OIntentMassiveInsert()); } @Override public void cleanup() throws DBException { // Set this thread's db reference (needed for thread safety in testing) ODatabaseRecordThreadLocal.INSTANCE.set(db); if (db != null) { db.close(); db = null; } } - /** - * Insert a record in the database. Any field/value pairs in the specified - * values HashMap will be written into the record with the specified record - * key. - * - * @param table - * The name of the table - * @param key - * The record key of the record to insert. - * @param values - * A HashMap of field/value pairs to insert in the record - * @return Zero on success, a non-zero error code on error. See this class's - * description for a discussion of error codes. - */ @Override - public Status insert(String table, String key, - HashMap values) { + public Status insert(String table, String key, HashMap values) { try { final ODocument document = new ODocument(CLASS); - for (Entry entry : StringByteIterator.getStringMap(values) - .entrySet()) { + for (Entry entry : StringByteIterator.getStringMap(values).entrySet()) { document.field(entry.getKey(), entry.getValue()); } document.save(); dictionary.put(key, document); return Status.OK; } catch (Exception e) { e.printStackTrace(); } return Status.ERROR; } - /** - * Delete a record from the database. - * - * @param table - * The name of the table - * @param key - * The record key of the record to delete. - * @return Zero on success, a non-zero error code on error. See this class's - * description for a discussion of error codes. - */ @Override public Status delete(String table, String key) { try { dictionary.remove(key); return Status.OK; } catch (Exception e) { e.printStackTrace(); } return Status.ERROR; } - /** - * Read a record from the database. Each field/value pair from the result will - * be stored in a HashMap. - * - * @param table - * The name of the table - * @param key - * The record key of the record to read. - * @param fields - * The list of fields to read, or null for all of them - * @param result - * A HashMap of field/value pairs for the result - * @return Zero on success, a non-zero error code on error or "not found". - */ @Override - public Status read(String table, String key, Set fields, - HashMap result) { + public Status read(String table, String key, Set fields, HashMap result) { try { final ODocument document = dictionary.get(key); if (document != null) { if (fields != null) { for (String field : fields) { - result.put(field, - new StringByteIterator((String) document.field(field))); + result.put(field, new StringByteIterator((String) document.field(field))); } } else { for (String field : document.fieldNames()) { - result.put(field, - new StringByteIterator((String) document.field(field))); + result.put(field, new StringByteIterator((String) document.field(field))); } } return Status.OK; } } catch (Exception e) { e.printStackTrace(); } return Status.ERROR; } - /** - * Update a record in the database. Any field/value pairs in the specified - * values HashMap will be written into the record with the specified record - * key, overwriting any existing values with the same field name. - * - * @param table - * The name of the table - * @param key - * The record key of the record to write. - * @param values - * A HashMap of field/value pairs to update in the record - * @return Zero on success, a non-zero error code on error. See this class's - * description for a discussion of error codes. - */ @Override - public Status update(String table, String key, - HashMap values) { + public Status update(String table, String key, HashMap values) { try { final ODocument document = dictionary.get(key); if (document != null) { - for (Entry entry : StringByteIterator - .getStringMap(values).entrySet()) { + for (Entry entry : StringByteIterator.getStringMap(values).entrySet()) { document.field(entry.getKey(), entry.getValue()); } document.save(); return Status.OK; } } catch (Exception e) { e.printStackTrace(); } return Status.ERROR; } - /** - * Perform a range scan for a set of records in the database. Each field/value - * pair from the result will be stored in a HashMap. - * - * @param table - * The name of the table - * @param startkey - * The record key of the first record to read. - * @param recordcount - * The number of records to read - * @param fields - * The list of fields to read, or null for all of them - * @param result - * A Vector of HashMaps, where each HashMap is a set field/value - * pairs for one record - * @return Zero on success, a non-zero error code on error. See this class's - * description for a discussion of error codes. - */ @Override - public Status scan(String table, String startkey, int recordcount, Set fields, Vector> result) { + public Status scan(String table, String startkey, int recordcount, Set fields, + Vector> result) { if (isRemote) { // Iterator methods needed for scanning are Unsupported for remote database connections. return Status.NOT_IMPLEMENTED; } try { int entrycount = 0; final OIndexCursor entries = dictionary.getIndex().iterateEntriesMajor(startkey, true, true); while (entries.hasNext() && entrycount < recordcount) { final Entry entry = entries.nextEntry(); final ODocument document = entry.getValue().getRecord(); final HashMap map = new HashMap(); result.add(map); for (String field : fields) { map.put(field, new StringByteIterator((String) document.field(field))); } entrycount++; } return Status.OK; } catch (Exception e) { e.printStackTrace(); } return Status.ERROR; } + + /** + * Access method to db variable for unit testing. + **/ + ODatabaseDocumentTx getDB() { + return db; + } } diff --git a/orientdb/src/main/java/com/yahoo/ycsb/db/package-info.java b/orientdb/src/main/java/com/yahoo/ycsb/db/package-info.java index df5a3732..d4568271 100644 --- a/orientdb/src/main/java/com/yahoo/ycsb/db/package-info.java +++ b/orientdb/src/main/java/com/yahoo/ycsb/db/package-info.java @@ -1,22 +1,22 @@ /* - * Copyright (c) 2014, Yahoo!, Inc. All rights reserved. + * Copyright (c) 2015 - 2016, Yahoo!, Inc. 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 YCSB binding for OrientDB. */ package com.yahoo.ycsb.db; diff --git a/orientdb/src/test/java/com/yahoo/ycsb/db/OrientDBClientTest.java b/orientdb/src/test/java/com/yahoo/ycsb/db/OrientDBClientTest.java index e9f3a58f..717637d0 100644 --- a/orientdb/src/test/java/com/yahoo/ycsb/db/OrientDBClientTest.java +++ b/orientdb/src/test/java/com/yahoo/ycsb/db/OrientDBClientTest.java @@ -1,238 +1,238 @@ /** * Copyright (c) 2015 - 2016 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; import com.orientechnologies.orient.core.dictionary.ODictionary; import com.orientechnologies.orient.core.record.ORecord; import com.orientechnologies.orient.core.record.impl.ODocument; import com.yahoo.ycsb.ByteIterator; import com.yahoo.ycsb.DBException; import com.yahoo.ycsb.StringByteIterator; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * Created by kruthar on 12/29/15. */ public class OrientDBClientTest { // TODO: This must be copied because it is private in OrientDBClient, but this should defer to table property. private static final String CLASS = "usertable"; private static final int FIELD_LENGTH = 32; private static final String FIELD_PREFIX = "FIELD"; private static final String KEY_PREFIX = "user"; private static final int NUM_FIELDS = 3; private static final String TEST_DB_URL = "memory:test"; private static ODictionary orientDBDictionary; private static OrientDBClient orientDBClient = null; @Before public void setup() throws DBException { orientDBClient = new OrientDBClient(); Properties p = new Properties(); // TODO: Extract the property names into final variables in OrientDBClient p.setProperty("orientdb.url", TEST_DB_URL); orientDBClient.setProperties(p); orientDBClient.init(); - orientDBDictionary = orientDBClient.db.getDictionary(); + orientDBDictionary = orientDBClient.getDB().getDictionary(); } @After public void teardown() throws DBException { if (orientDBClient != null) { orientDBClient.cleanup(); } } /* This is a copy of buildDeterministicValue() from core:com.yahoo.ycsb.workloads.CoreWorkload.java. That method is neither public nor static so we need a copy. */ private String buildDeterministicValue(String key, String fieldkey) { int size = FIELD_LENGTH; StringBuilder sb = new StringBuilder(size); sb.append(key); sb.append(':'); sb.append(fieldkey); while (sb.length() < size) { sb.append(':'); sb.append(sb.toString().hashCode()); } sb.setLength(size); return sb.toString(); } /* Inserts a row of deterministic values for the given insertKey using the orientDBClient. */ private Map insertRow(String insertKey) { HashMap insertMap = new HashMap<>(); for (int i = 0; i < 3; i++) { insertMap.put(FIELD_PREFIX + i, new StringByteIterator(buildDeterministicValue(insertKey, FIELD_PREFIX + i))); } orientDBClient.insert(CLASS, insertKey, insertMap); return insertMap; } @Test public void insertTest() { String insertKey = "user0"; Map insertMap = insertRow(insertKey); ODocument result = orientDBDictionary.get(insertKey); assertTrue("Assert a row was inserted.", result != null); for (int i = 0; i < NUM_FIELDS; i++) { assertEquals("Assert all inserted columns have correct values.", result.field(FIELD_PREFIX + i), insertMap.get(FIELD_PREFIX + i).toString()); } } @Test public void updateTest() { String preupdateString = "preupdate"; String user0 = "user0"; String user1 = "user1"; String user2 = "user2"; // Manually insert three documents for(String key: Arrays.asList(user0, user1, user2)) { ODocument doc = new ODocument(CLASS); for (int i = 0; i < NUM_FIELDS; i++) { doc.field(FIELD_PREFIX + i, preupdateString); } doc.save(); orientDBDictionary.put(key, doc); } HashMap updateMap = new HashMap<>(); for (int i = 0; i < NUM_FIELDS; i++) { updateMap.put(FIELD_PREFIX + i, new StringByteIterator(buildDeterministicValue(user1, FIELD_PREFIX + i))); } orientDBClient.update(CLASS, user1, updateMap); // Ensure that user0 record was not changed ODocument result = orientDBDictionary.get(user0); for (int i = 0; i < NUM_FIELDS; i++) { assertEquals("Assert first row fields contain preupdateString", result.field(FIELD_PREFIX + i), preupdateString); } // Check that all the columns have expected values for user1 record result = orientDBDictionary.get(user1); for (int i = 0; i < NUM_FIELDS; i++) { assertEquals("Assert updated row fields are correct", result.field(FIELD_PREFIX + i), updateMap.get(FIELD_PREFIX + i).toString()); } // Ensure that user2 record was not changed result = orientDBDictionary.get(user2); for (int i = 0; i < NUM_FIELDS; i++) { assertEquals("Assert third row fields contain preupdateString", result.field(FIELD_PREFIX + i), preupdateString); } } @Test public void readTest() { String insertKey = "user0"; Map insertMap = insertRow(insertKey); HashSet readFields = new HashSet<>(); HashMap readResultMap = new HashMap<>(); // Test reading a single field readFields.add("FIELD0"); orientDBClient.read(CLASS, insertKey, readFields, readResultMap); assertEquals("Assert that result has correct number of fields", readFields.size(), readResultMap.size()); for (String field: readFields) { assertEquals("Assert " + field + " was read correctly", insertMap.get(field).toString(), readResultMap.get(field).toString()); } readResultMap = new HashMap<>(); // Test reading all fields readFields.add("FIELD1"); readFields.add("FIELD2"); orientDBClient.read(CLASS, insertKey, readFields, readResultMap); assertEquals("Assert that result has correct number of fields", readFields.size(), readResultMap.size()); for (String field: readFields) { assertEquals("Assert " + field + " was read correctly", insertMap.get(field).toString(), readResultMap.get(field).toString()); } } @Test public void deleteTest() { String user0 = "user0"; String user1 = "user1"; String user2 = "user2"; insertRow(user0); insertRow(user1); insertRow(user2); orientDBClient.delete(CLASS, user1); assertNotNull("Assert user0 still exists", orientDBDictionary.get(user0)); assertNull("Assert user1 does not exist", orientDBDictionary.get(user1)); assertNotNull("Assert user2 still exists", orientDBDictionary.get(user2)); } @Test public void scanTest() { Map> keyMap = new HashMap<>(); for (int i = 0; i < 5; i++) { String insertKey = KEY_PREFIX + i; keyMap.put(insertKey, insertRow(insertKey)); } Set fieldSet = new HashSet<>(); fieldSet.add("FIELD0"); fieldSet.add("FIELD1"); int startIndex = 1; int resultRows = 3; Vector> resultVector = new Vector<>(); orientDBClient.scan(CLASS, KEY_PREFIX + startIndex, resultRows, fieldSet, resultVector); // Check the resultVector is the correct size assertEquals("Assert the correct number of results rows were returned", resultRows, resultVector.size()); /** * Part of the known issue about the broken iterator in orientdb is that the iterator * starts at index 1 instead of index 0. Because of this, to test it we must increment * the start index. When that known issue has been fixed, remove the increment below. * Track the issue here: https://github.com/orientechnologies/orientdb/issues/5541 * This fix was implemented for orientechnologies:orientdb-client:2.1.8 */ int testIndex = startIndex + 1; // <-- Remove the +1 when the known issue of broken iterator is fixed. // Check each vector row to make sure we have the correct fields for (HashMap result: resultVector) { assertEquals("Assert that this row has the correct number of fields", fieldSet.size(), result.size()); for (String field: fieldSet) { assertEquals("Assert this field is correct in this row", keyMap.get(KEY_PREFIX + testIndex).get(field).toString(), result.get(field).toString()); } testIndex++; } } }