package com.yahoo.ycsb.db; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.SortedMap; import java.util.Vector; import java.util.concurrent.TimeUnit; import oracle.kv.Consistency; import oracle.kv.Durability; import oracle.kv.FaultException; import oracle.kv.KVStore; import oracle.kv.KVStoreConfig; import oracle.kv.KVStoreFactory; import oracle.kv.Key; import oracle.kv.RequestLimitConfig; import oracle.kv.Value; import oracle.kv.ValueVersion; import com.yahoo.ycsb.ByteArrayByteIterator; import com.yahoo.ycsb.ByteIterator; import com.yahoo.ycsb.DB; import com.yahoo.ycsb.DBException; /** * A database interface layer for Oracle NoSQL Database. */ public class NoSqlDbClient extends DB { public static final int OK = 0; public static final int ERROR = -1; KVStore store; private int getPropertyInt(Properties properties, String key, int defaultValue) throws DBException { String p = properties.getProperty(key); int i = defaultValue; if (p != null) { try { i = Integer.parseInt(p); } catch (NumberFormatException e) { throw new DBException("Illegal number format in " + key + " property"); } } return i; } @Override public void init() throws DBException { Properties properties = getProperties(); /* Mandatory properties */ String storeName = properties.getProperty("storeName", "kvstore"); String[] helperHosts = properties.getProperty("helperHost", "localhost:5000").split(","); KVStoreConfig config = new KVStoreConfig(storeName, helperHosts); /* Optional properties */ String p; p = properties.getProperty("consistency"); if (p != null) { if (p.equalsIgnoreCase("ABSOLUTE")) { config.setConsistency(Consistency.ABSOLUTE); } else if (p.equalsIgnoreCase("NONE_REQUIRED")) { config.setConsistency(Consistency.NONE_REQUIRED); } else { throw new DBException("Illegal value in consistency property"); } } p = properties.getProperty("durability"); if (p != null) { if (p.equalsIgnoreCase("COMMIT_NO_SYNC")) { config.setDurability(Durability.COMMIT_NO_SYNC); } else if (p.equalsIgnoreCase("COMMIT_SYNC")) { config.setDurability(Durability.COMMIT_SYNC); } else if (p.equalsIgnoreCase("COMMIT_WRITE_NO_SYNC")) { config.setDurability(Durability.COMMIT_WRITE_NO_SYNC); } else { throw new DBException("Illegal value in durability property"); } } int maxActiveRequests = getPropertyInt(properties, "requestLimit.maxActiveRequests", RequestLimitConfig.DEFAULT_MAX_ACTIVE_REQUESTS); int requestThresholdPercent = getPropertyInt(properties, "requestLimit.requestThresholdPercent", RequestLimitConfig.DEFAULT_REQUEST_THRESHOLD_PERCENT); int nodeLimitPercent = getPropertyInt(properties, "requestLimit.nodeLimitPercent", RequestLimitConfig.DEFAULT_NODE_LIMIT_PERCENT); RequestLimitConfig requestLimitConfig; /* It is said that the constructor could throw NodeRequestLimitException in Javadoc, the exception is not provided */ // try { requestLimitConfig = new RequestLimitConfig(maxActiveRequests, requestThresholdPercent, nodeLimitPercent); // } catch (NodeRequestLimitException e) { // throw new DBException(e); // } config.setRequestLimit(requestLimitConfig); p = properties.getProperty("requestTimeout"); if (p != null) { long timeout = 1; try { timeout = Long.parseLong(p); } catch (NumberFormatException e) { throw new DBException("Illegal number format in requestTimeout property"); } try { // TODO Support other TimeUnit config.setRequestTimeout(timeout, TimeUnit.SECONDS); } catch (IllegalArgumentException e) { throw new DBException(e); } } try { store = KVStoreFactory.getStore(config); } catch (FaultException e) { throw new DBException(e); } } @Override public void cleanup() throws DBException { store.close(); } /** * Create a key object. * We map "table" and (YCSB's) "key" to a major component of the oracle.kv.Key, * and "field" to a minor component. * * @return An oracle.kv.Key object. */ private static Key createKey(String table, String key, String field) { List<String> majorPath = new ArrayList<String>(); majorPath.add(table); majorPath.add(key); if (field == null) { return Key.createKey(majorPath); } return Key.createKey(majorPath, field); } private static Key createKey(String table, String key) { return createKey(table, key, null); } private static String getFieldFromKey(Key key) { return key.getMinorPath().get(0); } @Override public int read(String table, String key, Set<String> fields, HashMap<String, ByteIterator> result) { Key kvKey = createKey(table, key); SortedMap<Key, ValueVersion> kvResult; try { kvResult = store.multiGet(kvKey, null, null); } catch (FaultException e) { System.err.println(e); return ERROR; } for (Map.Entry<Key, ValueVersion> entry : kvResult.entrySet()) { /* If fields is null, read all fields */ String field = getFieldFromKey(entry.getKey()); if (fields != null && !fields.contains(field)) { continue; } result.put(field, new ByteArrayByteIterator(entry.getValue().getValue().getValue())); } return OK; } @Override public int scan(String table, String startkey, int recordcount, Set<String> fields, Vector<HashMap<String, ByteIterator>> result) { System.err.println("Oracle NoSQL Database does not support Scan semantics"); return ERROR; } @Override public int update(String table, String key, HashMap<String, ByteIterator> values) { for (Map.Entry<String, ByteIterator> entry : values.entrySet()) { Key kvKey = createKey(table, key, entry.getKey()); Value kvValue = Value.createValue(entry.getValue().toArray()); try { store.put(kvKey, kvValue); } catch (FaultException e) { System.err.println(e); return ERROR; } } return OK; } @Override public int insert(String table, String key, HashMap<String, ByteIterator> values) { return update(table, key, values); } @Override public int delete(String table, String key) { Key kvKey = createKey(table, key); try { store.multiDelete(kvKey, null, null); } catch (FaultException e) { System.err.println(e); return ERROR; } return OK; } }