use of org.voltdb.catalog.CatalogMap 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.catalog.CatalogMap in project voltdb by VoltDB.
the class SwapTablesPlanNode method initializeSwapTablesPlanNode.
/**
* Fill out all of the serializable attributes of the node, validating
* its arguments' compatibility along the way to ensure successful
* execution.
* @param theTable the catalog definition of the 1st table swap argument
* @param otherTable the catalog definition of the 2nd table swap argument
* @throws PlannerErrorException if one or more compatibility validations fail
*/
public void initializeSwapTablesPlanNode(Table theTable, Table otherTable) {
String theName = theTable.getTypeName();
setTargetTableName(theName);
String otherName = otherTable.getTypeName();
m_otherTargetTableName = otherName;
FailureMessage failureMessage = new FailureMessage(theName, otherName);
validateTableCompatibility(theName, otherName, theTable, otherTable, failureMessage);
validateColumnCompatibility(theName, otherName, theTable, otherTable, failureMessage);
// Maintain sets of indexes and index-supported (UNIQUE) constraints
// and the primary key index found on otherTable.
// Removing them as they are matched by indexes/constraints on theTable
// and added to the list of swappable indexes should leave the sets empty.
HashSet<Index> otherIndexSet = new HashSet<>();
// The constraint set is actually a HashMap to retain the
// defining constraint name for help with error messages.
// Track the primary key separately since it should match one-to-one.
HashMap<Index, String> otherConstraintIndexMap = new HashMap<>();
Index otherPrimaryKeyIndex = null;
// Collect the system-defined (internal) indexes supporting constraints
// and the primary key index if any.
CatalogMap<Constraint> candidateConstraints = otherTable.getConstraints();
for (Constraint otherConstraint : candidateConstraints) {
Index otherIndex = otherConstraint.getIndex();
if (otherIndex == null) {
// effect on the swap table plan.
continue;
}
// Set aside the one primary key index for special handling.
if (otherConstraint.getType() == ConstraintType.PRIMARY_KEY.getValue()) {
otherPrimaryKeyIndex = otherIndex;
continue;
}
otherConstraintIndexMap.put(otherIndex, otherConstraint.getTypeName());
}
// Collect the user-defined (external) indexes on otherTable. The indexes
// in this set are removed as corresponding matches are found.
// System-generated indexes that support constraints are checked separately,
// so don't add them to this set.
CatalogMap<Index> candidateIndexes = otherTable.getIndexes();
for (Index otherIndex : candidateIndexes) {
if (otherIndex != otherPrimaryKeyIndex && !otherConstraintIndexMap.containsKey(otherIndex)) {
otherIndexSet.add(otherIndex);
}
}
// Collect the indexes that support constraints on theTable
HashSet<Index> theConstraintIndexSet = new HashSet<>();
Index thePrimaryKeyIndex = null;
for (Constraint constraint : theTable.getConstraints()) {
Index theIndex = constraint.getIndex();
if (theIndex == null) {
continue;
}
if (constraint.getType() == ConstraintType.PRIMARY_KEY.getValue()) {
thePrimaryKeyIndex = theIndex;
continue;
}
theConstraintIndexSet.add(constraint.getIndex());
}
// make sure the indexes are swappable.
if (thePrimaryKeyIndex != null && otherPrimaryKeyIndex != null) {
if (indexesCanBeSwapped(thePrimaryKeyIndex, otherPrimaryKeyIndex)) {
m_theIndexes.add(thePrimaryKeyIndex.getTypeName());
m_otherIndexes.add(otherPrimaryKeyIndex.getTypeName());
} else {
failureMessage.addReason("PRIMARY KEY constraints do not match on both tables");
}
} else if ((thePrimaryKeyIndex != null && otherPrimaryKeyIndex == null) || (thePrimaryKeyIndex == null && otherPrimaryKeyIndex != null)) {
failureMessage.addReason("one table has a PRIMARY KEY constraint and the other does not");
}
// Try to cross-reference each user-defined index on the two tables.
for (Index theIndex : theTable.getIndexes()) {
if (theConstraintIndexSet.contains(theIndex) || theIndex == thePrimaryKeyIndex) {
// Constraints are checked below.
continue;
}
boolean matched = false;
for (Index otherIndex : otherIndexSet) {
if (indexesCanBeSwapped(theIndex, otherIndex)) {
m_theIndexes.add(theIndex.getTypeName());
m_otherIndexes.add(otherIndex.getTypeName());
otherIndexSet.remove(otherIndex);
matched = true;
break;
}
}
if (matched) {
continue;
}
// No match: look for a likely near-match based on naming
// convention for the most helpful error message.
// Otherwise, give a more generic error message.
String theIndexName = theIndex.getTypeName();
String message = "the index " + theIndexName + " on table " + theName + " has no corresponding index in the other table";
String otherIndexName = theIndexName.replace(theName, otherName);
Index otherIndex = candidateIndexes.getIgnoreCase(otherIndexName);
if (otherIndex != null) {
message += "; the closest candidate (" + otherIndexName + ") has mismatches in the following attributes: " + String.join(", ", diagnoseIndexMismatch(theIndex, otherIndex));
}
failureMessage.addReason(message);
}
// matched along the way.
if (!otherIndexSet.isEmpty()) {
List<String> indexNames = otherIndexSet.stream().map(idx -> idx.getTypeName()).collect(Collectors.toList());
failureMessage.addReason("the table " + otherName + " contains these index(es) " + "which have no corresponding indexes on " + theName + ": " + "(" + String.join(", ", indexNames) + ")");
}
// constraints on the two tables.
for (Constraint theConstraint : theTable.getConstraints()) {
Index theIndex = theConstraint.getIndex();
if (theIndex == null) {
// effect on the swap table plan.
continue;
}
if (theConstraint.getType() == ConstraintType.PRIMARY_KEY.getValue()) {
// Primary key compatibility checked above.
continue;
}
boolean matched = false;
for (Entry<Index, String> otherEntry : otherConstraintIndexMap.entrySet()) {
Index otherIndex = otherEntry.getKey();
if (indexesCanBeSwapped(theIndex, otherIndex)) {
m_theIndexes.add(theIndex.getTypeName());
m_otherIndexes.add(otherIndex.getTypeName());
otherConstraintIndexMap.remove(otherIndex);
matched = true;
break;
}
}
if (matched) {
continue;
}
String theConstraintName = theConstraint.getTypeName();
failureMessage.addReason("the constraint " + theConstraintName + " on table " + theName + " " + "has no corresponding constraint on the other table");
}
// matched along the way.
if (!otherConstraintIndexMap.isEmpty()) {
StringBuilder sb = new StringBuilder();
sb.append("these constraints (or system internal index names) on table " + otherName + " " + "have no corresponding constraints on the other table: (");
String separator = "";
for (Entry<Index, String> remainder : otherConstraintIndexMap.entrySet()) {
String constraintName = remainder.getValue();
String description = (constraintName != null && !constraintName.equals("")) ? constraintName : ("<anonymous with system internal index name: " + remainder.getKey().getTypeName() + ">");
sb.append(separator).append(description);
separator = ", ";
}
sb.append(")");
failureMessage.addReason(sb.toString());
}
if (failureMessage.numFailures() > 0) {
throw new PlanningErrorException(failureMessage.getMessage());
}
}
Aggregations