/* This file is part of VoltDB.
* Copyright (C) 2008-2010 VoltDB L.L.C.
*
* VoltDB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* VoltDB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VoltDB. If not, see <http://www.gnu.org/licenses/>.
*/
package org.voltdb.utils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeMap;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.voltdb.VoltTable;
import org.voltdb.VoltType;
import org.voltdb.catalog.CatalogType;
import org.voltdb.catalog.Column;
import org.voltdb.catalog.ColumnRef;
import org.voltdb.catalog.Constraint;
import org.voltdb.catalog.ConstraintRef;
import org.voltdb.catalog.Database;
import org.voltdb.catalog.Index;
import org.voltdb.catalog.PlanFragment;
import org.voltdb.catalog.Table;
import org.voltdb.types.ConstraintType;
import org.voltdb.types.IndexType;
/**
*
*/
public abstract class CatalogUtil {
public static final String CATALOG_FILENAME = "catalog.txt";
/**
*
* @param from_column
* @return
*/
public static Column getForeignKeyParent(Column from_column) {
assert(from_column != null);
Column to_column = null;
for (Constraint catalog_const : CatalogUtil.getConstraints(from_column.getConstraints())) {
if (catalog_const.getType() == ConstraintType.FOREIGN_KEY.getValue()) {
assert(!catalog_const.getForeignkeycols().isEmpty());
for (ColumnRef catalog_col_ref : catalog_const.getForeignkeycols()) {
if (catalog_col_ref.getName().equals(from_column.getName())) {
assert(to_column == null);
to_column = catalog_col_ref.getColumn();
break;
}
} // FOR
if (to_column != null) break;
}
} // FOR
return (to_column);
}
/**
* Return the real Constraint objects for the ConstraintRefs
* @param map
* @return
*/
public static Collection<Constraint> getConstraints(Iterable<ConstraintRef> map) {
List<Constraint> ret = new ArrayList<Constraint>();
if (map != null) {
for (ConstraintRef ref : map) {
Constraint catalog_item = ref.getConstraint();
assert(catalog_item != null);
ret.add(catalog_item);
}
}
return (ret);
}
public static String loadCatalogFromJar(String pathToCatalog, Logger log) {
assert(pathToCatalog != null);
String serializedCatalog = null;
try {
serializedCatalog = JarReader.readFileFromJarfile(pathToCatalog, CATALOG_FILENAME);
} catch (Exception e) {
if (log != null)
log.l7dlog( Level.FATAL, LogKeys.host_VoltDB_CatalogReadFailure.name(), e);
return null;
}
return serializedCatalog;
}
/**
* Get a unique id for a plan fragment by munging the indices of it's parents
* and grandparents in the catalog.
*
* @param frag Catalog fragment to identify
* @return unique id for fragment
*/
public static long getUniqueIdForFragment(PlanFragment frag) {
long retval = 0;
CatalogType parent = frag.getParent();
retval = ((long) parent.getParent().getRelativeIndex()) << 32;
retval += ((long) parent.getRelativeIndex()) << 16;
retval += frag.getRelativeIndex();
return retval;
}
/**
*
* @param catalogTable
* @return An empty table with the same schema as a given catalog table.
*/
public static VoltTable getVoltTable(Table catalogTable) {
assert(catalogTable != null) : "Unexpected null catalog table";
List<Column> catalogColumns = CatalogUtil.getSortedCatalogItems(catalogTable.getColumns(), "index");
return CatalogUtil.getVoltTable(catalogColumns);
}
/**
*
* @param catalogTable
* @return An empty table with the same schema as a given catalog table.
*/
public static VoltTable getVoltTable(Collection<Column> catalogColumns) {
VoltTable.ColumnInfo[] columns = new VoltTable.ColumnInfo[catalogColumns.size()];
int i = 0;
for (Column catCol : catalogColumns) {
columns[i++] = new VoltTable.ColumnInfo(catCol.getTypeName(), VoltType.get((byte)catCol.getType()));
}
return new VoltTable(columns);
}
/**
* Given a set of catalog items, return a sorted list of them, sorted by
* the value of a specified field. The field is specified by name. If the
* field doesn't exist, trip an assertion. This is primarily used to sort
* a table's columns or a procedure's parameters.
*
* @param <T> The type of item to sort.
* @param items The set of catalog items.
* @param sortFieldName The name of the field to sort on.
* @return A list of catalog items, sorted on the specified field.
*/
public static <T extends CatalogType> List<T> getSortedCatalogItems(Collection<T> items, String sortFieldName) {
assert(items != null);
assert(sortFieldName != null);
// build a treemap based on the field value
TreeMap<Object, T> map = new TreeMap<Object, T>();
boolean hasField = false;
for (T item : items) {
// check the first time through for the field
if (hasField == false)
hasField = item.getFields().contains(sortFieldName);
assert(hasField == true);
map.put(item.getField(sortFieldName), item);
}
// create a sorted list from the map
ArrayList<T> retval = new ArrayList<T>();
for (T item : map.values()) {
retval.add(item);
}
return retval;
}
/**
* For a given Table catalog object, return the PrimaryKey Index catalog object
* @param catalogTable
* @return The index representing the primary key.
* @throws Exception if the table does not define a primary key
*/
public static Index getPrimaryKeyIndex(Table catalogTable) throws Exception {
// We first need to find the pkey constraint
Constraint catalog_constraint = null;
for (Constraint c : catalogTable.getConstraints()) {
if (c.getType() == ConstraintType.PRIMARY_KEY.getValue()) {
catalog_constraint = c;
break;
}
}
if (catalog_constraint == null) {
throw new Exception("ERROR: Table '" + catalogTable.getTypeName() + "' does not have a PRIMARY KEY constraint");
}
// And then grab the index that it is using
return (catalog_constraint.getIndex());
}
/**
* Return all the of the primary key columns for a particular table
* If the table does not have a primary key, then the returned list will be empty
* @param catalogTable
* @return An ordered list of the primary key columns
*/
public static Collection<Column> getPrimaryKeyColumns(Table catalogTable) {
Collection<Column> columns = new ArrayList<Column>();
Index catalog_idx = null;
try {
catalog_idx = CatalogUtil.getPrimaryKeyIndex(catalogTable);
} catch (Exception ex) {
// IGNORE
return (columns);
}
assert(catalog_idx != null);
for (ColumnRef catalog_col_ref : getSortedCatalogItems(catalog_idx.getColumns(), "index")) {
columns.add(catalog_col_ref.getColumn());
}
return (columns);
}
public static String toSchema(Database catalog_db, boolean include_fkeys) {
StringBuilder sb = new StringBuilder();
for (Table catalog_tbl : catalog_db.getTables()) {
sb.append(toSchema(catalog_tbl, include_fkeys)).append("\n");
} // FOR
return (sb.toString());
}
/**
* Convert a Table catalog object into the proper SQL DDL, including all indexes,
* constraints, and foreign key references.
* @param catalog_tbl
* @return SQL Schema text representing the table.
*/
public static String toSchema(Table catalog_tbl) {
return toSchema(catalog_tbl, true);
}
public static String toSchema(Table catalog_tbl, boolean include_fkeys) {
assert(!catalog_tbl.getColumns().isEmpty());
final String spacer = " ";
Set<Index> skip_indexes = new HashSet<Index>();
Set<Constraint> skip_constraints = new HashSet<Constraint>();
String ret = "CREATE TABLE " + catalog_tbl.getTypeName() + " (";
// Columns
String add = "\n";
for (Column catalog_col : CatalogUtil.getSortedCatalogItems(catalog_tbl.getColumns(), "index")) {
VoltType col_type = VoltType.get((byte)catalog_col.getType());
// this next assert would be great if we dealt with default values well
//assert(! ((catalog_col.getDefaultvalue() == null) && (catalog_col.getNullable() == false) ) );
ret += add + spacer + catalog_col.getTypeName() + " " +
col_type.toSQLString() +
(col_type == VoltType.STRING && catalog_col.getSize() > 0 ? "(" + catalog_col.getSize() + ")" : "");
// Default value
String defaultValue = catalog_col.getDefaultvalue();
// VoltType defaultType = VoltType.get(catalog_col.getDefaulttype());
// TODO: Shouldn't have to check whether the string contains "null"
boolean isDefaultValueNull = (defaultValue == null || defaultValue.equalsIgnoreCase("null"));
boolean isNullable = catalog_col.getNullable();
if (isDefaultValueNull) {
if (isNullable) ret += " DEFAULT NULL";
} else { // XXX: if (defaulttype != VoltType.VOLTFUNCTION) {
// TODO: Escape strings properly
ret += " DEFAULT '" + defaultValue + "'";
}
if (isNullable == false) ret += " NOT NULL";
// Single-column constraints
for (ConstraintRef catalog_const_ref : catalog_col.getConstraints()) {
Constraint catalog_const = catalog_const_ref.getConstraint();
ConstraintType const_type = ConstraintType.get(catalog_const.getType());
if (const_type == ConstraintType.FOREIGN_KEY && include_fkeys == false) continue;
// Check if there is another column in our table with the same constraint
// If there is, then we need to add it to the end of the table definition
boolean found = false;
for (Column catalog_other_col : catalog_tbl.getColumns()) {
if (catalog_other_col.equals(catalog_col)) continue;
if (catalog_other_col.getConstraints().getIgnoreCase(catalog_const.getTypeName()) != null) {
found = true;
break;
}
}
if (!found) {
switch (const_type) {
case FOREIGN_KEY: {
Table catalog_fkey_tbl = catalog_const.getForeignkeytable();
Column catalog_fkey_col = null;
for (ColumnRef ref : catalog_const.getForeignkeycols()) {
catalog_fkey_col = ref.getColumn();
break; // Nasty hack to get first item
}
assert(catalog_fkey_col != null);
ret += " REFERENCES " + catalog_fkey_tbl.getTypeName() + " (" + catalog_fkey_col.getTypeName() + ")";
skip_constraints.add(catalog_const);
break;
}
default:
// Nothing for now
}
}
}
add = ",\n";
}
// Constraints
for (Constraint catalog_const : catalog_tbl.getConstraints()) {
if (skip_constraints.contains(catalog_const)) continue;
ConstraintType const_type = ConstraintType.get(catalog_const.getType());
if (const_type == ConstraintType.FOREIGN_KEY && include_fkeys == false) continue;
// Primary Keys / Unique Constraints
if (const_type == ConstraintType.PRIMARY_KEY || const_type == ConstraintType.UNIQUE) {
Index catalog_idx = catalog_const.getIndex();
IndexType idx_type = IndexType.get(catalog_idx.getType());
String idx_suffix = idx_type.getSQLSuffix();
ret += add + spacer +
(!idx_suffix.isEmpty() ? "CONSTRAINT " + catalog_const.getTypeName() + " " : "") +
(const_type == ConstraintType.PRIMARY_KEY ? "PRIMARY KEY" : "UNIQUE") + " (";
String col_add = "";
for (ColumnRef catalog_colref : CatalogUtil.getSortedCatalogItems(catalog_idx.getColumns(), "index")) {
ret += col_add + catalog_colref.getColumn().getTypeName();
col_add = ", ";
} // FOR
ret += ")";
skip_indexes.add(catalog_idx);
// Foreign Key
} else if (const_type == ConstraintType.FOREIGN_KEY) {
Table catalog_fkey_tbl = catalog_const.getForeignkeytable();
String col_add = "";
String our_columns = "";
String fkey_columns = "";
for (ColumnRef catalog_colref : catalog_const.getForeignkeycols()) {
// The name of the ColumnRef is the column in our base table
Column our_column = catalog_tbl.getColumns().getIgnoreCase(catalog_colref.getTypeName());
assert(our_column != null);
our_columns += col_add + our_column.getTypeName();
Column fkey_column = catalog_colref.getColumn();
assert(fkey_column != null);
fkey_columns += col_add + fkey_column.getTypeName();
col_add = ", ";
}
ret += add + spacer + "CONSTRAINT " + catalog_const.getTypeName() + " " +
"FOREIGN KEY (" + our_columns + ") " +
"REFERENCES " + catalog_fkey_tbl.getTypeName() + " (" + fkey_columns + ")";
}
skip_constraints.add(catalog_const);
}
ret += "\n);\n";
// All other Indexes
for (Index catalog_idx : catalog_tbl.getIndexes()) {
if (skip_indexes.contains(catalog_idx)) continue;
ret += "CREATE INDEX " + catalog_idx.getTypeName() +
" ON " + catalog_tbl.getTypeName() + " (";
add = "";
for (ColumnRef catalog_colref : CatalogUtil.getSortedCatalogItems(catalog_idx.getColumns(), "index")) {
ret += add + catalog_colref.getColumn().getTypeName();
add = ", ";
}
ret += ");\n";
}
return ret;
}
}