/* 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.planner; import java.util.EnumSet; import java.util.HashMap; import java.util.Map; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONStringer; import org.voltdb.VoltType; import org.voltdb.catalog.Database; import org.voltdb.expressions.AbstractExpression; import org.voltdb.expressions.TupleValueExpression; import edu.brown.expressions.ExpressionUtil; /** * A PlanColumn organizes all of the schema and meta-data about a column that is * required by the planner. Each plan node has an output column list of * PlanColumns specifying its output schema. Column value types are stored in * the column's associated expression. * * Once the AST objects generated from the parsed HSQL are processed into plan * nodes, all column references should be managed as PlanColumn GUIDs. * * Eventually, AbstractExpressions should reference column GUIDs allowing * walking the expression should lead to the GUIDs for the expression input * columns - those inputs are sometimes referred to as origin columns in * comments and variable names. */ public class PlanColumn { public enum Members { GUID, TYPE, SIZE, NAME, INPUT_COLUMN_NAME, //For output columns, what was the name of the column in the input that it maps to INPUT_TABLE_NAME, SORT_ORDER, STORAGE, EXPRESSION; } public enum SortOrder { kAscending, kDescending, kUnsorted; protected static final Map<Integer, SortOrder> idx_lookup = new HashMap<Integer, SortOrder>(); protected static final Map<String, SortOrder> name_lookup = new HashMap<String, SortOrder>(); static { for (SortOrder vt : EnumSet.allOf(SortOrder.class)) { SortOrder.idx_lookup.put(vt.ordinal(), vt); SortOrder.name_lookup.put(vt.name().toLowerCase().intern(), vt); } } public static SortOrder get(Integer idx) { return (SortOrder.idx_lookup.get(idx)); } public static SortOrder get(String name) { return (SortOrder.name_lookup.get(name.toLowerCase().intern())); } }; public enum Storage { kPartitioned, kReplicated, kTemporary, // column in an intermediate table kUnknown; // TODO: eliminate this and make all columns known protected static final Map<Integer, Storage> idx_lookup = new HashMap<Integer, Storage>(); protected static final Map<String, Storage> name_lookup = new HashMap<String, Storage>(); static { for (Storage vt : EnumSet.allOf(Storage.class)) { Storage.idx_lookup.put(vt.ordinal(), vt); Storage.name_lookup.put(vt.name().toLowerCase().intern(), vt); } } public static Storage get(Integer idx) { return (Storage.idx_lookup.get(idx)); } public static Storage get(String name) { return (Storage.name_lookup.get(name.toLowerCase().intern())); } }; /** * Globally unique id identifying this column */ private final int m_guid; /** * Columns may be derived from other columns by expressions. For example, * c = a + b. Or c = a + 1. Or c = a where (a < b). The creating expression, * if any, is given. Those expressions in turn contain information (in * TupleValueExpression nodes, e.g.) about "origin columns", from which * this column may be derived. */ final AbstractExpression m_expression; /** * The sort order. This sort order may have been established by an ORDER BY * statement or as a result of the column's construction (scan of sorted * table column, for example). */ final SortOrder m_sortOrder; /** * Partitioned, replicated or intermediate table column */ final Storage m_storage; /** * Column's display name. If an output column, this will be the alias if an * alias was present in the SQL expression. */ final String m_displayName; final int m_hashCode; // // Constructors // public PlanColumn( int guid, AbstractExpression expression, String columnName, SortOrder sortOrder, Storage storage) { // all members are final and immutable (by implementation) m_guid = guid; m_expression = expression; m_displayName = columnName; m_sortOrder = sortOrder; m_storage = storage; m_hashCode = computeHashCode(m_expression, m_displayName, m_sortOrder, m_storage); PlannerContext.singleton().registerPlanColumn(this); /* Breaks for adhoc deser code.. if (expression instanceof TupleValueExpression) { assert(((TupleValueExpression)expression).getColumnAlias() != null); assert(((TupleValueExpression)expression).getColumnName() != null); } */ } protected static int computeHashCode(AbstractExpression expression, String columnName, SortOrder sortOrder, Storage storage) { StringBuilder sb = new StringBuilder() .append(columnName).append("|") .append(sortOrder).append("|") .append(storage).append("|") .append(expression); return (sb.toString().hashCode()); } @Override public int hashCode() { return (this.m_hashCode); } /** * Check if the two objects are equal in all regards except for the AbstractExpression * @param obj * @param ignore_exp * @return */ public boolean equals(Object obj, boolean ignore_exp, boolean ignore_guid) { if (obj instanceof PlanColumn) { PlanColumn other = (PlanColumn)obj; if (ignore_guid == false && this.m_guid != other.m_guid) return (false); if (!this.m_displayName.equals(other.m_displayName)) return (false); if (!this.m_sortOrder.equals(other.m_sortOrder)) return (false); /** DWU - ignore storage for now? Problematic in the future? **/ //if (!this.m_storage.equals(other.m_storage)) return (false); if (ignore_exp == false && !ExpressionUtil.equals(this.m_expression, other.m_expression)) return (false); return (true); } return (false); } @Override public boolean equals(Object obj) { return (this.equals(obj, false, false)); } // // Accessors: all return copies or immutable values // public String getDisplayName() { return new String(m_displayName); } public String originTableName() { TupleValueExpression tve = null; if ((m_expression instanceof TupleValueExpression) == true) tve = (TupleValueExpression)m_expression; else return null; if (tve.getTableName() != null) return new String(tve.getTableName()); else return null; } public String originColumnName() { TupleValueExpression tve = null; if ((m_expression instanceof TupleValueExpression) == true) tve = (TupleValueExpression)m_expression; else return null; if (tve.getColumnAlias() != null) return new String(tve.getColumnAlias()); else return null; } public int guid() { return m_guid; } public VoltType type() { return m_expression.getValueType(); } public int width() { return m_expression.getValueSize(); } public SortOrder getSortOrder() { return (this.m_sortOrder); } public Storage getStorage() { return (this.m_storage); } public AbstractExpression getExpression() { return m_expression; } @Override public String toString() { return ("PlanColumn(" + "guid=" + guid() + ", " + "name=" + getDisplayName() + ", " + "type=" + type() + ", " + "size=" + width() + ", " + "sort=" + m_sortOrder + ", " + "storage=" + m_storage + ")"); } public void toJSONString(JSONStringer stringer) throws JSONException { stringer.object(); stringer.key(Members.GUID.name()).value(guid()); stringer.key(Members.NAME.name()).value(getDisplayName()); stringer.key(Members.TYPE.name()).value(type().name()); stringer.key(Members.SIZE.name()).value(width()); stringer.key(Members.SORT_ORDER.name()).value(m_sortOrder.name()); stringer.key(Members.STORAGE.name()).value(m_storage.name()); if (originTableName() != null) { stringer.key(Members.INPUT_TABLE_NAME.name()).value(originTableName()); } else { stringer.key(Members.INPUT_TABLE_NAME.name()).value(""); } if (originColumnName() != null) { stringer.key(Members.INPUT_COLUMN_NAME.name()).value(originColumnName()); } else { stringer.key(Members.INPUT_COLUMN_NAME.name()).value(""); } if (m_expression != null) { stringer.key(Members.EXPRESSION.name()); stringer.object(); m_expression.toJSONString(stringer); stringer.endObject(); } else { stringer.key(Members.EXPRESSION.name()).value(""); } stringer.endObject(); } public static PlanColumn fromJSONObject(JSONObject obj, Database db) throws JSONException { // // Get the global id and check whether we already have this guy // int guid = obj.getInt(Members.GUID.name()); PlanColumn column = null; PlannerContext planner = PlannerContext.singleton(); synchronized (planner) { column = planner.get(guid); if (column == null) { String columnName = obj.getString(Members.NAME.name()); SortOrder sortOrder = SortOrder.get(obj.getString(Members.SORT_ORDER.name())); Storage storage = Storage.get(obj.getString(Members.STORAGE.name())); AbstractExpression expression = null; if (!obj.getString(Members.EXPRESSION.name()).isEmpty()) { JSONObject jsonExpression = obj.getJSONObject(Members.EXPRESSION.name()); expression = AbstractExpression.fromJSONObject(jsonExpression, db); } column = new PlanColumn(guid, expression, columnName, sortOrder, storage); planner.add(guid, column); } } // SYCHRONIZED return (column); } }