package edu.brown.catalog;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONStringer;
import org.voltdb.catalog.CatalogType;
import org.voltdb.catalog.Cluster;
import org.voltdb.catalog.Column;
import org.voltdb.catalog.Constraint;
import org.voltdb.catalog.Database;
import org.voltdb.catalog.Host;
import org.voltdb.catalog.Index;
import org.voltdb.catalog.MaterializedViewInfo;
import org.voltdb.catalog.ProcParameter;
import org.voltdb.catalog.Procedure;
import org.voltdb.catalog.Statement;
import org.voltdb.catalog.StmtParameter;
import org.voltdb.catalog.Table;
import edu.brown.catalog.special.MultiAttributeCatalogType;
import edu.brown.catalog.special.MultiColumn;
import edu.brown.catalog.special.MultiProcParameter;
import edu.brown.catalog.special.ReplicatedColumn;
import edu.brown.catalog.special.VerticalPartitionColumn;
import edu.brown.logging.LoggerUtil;
import edu.brown.logging.LoggerUtil.LoggerBoolean;
import edu.brown.utils.AbstractTreeWalker;
import edu.brown.utils.ClassUtil;
import edu.brown.utils.CollectionUtil;
public abstract class CatalogKey {
private static final Logger LOG = Logger.getLogger(CatalogKey.class);
private static final LoggerBoolean debug = new LoggerBoolean();
private static final LoggerBoolean trace = new LoggerBoolean();
static {
LoggerUtil.attachObserver(LOG, debug, trace);
}
private static final String PARENT_DELIMITER = ".";
private static final Pattern PARENT_DELIMITER_REGEX = Pattern.compile(Pattern.quote(PARENT_DELIMITER));
private static final String MULTIATTRIBUTE_DELIMITER = "#";
private static final Pattern MULTIATTRIBUTE_DELIMITER_REGEX = Pattern.compile(Pattern.quote(MULTIATTRIBUTE_DELIMITER));
private static final Map<CatalogType, String> CACHE_CREATEKEY = new HashMap<CatalogType, String>();
private static final Map<Database, Map<String, CatalogType>> CACHE_GETFROMKEY = new HashMap<Database, Map<String, CatalogType>>();
private static final Map<String, String> CACHE_NAMEFROMKEY = new HashMap<String, String>();
public static class InvalidCatalogKey extends RuntimeException {
private static final long serialVersionUID = 1L;
private final Class<? extends CatalogType> type;
private final String key;
public InvalidCatalogKey(String key, Class<? extends CatalogType> type) {
super("Invalid catalog key '" + key + "' for type '" + type.getSimpleName() + "'");
this.key = key;
this.type = type;
}
public Class<? extends CatalogType> getType() {
return (this.type);
}
public String getCatalogKey() {
return (this.key);
}
}
// --------------------------------------------------------------------------------------------
// KEY SERIALIZERS
// --------------------------------------------------------------------------------------------
/**
* Returns a String key representation of the column as "Parent.Child"
*
* @param catalog_col
* @return
*/
public static <T extends CatalogType> String createKey(T catalog_item) {
// There is a 7x speed-up when we use the cache versus always
// constructing a new key
String ret = CACHE_CREATEKEY.get(catalog_item);
if (ret != null)
return (ret);
if (catalog_item == null)
return (null);
JSONStringer stringer = new JSONStringer();
try {
createKey(catalog_item, stringer);
// IMPORTANT: We have to convert all double quotes to single-quotes
// so that
// the keys don't get escaped when stored in other JSON objects
ret = stringer.toString().replace("\"", "'");
CACHE_CREATEKEY.put(catalog_item, ret);
} catch (JSONException ex) {
throw new RuntimeException("Failed to create catalog key for " + catalog_item, ex);
}
return (ret);
}
@SuppressWarnings("unchecked")
private static <T extends CatalogType> void createKey(T catalog_item, JSONStringer stringer) throws JSONException {
assert (catalog_item.getParent() != null) : catalog_item + " has null parent";
stringer.object();
CatalogType parent = catalog_item.getParent();
String key = null;
// SPECIAL CASE: StmtParameter
// Since there may be Statement's with the same name but in different
// Procedures,
// we also want to include the Procedure name
if (catalog_item instanceof StmtParameter) {
assert (parent.getParent() != null);
key = parent.getParent().getName() + PARENT_DELIMITER + parent.getName();
// SPECIAL CASE: MultiAttributeCatalogType
} else if (catalog_item instanceof MultiAttributeCatalogType) {
MultiAttributeCatalogType multicatalog = (MultiAttributeCatalogType) catalog_item;
key = parent.getName() + MULTIATTRIBUTE_DELIMITER + multicatalog.getPrefix();
} else {
key = parent.getName();
}
assert (key.isEmpty() == false);
stringer.key(key);
// SPECIAL CASE: MultiAttributeCatalogType
if (catalog_item instanceof MultiAttributeCatalogType) {
MultiAttributeCatalogType multicatalog = (MultiAttributeCatalogType) catalog_item;
stringer.array();
Iterator<? extends CatalogType> it = multicatalog.iterator();
while (it.hasNext()) {
// We support nested MultiAttribute objects...
CatalogKey.createKey(it.next(), stringer);
} // WHILE
stringer.endArray();
} else {
stringer.value(catalog_item.getName());
}
stringer.endObject();
}
/**
* Returns a String key representation of the column as {"Parent.Child"
*
* @param parent
* @param child
* @return
*/
public static String createKey(String parent, String child) {
return (String.format("{'%s':'%s'}", parent, child));
}
/**
* Convert the list of CatalogType objects into keys
*
* @param map
* @return
*/
public static Collection<String> createKeys(Iterable<? extends CatalogType> map) {
Collection<String> keys = new ArrayList<String>();
for (CatalogType catalog_item : map) {
keys.add(createKey(catalog_item));
} // FOR
return (keys);
}
// --------------------------------------------------------------------------------------------
// KEY DESERIALIZERS
// --------------------------------------------------------------------------------------------
/**
* Given a String key generated by createKey(), return the corresponding
* catalog object for the given Database catalog. If the parent object does
* not exist, this function will return null. If the parent exists but the
* child does not exist, then it trips an assert
*
* @param catalog_db
* @param key
* @return
*/
@SuppressWarnings("unchecked")
public static <T extends CatalogType> T getFromKey(Database catalog_db, String key, Class<T> catalog_class) throws InvalidCatalogKey {
if (debug.val)
LOG.debug("Grabbing " + catalog_class + " object for '" + key + "'");
assert (catalog_db != null);
assert (catalog_class != null);
// Caching...
Map<String, CatalogType> cache = CatalogKey.CACHE_GETFROMKEY.get(catalog_db);
if (cache != null) {
if (cache.containsKey(key))
return (T) cache.get(key);
} else {
cache = new HashMap<String, CatalogType>();
CatalogKey.CACHE_GETFROMKEY.put(catalog_db, cache);
}
T catalog_item = null;
try {
JSONObject jsonObject = new JSONObject(key.replace("'", "\""));
catalog_item = getFromKey(catalog_db, jsonObject, catalog_class);
} catch (JSONException ex) {
// OLD VERSION
catalog_item = CatalogKeyOldVersion.getFromKey(catalog_db, key, catalog_class);
if (catalog_item == null) {
throw new InvalidCatalogKey(key, catalog_class);
}
}
cache.put(key, catalog_item);
return (catalog_item);
}
@SuppressWarnings("unchecked")
private static <T extends CatalogType> T getFromKey(Database catalog_db, JSONObject jsonObject, Class<T> catalog_class) throws JSONException {
if (debug.val)
LOG.debug("Retrieving catalog key for " + jsonObject);
T catalog_child = null;
CatalogType catalog_parent = null;
String parent_key = CollectionUtil.first(jsonObject.keys());
String orig_parent_key = parent_key;
String multiattribute_key = null;
String child_key = jsonObject.getString(parent_key);
// SPECIAL CASE: MultiAttribute
if (parent_key.contains(MULTIATTRIBUTE_DELIMITER)) {
String split[] = MULTIATTRIBUTE_DELIMITER_REGEX.split(parent_key);
assert (split.length == 2);
parent_key = split[0];
multiattribute_key = split[1];
}
List<Class<?>> superclasses = ClassUtil.getSuperClasses(catalog_class);
// Get the parent based on the type of the object they want back
if (superclasses.contains(Column.class) || catalog_class.equals(Index.class) || catalog_class.equals(Constraint.class) || catalog_class.equals(MaterializedViewInfo.class)) {
catalog_parent = catalog_db.getTables().get(parent_key);
} else if (catalog_class.equals(Statement.class) || superclasses.contains(ProcParameter.class)) {
catalog_parent = catalog_db.getProcedures().get(parent_key);
} else if (catalog_class.equals(Table.class) || catalog_class.equals(Procedure.class)) {
catalog_parent = catalog_db;
} else if (catalog_class.equals(Host.class)) {
catalog_parent = (Cluster) catalog_db.getParent();
// SPECIAL CASE: StmtParameter
} else if (catalog_class.equals(StmtParameter.class)) {
String split[] = PARENT_DELIMITER_REGEX.split(parent_key);
assert (split.length == 2);
Procedure catalog_proc = catalog_db.getProcedures().getIgnoreCase(split[0]);
assert (catalog_proc != null);
catalog_parent = catalog_proc.getStatements().getIgnoreCase(split[1]);
}
// Don't throw this error because it may be a dynamic catalog type that
// we use for the Markov stuff
// } else {
// assert(false) : "Unexpected Catalog key type '" + catalog_class +
// "'";
// }
// It's ok for the parent to be missing, but it's *not* ok if the child
// is missing
if (catalog_parent != null) {
if (debug.val) {
LOG.debug("Catalog Parent: " + CatalogUtil.getDisplayName(catalog_parent));
LOG.debug("MultiAttribute Key: " + multiattribute_key);
LOG.debug("Child Key: " + child_key);
}
// COLUMN
if (superclasses.contains(Column.class)) {
// SPECIAL CASE: Replicated Column
if (child_key.equals(ReplicatedColumn.COLUMN_NAME)) {
catalog_child = (T) ReplicatedColumn.get((Table) catalog_parent);
// SPECIAL CASE: VerticalPartitionColumn
} else if (multiattribute_key != null && multiattribute_key.equalsIgnoreCase(VerticalPartitionColumn.PREFIX)) {
JSONArray jsonArray = jsonObject.getJSONArray(orig_parent_key);
Column params[] = new Column[jsonArray.length()];
for (int i = 0; i < params.length; i++) {
params[i] = getFromKey(catalog_db, jsonArray.getJSONObject(i), Column.class);
} // FOR
assert (params.length == 2) : "Invalid VerticalPartitionColumn Key: " + child_key;
catalog_child = (T) VerticalPartitionColumn.get(params[0], (MultiColumn) params[1]);
// SPECIAL CASE: MultiColumn
} else if (multiattribute_key != null && multiattribute_key.equals(MultiColumn.PREFIX)) {
JSONArray jsonArray = jsonObject.getJSONArray(orig_parent_key);
Column params[] = new Column[jsonArray.length()];
for (int i = 0; i < params.length; i++) {
params[i] = getFromKey(catalog_db, jsonArray.getJSONObject(i), Column.class);
assert (params[i] != null) : "Invalid catalog key " + jsonArray.getJSONObject(i);
} // FOR
assert (params.length > 0) : "Invalid MultiColumn Key: " + child_key;
catalog_child = (T) MultiColumn.get(params);
// Regular Columns
} else {
catalog_child = (T) ((Table) catalog_parent).getColumns().getIgnoreCase(child_key);
}
// INDEX
} else if (superclasses.contains(Index.class)) {
catalog_child = (T) ((Table) catalog_parent).getIndexes().getIgnoreCase(child_key);
// CONSTRAINT
} else if (superclasses.contains(Constraint.class)) {
catalog_child = (T) ((Table) catalog_parent).getConstraints().getIgnoreCase(child_key);
// MATERIALIZEDVIEW
} else if (superclasses.contains(MaterializedViewInfo.class)) {
catalog_child = (T) ((Table) catalog_parent).getViews().getIgnoreCase(child_key);
// PROCPARAMETER
} else if (superclasses.contains(ProcParameter.class)) {
// SPECIAL CASE: MultiProcParameter
if (multiattribute_key != null && multiattribute_key.equalsIgnoreCase(MultiProcParameter.PREFIX)) {
JSONArray jsonArray = jsonObject.getJSONArray(orig_parent_key);
ProcParameter params[] = new ProcParameter[jsonArray.length()];
for (int i = 0; i < params.length; i++) {
params[i] = getFromKey(catalog_db, jsonArray.getJSONObject(i), ProcParameter.class);
} // FOR
assert (params.length > 1) : "Invalid MultiProcParameter Key: " + child_key;
catalog_child = (T) MultiProcParameter.get(params);
// Regular ProcParameter
} else {
catalog_child = (T) ((Procedure) catalog_parent).getParameters().getIgnoreCase(child_key);
}
// STATEMENT
} else if (superclasses.contains(Statement.class)) {
catalog_child = (T) ((Procedure) catalog_parent).getStatements().getIgnoreCase(child_key);
// STMTPARAMETER
} else if (superclasses.contains(StmtParameter.class)) {
catalog_child = (T) ((Statement) catalog_parent).getParameters().get(child_key);
// TABLE
} else if (superclasses.contains(Table.class)) {
catalog_child = (T) ((Database) catalog_parent).getTables().getIgnoreCase(child_key);
if (catalog_child == null) {
LOG.debug("TABLES: " + CatalogUtil.debug(((Database) catalog_parent).getTables()));
}
// PROCEDURE
} else if (superclasses.contains(Procedure.class)) {
catalog_child = (T) ((Database) catalog_parent).getProcedures().getIgnoreCase(child_key);
// HOST
} else if (superclasses.contains(Host.class)) {
catalog_child = (T) ((Cluster) catalog_parent).getHosts().getIgnoreCase(child_key);
// UNKNOWN!
} else {
LOG.fatal("Invalid child class '" + catalog_class + "' for catalog key " + child_key);
assert (false);
}
return (catalog_child);
}
return (null);
}
/**
* Return the name of the catalog object from the key
*
* @param catalog_key
* @return
*/
public static String getNameFromKey(String catalog_key) {
assert (catalog_key != null);
String name = CACHE_NAMEFROMKEY.get(catalog_key);
if (name == null) {
try {
JSONObject jsonObject = new JSONObject(catalog_key);
String key = CollectionUtil.first(jsonObject.keys());
assert (key != null);
name = jsonObject.getString(key);
} catch (Throwable ex) {
// OLD VERSION
name = CatalogKeyOldVersion.getNameFromKey(catalog_key);
if (name == null) {
throw new RuntimeException("Failed to retrieve item name from key '" + catalog_key + "'", ex);
}
}
CACHE_NAMEFROMKEY.put(catalog_key, name);
}
return (name);
}
public static <T extends CatalogType> Collection<T> getFromKeys(Database catalog_db, Collection<String> keys, Class<T> catalog_class, Collection<T> items) {
for (String key : keys) {
items.add(CatalogKey.getFromKey(catalog_db, key, catalog_class));
} // FOR
return (items);
}
public static <T extends CatalogType> Collection<T> getFromKeys(Database catalog_db, Collection<String> keys, Class<T> catalog_class) {
return (getFromKeys(catalog_db, keys, catalog_class, new ArrayList<T>()));
}
/**
* CatalogKey -> CatalogType.fullName()
*
* @param catalog_db
* @param types
* @return
*/
public static Map<Object, String> getDebugMap(Database catalog_db, final Class<? extends CatalogType>... types) {
final Map<Object, String> debugMap = new HashMap<Object, String>();
new AbstractTreeWalker<CatalogType>() {
@SuppressWarnings("unchecked")
@Override
protected void populate_children(AbstractTreeWalker.Children<CatalogType> children, CatalogType element) {
for (String field : element.getChildFields()) {
Collection<CatalogType> m = (Collection<CatalogType>) element.getChildren(field);
assert (m != null);
children.addAfter(m);
} // FOR
}
@Override
protected void callback(CatalogType element) {
List<Class<?>> superclasses = ClassUtil.getSuperClasses(element.getClass());
for (Class<? extends CatalogType> t : types) {
if (superclasses.contains(t)) {
debugMap.put(CatalogKey.createKey(element), element.fullName());
}
} // FOR
}
}.traverse(catalog_db);
return (debugMap);
}
}