use of org.voltdb.planner.parseinfo.StmtSubqueryScan in project voltdb by VoltDB.
the class InsertSubPlanAssembler method nextPlan.
@Override
AbstractPlanNode nextPlan() {
if (m_bestAndOnlyPlanWasGenerated) {
return null;
}
// We may generate a few different plans for the subquery, but by the time
// we get here, we'll generate only one plan for the INSERT statement itself.
// Mostly this method exists to check that we can find a valid partitioning
// for the statement.
m_bestAndOnlyPlanWasGenerated = true;
ParsedInsertStmt insertStmt = (ParsedInsertStmt) m_parsedStmt;
Table targetTable = insertStmt.m_tableList.get(0);
targetTable.getTypeName();
StmtSubqueryScan subquery = insertStmt.getSubqueryScan();
boolean subqueryIsMultiFragment = subquery.getBestCostPlan().rootPlanGraph.hasAnyNodeOfType(PlanNodeType.SEND);
if (targetTable.getIsreplicated()) {
// setUpForNewPlans already validates this
assert (!m_partitioning.wasSpecifiedAsSingle() && !m_partitioning.isInferredSingle());
// Cannot access any partitioned tables in subquery for replicated table
if (!subquery.getIsReplicated()) {
throw new PlanningErrorException("Subquery in " + getSqlType() + " INTO ... SELECT statement may not access " + "partitioned data for insertion into replicated table " + targetTable.getTypeName() + ".");
}
} else if (!m_partitioning.wasSpecifiedAsSingle()) {
if (subqueryIsMultiFragment) {
// What is the appropriate level of detail for this message?
m_recentErrorMsg = getSqlType() + " INTO ... SELECT statement subquery is too complex. " + "Please either simplify the subquery or use a SELECT followed by an INSERT.";
return null;
}
Column partitioningCol = targetTable.getPartitioncolumn();
if (partitioningCol == null) {
assert (m_targetIsExportTable);
m_recentErrorMsg = "The target table for an INSERT INTO ... SELECT statement is an " + "stream with no partitioning column defined. " + "This is not currently supported. Please define a " + "partitioning column for this stream to use it with INSERT INTO ... SELECT.";
return null;
}
List<StmtTableScan> tables = new ArrayList<>();
StmtTargetTableScan stmtTargetTableScan = new StmtTargetTableScan(targetTable);
tables.add(stmtTargetTableScan);
tables.add(subquery);
// Create value equivalence between the partitioning column of the target table
// and the corresponding expression produced by the subquery.
HashMap<AbstractExpression, Set<AbstractExpression>> valueEquivalence = new HashMap<>();
int i = 0;
boolean setEquivalenceForPartitioningCol = false;
for (Column col : insertStmt.m_columns.keySet()) {
if (partitioningCol.compareTo(col) == 0) {
List<SchemaColumn> partitioningColumns = stmtTargetTableScan.getPartitioningColumns();
assert (partitioningColumns.size() == 1);
AbstractExpression targetPartitionColExpr = partitioningColumns.get(0).getExpression();
TupleValueExpression selectedExpr = subquery.getOutputExpression(i);
assert (!valueEquivalence.containsKey(targetPartitionColExpr));
assert (!valueEquivalence.containsKey(selectedExpr));
Set<AbstractExpression> equivSet = new HashSet<>();
equivSet.add(targetPartitionColExpr);
equivSet.add(selectedExpr);
valueEquivalence.put(targetPartitionColExpr, equivSet);
valueEquivalence.put(selectedExpr, equivSet);
setEquivalenceForPartitioningCol = true;
}
++i;
}
if (!setEquivalenceForPartitioningCol) {
// partitioning column of target table is not being set from value produced by the subquery.
m_recentErrorMsg = "Partitioning column must be assigned a value " + "produced by the subquery in an " + getSqlType() + " INTO ... SELECT statement.";
return null;
}
m_partitioning.analyzeForMultiPartitionAccess(tables, valueEquivalence);
if (!m_partitioning.isJoinValid()) {
m_recentErrorMsg = "Partitioning could not be determined for " + getSqlType() + " INTO ... SELECT statement. " + "Please ensure that statement does not attempt to copy row data from one partition to another, " + "which is unsupported.";
return null;
}
}
return subquery.getBestCostPlan().rootPlanGraph;
}
use of org.voltdb.planner.parseinfo.StmtSubqueryScan in project voltdb by VoltDB.
the class AbstractParsedStmt method parseSubqueryExpression.
/**
* Parse an expression subquery
*/
private SelectSubqueryExpression parseSubqueryExpression(VoltXMLElement exprNode) {
assert (exprNode.children.size() == 1);
VoltXMLElement subqueryElmt = exprNode.children.get(0);
AbstractParsedStmt subqueryStmt = parseSubquery(subqueryElmt);
// add table to the query cache
String withoutAlias = null;
StmtSubqueryScan stmtSubqueryScan = addSubqueryToStmtCache(subqueryStmt, withoutAlias);
// Set to the default SELECT_SUBQUERY. May be overridden depending on the context
return new SelectSubqueryExpression(ExpressionType.SELECT_SUBQUERY, stmtSubqueryScan);
}
use of org.voltdb.planner.parseinfo.StmtSubqueryScan in project voltdb by VoltDB.
the class StatementPartitioning method analyzeForMultiPartitionAccess.
/**
* Given the query's list of tables and its collection(s) of equality-filtered columns and their equivalents,
* determine whether all joins involving partitioned tables can be executed locally on a single partition.
* This is only the case when they include equality comparisons between partition key columns.
* VoltDB will reject joins of multiple partitioned tables unless all their partition keys are
* constrained to be equal to each other.
* Example: select * from T1, T2 where T1.ID = T2.ID
* Additionally, in this case, there may be a constant equality filter on any of the columns,
* which we want to extract as our SP partitioning parameter.
*
* @param tableAliasList The tables.
* @param valueEquivalence Their column equality filters
* @return the number of independently partitioned tables
* -- partitioned tables that aren't joined or filtered by the same value.
* The caller can raise an alarm if there is more than one.
*/
public void analyzeForMultiPartitionAccess(Collection<StmtTableScan> scans, HashMap<AbstractExpression, Set<AbstractExpression>> valueEquivalence) {
//* enable to debug */ System.out.println("DEBUG: analyze4MPAccess w/ scans:" + scans.size() + " filters:" + valueEquivalence.size());
TupleValueExpression tokenPartitionKey = null;
Set<Set<AbstractExpression>> eqSets = new HashSet<Set<AbstractExpression>>();
int unfilteredPartitionKeyCount = 0;
// reset this flag to forget the last result of the multiple partition access path.
// AdHoc with parameters will call this function at least two times
// By default this flag should be true.
setJoinValid(true);
setJoinInvalidReason(null);
boolean subqueryHasReceiveNode = false;
boolean hasPartitionedTableJoin = false;
// Iterate over the tables to collect partition columns.
for (StmtTableScan tableScan : scans) {
// Replicated tables don't need filter coverage.
if (tableScan.getIsReplicated()) {
continue;
}
// The partition column can be null in an obscure edge case.
// The table is declared non-replicated yet specifies no partitioning column.
// This can occur legitimately when views based on partitioned tables neglect to group by the partition column.
// The interpretation of this edge case is that the table has "randomly distributed data".
// In such a case, the table is valid for use by MP queries only and can only be joined with replicated tables
// because it has no recognized partitioning join key.
List<SchemaColumn> columnsNeedingCoverage = tableScan.getPartitioningColumns();
if (tableScan instanceof StmtSubqueryScan) {
StmtSubqueryScan subScan = (StmtSubqueryScan) tableScan;
subScan.promoteSinglePartitionInfo(valueEquivalence, eqSets);
CompiledPlan subqueryPlan = subScan.getBestCostPlan();
if ((!subScan.canRunInOneFragment()) || ((subqueryPlan != null) && subqueryPlan.rootPlanGraph.hasAnyNodeOfClass(AbstractReceivePlanNode.class))) {
if (subqueryHasReceiveNode) {
// Has found another subquery with receive node on the same level
// Not going to support this kind of subquery join with 2 fragment plan.
setJoinValid(false);
setJoinInvalidReason("This multipartition query is not plannable. " + "It has a subquery which cannot be single partition.");
// Still needs to count the independent partition tables
break;
}
subqueryHasReceiveNode = true;
if (subScan.isTableAggregate()) {
// Any process based on this subquery should require 1 fragment only.
continue;
}
} else {
// this subquery partition table without receive node
hasPartitionedTableJoin = true;
}
} else {
// This table is a partition table
hasPartitionedTableJoin = true;
}
boolean unfiltered = true;
for (AbstractExpression candidateColumn : valueEquivalence.keySet()) {
if (!(candidateColumn instanceof TupleValueExpression)) {
continue;
}
TupleValueExpression candidatePartitionKey = (TupleValueExpression) candidateColumn;
if (!canCoverPartitioningColumn(candidatePartitionKey, columnsNeedingCoverage)) {
continue;
}
unfiltered = false;
if (tokenPartitionKey == null) {
tokenPartitionKey = candidatePartitionKey;
}
eqSets.add(valueEquivalence.get(candidatePartitionKey));
}
if (unfiltered) {
++unfilteredPartitionKeyCount;
}
}
// end for each table StmtTableScan in the collection
m_countOfIndependentlyPartitionedTables = eqSets.size() + unfilteredPartitionKeyCount;
//* enable to debug */ System.out.println("DEBUG: analyze4MPAccess found: " + m_countOfIndependentlyPartitionedTables + " = " + eqSets.size() + " + " + unfilteredPartitionKeyCount);
if (m_countOfIndependentlyPartitionedTables > 1) {
setJoinValid(false);
setJoinInvalidReason("This query is not plannable. " + "The planner cannot guarantee that all rows would be in a single partition.");
}
// on outer level. Not going to support this kind of join.
if (subqueryHasReceiveNode && hasPartitionedTableJoin) {
setJoinValid(false);
setJoinInvalidReason("This query is not plannable. It has a subquery which needs cross-partition access.");
}
if ((unfilteredPartitionKeyCount == 0) && (eqSets.size() == 1)) {
for (Set<AbstractExpression> partitioningValues : eqSets) {
for (AbstractExpression constExpr : partitioningValues) {
if (constExpr instanceof TupleValueExpression) {
continue;
}
VoltType valueType = tokenPartitionKey.getValueType();
addPartitioningExpression(tokenPartitionKey.getTableName() + '.' + tokenPartitionKey.getColumnName(), constExpr, valueType);
// Only need one constant value.
break;
}
}
}
}
use of org.voltdb.planner.parseinfo.StmtSubqueryScan in project voltdb by VoltDB.
the class ParsedUnionStmt method breakUpSetOpSubquery.
/**
* Break up UNION/INTERSECT (ALL) set ops into individual selects that are part
* of the IN/EXISTS subquery into multiple expressions for each set op child
* combined by the conjunction AND/OR expression.
* col IN ( queryA UNION queryB ) - > col IN (queryA) OR col IN (queryB)
* col IN ( queryA INTERSECTS queryB ) - > col IN (queryA) AND col IN (queryB)
* The EXCEPT set op is LEFT as is
* Also the ALL qualifier is dropped because IN/EXISTS expressions only
* need just one tuple in the results set
*
* @param subqueryExpr - IN/EXISTS expression with a possible SET OP subquery
* @return simplified expression
*/
protected static AbstractExpression breakUpSetOpSubquery(AbstractExpression expr) {
assert (expr != null);
SelectSubqueryExpression subqueryExpr = null;
if (expr.getExpressionType() == ExpressionType.COMPARE_EQUAL && expr.getRight() instanceof SelectSubqueryExpression) {
subqueryExpr = (SelectSubqueryExpression) expr.getRight();
} else if (expr.getExpressionType() == ExpressionType.OPERATOR_EXISTS && expr.getLeft() instanceof SelectSubqueryExpression) {
subqueryExpr = (SelectSubqueryExpression) expr.getLeft();
}
if (subqueryExpr == null) {
return expr;
}
AbstractParsedStmt subquery = subqueryExpr.getSubqueryStmt();
if (!(subquery instanceof ParsedUnionStmt)) {
return expr;
}
ParsedUnionStmt setOpStmt = (ParsedUnionStmt) subquery;
if (UnionType.EXCEPT == setOpStmt.m_unionType || UnionType.EXCEPT_ALL == setOpStmt.m_unionType) {
setOpStmt.m_unionType = UnionType.EXCEPT;
return expr;
}
if (UnionType.UNION_ALL == setOpStmt.m_unionType) {
setOpStmt.m_unionType = UnionType.UNION;
} else if (UnionType.INTERSECT_ALL == setOpStmt.m_unionType) {
setOpStmt.m_unionType = UnionType.INTERSECT;
}
ExpressionType conjuctionType = (setOpStmt.m_unionType == UnionType.UNION) ? ExpressionType.CONJUNCTION_OR : ExpressionType.CONJUNCTION_AND;
AbstractExpression retval = null;
AbstractParsedStmt parentStmt = subquery.m_parentStmt;
// It's a subquery which means it must have a parent
assert (parentStmt != null);
for (AbstractParsedStmt child : setOpStmt.m_children) {
// add table to the query cache
String withoutAlias = null;
StmtSubqueryScan tableCache = parentStmt.addSubqueryToStmtCache(child, withoutAlias);
AbstractExpression childSubqueryExpr = new SelectSubqueryExpression(subqueryExpr.getExpressionType(), tableCache);
AbstractExpression newExpr = null;
try {
newExpr = expr.getExpressionType().getExpressionClass().newInstance();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage(), e);
}
newExpr.setExpressionType(expr.getExpressionType());
if (ExpressionType.COMPARE_EQUAL == expr.getExpressionType()) {
newExpr.setLeft(expr.getLeft().clone());
newExpr.setRight(childSubqueryExpr);
assert (newExpr instanceof ComparisonExpression);
((ComparisonExpression) newExpr).setQuantifier(((ComparisonExpression) expr).getQuantifier());
} else {
newExpr.setLeft(childSubqueryExpr);
}
// Recurse
newExpr = ParsedUnionStmt.breakUpSetOpSubquery(newExpr);
if (retval == null) {
retval = newExpr;
} else {
retval = new ConjunctionExpression(conjuctionType, retval, newExpr);
}
}
return retval;
}
use of org.voltdb.planner.parseinfo.StmtSubqueryScan in project voltdb by VoltDB.
the class ParsedInsertStmt method parse.
@Override
void parse(VoltXMLElement stmtNode) {
// but those table scans will belong to the corresponding ParsedSelectStmt
assert (m_tableList.isEmpty());
String tableName = stmtNode.attributes.get("table");
// Need to add the table to the cache. It may be required to resolve the
// correlated TVE in case of WHERE clause contains IN subquery
Table table = getTableFromDB(tableName);
addTableToStmtCache(table, tableName);
m_tableList.add(table);
for (VoltXMLElement node : stmtNode.children) {
if (node.name.equals("columns")) {
parseTargetColumns(node, table, m_columns);
} else if (node.name.equals(SELECT_NODE_NAME)) {
m_subquery = new StmtSubqueryScan(parseSubquery(node), "__VOLT_INSERT_SUBQUERY__");
// Until scalar subqueries are allowed in INSERT ... VALUES statements,
// The top-level SELECT subquery in an INSERT ... SELECT statement
// is the only possible subselect in an INSERT statement.
m_scans.add(m_subquery);
} else if (node.name.equals(UNION_NODE_NAME)) {
throw new PlanningErrorException("INSERT INTO ... SELECT is not supported for UNION or other set operations.");
}
}
calculateContentDeterminismMessage();
}
Aggregations