Search in sources :

Example 1 with CatalogMap

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;
}
Also used : StmtSubqueryScan(org.voltdb.planner.parseinfo.StmtSubqueryScan) AbstractPlanNode(org.voltdb.plannodes.AbstractPlanNode) Table(org.voltdb.catalog.Table) Constraint(org.voltdb.catalog.Constraint) InsertPlanNode(org.voltdb.plannodes.InsertPlanNode) MaterializePlanNode(org.voltdb.plannodes.MaterializePlanNode) Constraint(org.voltdb.catalog.Constraint) AbstractExpression(org.voltdb.expressions.AbstractExpression) Column(org.voltdb.catalog.Column) SchemaColumn(org.voltdb.plannodes.SchemaColumn) ColumnRef(org.voltdb.catalog.ColumnRef) Map(java.util.Map) CatalogMap(org.voltdb.catalog.CatalogMap) HashMap(java.util.HashMap) NodeSchema(org.voltdb.plannodes.NodeSchema)

Example 2 with CatalogMap

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());
    }
}
Also used : Iterator(java.util.Iterator) ConstraintType(org.voltdb.types.ConstraintType) ColumnRef(org.voltdb.catalog.ColumnRef) MaterializedViewHandlerInfo(org.voltdb.catalog.MaterializedViewHandlerInfo) Table(org.voltdb.catalog.Table) HashMap(java.util.HashMap) Column(org.voltdb.catalog.Column) PlanningErrorException(org.voltdb.planner.PlanningErrorException) Collectors(java.util.stream.Collectors) Constraint(org.voltdb.catalog.Constraint) JSONException(org.json_voltpatches.JSONException) ArrayList(java.util.ArrayList) HashSet(java.util.HashSet) JSONStringer(org.json_voltpatches.JSONStringer) Index(org.voltdb.catalog.Index) List(java.util.List) Entry(java.util.Map.Entry) JSONObject(org.json_voltpatches.JSONObject) Database(org.voltdb.catalog.Database) CatalogMap(org.voltdb.catalog.CatalogMap) PlanNodeType(org.voltdb.types.PlanNodeType) MaterializedViewInfo(org.voltdb.catalog.MaterializedViewInfo) PlanningErrorException(org.voltdb.planner.PlanningErrorException) HashMap(java.util.HashMap) Constraint(org.voltdb.catalog.Constraint) Index(org.voltdb.catalog.Index) HashSet(java.util.HashSet)

Aggregations

HashMap (java.util.HashMap)2 CatalogMap (org.voltdb.catalog.CatalogMap)2 Column (org.voltdb.catalog.Column)2 ColumnRef (org.voltdb.catalog.ColumnRef)2 Constraint (org.voltdb.catalog.Constraint)2 Table (org.voltdb.catalog.Table)2 ArrayList (java.util.ArrayList)1 HashSet (java.util.HashSet)1 Iterator (java.util.Iterator)1 List (java.util.List)1 Map (java.util.Map)1 Entry (java.util.Map.Entry)1 Collectors (java.util.stream.Collectors)1 JSONException (org.json_voltpatches.JSONException)1 JSONObject (org.json_voltpatches.JSONObject)1 JSONStringer (org.json_voltpatches.JSONStringer)1 Database (org.voltdb.catalog.Database)1 Index (org.voltdb.catalog.Index)1 MaterializedViewHandlerInfo (org.voltdb.catalog.MaterializedViewHandlerInfo)1 MaterializedViewInfo (org.voltdb.catalog.MaterializedViewInfo)1