/** * Copyright 2011, Big Switch Networks, Inc. * Originally created by David Erickson, Stanford University * * 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. **/ package net.floodlightcontroller.storage.nosql; import java.lang.Class; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TimeZone; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.floodlightcontroller.storage.AbstractStorageSource; import net.floodlightcontroller.storage.CompoundPredicate; import net.floodlightcontroller.storage.IPredicate; import net.floodlightcontroller.storage.IQuery; import net.floodlightcontroller.storage.IResultSet; import net.floodlightcontroller.storage.OperatorPredicate; import net.floodlightcontroller.storage.RowOrdering; import net.floodlightcontroller.storage.StorageException; import net.floodlightcontroller.storage.StorageSourceNotification; import net.floodlightcontroller.storage.TypeMismatchStorageException; public abstract class NoSqlStorageSource extends AbstractStorageSource { protected static Logger log = LoggerFactory.getLogger(NoSqlStorageSource.class); public enum ColumnIndexMode { NOT_INDEXED, RANGE_INDEXED, EQUALITY_INDEXED }; protected static final String DEFAULT_PRIMARY_KEY_NAME = "id"; private Map<String,String> tablePrimaryKeyMap = new HashMap<String,String>(); private Map<String, Map<String,ColumnIndexMode>> tableIndexedColumnMap = new HashMap<String,Map<String,ColumnIndexMode>>(); abstract static class NoSqlPredicate { public boolean incorporateComparison(String columnName, OperatorPredicate.Operator operator, Comparable<?> value, CompoundPredicate.Operator parentOperator) { return false; } public boolean canExecuteEfficiently() { return false; } public List<Map<String,Object>> execute(String[] columnNames) { assert(false); return null; } abstract public boolean matchesRow(Map<String,Object> row); } @SuppressWarnings({ "unchecked", "rawtypes" }) static class NoSqlRangePredicate extends NoSqlPredicate { NoSqlStorageSource storageSource; String tableName; String columnName; Comparable<?> startValue; boolean startInclusive; Comparable<?> endValue; boolean endInclusive; NoSqlRangePredicate(NoSqlStorageSource storageSource, String tableName, String columnName, Comparable<?> startValue, boolean startInclusive, Comparable<?> endValue, boolean endInclusive) { this.storageSource = storageSource; this.tableName = tableName; this.columnName = columnName; this.startValue = startValue; this.startInclusive = startInclusive; this.endValue = endValue; this.endInclusive = endInclusive; } public boolean incorporateComparison(String columnName, OperatorPredicate.Operator operator, Comparable<?> value, CompoundPredicate.Operator parentOperator) { assert(operator != null); assert(parentOperator != null); // Must be the same column to incorporate if (!this.columnName.equals(columnName)) return false; // The only time we allow a null value is if it's an EQ operator. // In that case we can only incorporate if this predicate is also // a null equality predicate. if (value == null) { return ((operator == OperatorPredicate.Operator.EQ) && (startValue == null) && (endValue == null) && startInclusive && endInclusive); } // Don't incorporate parameterized values if (value instanceof String) { String s = (String)value; if (s.startsWith("?") && s.endsWith("?")) { return false; } } if (parentOperator == CompoundPredicate.Operator.AND) { switch (operator) { case EQ: if (matchesValue(value)) { startValue = endValue = value; startInclusive = endInclusive = true; return true; } break; case LT: if ((endValue == null) || (((Comparable)value).compareTo(endValue) <= 0)) { endValue = value; endInclusive = false; return true; } break; case LTE: if ((endValue == null) || (((Comparable)value).compareTo(endValue) < 0)) { endValue = value; endInclusive = true; return true; } break; case GT: if ((startValue == null) || (((Comparable)value).compareTo(startValue) >= 0)) { startValue = value; startInclusive = false; return true; } break; case GTE: if ((startValue == null) || (((Comparable)value).compareTo(startValue) > 0)) { startValue = value; startInclusive = true; return true; } break; } } else { switch (operator) { case EQ: if (matchesValue(value)) return true; break; case LT: if ((endValue == null) || (((Comparable)value).compareTo(endValue) > 0)) { endValue = value; endInclusive = false; return true; } break; case LTE: if ((endValue == null) || (((Comparable)value).compareTo(endValue) >= 0)) { endValue = value; endInclusive = true; return true; } break; case GT: if ((startValue == null) || (((Comparable)value).compareTo(startValue) < 0)) { startValue = value; startInclusive = false; return true; } break; case GTE: if ((startValue == null) || (((Comparable)value).compareTo(startValue) <= 0)) { startValue = value; startInclusive = true; return true; } break; } } return false; } private boolean isEqualityRange() { return (startValue == endValue) && startInclusive && endInclusive; } public boolean canExecuteEfficiently() { ColumnIndexMode indexMode = storageSource.getColumnIndexMode(tableName, columnName); switch (indexMode) { case NOT_INDEXED: return false; case RANGE_INDEXED: return true; case EQUALITY_INDEXED: return isEqualityRange(); } return true; } public List<Map<String,Object>> execute(String columnNameList[]) { List<Map<String,Object>> rowList; if (isEqualityRange()) rowList = storageSource.executeEqualityQuery(tableName, columnNameList, columnName, startValue); else rowList = storageSource.executeRangeQuery(tableName, columnNameList, columnName, startValue, startInclusive, endValue, endInclusive); return rowList; } Comparable<?> coerceValue(Comparable<?> value, Class targetClass) { if (value == null) return null; if (value.getClass() == targetClass) return value; // FIXME: For now we convert by first converting the source value to a // string and then converting to the target type. This logic probably needs // another pass to make it more robust/optimized. String s = value.toString(); Comparable<?> obj = null; try { if (targetClass == Integer.class) { obj = new Integer(s); } else if (targetClass == Long.class) { obj = new Long(s); } else if (targetClass == Short.class) { obj = new Short(s); } else if (targetClass == Boolean.class) { obj = new Boolean(s); } else if (targetClass == Float.class) { obj = new Float(s); } else if (targetClass == Double.class) { obj = new Double(s); } else if (targetClass == Byte.class) { obj = new Byte(s); } else if (targetClass == String.class) { obj = s; } else if (targetClass == Date.class) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); try { obj = dateFormat.parse(s); } catch (ParseException exc) { throw new TypeMismatchStorageException(Date.class.getName(), value.getClass().getName(), "???"); } } } catch (Exception exc) { // Ignore the exception here. In this case obj will not be set, so we'll // throw the StorageException below when we check for a null obj. } if (obj == null) throw new StorageException("Column value could not be coerced to the correct type"); return obj; } boolean matchesValue(Comparable<?> value) { boolean isNullEqPredicate = (startValue == null) && (endValue == null) && startInclusive && endInclusive; if (value == null) return isNullEqPredicate; if (isNullEqPredicate) return false; int result; Comparable<?> coercedValue; if (startValue != null) { coercedValue = coerceValue(value, startValue.getClass()); result = ((Comparable)coercedValue).compareTo(startValue); if ((result < 0) || (!startInclusive && (result == 0))) return false; } if (endValue != null) { coercedValue = coerceValue(value, endValue.getClass()); result = ((Comparable)coercedValue).compareTo(endValue); if ((result > 0) || (!endInclusive && (result == 0))) return false; } return true; } public boolean matchesRow(Map<String,Object> row) { Comparable value = (Comparable)row.get(columnName); return matchesValue(value); } } static class NoSqlOperatorPredicate extends NoSqlPredicate { NoSqlStorageSource storageSource; String columnName; OperatorPredicate.Operator operator; Object value; NoSqlOperatorPredicate(NoSqlStorageSource storageSource, String columnName, OperatorPredicate.Operator operator, Object value) { this.storageSource = storageSource; this.columnName = columnName; this.operator = operator; this.value = value; } public boolean incorporateComparison(String columnName, OperatorPredicate.Operator operator, Comparable<?> value, CompoundPredicate.Operator parentOperator) { return false; } public boolean canExecuteEfficiently() { return false; } public List<Map<String,Object>> execute(String columnNames[]) { throw new StorageException("Unimplemented predicate."); } public boolean matchesRow(Map<String,Object> row) { return false; } } static class NoSqlCompoundPredicate extends NoSqlPredicate { NoSqlStorageSource storageSource; CompoundPredicate.Operator operator; boolean negated; String tableName; List<NoSqlPredicate> predicateList; NoSqlCompoundPredicate(NoSqlStorageSource storageSource, String tableName, CompoundPredicate.Operator operator, boolean negated, List<NoSqlPredicate> predicateList) { this.storageSource = storageSource; this.tableName = tableName; this.operator = operator; this.negated = negated; this.predicateList = predicateList; } public boolean incorporateComparison(String columnName, OperatorPredicate.Operator operator, Comparable<?> value, CompoundPredicate.Operator parentOperator) { // It may be possible to incorporate other operator predicate into this one, // but it would need to take into account the negated attribute and I'd need // to think about it some more to make sure it was correct, so for now we just // disallow incorporation //if (parentOperator == this.operator) { // for (NoSqlPredicate predicate: predicateList) { // if (predicate.incorporateComparison(columnName, operator, value, parentOperator)) // return true; // } //} return false; } public boolean canExecuteEfficiently() { if (operator == CompoundPredicate.Operator.AND) { for (NoSqlPredicate predicate: predicateList) { if (predicate.canExecuteEfficiently()) { return true; } } return false; } else { for (NoSqlPredicate predicate: predicateList) { if (!predicate.canExecuteEfficiently()) { return false; } } return true; } } @SuppressWarnings({ "unchecked", "rawtypes" }) static class RowComparator implements Comparator<Map<String,Object>> { private String primaryKeyName; public RowComparator(String primaryKeyName) { this.primaryKeyName = primaryKeyName; } public int compare(Map<String,Object> row1, Map<String,Object> row2) { Comparable key1 = (Comparable)row1.get(primaryKeyName); Comparable key2 = (Comparable)row2.get(primaryKeyName); return key1.compareTo(key2); } public boolean equals(Object obj) { if (!(obj instanceof RowComparator)) return false; RowComparator rc = (RowComparator)obj; if (rc.primaryKeyName == null) return this.primaryKeyName == null; return rc.primaryKeyName.equals(this.primaryKeyName); } } @SuppressWarnings({ "unchecked", "rawtypes" }) private List<Map<String,Object>> combineRowLists(String primaryKeyName, List<Map<String,Object>> list1, List<Map<String,Object>> list2, CompoundPredicate.Operator operator) { ArrayList<Map<String,Object>> combinedRowList = new ArrayList<Map<String,Object>>(); RowComparator rc = new RowComparator(primaryKeyName); Collections.sort(list1, rc); Collections.sort(list2,rc); Iterator<Map<String,Object>> iterator1 = list1.iterator(); Iterator<Map<String,Object>> iterator2 = list2.iterator(); boolean update1 = true; boolean update2 = true; Map<String,Object> row1 = null; Map<String,Object> row2 = null; Comparable<?> key1 = null; Comparable<?> key2 = null; while (true) { if (update1) { if (iterator1.hasNext()) { row1 = iterator1.next(); key1 = (Comparable<?>)row1.get(primaryKeyName); } else { row1 = null; } } if (update2) { if (iterator2.hasNext()) { row2 = iterator2.next(); key2 = (Comparable<?>)row2.get(primaryKeyName); } else { row2 = null; } } update1 = update2 = false; if (operator == CompoundPredicate.Operator.AND) { if ((row1 == null) || (row2 == null)) break; int result = ((Comparable)key1).compareTo(key2); if (result == 0) { combinedRowList.add(row1); update1 = update2 = true; } else if (result < 0) { update1 = true; } else { update2 = true; } } else { if (row1 != null) { if (row2 != null) { int result = ((Comparable)key1).compareTo(key2); if (result <= 0) { combinedRowList.add(row1); update1 = true; update2 = (result == 0); } else { combinedRowList.add(row2); update2 = true; } } else { combinedRowList.add(row1); update1 = true; } } else if (row2 != null) { combinedRowList.add(row2); update2 = true; } else { break; } } } return combinedRowList; } public List<Map<String,Object>> execute(String columnNames[]) { List<Map<String,Object>> combinedRowList = null; Set<NoSqlPredicate> inefficientPredicates = new HashSet<NoSqlPredicate>(); for (NoSqlPredicate predicate: predicateList) { if (predicate.canExecuteEfficiently()) { List<Map<String,Object>> rowList = predicate.execute(columnNames); if (combinedRowList != null) { String primaryKeyName = storageSource.getTablePrimaryKeyName(tableName); combinedRowList = combineRowLists(primaryKeyName, combinedRowList, rowList, operator); } else { combinedRowList = rowList; } } else { inefficientPredicates.add(predicate); } } if (inefficientPredicates.isEmpty()) return combinedRowList; List<Map<String,Object>> filteredRowList = new ArrayList<Map<String,Object>>(); for (Map<String,Object> row: combinedRowList) { for (NoSqlPredicate predicate: inefficientPredicates) { if (predicate.matchesRow(row)) { filteredRowList.add(row); } } } return filteredRowList; } public boolean matchesRow(Map<String,Object> row) { if (operator == CompoundPredicate.Operator.AND) { for (NoSqlPredicate predicate : predicateList) { if (!predicate.matchesRow(row)) { return false; } } return true; } else { for (NoSqlPredicate predicate : predicateList) { if (predicate.matchesRow(row)) { return true; } } return false; } } } public NoSqlStorageSource() { super(); } @Override public void createTable(String tableName, Set<String> indexedColumns) { super.createTable(tableName, indexedColumns); if (indexedColumns == null) return; for (String columnName : indexedColumns) { setColumnIndexMode(tableName, columnName, ColumnIndexMode.EQUALITY_INDEXED); } } public void setTablePrimaryKeyName(String tableName, String primaryKeyName) { if ((tableName == null) || (primaryKeyName == null)) throw new NullPointerException(); tablePrimaryKeyMap.put(tableName, primaryKeyName); } protected String getTablePrimaryKeyName(String tableName) { String primaryKeyName = tablePrimaryKeyMap.get(tableName); if (primaryKeyName == null) primaryKeyName = DEFAULT_PRIMARY_KEY_NAME; return primaryKeyName; } protected ColumnIndexMode getColumnIndexMode(String tableName, String columnName) { ColumnIndexMode columnIndexMode = null; Map<String, ColumnIndexMode> indexedColumnMap = tableIndexedColumnMap.get(tableName); if (indexedColumnMap != null) columnIndexMode = indexedColumnMap.get(columnName); if (columnIndexMode == null) return ColumnIndexMode.NOT_INDEXED; return columnIndexMode; } public void setColumnIndexMode(String tableName, String columnName, ColumnIndexMode indexMode) { Map<String, ColumnIndexMode> indexedColumnMap = tableIndexedColumnMap.get(tableName); if (indexedColumnMap == null) { indexedColumnMap = new HashMap<String,ColumnIndexMode>(); tableIndexedColumnMap.put(tableName, indexedColumnMap); } indexedColumnMap.put(columnName, indexMode); } Comparable<?> getOperatorPredicateValue(OperatorPredicate predicate, Map<String,Comparable<?>> parameterMap) { Comparable<?> value = predicate.getValue(); if (value instanceof String) { String stringValue = (String) value; if ((stringValue.charAt(0) == '?') && (stringValue.charAt(stringValue.length()-1) == '?')) { String parameterName = stringValue.substring(1,stringValue.length()-1); value = parameterMap.get(parameterName); } } return value; } NoSqlPredicate convertPredicate(IPredicate predicate, String tableName, Map<String,Comparable<?>> parameterMap) { if (predicate == null) return null; NoSqlPredicate convertedPredicate = null; if (predicate instanceof CompoundPredicate) { CompoundPredicate compoundPredicate = (CompoundPredicate)predicate; ArrayList<NoSqlPredicate> noSqlPredicateList = new ArrayList<NoSqlPredicate>(); for (IPredicate childPredicate: compoundPredicate.getPredicateList()) { boolean incorporated = false; if (childPredicate instanceof OperatorPredicate) { OperatorPredicate childOperatorPredicate = (OperatorPredicate)childPredicate; for (NoSqlPredicate childNoSqlPredicate: noSqlPredicateList) { incorporated = childNoSqlPredicate.incorporateComparison( childOperatorPredicate.getColumnName(), childOperatorPredicate.getOperator(), getOperatorPredicateValue(childOperatorPredicate, parameterMap), compoundPredicate.getOperator()); if (incorporated) break; } } if (!incorporated) { NoSqlPredicate noSqlPredicate = convertPredicate(childPredicate, tableName, parameterMap); noSqlPredicateList.add(noSqlPredicate); } } convertedPredicate = new NoSqlCompoundPredicate(this, tableName, compoundPredicate.getOperator(), compoundPredicate.isNegated(), noSqlPredicateList); } else if (predicate instanceof OperatorPredicate) { OperatorPredicate operatorPredicate = (OperatorPredicate) predicate; Comparable<?> value = getOperatorPredicateValue(operatorPredicate, parameterMap); switch (operatorPredicate.getOperator()) { case EQ: convertedPredicate = new NoSqlRangePredicate(this, tableName, operatorPredicate.getColumnName(), value, true, value, true); break; case LT: convertedPredicate = new NoSqlRangePredicate(this, tableName, operatorPredicate.getColumnName(), null, false, value, false); break; case LTE: convertedPredicate = new NoSqlRangePredicate(this, tableName, operatorPredicate.getColumnName(), null, false, value, true); break; case GT: convertedPredicate = new NoSqlRangePredicate(this, tableName, operatorPredicate.getColumnName(), value, false, null, false); break; case GTE: convertedPredicate = new NoSqlRangePredicate(this, tableName, operatorPredicate.getColumnName(), value, true, null, false); break; default: convertedPredicate = new NoSqlOperatorPredicate(this, operatorPredicate.getColumnName(), operatorPredicate.getOperator(), value); } } else { throw new StorageException("Unknown predicate type"); } return convertedPredicate; } @SuppressWarnings({ "unchecked", "rawtypes" }) class RowComparator implements Comparator<Map<String,Object>> { private RowOrdering rowOrdering; public RowComparator(RowOrdering rowOrdering) { this.rowOrdering = rowOrdering; } public int compare(Map<String,Object> row1, Map<String,Object> row2) { if (rowOrdering == null) return 0; for (RowOrdering.Item item: rowOrdering.getItemList()) { Comparable key1 = (Comparable)row1.get(item.getColumn()); Comparable key2 = (Comparable)row2.get(item.getColumn()); int result = key1.compareTo(key2); if (result != 0) { if (item.getDirection() == RowOrdering.Direction.DESCENDING) result = -result; return result; } } return 0; } public boolean equals(Object obj) { if (!(obj instanceof RowComparator)) return false; RowComparator rc = (RowComparator)obj; if (rc.rowOrdering == null) return this.rowOrdering == null; return rc.rowOrdering.equals(this.rowOrdering); } } private NoSqlResultSet executeParameterizedQuery(String tableName, String[] columnNameList, IPredicate predicate, RowOrdering rowOrdering, Map<String,Comparable<?>> parameterMap) { NoSqlPredicate noSqlPredicate = convertPredicate(predicate, tableName, parameterMap); List<Map<String,Object>> rowList; if ((noSqlPredicate != null) && noSqlPredicate.canExecuteEfficiently()) { rowList = noSqlPredicate.execute(columnNameList); } else { rowList = new ArrayList<Map<String,Object>>(); Collection<Map<String,Object>> allRowList = getAllRows(tableName, columnNameList); for (Map<String,Object> row: allRowList) { if ((noSqlPredicate == null) || noSqlPredicate.matchesRow(row)) { rowList.add(row); } } } if (rowOrdering != null) Collections.sort(rowList, new RowComparator(rowOrdering)); return new NoSqlResultSet(this, tableName, rowList); } @Override public IQuery createQuery(String tableName, String[] columnNameList, IPredicate predicate, RowOrdering rowOrdering) { return new NoSqlQuery(tableName, columnNameList, predicate, rowOrdering); } @Override public IResultSet executeQueryImpl(IQuery query) { NoSqlQuery noSqlQuery = (NoSqlQuery) query; return executeParameterizedQuery(noSqlQuery.getTableName(), noSqlQuery.getColumnNameList(), noSqlQuery.getPredicate(), noSqlQuery.getRowOrdering(), noSqlQuery.getParameterMap()); } protected void sendNotification(String tableName, StorageSourceNotification.Action action, List<Map<String,Object>> rows) { Set<Object> rowKeys = new HashSet<Object>(); String primaryKeyName = getTablePrimaryKeyName(tableName); for (Map<String,Object> row : rows) { Object rowKey = row.get(primaryKeyName); rowKeys.add(rowKey); } StorageSourceNotification notification = new StorageSourceNotification(tableName, action, rowKeys); notifyListeners(notification); } protected void sendNotification(String tableName, StorageSourceNotification.Action action, Set<Object> rowKeys) { StorageSourceNotification notification = new StorageSourceNotification(tableName, action, rowKeys); notifyListeners(notification); } protected void insertRowsAndNotify(String tableName, List<Map<String,Object>> insertRowList) { insertRows(tableName, insertRowList); sendNotification(tableName, StorageSourceNotification.Action.MODIFY, insertRowList); } @Override public void insertRowImpl(String tableName, Map<String, Object> values) { ArrayList<Map<String,Object>> rowList = new ArrayList<Map<String,Object>>(); rowList.add(values); insertRowsAndNotify(tableName, rowList); } protected void updateRowsAndNotify(String tableName, Set<Object> rowKeys, Map<String,Object> updateRowList) { updateRows(tableName, rowKeys, updateRowList); sendNotification(tableName, StorageSourceNotification.Action.MODIFY, rowKeys); } protected void updateRowsAndNotify(String tableName, List<Map<String,Object>> updateRowList) { updateRows(tableName, updateRowList); sendNotification(tableName, StorageSourceNotification.Action.MODIFY, updateRowList); } @Override public void updateMatchingRowsImpl(String tableName, IPredicate predicate, Map<String,Object> values) { String primaryKeyName = getTablePrimaryKeyName(tableName); String[] columnNameList = {primaryKeyName}; IResultSet resultSet = executeQuery(tableName, columnNameList, predicate, null); Set<Object> rowKeys = new HashSet<Object>(); while (resultSet.next()) { String rowKey = resultSet.getString(primaryKeyName); rowKeys.add(rowKey); } updateRowsAndNotify(tableName, rowKeys, values); } @Override public void updateRowImpl(String tableName, Object rowKey, Map<String,Object> values) { Map<String,Object> valuesWithKey = new HashMap<String,Object>(values); String primaryKeyName = getTablePrimaryKeyName(tableName); valuesWithKey.put(primaryKeyName, rowKey); List<Map<String,Object>> rowList = new ArrayList<Map<String,Object>>(); rowList.add(valuesWithKey); updateRowsAndNotify(tableName, rowList); } @Override public void updateRowImpl(String tableName, Map<String,Object> values) { List<Map<String,Object>> rowKeys = new ArrayList<Map<String,Object>>(); rowKeys.add(values); updateRowsAndNotify(tableName, rowKeys); } protected void deleteRowsAndNotify(String tableName, Set<Object> rowKeyList) { deleteRows(tableName, rowKeyList); sendNotification(tableName, StorageSourceNotification.Action.DELETE, rowKeyList); } @Override public void deleteRowImpl(String tableName, Object key) { HashSet<Object> keys = new HashSet<Object>(); keys.add(key); deleteRowsAndNotify(tableName, keys); } @Override public IResultSet getRowImpl(String tableName, Object rowKey) { List<Map<String,Object>> rowList = new ArrayList<Map<String,Object>>(); Map<String,Object> row = getRow(tableName, null, rowKey); if (row != null) rowList.add(row); NoSqlResultSet resultSet = new NoSqlResultSet(this, tableName, rowList); return resultSet; } // Below are the methods that must be implemented by the subclasses protected abstract Collection<Map<String,Object>> getAllRows(String tableName, String[] columnNameList); protected abstract Map<String,Object> getRow(String tableName, String[] columnNameList, Object rowKey); protected abstract List<Map<String,Object>> executeEqualityQuery(String tableName, String[] columnNameList, String predicateColumnName, Comparable<?> value); protected abstract List<Map<String,Object>> executeRangeQuery(String tableName, String[] columnNameList, String predicateColumnName, Comparable<?> startValue, boolean startInclusive, Comparable<?> endValue, boolean endInclusive); protected abstract void insertRows(String tableName, List<Map<String,Object>> insertRowList); protected abstract void updateRows(String tableName, Set<Object> rowKeys, Map<String,Object> updateColumnMap); }