/* This file is part of VoltDB.
* Copyright (C) 2008-2014 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.planner;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.voltdb.catalog.Column;
import org.voltdb.catalog.Database;
import org.voltdb.catalog.Table;
import org.voltdb.expressions.AbstractExpression;
import org.voltdb.expressions.TupleValueExpression;
import org.voltdb.planner.parseinfo.StmtSubqueryScan;
import org.voltdb.planner.parseinfo.StmtTableScan;
import org.voltdb.planner.parseinfo.StmtTargetTableScan;
import org.voltdb.plannodes.AbstractPlanNode;
import org.voltdb.plannodes.SchemaColumn;
import org.voltdb.types.PlanNodeType;
public class InsertSubPlanAssembler extends SubPlanAssembler {
private boolean m_bestAndOnlyPlanWasGenerated = false;
InsertSubPlanAssembler(Database db, AbstractParsedStmt parsedStmt,
StatementPartitioning partitioning) {
super(db, parsedStmt, partitioning);
}
@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.getSubqueries().get(0);
if (targetTable.getIsreplicated()) {
// must not be single-partition insert if targeting a replicated table
// 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()) {
// [assume that c1 is the partitioning column]
// INSERT INTO t1 (c1, c2, c3, ...)
// SELECT e1, e2, e3, ... FROM ...
//
// can be analyzed as if it was
//
// SELECT COUNT(*)
// FROM t1
// INNER JOIN
// (SELECT e1, e2, e3, ... FROM ...) AS insert_subquery
// ON t1.c1 = insert_subquery.e1;
//
// Build the corresponding data structures for analysis by StatementPartitioning.
if (subquery.getBestCostPlan().rootPlanGraph.hasAnyNodeOfType(PlanNodeType.SEND)) {
// 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;
}
List<StmtTableScan> tables = new ArrayList<>();
StmtTargetTableScan stmtTargetTableScan = new StmtTargetTableScan(targetTable, targetTable.getTypeName());
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;
Column partitioningCol = targetTable.getPartitioncolumn();
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;
}
public String getSqlType() {
if (m_parsedStmt.m_isUpsert) {
return "UPSERT";
}
return "INSERT";
}
}