use of org.voltdb.planner.parseinfo.StmtSubqueryScan in project voltdb by VoltDB.
the class PlanAssembler method getNextInsertPlan.
/**
* Get the next (only) plan for a SQL insertion. Inserts are pretty simple
* and this will only generate a single plan.
*
* @return The next (only) plan for a given insert statement, then null.
*/
private CompiledPlan getNextInsertPlan() {
// do it the right way once, then return null after that
if (m_bestAndOnlyPlanWasGenerated) {
return null;
}
m_bestAndOnlyPlanWasGenerated = true;
// figure out which table we're inserting into
assert (m_parsedInsert.m_tableList.size() == 1);
Table targetTable = m_parsedInsert.m_tableList.get(0);
StmtSubqueryScan subquery = m_parsedInsert.getSubqueryScan();
CompiledPlan retval = null;
String isContentDeterministic = null;
if (subquery != null) {
isContentDeterministic = subquery.calculateContentDeterminismMessage();
if (subquery.getBestCostPlan() == null) {
// in getBestCostPlan, above.
throw new PlanningErrorException("INSERT INTO ... SELECT subquery could not be planned: " + m_recentErrorMsg);
}
boolean targetIsExportTable = tableListIncludesExportOnly(m_parsedInsert.m_tableList);
InsertSubPlanAssembler subPlanAssembler = new InsertSubPlanAssembler(m_catalogDb, m_parsedInsert, m_partitioning, targetIsExportTable);
AbstractPlanNode subplan = subPlanAssembler.nextPlan();
if (subplan == null) {
throw new PlanningErrorException(subPlanAssembler.m_recentErrorMsg);
}
assert (m_partitioning.isJoinValid());
// Use the subquery's plan as the basis for the insert plan.
retval = subquery.getBestCostPlan();
} else {
retval = new CompiledPlan();
}
retval.setReadOnly(false);
// for the INSERT ... SELECT ... case, by analyzing the subquery.
if (m_parsedInsert.m_isUpsert) {
boolean hasPrimaryKey = false;
for (Constraint constraint : targetTable.getConstraints()) {
if (constraint.getType() != ConstraintType.PRIMARY_KEY.getValue()) {
continue;
}
hasPrimaryKey = true;
boolean targetsPrimaryKey = false;
for (ColumnRef colRef : constraint.getIndex().getColumns()) {
int primary = colRef.getColumn().getIndex();
for (Column targetCol : m_parsedInsert.m_columns.keySet()) {
if (targetCol.getIndex() == primary) {
targetsPrimaryKey = true;
break;
}
}
if (!targetsPrimaryKey) {
throw new PlanningErrorException("UPSERT on table \"" + targetTable.getTypeName() + "\" must specify a value for primary key \"" + colRef.getColumn().getTypeName() + "\".");
}
}
}
if (!hasPrimaryKey) {
throw new PlanningErrorException("UPSERT is not allowed on table \"" + targetTable.getTypeName() + "\" that has no primary key.");
}
}
CatalogMap<Column> targetTableColumns = targetTable.getColumns();
for (Column col : targetTableColumns) {
boolean needsValue = (!m_parsedInsert.m_isUpsert) && (col.getNullable() == false) && (col.getDefaulttype() == 0);
if (needsValue && !m_parsedInsert.m_columns.containsKey(col)) {
// This check could be done during parsing?
throw new PlanningErrorException("Column " + col.getName() + " has no default and is not nullable.");
}
// hint that this statement can be executed SP.
if (col.equals(m_partitioning.getPartitionColForDML()) && subquery == null) {
// When AdHoc insert-into-select is supported, we'll need to be able to infer
// partitioning of the sub-select
AbstractExpression expr = m_parsedInsert.getExpressionForPartitioning(col);
String fullColumnName = targetTable.getTypeName() + "." + col.getTypeName();
m_partitioning.addPartitioningExpression(fullColumnName, expr, expr.getValueType());
}
}
NodeSchema matSchema = null;
if (subquery == null) {
matSchema = new NodeSchema();
}
int[] fieldMap = new int[m_parsedInsert.m_columns.size()];
int i = 0;
// - For VALUES(...) insert statements, build the materialize node's schema
for (Map.Entry<Column, AbstractExpression> e : m_parsedInsert.m_columns.entrySet()) {
Column col = e.getKey();
fieldMap[i] = col.getIndex();
if (matSchema != null) {
AbstractExpression valExpr = e.getValue();
valExpr.setInBytes(col.getInbytes());
// Patch over any mismatched expressions with an explicit cast.
// Most impossible-to-cast type combinations should have already been caught by the
// parser, but there are also runtime checks in the casting code
// -- such as for out of range values.
valExpr = castExprIfNeeded(valExpr, col);
matSchema.addColumn(AbstractParsedStmt.TEMP_TABLE_NAME, AbstractParsedStmt.TEMP_TABLE_NAME, col.getTypeName(), col.getTypeName(), valExpr);
}
i++;
}
// the root of the insert plan may be an InsertPlanNode, or
// it may be a scan plan node. We may do an inline InsertPlanNode
// as well.
InsertPlanNode insertNode = new InsertPlanNode();
insertNode.setTargetTableName(targetTable.getTypeName());
if (subquery != null) {
insertNode.setSourceIsPartitioned(!subquery.getIsReplicated());
}
// The field map tells the insert node
// where to put values produced by child into the row to be inserted.
insertNode.setFieldMap(fieldMap);
AbstractPlanNode root = insertNode;
if (matSchema != null) {
MaterializePlanNode matNode = new MaterializePlanNode(matSchema);
// connect the insert and the materialize nodes together
insertNode.addAndLinkChild(matNode);
retval.statementGuaranteesDeterminism(false, true, isContentDeterministic);
} else {
ScanPlanNodeWithInlineInsert planNode = (retval.rootPlanGraph instanceof ScanPlanNodeWithInlineInsert) ? ((ScanPlanNodeWithInlineInsert) retval.rootPlanGraph) : null;
// Inline upsert might be possible, but not now.
if (planNode != null && (!m_parsedInsert.m_isUpsert) && (!planNode.hasInlineAggregateNode())) {
planNode.addInlinePlanNode(insertNode);
root = planNode.getAbstractNode();
} else {
// Otherwise just make it out-of-line.
insertNode.addAndLinkChild(retval.rootPlanGraph);
}
}
if (m_partitioning.wasSpecifiedAsSingle() || m_partitioning.isInferredSingle()) {
insertNode.setMultiPartition(false);
retval.rootPlanGraph = root;
return retval;
}
insertNode.setMultiPartition(true);
// Add a compensating sum of modified tuple counts or a limit 1
// AND a send on top of a union-like receive node.
boolean isReplicated = targetTable.getIsreplicated();
retval.rootPlanGraph = addCoordinatorToDMLNode(root, isReplicated);
return retval;
}
use of org.voltdb.planner.parseinfo.StmtSubqueryScan in project voltdb by VoltDB.
the class SelectSubPlanAssembler method getSelectSubPlanForJoinNode.
/**
* Given a specific join node and access path set for inner and outer tables, construct the plan
* that gives the right tuples.
*
* @param joinNode The join node to build the plan for.
* @param isInnerTable True if the join node is the inner node in the join
* @return A completed plan-sub-graph that should match the correct tuples from the
* correct tables.
*/
private AbstractPlanNode getSelectSubPlanForJoinNode(JoinNode joinNode) {
assert (joinNode != null);
if (joinNode instanceof BranchNode) {
BranchNode branchJoinNode = (BranchNode) joinNode;
// Outer node
AbstractPlanNode outerScanPlan = getSelectSubPlanForJoinNode(branchJoinNode.getLeftNode());
if (outerScanPlan == null) {
return null;
}
// Inner Node.
AbstractPlanNode innerScanPlan = getSelectSubPlanForJoinNode((branchJoinNode).getRightNode());
if (innerScanPlan == null) {
return null;
}
// Join Node
IndexSortablePlanNode answer = getSelectSubPlanForJoin(branchJoinNode, outerScanPlan, innerScanPlan);
// branch node is an inner join.
if ((answer != null) && (branchJoinNode.getJoinType() == JoinType.INNER) && outerScanPlan instanceof IndexSortablePlanNode) {
IndexUseForOrderBy indexUseForJoin = answer.indexUse();
IndexUseForOrderBy indexUseFromScan = ((IndexSortablePlanNode) outerScanPlan).indexUse();
indexUseForJoin.setWindowFunctionUsesIndex(indexUseFromScan.getWindowFunctionUsesIndex());
indexUseForJoin.setWindowFunctionIsCompatibleWithOrderBy(indexUseFromScan.isWindowFunctionCompatibleWithOrderBy());
indexUseForJoin.setFinalExpressionOrderFromIndexScan(indexUseFromScan.getFinalExpressionOrderFromIndexScan());
indexUseForJoin.setSortOrderFromIndexScan(indexUseFromScan.getSortOrderFromIndexScan());
}
if (answer == null) {
return null;
}
return answer.planNode();
}
// End of recursion
AbstractPlanNode scanNode = getAccessPlanForTable(joinNode);
// Connect the sub-query tree if any
if (joinNode instanceof SubqueryLeafNode) {
StmtSubqueryScan tableScan = ((SubqueryLeafNode) joinNode).getSubqueryScan();
CompiledPlan subQueryPlan = tableScan.getBestCostPlan();
assert (subQueryPlan != null);
assert (subQueryPlan.rootPlanGraph != null);
// The sub-query best cost plan needs to be un-linked from the previous parent plan
// it's the same child plan that gets re-attached to many parents one at a time
subQueryPlan.rootPlanGraph.disconnectParents();
scanNode.addAndLinkChild(subQueryPlan.rootPlanGraph);
}
return scanNode;
}
use of org.voltdb.planner.parseinfo.StmtSubqueryScan in project voltdb by VoltDB.
the class AbstractParsedStmt method parseTable.
/**
*
* @param tableNode
*/
private void parseTable(VoltXMLElement tableNode) {
String tableName = tableNode.attributes.get("table");
assert (tableName != null);
String tableAlias = tableNode.attributes.get("tablealias");
if (tableAlias == null) {
tableAlias = tableName;
}
// Hsql rejects name conflicts in a single query
m_tableAliasListAsJoinOrder.add(tableAlias);
VoltXMLElement subqueryElement = null;
// Possible sub-query
for (VoltXMLElement childNode : tableNode.children) {
if (!childNode.name.equals("tablesubquery")) {
continue;
}
if (childNode.children.isEmpty()) {
continue;
}
// sub-query FROM (SELECT ...)
subqueryElement = childNode.children.get(0);
break;
}
// add table to the query cache before processing the JOIN/WHERE expressions
// The order is important because processing sub-query expressions assumes that
// the sub-query is already registered
StmtTableScan tableScan = null;
Table table = null;
// In case of a subquery we need to preserve its filter expressions
AbstractExpression simplifiedSubqueryFilter = null;
if (subqueryElement == null) {
table = getTableFromDB(tableName);
assert (table != null);
tableScan = addTableToStmtCache(table, tableAlias);
m_tableList.add(table);
} else {
AbstractParsedStmt subquery = parseFromSubQuery(subqueryElement);
StmtSubqueryScan subqueryScan = addSubqueryToStmtCache(subquery, tableAlias);
tableScan = subqueryScan;
StmtTargetTableScan simpler = simplifierForSubquery(subquery);
if (simpler != null) {
tableScan = addSimplifiedSubqueryToStmtCache(subqueryScan, simpler);
table = simpler.getTargetTable();
// Extract subquery's filters
assert (subquery.m_joinTree != null);
// Adjust the table alias in all TVEs from the eliminated
// subquery expressions. Example:
// SELECT TA2.CA FROM (SELECT C CA FROM T TA1 WHERE C > 0) TA2
// The table alias TA1 from the original TVE (T)TA1.C from the
// subquery WHERE condition needs to be replaced with the alias
// TA2. The new TVE will be (T)TA2.C.
// The column alias does not require an adjustment.
simplifiedSubqueryFilter = subquery.m_joinTree.getAllFilters();
List<TupleValueExpression> tves = ExpressionUtil.getTupleValueExpressions(simplifiedSubqueryFilter);
for (TupleValueExpression tve : tves) {
tve.setTableAlias(tableScan.getTableAlias());
tve.setOrigStmtId(m_stmtId);
}
}
}
AbstractExpression joinExpr = parseJoinCondition(tableNode);
AbstractExpression whereExpr = parseWhereCondition(tableNode);
if (simplifiedSubqueryFilter != null) {
// Add subqueruy's expressions as JOIN filters to make sure they will
// stay at the node level in case of an OUTER joins and won't affect
// the join simplification process:
// select * from T LEFT JOIN (select C FROM T1 WHERE C > 2) S ON T.C = S.C;
joinExpr = (joinExpr != null) ? ExpressionUtil.combine(joinExpr, simplifiedSubqueryFilter) : simplifiedSubqueryFilter;
}
// The join type of the leaf node is always INNER
// For a new tree its node's ids start with 0 and keep incrementing by 1
int nodeId = (m_joinTree == null) ? 0 : m_joinTree.getId() + 1;
JoinNode leafNode;
if (table != null) {
assert (tableScan instanceof StmtTargetTableScan);
leafNode = new TableLeafNode(nodeId, joinExpr, whereExpr, (StmtTargetTableScan) tableScan);
} else {
assert (tableScan instanceof StmtSubqueryScan);
leafNode = new SubqueryLeafNode(nodeId, joinExpr, whereExpr, (StmtSubqueryScan) tableScan);
leafNode.updateContentDeterminismMessage(((StmtSubqueryScan) tableScan).calculateContentDeterminismMessage());
}
if (m_joinTree == null) {
// this is the first table
m_joinTree = leafNode;
} else {
// Build the tree by attaching the next table always to the right
// The node's join type is determined by the type of its right node
JoinType joinType = JoinType.get(tableNode.attributes.get("jointype"));
assert (joinType != JoinType.INVALID);
JoinNode joinNode = new BranchNode(nodeId + 1, joinType, m_joinTree, leafNode);
m_joinTree = joinNode;
}
}
use of org.voltdb.planner.parseinfo.StmtSubqueryScan in project voltdb by VoltDB.
the class AbstractParsedStmt method orderByColumnsCoverUniqueKeys.
/**
* Order by Columns or expressions has to operate on the display columns or expressions.
* @return
*/
protected boolean orderByColumnsCoverUniqueKeys() {
// In theory, if EVERY table in the query has a uniqueness constraint
// (primary key or other unique index) on columns that are all listed in the ORDER BY values,
// the result is deterministic.
// This holds regardless of whether the associated index is actually used in the selected plan,
// so this check is plan-independent.
//
// baseTableAliases associates table aliases with the order by
// expressions which reference them. Presumably by using
// table aliases we will map table scans to expressions rather
// than tables to expressions, and not confuse ourselves with
// different instances of the same table in self joins.
HashMap<String, List<AbstractExpression>> baseTableAliases = new HashMap<>();
for (ParsedColInfo col : orderByColumns()) {
AbstractExpression expr = col.expression;
//
// Compute the set of tables mentioned in the expression.
// 1. Search out all the TVEs.
// 2. Throw the aliases of the tables of each of these into a HashSet.
// The table must have an alias. It might not have a name.
// 3. If the HashSet has size > 1 we can't use this expression.
//
List<TupleValueExpression> baseTVEExpressions = expr.findAllTupleValueSubexpressions();
Set<String> baseTableNames = new HashSet<>();
for (TupleValueExpression tve : baseTVEExpressions) {
String tableAlias = tve.getTableAlias();
assert (tableAlias != null);
baseTableNames.add(tableAlias);
}
if (baseTableNames.size() != 1) {
// Neither are (nonsense) constant (table-less) expressions.
continue;
}
// Everything in the baseTVEExpressions table is a column
// in the same table and has the same alias. So just grab the first one.
// All we really want is the alias.
AbstractExpression baseTVE = baseTVEExpressions.get(0);
String nextTableAlias = ((TupleValueExpression) baseTVE).getTableAlias();
// and disappear.
assert (nextTableAlias != null);
List<AbstractExpression> perTable = baseTableAliases.get(nextTableAlias);
if (perTable == null) {
perTable = new ArrayList<>();
baseTableAliases.put(nextTableAlias, perTable);
}
perTable.add(expr);
}
if (m_tableAliasMap.size() > baseTableAliases.size()) {
// like Unique Index nested loop join, etc.
return false;
}
boolean allScansAreDeterministic = true;
for (Entry<String, List<AbstractExpression>> orderedAlias : baseTableAliases.entrySet()) {
List<AbstractExpression> orderedAliasExprs = orderedAlias.getValue();
StmtTableScan tableScan = getStmtTableScanByAlias(orderedAlias.getKey());
if (tableScan == null) {
assert (false);
return false;
}
if (tableScan instanceof StmtSubqueryScan) {
// don't yet handle FROM clause subquery, here.
return false;
}
Table table = ((StmtTargetTableScan) tableScan).getTargetTable();
// This table's scans need to be proven deterministic.
allScansAreDeterministic = false;
// Search indexes for one that makes the order by deterministic
for (Index index : table.getIndexes()) {
// skip non-unique indexes
if (!index.getUnique()) {
continue;
}
// get the list of expressions for the index
List<AbstractExpression> indexExpressions = new ArrayList<>();
String jsonExpr = index.getExpressionsjson();
// if this is a pure-column index...
if (jsonExpr.isEmpty()) {
for (ColumnRef cref : index.getColumns()) {
Column col = cref.getColumn();
TupleValueExpression tve = new TupleValueExpression(table.getTypeName(), orderedAlias.getKey(), col.getName(), col.getName(), col.getIndex());
indexExpressions.add(tve);
}
} else // if this is a fancy expression-based index...
{
try {
indexExpressions = AbstractExpression.fromJSONArrayString(jsonExpr, tableScan);
} catch (JSONException e) {
e.printStackTrace();
assert (false);
continue;
}
}
// ORDER BY A.unique_id, A.b_id
if (orderedAliasExprs.containsAll(indexExpressions)) {
allScansAreDeterministic = true;
break;
}
}
// ALL tables' scans need to have proved deterministic
if (!allScansAreDeterministic) {
return false;
}
}
return true;
}
use of org.voltdb.planner.parseinfo.StmtSubqueryScan in project voltdb by VoltDB.
the class AbstractParsedStmt method addSubqueryToStmtCache.
/**
* Add a sub-query to the statement cache.
* @param subquery
* @param tableAlias
* @return the cache entry
*/
protected StmtSubqueryScan addSubqueryToStmtCache(AbstractParsedStmt subquery, String tableAlias) {
assert (subquery != null);
// generate a unique one for internal use.
if (tableAlias == null) {
tableAlias = AbstractParsedStmt.TEMP_TABLE_NAME + "_" + subquery.m_stmtId;
}
StmtSubqueryScan subqueryScan = new StmtSubqueryScan(subquery, tableAlias, m_stmtId);
StmtTableScan prior = m_tableAliasMap.put(tableAlias, subqueryScan);
assert (prior == null);
return subqueryScan;
}
Aggregations