/* This file is part of VoltDB.
* Copyright (C) 2008-2017 VoltDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with VoltDB. If not, see <http://www.gnu.org/licenses/>.
*/
package org.voltdb.expressions;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.json_voltpatches.JSONArray;
import org.json_voltpatches.JSONException;
import org.json_voltpatches.JSONObject;
import org.json_voltpatches.JSONStringer;
import org.voltdb.VoltType;
import org.voltdb.planner.AbstractParsedStmt;
import org.voltdb.planner.CompiledPlan;
import org.voltdb.planner.parseinfo.StmtSubqueryScan;
import org.voltdb.plannodes.AbstractPlanNode;
import org.voltdb.types.ExpressionType;
/**
* Expression to represent a select sub query (SELECT ...).
*
*/
public class SelectSubqueryExpression extends AbstractSubqueryExpression {
public enum Members {
OTHER_PARAM_IDX;
}
// subquery
private StmtSubqueryScan m_subquery;
// List of all correlated parameter indexes this subquery and its descendants depend on
// They may originate at different levels in the subquery hierarchy.
private List<Integer> m_allParameterIdxList = new ArrayList<Integer>();
// SelectSubqueryExpression can be changed to a ScalarSubqueryExpression in certain contexts
// By default, AbstractSubqueryExpression use the BigInt as the return type because of possible
// optimization all the other IN/EXISTS into EXISTS (SELECT 1 FROM...).
// However, scalar subquery is used quite different and temporary hides under this class.
// Eventually, scalar subquery is changed to a expression with one child as SelectSubqueryExpression.
private VoltType m_scalarExprType = null;
/**
* Create a new SubqueryExpression. The type can be either:
* SCALAR_SUBQUERY - SELECT A, (SELECT C...) FROM .... - single row one column
* SELECT_SUBQUERY - WHERE (...) IN (SELECT C1, C2 ...) - multiple rows
* @param subqueryType
* @param subquery The parsed statement
*/
public SelectSubqueryExpression(ExpressionType type, StmtSubqueryScan subquery) {
super();
m_type = type;
assert(subquery != null);
m_subquery = subquery;
assert(m_subquery.getSubqueryStmt() != null);
m_subqueryId = m_subquery.getSubqueryStmt().m_stmtId;
if (m_subquery.getBestCostPlan() != null && m_subquery.getBestCostPlan().rootPlanGraph != null) {
m_subqueryNode = m_subquery.getBestCostPlan().rootPlanGraph;
m_subqueryNodeId = m_subqueryNode.getPlanNodeId();
}
m_args = new ArrayList<AbstractExpression>();
resolveCorrelations();
m_scalarExprType = m_valueType;
if (m_subquery.getOutputSchema().size() == 1) {
// potential scalar sub-query
m_scalarExprType = m_subquery.getOutputSchema().get(0).getType();
}
}
/**
* Create a new empty SubqueryExpression for loading from JSON
* @param subquey The parsed statement
*/
public SelectSubqueryExpression() {
super();
setExpressionType(ExpressionType.SELECT_SUBQUERY);
}
public StmtSubqueryScan getSubqueryScan() {
return m_subquery;
}
public AbstractParsedStmt getSubqueryStmt() {
return (m_subquery == null) ? null : m_subquery.getSubqueryStmt();
}
@Override
public int getSubqueryId() {
return m_subqueryId;
}
@Override
public int getSubqueryNodeId() {
return m_subqueryNodeId;
}
@Override
public AbstractPlanNode getSubqueryNode() {
return m_subqueryNode;
}
/**
* This function should only be called when this expression should be changed to ScalarSubqueryExpression.
*/
public void changeToScalarExprType() {
m_valueType = m_scalarExprType;
m_valueSize = m_valueType.getMaxLengthInBytes();
}
/**
* From JSON
*/
@Override
public void setSubqueryNode(AbstractPlanNode subqueryNode) {
assert(subqueryNode != null);
m_subqueryNode = subqueryNode;
if (m_subquery != null && m_subquery.getBestCostPlan() != null) {
m_subquery.getBestCostPlan().rootPlanGraph = m_subqueryNode;
}
resetSubqueryNodeId();
}
@Override
public int overrideSubqueryNodeIds(int newId) {
assert(m_subquery != null);
CompiledPlan subqueryPlan = m_subquery.getBestCostPlan();
newId = subqueryPlan.resetPlanNodeIds(newId);
resetSubqueryNodeId();
return newId;
}
@Override
public SelectSubqueryExpression clone() {
SelectSubqueryExpression clone = (SelectSubqueryExpression) super.clone();
if (!m_allParameterIdxList.isEmpty()) {
clone.m_allParameterIdxList = new ArrayList<Integer>();
for (Integer paramIdx : m_allParameterIdxList) {
clone.m_allParameterIdxList.add(new Integer(paramIdx.intValue()));
}
}
return clone;
}
@Override
public void validate() throws Exception {
super.validate();
if ((m_right != null) || (m_left != null))
throw new Exception("ERROR: A subquery expression has child expressions for '" + this + "'");
}
@Override
public void toJSONString(JSONStringer stringer) throws JSONException {
super.toJSONString(stringer);
// Output the correlated parameter ids that this subquery or its descendants
// depends upon but originate at the grandparent level and do not need to be set
// by this subquery
if (!m_allParameterIdxList.isEmpty()) {
// Calculate the difference between two sets of parameters
Set<Integer> allParams = new HashSet<Integer>();
allParams.addAll(m_allParameterIdxList);
allParams.removeAll(getParameterIdxList());
if (!allParams.isEmpty()) {
stringer.key(Members.OTHER_PARAM_IDX.name()).array();
for (Integer idx : allParams) {
stringer.value(idx);
}
stringer.endArray();
}
}
}
@Override
protected void loadFromJSONObject(JSONObject obj) throws JSONException {
super.loadFromJSONObject(obj);
if (obj.has(Members.OTHER_PARAM_IDX.name())) {
JSONArray otherParamIdxArray = obj.getJSONArray(Members.OTHER_PARAM_IDX.name());
int paramSize = otherParamIdxArray.length();
for (int i = 0; i < paramSize; ++i) {
m_allParameterIdxList.add(otherParamIdxArray.getInt(i));
}
m_allParameterIdxList.addAll(getParameterIdxList());
}
}
@Override
public String explain(String impliedTableName) {
if (m_subqueryNode != null) {
// Surround the explained subquery by the 'Subquery_#' tags.The explained subquery
// will be extracted into a separated line from the final explain string
StringBuilder sb = new StringBuilder();
m_subqueryNode.explainPlan_recurse(sb, "");
String result = "(" + SUBQUERY_TAG + m_subqueryId + " " + sb.toString()
+ SUBQUERY_TAG + m_subqueryId + "";
if (m_args != null && ! m_args.isEmpty()) {
String connector = "\n on arguments (";
for (AbstractExpression arg : m_args) {
result += connector + arg.explain(impliedTableName);
connector = ", ";
}
result += ")\n";
}
result +=")";
return result;
} else {
return "(Subquery: null)";
}
}
/**
* Resolve the subquery's correlated TVEs (and, in one special case, aggregates)
* that became ParameterValueExpressions in the subquery statement (or its children).
* If they reference a column from the parent statement (getOrigStmtId() == parentStmt.m_stmtId)
* that PVE will have to be initialized by this subquery expression in the back-end executor.
* Otherwise, the TVE references a grandparent statement with its own subquery expression,
* so just add it to the parent statement's set of correlated TVEs needing to be resolved later
* at a higher level.
*/
public void resolveCorrelations() {
AbstractParsedStmt subqueryStmt = m_subquery.getSubqueryStmt();
AbstractParsedStmt parentStmt = subqueryStmt.m_parentStmt;
// we must have a parent - it's a subquery statement
assert(parentStmt != null);
// Preserve indexes of all parameters this subquery depends on.
// It might include parameters from its nested child subqueries that
// the subquery statement could not resolve itself and had to "move up".
m_allParameterIdxList.addAll(subqueryStmt.m_parameterTveMap.keySet());
for (Map.Entry<Integer, AbstractExpression> entry : subqueryStmt.m_parameterTveMap.entrySet()) {
Integer paramIdx = entry.getKey();
AbstractExpression expr = entry.getValue();
if (expr instanceof TupleValueExpression) {
TupleValueExpression tve = (TupleValueExpression) expr;
if (tve.getOrigStmtId() == parentStmt.m_stmtId) {
// TVE originates from the statement that this SubqueryExpression belongs to
addArgumentParameter(paramIdx, expr);
}
else {
// TVE originates from a statement above this parent. Move it up.
parentStmt.m_parameterTveMap.put(paramIdx, expr);
}
}
else if (expr instanceof AggregateExpression) {
// An aggregate expression is always from THIS parent statement.
addArgumentParameter(paramIdx, expr);
}
else {
// so far it should be either AggregateExpression or TupleValueExpression types
assert(false);
}
}
subqueryStmt.m_parameterTveMap.clear();
}
public String calculateContentDeterminismMessage() {
return m_subquery.calculateContentDeterminismMessage();
}
}