| /********************************************************************** |
| // @@@ START COPYRIGHT @@@ |
| // |
| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| // |
| // @@@ END COPYRIGHT @@@ |
| **********************************************************************/ |
| /* -*-C++-*- |
| ****************************************************************************** |
| * |
| * File: OptTrigger.cpp |
| * Description: Implementation of after trigger reorder & transformation |
| * |
| * |
| * Created: 06/25/98 |
| * Language: C++ |
| * |
| * |
| * |
| * |
| * |
| *******************************************************************************/ |
| //----------------------------------------------------------------------------- |
| // |
| // Triggers internal spec describes the input tree to OptTriggersBackbone. |
| // |
| //----------------------------------------------------------------------------- |
| #include <stdlib.h> |
| #include <search.h> |
| |
| #include "GroupAttr.h" |
| #include "AllItemExpr.h" |
| #include "AllRelExpr.h" |
| #include "Triggers.h" |
| #include "OptTrigger.h" |
| #include "BindWA.h" |
| #include "ItemSample.h" |
| #include "ChangesTable.h" |
| #include "NormWA.h" |
| |
| //----------------------------------------------------------------------------------- |
| // Static utility methods (part of OptTriggersBackbone class) |
| //----------------------------------------------------------------------------------- |
| |
| //----------------------------------------------------------------------------------- |
| // |
| // |
| // SetUnionCharacteristicInputs |
| // |
| // Calculate union Characteristic Inputs based on both children. |
| // The parent input is the aggregate input of the children after reducing the |
| // duplicates. |
| // |
| //----------------------------------------------------------------------------------- |
| |
| void |
| OptTriggersBackbone::SetUnionCharacteristicInputs(RelExpr *pUnion) |
| { |
| ValueIdSet tmpInput = ((RelExpr *)pUnion->getChild(1))->getGroupAttr()->getCharacteristicInputs(); |
| ValueIdSet tmpInputLeft = ((RelExpr *)pUnion->getChild(0))->getGroupAttr()->getCharacteristicInputs(); |
| |
| // reduce duplicates between left and right |
| tmpInput.removeCoveredExprs(tmpInputLeft); |
| tmpInputLeft.removeCoveredExprs(tmpInput); |
| |
| // add left |
| tmpInput += tmpInputLeft; |
| |
| pUnion->getGroupAttr()->setCharacteristicInputs(tmpInput); |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // |
| // |
| // SetTsjCharacteristicInputs |
| // |
| // Calculate TSJ Characteristic Inputs based on its children. |
| // The parent input is the aggregate input of the children after reducing the |
| // duplicates and the output of the left child, which is the input of the right |
| // child. |
| // |
| //----------------------------------------------------------------------------------- |
| |
| void |
| OptTriggersBackbone::SetTsjCharacteristicInputs(RelExpr *pTsj) |
| { |
| // add left |
| ValueIdSet tmpInput = ((RelExpr *)pTsj->getChild(0))->getGroupAttr()->getCharacteristicInputs(); |
| ValueIdSet rightTopInput = ((RelExpr *)pTsj->getChild(1))->getGroupAttr()->getCharacteristicInputs(); |
| |
| // remove the input that are covered by the tsj left child outputs |
| rightTopInput.removeCoveredExprs(((RelExpr *)pTsj->getChild(0))->getGroupAttr()->getCharacteristicOutputs()); |
| // reduce duplicates |
| rightTopInput.removeCoveredExprs(tmpInput); |
| tmpInput.removeCoveredExprs(rightTopInput); |
| |
| // add right |
| tmpInput += rightTopInput; |
| pTsj->getGroupAttr()->setCharacteristicInputs(tmpInput); |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // This method mainly deals with how to properly handle the UniqueExecuteId, |
| // without using the same BindWA used for binding the rest of the tree: |
| // We want the UniqueExecuteId function to have the same ValueId as in the rest of |
| // the tree. To achieve this, we create a RETDesc in the current scope of the new |
| // BindWA, and add to it the ValueId used for the UniqueExecId (saved in the |
| // BindInfo), and give it a name as if it was a "virtual" column. The built sub-trees |
| // will use a ColReference with this name, and when bound, will find this ValueId in |
| // an upper scope, and correctly mark the ValueId as an input. |
| //----------------------------------------------------------------------------------- |
| |
| void |
| OptTriggersBackbone::PrepareNewBindWA(BindWA &bindWA, TriggerBindInfo *bindInfo) |
| { |
| // Set the IudId from this backbone into the BindWA. |
| bindWA.setUniqueIudNum(bindInfo->getBackboneIudNum()); |
| |
| RETDesc *retDesc = new(CmpCommon::statementHeap()) RETDesc(&bindWA); |
| |
| // Add the existing ExecId to the current RETDesc. |
| ColRefName *execIdName = new(CmpCommon::statementHeap()) |
| ColRefName(InliningInfo::getExecIdVirtualColName()); |
| ValueId topExecId = bindInfo->getExecuteId()->getValueId(); |
| retDesc->addColumn(&bindWA, *execIdName, topExecId); |
| |
| bindWA.getCurrentScope()->setRETDesc(retDesc); |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // Complete the necessary compilation steps: Bind, Transform and Normalize. |
| //----------------------------------------------------------------------------------- |
| |
| RelExpr * |
| OptTriggersBackbone::BindAndNormalizeNewlyConstructedTree(BindWA *bindWA, RelExpr *topNode) |
| { |
| // Bind the tree. |
| topNode = topNode->bindNode(bindWA); |
| CMPASSERT(topNode != NULL && !bindWA->errStatus()); |
| |
| // Create a new NormWA for the normalization.. |
| NormWA normWA(CmpCommon::context()); |
| normWA.allocateAndSetVEGRegion(IMPORT_AND_EXPORT, topNode); |
| ExprGroupId eg(topNode); |
| topNode->transformNode(normWA, eg); |
| topNode = eg.getPtr(); |
| // The rewrite phase has to be invoked explicitly, instead of by the |
| // RelRoot at the top of the tree. |
| topNode->rewriteNode(normWA); |
| CMPASSERT (topNode != NULL); |
| topNode = topNode->normalizeNode(normWA); |
| CMPASSERT (topNode != NULL); |
| |
| return topNode; |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // Methods For Class OptTrigger |
| //----------------------------------------------------------------------------------- |
| |
| //----------------------------------------------------------------------------------- |
| // |
| // The ctor of OptTrigger |
| // |
| //----------------------------------------------------------------------------------- |
| |
| OptTrigger::OptTrigger(SubTreeAccessSet *treeAccessSet, |
| NABoolean isRowTrigger, |
| RelExpr *triggerSubTree, |
| RelExpr *parentSubTree) |
| : treeAccessSet_(treeAccessSet), |
| isRowTrigger_(isRowTrigger), |
| triggerSubtree_(triggerSubTree), |
| triggerParentSubtree_(parentSubTree) |
| { |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // Methods For Class OptTriggerList |
| //----------------------------------------------------------------------------------- |
| |
| //----------------------------------------------------------------------------------- |
| // |
| // createUnionSubTree |
| // |
| // Build left union tree from the trigger list (called by toTree) |
| // |
| //----------------------------------------------------------------------------------- |
| |
| RelExpr * |
| OptTriggerList::createUnionSubTree() const |
| { |
| // Put the first trigger in the group in previous sub tree |
| OptTriggerPtr optTrigger = (*this)[0]; |
| RelExpr *pPrev = (RelExpr *)optTrigger->getTriggerSubTree(); |
| |
| for (CollIndex i = 1; i < entries(); i++) |
| { |
| // Create a union on top of the previous sub tree and the current trigger |
| // If getTriggerParentSubTree() exists it can be reused as the union |
| optTrigger = (*this)[i]; |
| Union *pUnion = (Union *)optTrigger->getTriggerParentSubTree(); |
| if (pUnion == NULL){ |
| pUnion = new(CmpCommon::statementHeap()) |
| Union(pPrev, (RelExpr *)optTrigger->getTriggerSubTree(),NULL,NULL, |
| REL_UNION,CmpCommon::statementHeap(),TRUE); |
| } |
| else{ |
| // reuse |
| pUnion->setChild(0,(ExprNode *)pPrev); |
| pUnion->setChild(1,(ExprNode *)optTrigger->getTriggerSubTree()); |
| } |
| |
| pUnion->setGroupAttr(new(CmpCommon::statementHeap()) GroupAttributes()); |
| // set characteristic input based on children |
| OptTriggersBackbone::SetUnionCharacteristicInputs(pUnion); |
| |
| pUnion->synthLogProp(); |
| |
| pPrev = pUnion; |
| } |
| |
| return pPrev; |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // Methods For Class OptTriggerGroup |
| //----------------------------------------------------------------------------------- |
| |
| //----------------------------------------------------------------------------------- |
| // |
| // The ctor of OptTriggerGroup |
| // |
| //----------------------------------------------------------------------------------- |
| |
| OptTriggerGroup::OptTriggerGroup(TriggerGroupFlags flag, |
| OptTriggersBackbone *backbone) |
| : groupAccessSet_(CmpCommon::statementHeap()), |
| Flags_(flag), |
| backbone_(backbone) |
| { |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // |
| // isConflicting |
| // |
| // Check if the given access set conflict with the group access set |
| // |
| //----------------------------------------------------------------------------------- |
| |
| NABoolean |
| OptTriggerGroup::isConflicting(const SubTreeAccessSet *accessSet) const |
| { |
| //If the group is empty or the access set is null return FALSE - no conflict |
| if (entries() == 0 || accessSet == NULL) |
| return FALSE; |
| |
| |
| //check if the given accessSet conflict with the group |
| return (groupAccessSet_.isConflicting(accessSet)); |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // addTrigger |
| // |
| // add trigger to the group and trigger access set to group access set |
| // |
| //----------------------------------------------------------------------------------- |
| |
| void |
| OptTriggerGroup::addTrigger(const OptTriggerPtr trigger) |
| { |
| if (trigger->isRowTrigger()) |
| rowTriggerGroup_.insert(trigger); |
| else |
| statmentTriggerGroup_.insert(trigger); |
| |
| groupAccessSet_.add(trigger->getAccessSet()); |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // |
| // toTree |
| // |
| // Write group sub tree |
| // |
| // Group can be a cobmination of row triggers and statment triggers. |
| // |
| // All row triggers will be organized in one left union tree. |
| // If it is a PIPELINED_ROW_TRIGGERS, then this tree is the group tree |
| // representation. |
| // If is a BLOCKED_GROUP then the tree has additional temp scan with TSJ feeding |
| // the row trigger tree. |
| // All statement triggers will be organized in another left union tree. |
| // If the group has both row and statement triggers then the two sub trees will have |
| // additional union on top of them. |
| // |
| //----------------------------------------------------------------------------------- |
| |
| RelExpr * |
| OptTriggerGroup::toTree() |
| { |
| RelExpr *pRowSubTree = NULL; |
| RelExpr *pStatmentUnion = NULL; |
| |
| // Create row triggers sub-tree |
| if (rowTriggerGroup_.entries()) |
| { |
| // Create row trigger sub-tree |
| pRowSubTree = createRowTriggersSubTree(); |
| |
| // In pipelined group return the sub tree as is and it will be connected |
| // to the flow coming from the IUD. |
| if (isPipeLinedRowTriggesGroup()) |
| { |
| return(pRowSubTree); |
| } |
| } |
| |
| // Create statment triggers left union sub-tree |
| if (statmentTriggerGroup_.entries()) |
| { |
| pStatmentUnion = statmentTriggerGroup_.createUnionSubTree(); |
| } |
| |
| // If there is no mix of statement triggers and row trigger return |
| if (pStatmentUnion == NULL) |
| return pRowSubTree; |
| if (pRowSubTree == NULL) |
| return pStatmentUnion; |
| |
| // Connect row trigger sub-tree and statement trigger sub-tree with union |
| RelExpr *pUnion = new(CmpCommon::statementHeap()) Union |
| (pRowSubTree, pStatmentUnion, NULL, NULL, REL_UNION, |
| CmpCommon::statementHeap(),TRUE); |
| |
| pUnion->setGroupAttr(new(CmpCommon::statementHeap()) GroupAttributes()); |
| |
| // Set characteristic input based on children, triggers have no outputs |
| OptTriggersBackbone::SetUnionCharacteristicInputs(pUnion); |
| |
| pUnion->synthLogProp(); |
| return pUnion; |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // |
| // createRowTriggersSubTree |
| // |
| // This method returns the built sub-tree of all row-triggers in the group along with |
| // the generated feeding temp-table sub-tree. |
| // |
| //----------------------------------------------------------------------------------- |
| |
| RelExpr * |
| OptTriggerGroup::createRowTriggersSubTree() |
| { |
| // Create row trigger left union tree |
| RelExpr *pRowUnion = rowTriggerGroup_.createUnionSubTree(); |
| |
| if (isPipeLinedRowTriggesGroup()) |
| { |
| // In pipelined group, there's no need to connect to the feeding sub-tree. |
| return(pRowUnion); |
| } |
| |
| // Generate the temporary table scan to feed the triggers |
| reqOutput_ = pRowUnion->getGroupAttr()->getCharacteristicInputs(); |
| RelExpr *pLeft = buildTempScanTree(); |
| |
| // Create TSJ between the generated temp-scan and the triggers' sub-tree |
| RelExpr *pTsj = new(CmpCommon::statementHeap()) Join(pLeft, pRowUnion, REL_TSJ); |
| pTsj->setGroupAttr(new(CmpCommon::statementHeap()) GroupAttributes()); |
| |
| OptTriggersBackbone::SetTsjCharacteristicInputs(pTsj); |
| |
| // disable parallele execution for TSJs that control row triggers |
| // execution. Parallel execution for triggers TSJ introduces the |
| // potential for non-deterministic execution |
| pTsj->getInliningInfo().setFlags(II_SingleExecutionForTriggersTSJ); |
| |
| pTsj->synthLogProp(); |
| |
| return pTsj; |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // |
| // buildTempScanTree |
| // |
| // Build the subtree with temp Scan nodes, and connect it to the triggers using |
| // a MapValueIds node. |
| // This tree looks like this one of these three options: |
| // |
| // MapValueIds MapValueIds MapValueIds |
| // | | | |
| // RelRoot RelRoot RelRoot |
| // | | | |
| // Join Temp Scan Temp Scan |
| // / \ @OLD @NEW |
| // Temp Scan Temp Scan |
| // @OLD @NEW |
| // |
| // The first is used only for Update operations when the triggers in this |
| // group actually use both the OLD and the NEW values. |
| // |
| //----------------------------------------------------------------------------------- |
| RelExpr * |
| OptTriggerGroup::buildTempScanTree() |
| { |
| // Create a new BindWA - we don't want to have anything left in some RETDesc |
| // that may affect this binding. |
| BindWA bindWA(ActiveSchemaDB(), CmpCommon::context()); |
| TriggerBindInfo *bindInfo = backbone_->getTriggerBindInfo(); |
| |
| // Prepare the new BindWA for binding our little sub-tree. |
| OptTriggersBackbone::PrepareNewBindWA(bindWA, bindInfo); |
| |
| // Analyze the mappings between the ValuesIds required by the triggers, and |
| // the OLD and NEW values that will be produced by the temp Scans. |
| OptColumnMapping colMapping(bindInfo, |
| backbone_->getTriggeringNode()->getOperatorType(), |
| CmpCommon::statementHeap()); |
| colMapping.initializeMappings(reqOutput_); |
| |
| // Construct the temp Scan node, or Join of two Scans.. |
| RelExpr *topNode = buildTempTableScanNodes(colMapping, &bindWA); |
| |
| // Put a RelRoot node on top of it, with a select list of just the minimal |
| // columns required by the triggers. |
| ItemExpr *selectList = colMapping.buildSelectListFromNeededInputs(); |
| topNode = new(CmpCommon::statementHeap()) |
| RelRoot(topNode, REL_ROOT, selectList); |
| |
| // Do the necessary compilation steps. |
| topNode = OptTriggersBackbone::BindAndNormalizeNewlyConstructedTree(&bindWA, topNode); |
| |
| // Now that we know the ValueIds produced by the Scans, build the |
| // MapValueIds node on top of the tree, so the triggers will get the |
| // ValueIds they are expecting. |
| topNode = colMapping.buildMapValueIdNode(topNode); |
| |
| return topNode; |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // If both NEW and OLD values are needed for an Update trigger, create a Join of |
| // two temp Scan nodes. Otherwise - create just the tempScan node that is needed. |
| //----------------------------------------------------------------------------------- |
| |
| RelExpr * |
| OptTriggerGroup::buildTempTableScanNodes(OptColumnMapping& colMapping, |
| BindWA *bindWA) |
| { |
| // Create a temp table object (derived from ChangesTable). |
| TriggersTempTable tempTableObj(backbone_->getTriggeringNode(), bindWA); |
| |
| // Make the temp table object use the UniqueExecuteID we already have. |
| tempTableObj.setBoundExecId(backbone_->createNewExecIdRef()); |
| |
| if (colMapping.isNewNeeded() && colMapping.isOldNeeded()) |
| { |
| return tempTableObj.buildOldAndNewJoin(); |
| } |
| |
| if (colMapping.isNewNeeded()) |
| { |
| NAString newCorrName(NEWCorr); |
| RelExpr *newSubTree = NULL; |
| |
| // The NEW values are needed, so create a Scan node on the inserted rows |
| // and call it @NEW. |
| tempTableObj.addCorrelationName(newCorrName); |
| newSubTree = tempTableObj.buildScan(ChangesTable::INSERTED_ROWS); |
| backbone_->forceCardinalityAsIud(newSubTree); |
| CMPASSERT(!colMapping.isOldNeeded()) |
| return newSubTree; |
| } |
| else |
| { |
| NAString oldCorrName(OLDCorr); |
| RelExpr *oldSubTree = NULL; |
| |
| CMPASSERT(colMapping.isOldNeeded()) |
| // The OLD values are needed, so create a Scan node on the deleted rows |
| // and call it @OLD. |
| tempTableObj.addCorrelationName(oldCorrName); |
| oldSubTree = tempTableObj.buildScan(ChangesTable::DELETED_ROWS); |
| backbone_->forceCardinalityAsIud(oldSubTree); |
| CMPASSERT(!colMapping.isNewNeeded()) |
| return oldSubTree; |
| } |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // Methods For Class OptTriggersBackbone |
| //----------------------------------------------------------------------------------- |
| |
| //----------------------------------------------------------------------------------- |
| // |
| // |
| // ~OptTriggersBackbone |
| // |
| //----------------------------------------------------------------------------------- |
| |
| OptTriggersBackbone::~OptTriggersBackbone() |
| { |
| if (triggerList_) |
| { |
| for (CollIndex i = 1; i < triggerList_->entries(); i++) |
| { |
| OptTriggerPtr optTrigger = (*triggerList_)[i]; |
| // exclude coverage - we don't seem to handle macro expansion very well |
| NADELETEBASIC(optTrigger, CmpCommon::statementHeap()); // LCOV_EXCL_LINE |
| } |
| NADELETEBASIC(triggerList_, CmpCommon::statementHeap()); // LCOV_EXCL_LINE |
| } |
| |
| if (triggerGroups_) |
| { |
| for (CollIndex i = 1; i < triggerGroups_->entries(); i++) |
| { |
| OptTriggerGroup *optGroup = (*triggerGroups_)[i]; |
| NADELETE(optGroup, OptTriggerGroup, (CmpCommon::statementHeap())); // LCOV_EXCL_LINE |
| } |
| NADELETEBASIC(triggerGroups_, CmpCommon::statementHeap()); // LCOV_EXCL_LINE |
| } |
| |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // |
| // |
| // init |
| // |
| // this method scans the tree, and tags the key nodes in it for further process. |
| // |
| //----------------------------------------------------------------------------------- |
| |
| void OptTriggersBackbone::init() |
| { |
| BindWA *bindWA = (drivingBackbone_->getRETDesc())->getBindWA(); |
| |
| pNext_ = drivingBackbone_; |
| |
| initBeforeTriggerDrivingNode(); |
| initTempDeleteDrivingNodeAndAdvance(); |
| initStatmentTriggerDrivingNodeAndAdvance(); |
| initPipeLineDrivingNode(); |
| if (pNext_->getInliningInfo().isDrivingPipelinedActions()) |
| { |
| initRowTriggerDrivingNode(); |
| initRiDrivingNode(); |
| advanceInTree(); |
| } |
| |
| if (bindWA->getHostArraysArea() && !bindWA->getHostArraysArea()->getTolerateNonFatalError()) |
| { |
| // If there are no before triggers, temp insert must exist |
| CMPASSERT(beforeTriggersExist_ || pNext_->getInliningInfo().isDrivingTempInsert()); |
| } |
| |
| if (pNext_->getInliningInfo().isDrivingTempInsert()) |
| { |
| tempInsertDrivingNodeParent_ = pParent_; |
| tempInsertDrivingNode_ = pNext_; |
| CMPASSERT(tempInsertDrivingNode_->getOperator().match(REL_ANY_TSJ)) |
| advanceInTree(); |
| } |
| |
| // Initialize iudDrivingNode_, that includes also IM if exists. |
| iudDrivingNodeParent_ = pParent_; |
| iudDrivingNode_ = pNext_; |
| |
| // Initialize the triggering IUD node |
| iudNode_ = findLeftmostGU(iudDrivingNode_); |
| CMPASSERT(iudNode_->getOperator().match(REL_ANY_GEN_UPDATE)) |
| |
| // Initialize the triggerBindInfo_ member |
| triggerBindInfo_ = iudNode_->getInliningInfo().getTriggerBindInfo(); |
| CMPASSERT(triggerBindInfo_ != NULL) |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // |
| // |
| // getTransformedTree |
| // |
| // transform the tree and return the upper node |
| // the upper node might change if the temp delete have been removed |
| // |
| //----------------------------------------------------------------------------------- |
| |
| RelExpr * |
| OptTriggersBackbone::getTransformedTree() |
| { |
| RelExpr *backboneRoot = |
| (hasBeforeTriggers() ? beforeTriggerDrivingNode_ : tempDeleteDrivingNode_); |
| |
| // |
| // If there are NO conflicts in the access set (of IUD, IM, RI and after triggers) |
| // Do minimal transformation, end exit |
| // |
| |
| if (!hasConflicts()) |
| { |
| // if there are no before triggers remove temp insert and temp delete |
| if (!hasBeforeTriggers()) |
| { |
| removeTempInsert(); |
| backboneRoot = removeTempDelete(); |
| } |
| |
| if (pipeLineDrivingNode_) |
| { |
| // pipelined action sub tree is created in a fixed form with dummy actions |
| // replacing non-exist subtrees like RI or Row Triggers. Dummy actions |
| // need to be cleaned. |
| RelExpr *res = cleanDummies((RelExpr *)pipeLineDrivingNode_->getChild(1)); |
| |
| // if there are no pipelined action, eliminate the pipeLineDrivingNode_ |
| if (res == NULL){ |
| // remove pipline actions |
| pipeLineDrivingNodeParent_->setChild(0, pipeLineDrivingNode_->getChild(0)); |
| pipeLineDrivingNodeParent_->getGroupAttr()->clearLogProperties(); |
| pipeLineDrivingNodeParent_->synthLogProp(); |
| } |
| else if (res != (RelExpr *)pipeLineDrivingNode_->getChild(1)) |
| { |
| pipeLineDrivingNode_->setChild(1, res); // new child -reconnect |
| pipeLineDrivingNode_->getGroupAttr()->clearLogProperties(); |
| pipeLineDrivingNode_->synthLogProp(); |
| } |
| } |
| |
| return(backboneRoot); |
| } |
| |
| createTriggerList(); |
| reorder(); |
| group(); |
| backboneRoot = toTree(); |
| |
| fixCardinalityAndCollectTempUsage(backboneRoot,TRUE); |
| fixTempInsertSubtree(); |
| |
| #ifdef TempTableDebug |
| // |
| // if you need to see the temp table data for debug, define TempTableDebug |
| // you will have to delete temp table data manually between every two calls |
| backboneRoot = removeTempDelete(); |
| #endif |
| |
| return(backboneRoot); |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // |
| // hasConflicts |
| // |
| // Are there any conflicts in the IUD + after triggers backbone |
| //----------------------------------------------------------------------------------- |
| |
| NABoolean |
| OptTriggersBackbone::hasConflicts() const |
| { |
| if (statmentTriggerDrivingNode_) |
| { |
| if (statmentTriggerDrivingNode_->getAccessSet0()-> |
| isConflicting(statmentTriggerDrivingNode_->getAccessSet1())) |
| return TRUE; |
| |
| if (leftTreeHasConflicts(statmentTriggerDrivingNode_)) |
| return TRUE; |
| } |
| |
| if (pipeLineDrivingNode_) |
| { |
| if (pipeLineDrivingNode_->getAccessSet0()-> |
| isConflicting(pipeLineDrivingNode_->getAccessSet1())) |
| return TRUE; |
| |
| if (rowTriggerDrivingNode_) |
| { |
| // conflict with RI |
| if (rowTriggerDrivingNode_->getAccessSet0()-> |
| isConflicting(rowTriggerDrivingNode_->getAccessSet1())) |
| return TRUE; |
| |
| if (leftTreeHasConflicts(rowTriggerDrivingNode_)) |
| return TRUE; |
| } |
| // |
| // add RI internal conflicts check here (leftTreeHasConflicts(riDrivingNode_)) |
| // |
| } |
| return FALSE; |
| } |
| |
| // Are there conflicts in the left linear tree? |
| NABoolean |
| OptTriggersBackbone::leftTreeHasConflicts(RelExpr *drivingNode) const |
| { |
| for (RelExpr *pUnion = (RelExpr *)drivingNode->getChild(1); |
| pUnion; |
| pUnion = (RelExpr *)pUnion->getChild(0)) |
| { |
| // a trigger - end of sub tree |
| if (pUnion->getInliningInfo().isTriggerRoot()) |
| return FALSE ; |
| |
| // verify that it is a left linear tree |
| CMPASSERT(pUnion->getOperatorType() == REL_UNION); |
| CMPASSERT(((RelExpr *)(pUnion->getChild(1)))->getInliningInfo().isTriggerRoot()) |
| |
| if (pUnion->getAccessSet0()->isConflicting(pUnion->getAccessSet1())) |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // |
| // createTriggerList |
| // |
| // create a trigger list from statement triggers and row triggers |
| //----------------------------------------------------------------------------------- |
| |
| void |
| OptTriggersBackbone::createTriggerList() |
| { |
| triggerList_ = new(CmpCommon::statementHeap())(OptTriggerList); |
| |
| if (statmentTriggerDrivingNode_) |
| addToTriggerList(statmentTriggerDrivingNode_, FALSE); |
| |
| if (rowTriggerDrivingNode_) |
| addToTriggerList(rowTriggerDrivingNode_, TRUE); |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // |
| // |
| // removeTempInsert |
| // |
| //----------------------------------------------------------------------------------- |
| |
| void |
| OptTriggersBackbone::removeTempInsert() |
| { |
| tempInsertDrivingNodeParent_->setChild(0, tempInsertDrivingNode_->getChild(0)); |
| tempInsertDrivingNodeParent_->getGroupAttr()->clearLogProperties(); |
| tempInsertDrivingNodeParent_->synthLogProp(); |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // |
| // |
| // removeTempDelete |
| // |
| //----------------------------------------------------------------------------------- |
| |
| RelExpr * |
| OptTriggersBackbone::removeTempDelete() |
| { |
| if (hasBeforeTriggers()) |
| { |
| //The removal of temp delete in before trigger is used only as debug option |
| beforeTriggerDrivingNode_-> |
| setChild(1,(ExprNode *)tempDeleteDrivingNode_->getChild(0)); |
| return beforeTriggerDrivingNode_; |
| } |
| return ((RelExpr *)tempDeleteDrivingNode_->getChild(0)); |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // |
| // addToTriggerList |
| // |
| // locate all triggers under a drivingNode and add them to triggerList_ |
| // The tree of unions under statmentTriggerDrivingNode_ or rowTriggerDrivingNode_ is |
| // a left linear tree |
| // |
| //----------------------------------------------------------------------------------- |
| |
| void |
| OptTriggersBackbone::addToTriggerList(RelExpr *drivingNode, NABoolean isRowTrigger) |
| { |
| OptTriggerPtr trigger; |
| SubTreeAccessSet *treeAccessSet; |
| RelExpr *parent = drivingNode; |
| |
| for (RelExpr *pUnion = (RelExpr *)drivingNode->getChild(1); |
| pUnion; |
| pUnion = (RelExpr *)pUnion->getChild(0)) |
| { |
| if (pUnion->getInliningInfo().isTriggerRoot()) |
| { |
| // a trigger - end of the left linear tree |
| if (pUnion == (RelExpr *)drivingNode->getChild(1)) //single trigger |
| treeAccessSet = parent->getAccessSet1(); |
| else |
| treeAccessSet = parent->getAccessSet0(); |
| |
| trigger = new(CmpCommon::statementHeap()) |
| OptTrigger(treeAccessSet, isRowTrigger, pUnion, NULL); |
| triggerList_->insert(trigger); |
| break; |
| } |
| |
| // verify that it is a left linear tree |
| CMPASSERT(pUnion->getOperatorType() == REL_UNION); |
| CMPASSERT(((RelExpr *)pUnion->getChild(1))->getInliningInfo().isTriggerRoot()); |
| |
| // set OptTrigger where trigger root is the right child of pUnion |
| treeAccessSet = pUnion->getAccessSet1(); |
| trigger = new(CmpCommon::statementHeap()) |
| OptTrigger(treeAccessSet, |
| isRowTrigger, |
| (RelExpr *)pUnion->getChild(1), pUnion); |
| triggerList_->insert(trigger); |
| |
| parent = pUnion; |
| } |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // |
| // optTriggerTimeCompare |
| // |
| // used in qsort |
| // |
| // Return Value |
| // -1 elem1 less than elem2 |
| // 0 elem1 equivalent elem2 |
| // 1 elem1 greater than elem2 |
| //----------------------------------------------------------------------------------- |
| |
| static Int32 optTriggerTimeCompare(const void *elem1, const void *elem2) |
| { |
| Int64 timestamp1 = (*(OptTriggerPtr *)elem1)->getTriggerTimeStamp(); |
| Int64 timestamp2 = (*(OptTriggerPtr *)elem2)->getTriggerTimeStamp(); |
| |
| if (timestamp1 < timestamp2) |
| return -1; |
| if (timestamp1 == timestamp2) |
| return 0; |
| return 1; |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // reorder |
| // |
| // set a new final order to the triggers, the order is based on timestamp and conflicts |
| //----------------------------------------------------------------------------------- |
| |
| void |
| OptTriggersBackbone::reorder() |
| { |
| CollIndex idx; |
| #pragma nowarn(1506) // warning elimination |
| Int32 triggerArraySize = triggerList_->entries(); |
| #pragma warn(1506) // warning elimination |
| // optTriggerPtrArray - array of pointers to OptTrigger , used for qsort |
| OptTriggerPtr *optTriggerPtrArray = new(CmpCommon::statementHeap()) |
| OptTriggerPtr[triggerArraySize]; |
| // copy triggerList to triggerArray |
| for (idx = 0; idx < triggerList_->entries(); idx++) |
| optTriggerPtrArray[idx] = (*triggerList_)[idx]; |
| |
| // sort trigger array by timestamp |
| opt_qsort(optTriggerPtrArray, |
| triggerArraySize, |
| sizeof(OptTriggerPtr), |
| optTriggerTimeCompare); |
| |
| // move row triggers to the begining, as far as the access set conflicts allow |
| for (Int32 i = 1 ; i< triggerArraySize; i++) |
| { |
| // if it is a statment trigger, don't try to move it |
| if (!optTriggerPtrArray[i]->isRowTrigger()) |
| continue; |
| |
| // if row trigger try to move it to the begining of the list |
| for (Int32 j = i ; j > 0 ; j--) |
| { |
| // if trigger j don't conflict with trigger j-1 replace them |
| const SubTreeAccessSet *accessSet1 = optTriggerPtrArray[j]->getAccessSet(); |
| const SubTreeAccessSet *accessSet2= optTriggerPtrArray[j-1]->getAccessSet(); |
| |
| if (accessSet1->isConflicting(accessSet2)) |
| break; // Conflict - can't move the current trigger any more |
| |
| // move the row trigger one step back |
| OptTriggerPtr sav = optTriggerPtrArray[j-1]; |
| optTriggerPtrArray[j-1] = optTriggerPtrArray[j]; |
| optTriggerPtrArray[j] = sav; |
| } |
| } |
| |
| // set triggerList_ based on the reordered array optTriggerPtrArray |
| triggerList_->clear(); |
| for (Int32 k = 0 ; k < triggerArraySize; k++) |
| triggerList_->insert(optTriggerPtrArray[k]); |
| |
| // free the temporary array |
| // exclude coverage - we don't seem to handle macro expansion very well |
| NADELETEBASIC(optTriggerPtrArray, (CmpCommon::statementHeap())); // LCOV_EXCL_LINE |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // |
| // group |
| // |
| // Divide the triggers in the given order to groups |
| // Triggers in one group should have no conflicts between them and thats why the order |
| // inside a group is not important |
| //----------------------------------------------------------------------------------- |
| |
| void |
| OptTriggersBackbone::group() |
| { |
| // |
| // First, Second and third groups are one logical group that does not have conflicts |
| // between the members of the group. |
| // |
| // if a trigger don't conflict with the IUD & RI |
| // { |
| // row triggers go to first group (pPipelinedGroup) |
| // statment triggers go to second group (pIudParallelGroup) |
| // } |
| // else |
| // { |
| // the trigger goes to the third group which is a blocked group that read from |
| // temporary table |
| // } |
| |
| // iudAccessSet represents the access set of the sub tree below the triggers and RI |
| SubTreeAccessSet *iudAccessSet; |
| SubTreeAccessSet *riAccessSet = NULL; |
| |
| if (tempInsertDrivingNode_) |
| iudAccessSet = tempInsertDrivingNodeParent_->getAccessSet0(); |
| else |
| iudAccessSet = iudDrivingNodeParent_->getAccessSet0(); |
| |
| // RI if exist are always in the left side of the union |
| if (riDrivingNode_) |
| riAccessSet = riDrivingNode_->getAccessSet0(); |
| |
| OptTriggerGroup *pPipelinedGroup = new(CmpCommon::statementHeap()) |
| OptTriggerGroup(OptTriggerGroup::PIPELINED_ROW_TRIGGERS, this); |
| OptTriggerGroup *pIudParallelGroup = new(CmpCommon::statementHeap()) |
| OptTriggerGroup(OptTriggerGroup::IUD_PARALLEL_STATEMENT_TRIGGER, this); |
| OptTriggerGroup *pBlockedGroup = new(CmpCommon::statementHeap()) |
| OptTriggerGroup(OptTriggerGroup::BLOCKED_GROUP, this); |
| |
| triggerGroups_ = new(CmpCommon::statementHeap())(OptTriggerGroupList); |
| |
| // the for loop stops on the first trigger conflicting with any |
| // previous trigger |
| CollIndex i = 0; |
| for (i = 0; i < triggerList_->entries(); i++) |
| { |
| OptTriggerPtr currentTrigger = (*triggerList_)[i]; |
| |
| // If current trigger conflicts with group1 or group2 or group3 |
| // we wil start a new group. |
| if (pPipelinedGroup->isConflicting(currentTrigger->getAccessSet()) || |
| pIudParallelGroup->isConflicting(currentTrigger->getAccessSet()) || |
| pBlockedGroup->isConflicting(currentTrigger->getAccessSet())) |
| break; |
| |
| // If current trigger conflicts with IUD, Temp Table, IM or RI |
| // add it to the third group |
| // else |
| // add it to the first or second group |
| if (currentTrigger->getAccessSet()->isConflicting(iudAccessSet) || |
| (riAccessSet && |
| currentTrigger->getAccessSet()->isConflicting(riAccessSet))) |
| { |
| pBlockedGroup->addTrigger(currentTrigger); |
| } |
| else |
| { |
| if (currentTrigger->isRowTrigger()) |
| pPipelinedGroup->addTrigger(currentTrigger); |
| else |
| pIudParallelGroup->addTrigger(currentTrigger); |
| } |
| } |
| |
| // if there are elements in group1/group2/group3 add them to group list |
| if (pPipelinedGroup->entries()) |
| triggerGroups_->insert(pPipelinedGroup); |
| if (pIudParallelGroup->entries()) |
| triggerGroups_->insert(pIudParallelGroup); |
| |
| |
| OptTriggerGroup *currentGroup; |
| if (pBlockedGroup->entries()) |
| currentGroup = pBlockedGroup; |
| else |
| { |
| // create the blocked groups |
| currentGroup = new(CmpCommon::statementHeap()) |
| OptTriggerGroup(OptTriggerGroup::BLOCKED_GROUP, this); |
| } |
| |
| // this for loop continues from the trigger that caused the end of the previous |
| // for loop |
| CollIndex j = i; |
| for (j = i; j < triggerList_->entries(); j++) |
| { |
| OptTriggerPtr currentTrigger = (*triggerList_)[j]; |
| // If current trigger conflict with the currentGroup |
| // close the current group and open a new one |
| if (currentGroup->isConflicting(currentTrigger->getAccessSet())) |
| { |
| triggerGroups_->insert(currentGroup); |
| // triggers are sepearated to new group whenever there is a conflict betweeen |
| // the next trigger to the current group |
| currentGroup = new(CmpCommon::statementHeap()) |
| OptTriggerGroup(OptTriggerGroup::BLOCKED_GROUP, this); |
| } |
| // insert to current group |
| currentGroup->addTrigger(currentTrigger); |
| } |
| |
| // if there are elements in currentGroup add it to group list |
| if (currentGroup->entries()) |
| triggerGroups_->insert(currentGroup); |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // |
| // cleanDummies |
| // |
| // pipelined action sub tree is created in a fixed form with dummy action replacing |
| // empty subtrees like RI or Row triggers. |
| // clean union sub tree, if both children are dummy the sub tree is dummy |
| // if one of the children is dummy, the other child replace the union |
| //----------------------------------------------------------------------------------- |
| |
| RelExpr * |
| OptTriggersBackbone::cleanDummies(RelExpr *parent) |
| { |
| RelExpr *leftChild = (RelExpr *)parent->getChild(0); |
| RelExpr *rightChild = (RelExpr *)parent->getChild(1); |
| |
| |
| if (leftChild->getInliningInfo().isDummy() && |
| rightChild->getInliningInfo().isDummy()) |
| return NULL; |
| |
| if (!leftChild->getInliningInfo().isDummy() && |
| !rightChild->getInliningInfo().isDummy()) |
| return parent; |
| |
| if (leftChild->getInliningInfo().isDummy()) |
| return rightChild; |
| return leftChild; |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // |
| // |
| // |
| // Replace the original row trigger sub tree under pipelined actions |
| // with the row triggers sub set that the transformation found qualified to be placed |
| // in the pipelined actions |
| // |
| //----------------------------------------------------------------------------------- |
| |
| void |
| OptTriggersBackbone::replacePipelinedRowTriggers(RelExpr *rowTriggerTree) |
| { |
| // connect the piplined row triggers to tree in rowTriggerDrivingNode_ |
| rowTriggerDrivingNode_->setChild(1, rowTriggerTree); |
| |
| OptTriggersBackbone::SetUnionCharacteristicInputs(rowTriggerDrivingNode_); |
| |
| rowTriggerDrivingNode_->getGroupAttr()->clearLogProperties(); |
| rowTriggerDrivingNode_->synthLogProp(); |
| |
| OptTriggersBackbone::SetTsjCharacteristicInputs(pipeLineDrivingNode_); |
| |
| pipeLineDrivingNode_->getGroupAttr()->clearLogProperties(); |
| pipeLineDrivingNode_->synthLogProp(); |
| |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // toTree |
| // |
| // write the final backbone sub tree |
| //----------------------------------------------------------------------------------- |
| |
| RelExpr * |
| OptTriggersBackbone::toTree() |
| { |
| // Rebuild the tree bottom up |
| RelExpr *childNode = iudDrivingNode_; |
| |
| if (tempInsertDrivingNode_) |
| childNode = tempInsertDrivingNode_; |
| |
| if (pipeLineDrivingNode_) |
| { |
| childNode = pipeLineDrivingNode_; |
| |
| // if first group is not PIPELINED_ROW_TRIGGERS |
| // mark the original row triggers sub tree for removal |
| if (rowTriggerDrivingNode_ && !(*triggerGroups_)[0]->isPipeLinedRowTriggesGroup()) |
| { |
| RelExpr *rowTriggers = (RelExpr *)rowTriggerDrivingNode_->getChild(1); |
| rowTriggers->getInliningInfo().setFlags(II_DummyStatement); |
| } |
| } |
| |
| for (CollIndex i = 0; i < triggerGroups_->entries(); i++) |
| { |
| OptTriggerGroup *optTriggerGroup = (*triggerGroups_)[i]; |
| |
| // if first group isPipeLinedRowTriggesGroup |
| if (i==0 && optTriggerGroup->isPipeLinedRowTriggesGroup()) |
| { |
| // Get the new row trigger sub tree, that belong to the pipelined actions |
| RelExpr *rowTriggerTree = optTriggerGroup->toTree(); |
| replacePipelinedRowTriggers(rowTriggerTree); |
| continue; |
| } |
| |
| // group with union or ordered union on top of it |
| RelExpr *triggersUnion = optTriggerGroup->toTree(); |
| Union *pOrder = new(CmpCommon::statementHeap())Union |
| (childNode, triggersUnion, NULL, NULL, REL_UNION, |
| CmpCommon::statementHeap(),TRUE); |
| if (optTriggerGroup->isBlockedGroup()) |
| pOrder->setOrderedUnion(); |
| |
| BindWA *bindWA = (drivingBackbone_->getRETDesc())->getBindWA(); |
| |
| if (bindWA && bindWA->getHostArraysArea() && |
| bindWA->getHostArraysArea()->getTolerateNonFatalError()) |
| { |
| pOrder->setInNotAtomicStatement(); |
| } |
| |
| pOrder->setGroupAttr(new(CmpCommon::statementHeap()) GroupAttributes()); |
| OptTriggersBackbone::SetUnionCharacteristicInputs(pOrder); |
| pOrder->synthLogProp(); |
| childNode = pOrder; |
| |
| // we perform cleanDummies in the first group after pipeline |
| // because if the pipelined action subtree is empty this group is the |
| // new parent of the pipeline left child |
| if (pOrder->getChild(0) == pipeLineDrivingNode_) |
| { |
| // Pipelined action sub tree is created in a fix form with dummy |
| // action replacing empty subtrees like RI or Row triggers. |
| // Dummy actions need to be cleaned. |
| RelExpr *res = |
| cleanDummies((RelExpr *)pipeLineDrivingNode_->getChild(1)); |
| // if piplined actions empty, remove the pipeline Tsj |
| if (res == NULL) |
| { |
| ValueIdSet emptyIdSet; |
| RelExpr *child = (RelExpr *)pipeLineDrivingNode_->getChild(0); |
| pOrder->setChild(0, child); |
| |
| // If pipelined actions are eliminated |
| // The output from its first child is no longer needed |
| // Change child from left join to join and clear its |
| // characteristic outputs |
| |
| |
| if (child->getOperatorType() == REL_LEFT_TSJ) |
| child->setOperatorType(REL_TSJ); |
| else if (child->getOperatorType() == REL_LEFT_JOIN) |
| child->setOperatorType(REL_JOIN); |
| |
| child->getGroupAttr()->setCharacteristicOutputs(emptyIdSet); |
| |
| // Generic Update |
| if (child->getOperator().match(REL_ANY_GEN_UPDATE)) |
| ((GenericUpdate *)child)->setPotentialOutputValues(emptyIdSet); |
| |
| pOrder->getGroupAttr()->clearLogProperties(); |
| pOrder->synthLogProp(); |
| } |
| else if (res != pipeLineDrivingNode_->getChild(1)) |
| { |
| pipeLineDrivingNode_->setChild(1, res); |
| |
| pipeLineDrivingNode_->getGroupAttr()->clearLogProperties(); |
| pipeLineDrivingNode_->synthLogProp(); |
| } |
| } |
| } |
| |
| // reconnect the triggers sub tree to the temp delete tsj |
| tempDeleteDrivingNode_->setChild(0, (ExprNode *)childNode); |
| |
| |
| |
| // return the top node of the backbone |
| if (hasBeforeTriggers()) |
| return (beforeTriggerDrivingNode_); |
| return (tempDeleteDrivingNode_); |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // |
| // forceCardinalityAsIud |
| // |
| // Force the cardinality of the given node to be the same as the original IUD that |
| // is the basis for the whole backbone. |
| // |
| //----------------------------------------------------------------------------------- |
| |
| void OptTriggersBackbone::forceCardinalityAsIud(RelExpr *expr) const |
| { |
| #pragma nowarn(1506) // warning elimination |
| #pragma warning (disable : 4244) //warning elimination |
| |
| CostScalar card = |
| getIudDrivingNode()->getGroupAttr()->getResultCardinalityForEmptyInput(); |
| expr->getInliningInfo().BuildForceCardinalityInfo(1.0, // factor ignored |
| card.getValue(), |
| CmpCommon::statementHeap()); |
| #pragma warning (default : 4244) //warning elimination |
| #pragma warn(1506) // warning elimination |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // |
| // createNewExecIdRef |
| // |
| // This method returns the column reference for the virtual column used in selection |
| // from the temp-table. The BindWA is prepared in a way that this column name appears |
| // with the right ValueId. |
| // |
| //----------------------------------------------------------------------------------- |
| |
| ColReference *OptTriggersBackbone::createNewExecIdRef() |
| { |
| ColRefName *execIdName = new(CmpCommon::statementHeap()) |
| ColRefName(InliningInfo::getExecIdVirtualColName()); |
| return new(CmpCommon::statementHeap()) ColReference(execIdName); |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // fixCardinalityAndCollectTempUsage |
| // |
| // fix all temp-table access nodes to use the same cardinality as the |
| // iudDrivingNode. |
| // Recursively scan the final tree, and find each node that access the |
| // temp-table. For each such node, fix the cardinality to be the same as the |
| // iudDrivingNode. The scanning doesn't include the subtree that includes the |
| // insert node into the temp-table. |
| // In addition, the usage of the temp-table is collected for each scan node, so |
| // later we'll know whether the insert into the temp-table might be optimized. |
| //----------------------------------------------------------------------------------- |
| |
| void OptTriggersBackbone::fixCardinalityAndCollectTempUsage(RelExpr *rootNode, |
| NABoolean enterInnerBackbone) |
| { |
| // stop condition |
| // we stop the recursion on the following conditions: |
| // 1. we reached the end of tree |
| // 2. we reached the temp-insert subtree. This sub-tree is not scanned since the |
| // cardinality usually set correctly by the optimizer according to the driving |
| // IUD node. In update operations the insert is fed by a union of tuples, so |
| // setting the cardinality is not that straight forward, so we avoid it. |
| if (rootNode == NULL || rootNode == tempInsertDrivingNode_) |
| { |
| return; |
| } |
| |
| // find whether rootNode is a root of a backbone of triggers. |
| InliningInfo &info = rootNode->getInliningInfo(); |
| NABoolean backboneRoot = FALSE; |
| if (info.isDrivingBeforeTriggers() || |
| (info.isDrivingTempDelete() && !info.isBeforeTriggersExist())) |
| { |
| backboneRoot = TRUE; |
| } |
| |
| // don't enter encountered backbone if instructed not to do so. |
| if (!enterInnerBackbone && backboneRoot) |
| { |
| // if instructed not to enter inner backbones and we've reached such |
| // backbone, do the job only for the IUD node that is the basis for |
| // this inner backbone, since this is the only place in the inner |
| // backbone where the temp-table values of the current backbone might |
| // be used. Other sub-trees will use the new values generated in this |
| // inner backbone. |
| GenericUpdate *iudNode = findLeftmostGU(rootNode); |
| fixCardinalityAndCollectTempUsage(iudNode, FALSE); |
| return; // skip inner backbone - stop recursion |
| } |
| |
| // handle scan nodes on temp-table |
| if (rootNode->getOperatorType() == REL_SCAN) |
| { |
| Scan *scan = (Scan *) rootNode; |
| if (scan->getTableName().getSpecialType() == ExtendedQualName::TRIGTEMP_TABLE) |
| { |
| forceCardinalityAsIud(scan); |
| collectTempTableUsage(scan); |
| } |
| } |
| |
| // handle GU nodes on temp-table |
| if (rootNode->getOperator().match(REL_ANY_GEN_UPDATE)) |
| { |
| GenericUpdate *gu = (GenericUpdate *) rootNode; |
| if (gu->getTableName().getSpecialType() == ExtendedQualName::TRIGTEMP_TABLE) |
| { |
| forceCardinalityAsIud(gu); |
| } |
| } |
| |
| // recursive call for each child |
| for (Int32 i = 0; i < rootNode->getArity(); i++) |
| { |
| fixCardinalityAndCollectTempUsage(rootNode->getChild(i)->castToRelExpr(), FALSE); |
| } |
| |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // collectTempTableUsage |
| // |
| // This method collects information how the temp-table is actually used. It |
| // looks for a predicate on the "@UNIQUE_IUD_NO" special column of the |
| // temp-table. If the predicate found, if looks at the constant value this column |
| // is compared against (the constant must exist and be numric). If the value is |
| // even, the selected rows from the temp-table will contain the NEW values, |
| // otherwise it will contain the OLD values. This way we find the actual usage of |
| // the values from the temp-table. |
| //----------------------------------------------------------------------------------- |
| void OptTriggersBackbone::collectTempTableUsage(RelExpr *node) |
| { |
| ValueIdSet preds = node->selectionPred(); |
| for (ValueId vid = preds.init(); preds.next(vid); preds.advance(vid)) |
| { |
| if (vid.getItemExpr()->getOperatorType() != ITM_VEG_PREDICATE) |
| { |
| continue; // only VEG predicates are inspected |
| } |
| |
| VEG *referencedVEG = ((VEGPredicate*)vid.getItemExpr())->getVEG(); |
| ValueIdSet valuesInVEG = referencedVEG->getAllValues(); |
| |
| // look for the UNIQUEIUD_COLUMN base column in the VEG |
| for (ValueId vid2 = valuesInVEG.init(); |
| valuesInVEG.next(vid2); |
| valuesInVEG.advance(vid2)) |
| { |
| if (vid2.getItemExpr()->getOperatorType() != ITM_BASECOLUMN) |
| { |
| continue; // look only at base columns |
| } |
| |
| BaseColumn *bc = vid2.castToBaseColumn(); |
| if (!bc->getColName().compareTo(UNIQUEIUD_COLUMN)) |
| { |
| // the current VEG is the desired predicate |
| setUsageAccordingToUniqueUidPredicate(valuesInVEG); |
| } |
| } |
| } |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // setUsageAccordingToUniqueUidPredicate |
| // |
| // The given predicate is using the UNIQUEIUD_COLUMN column. This predicate |
| // controls which rows actually fetched from the temp-table, so it gives us the |
| // knowledge which rows in the temp-table are actually used. |
| // The predicate containing the UNIQUEIUD_COLUMN column must also contain a |
| // constant unmeric value that it is compared against. The constant might be |
| // referenced directly or indirectly by inner VEG reference (for example). |
| // Selection of rows with even value means fetching NEW values, while selection |
| // of rows with odd value means fetching OLD values. |
| //----------------------------------------------------------------------------------- |
| void OptTriggersBackbone::setUsageAccordingToUniqueUidPredicate(const ValueIdSet &uniqueUidPred) |
| { |
| ItemExpr *constValue = NULL; |
| uniqueUidPred.referencesAConstValue(&constValue); |
| |
| // This predicate must reference a numeric constant value |
| CMPASSERT(constValue != NULL && constValue->getOperatorType() == ITM_CONSTANT); |
| CMPASSERT(((ConstValue *)constValue)->canGetExactNumericValue()); |
| |
| Int64 uniqueUidValue = ((ConstValue *)constValue)->getExactNumericValue(); |
| |
| // if constant is odd - this scan fetches OLD values, otherwise it fetches NEW values |
| if (uniqueUidValue % 2) |
| { |
| tempTableUsage_ |= ChangesTable::DELETED_ROWS; |
| } |
| else |
| { |
| tempTableUsage_ |= ChangesTable::INSERTED_ROWS; |
| } |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // fixTempInsertSubtree |
| // |
| // In case only one set of values (NEW or OLD) from the temp-table is in use, |
| // there's no point inserting the other set from the beginning. Since the |
| // insertion of both sets has bad affection on performance, we try as much as |
| // we could to avoid it when not really necessary. |
| //----------------------------------------------------------------------------------- |
| |
| void OptTriggersBackbone::fixTempInsertSubtree() |
| { |
| BindWA *obindWA = (drivingBackbone_->getRETDesc())->getBindWA(); |
| if ((tempTableUsage_ == (ChangesTable::INSERTED_ROWS | ChangesTable::DELETED_ROWS)) || (obindWA->getHostArraysArea() && !obindWA->getHostArraysArea()->getTolerateNonFatalError()) ) |
| { |
| return; // both NEW and OLD sets are used - nothing to optimize |
| } |
| |
| // Create a new BindWA - we don't want to have anything left in some RETDesc |
| // that may affect this binding. |
| BindWA bindWA(ActiveSchemaDB(), CmpCommon::context()); |
| |
| // Prepare the new BindWA for binding our little sub-tree. |
| // In addition, add the OLD and NEW column list from the original RETDesc, so |
| // these columns will be available for the temp-table insert node during bind |
| // time. |
| OptTriggersBackbone::PrepareNewBindWA(bindWA, triggerBindInfo_); |
| RETDesc *retDesc = bindWA.getCurrentScope()->getRETDesc(); |
| retDesc->addColumns(&bindWA, |
| *(triggerBindInfo_->getIudColumnList()), |
| USER_AND_SYSTEM_COLUMNS); |
| |
| // build the optimized version of the tmep-insert sub-tree |
| RelExpr *topNode = buildOptimizedTempInsert(&bindWA); |
| |
| // Since this is a compilation with GenericUpdate node, we must mark it |
| // as internal compilation, so no real root will be searched |
| CmpContext::InternalCompileEnum savedMode = |
| CmpCommon::context()->internalCompile(); |
| CmpCommon::context()->internalCompile() = CmpContext::INTERNAL_MODULENAME; |
| |
| // Do the necessary compilation steps. |
| topNode = OptTriggersBackbone::BindAndNormalizeNewlyConstructedTree(&bindWA, topNode); |
| |
| // return to the mode we were in before the above compilation |
| CmpCommon::context()->internalCompile() = savedMode; |
| |
| // Find the node that drives the temp-insert subtree. |
| if (obindWA->getHostArraysArea() && !obindWA->getHostArraysArea()->getTolerateNonFatalError()) |
| { |
| RelExpr *insertDrivingNode = tempInsertDrivingNode_; |
| if (insertDrivingNode == NULL) |
| { |
| CMPASSERT(beforeTriggersExist_); |
| insertDrivingNode = beforeTriggerDrivingNode_->child(0)->child(1); |
| CMPASSERT(insertDrivingNode->getOperatorType() == REL_UNARY_INSERT || |
| insertDrivingNode->getOperatorType() == REL_LEAF_INSERT); |
| } |
| |
| // connect sub-tree to the backbone tree |
| insertDrivingNode->setChild(1, topNode); |
| insertDrivingNode->getGroupAttr()->clearLogProperties(); |
| insertDrivingNode->synthLogProp(); |
| } |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // buildOptimizedTempInsert |
| // |
| // Here the optimized version of the temp-insert sub-tree is constructed. |
| //----------------------------------------------------------------------------------- |
| |
| RelExpr *OptTriggersBackbone::buildOptimizedTempInsert(BindWA *bindWA) |
| { |
| // Create a temp table object (derived from ChangesTable). |
| TriggersTempTable tempTableObj(iudNode_, bindWA); |
| |
| // Make the temp table object use the UniqueExecuteID we already have. |
| tempTableObj.setBoundExecId(createNewExecIdRef()); |
| |
| RelExpr *topNode = NULL; |
| if (tempTableUsage_ & ChangesTable::INSERTED_ROWS) |
| { |
| topNode = tempTableObj.buildInsert(TRUE, ChangesTable::INSERTED_ROWS); |
| } |
| else |
| { |
| topNode = tempTableObj.buildInsert(TRUE, ChangesTable::DELETED_ROWS); |
| } |
| topNode = new (CmpCommon::statementHeap()) RelRoot(topNode); |
| CMPASSERT(topNode); // make sure topNode is pointing to the result tree |
| |
| return topNode; |
| } |
| |
| //----------------------------------------------------------------------------- |
| // initBeforeTriggerDrivingNode |
| // |
| // sets a pointer to the node that drives the before triggers (if exist). |
| // This method also sets a flag that before triggers exist. |
| //----------------------------------------------------------------------------- |
| |
| void |
| OptTriggersBackbone::initBeforeTriggerDrivingNode() |
| { |
| if (pNext_->getInliningInfo().isDrivingBeforeTriggers()) |
| { |
| beforeTriggerDrivingNode_ = pNext_; |
| beforeTriggersExist_ = TRUE; |
| } |
| } |
| |
| //----------------------------------------------------------------------------- |
| // initTempDeleteDrivingNode |
| // |
| // sets a pointer to the node that drives the delete from the temp-table. |
| //----------------------------------------------------------------------------- |
| |
| void |
| OptTriggersBackbone::initTempDeleteDrivingNodeAndAdvance() |
| { |
| const InliningInfo &info = pNext_->getInliningInfo(); |
| |
| // The root must either drive before triggers or temp-table delete |
| CMPASSERT(beforeTriggersExist_ || info.isDrivingTempDelete()) |
| |
| if (beforeTriggersExist_) |
| { |
| CMPASSERT(info.isDrivingBeforeTriggers()) |
| tempDeleteDrivingNode_ = pNext_->getChild(1)->castToRelExpr(); |
| } |
| else |
| { |
| tempDeleteDrivingNode_ = pNext_; |
| } |
| |
| // Sanity check for the pointer |
| CMPASSERT(tempDeleteDrivingNode_->getInliningInfo().isDrivingTempDelete()) |
| |
| pNext_ = tempDeleteDrivingNode_; |
| advanceInTree(); |
| } |
| |
| //----------------------------------------------------------------------------- |
| // initStatmentTriggerDrivingNode |
| // |
| // sets a pointer to the node that drives the statement triggers. All the |
| // triggers are connected using UNION nodes. |
| //----------------------------------------------------------------------------- |
| |
| void |
| OptTriggersBackbone::initStatmentTriggerDrivingNodeAndAdvance() |
| { |
| if (pNext_->getInliningInfo().isDrivingStatementTrigger()) |
| { |
| statmentTriggerDrivingNode_ = pNext_; |
| CMPASSERT(statmentTriggerDrivingNode_->getOperatorType() == REL_UNION); |
| advanceInTree(); |
| } |
| } |
| |
| //----------------------------------------------------------------------------- |
| // initPipeLineDrivingNode |
| // |
| // sets a pointer to the node that drives all the pipelined actions, i.e. |
| // RI, row triggers etc. |
| //----------------------------------------------------------------------------- |
| |
| void |
| OptTriggersBackbone::initPipeLineDrivingNode() |
| { |
| if (pNext_->getInliningInfo().isDrivingPipelinedActions()) |
| { |
| pipeLineDrivingNodeParent_ = pParent_; |
| pipeLineDrivingNode_ = pNext_; |
| } |
| } |
| |
| //----------------------------------------------------------------------------- |
| // initRowTriggerDrivingNode |
| // |
| // sets a pointer to the node that drives the row triggers. All the |
| // triggers are connected using UNION nodes. |
| //----------------------------------------------------------------------------- |
| |
| void |
| OptTriggersBackbone::initRowTriggerDrivingNode() |
| { |
| CMPASSERT(pNext_->getInliningInfo().isDrivingPipelinedActions()) |
| RelExpr *e = pNext_->getChild(1)->castToRelExpr(); |
| |
| if (e->getInliningInfo().isDrivingRowTrigger()) |
| { |
| rowTriggerDrivingNode_ = e; |
| CMPASSERT(rowTriggerDrivingNode_->getOperatorType() == REL_UNION); |
| } |
| } |
| |
| //----------------------------------------------------------------------------- |
| // initRiDrivingNode |
| // |
| // sets a pointer to the node that drives the RI. All the RI constraints |
| // are connected using UNION nodes. |
| //----------------------------------------------------------------------------- |
| |
| void |
| OptTriggersBackbone::initRiDrivingNode() |
| { |
| CMPASSERT(pNext_->getInliningInfo().isDrivingPipelinedActions()) |
| RelExpr *e = pNext_->getChild(1)->castToRelExpr(); |
| |
| if (e->getInliningInfo().isDrivingRI()) |
| { |
| riDrivingNode_ = e; |
| CMPASSERT(riDrivingNode_->getOperatorType() == REL_UNION); |
| } |
| } |
| |
| //----------------------------------------------------------------------------- |
| // findLeftmostGU |
| // |
| // find the leftmost GenericUpdate node in the tree. This node is the one |
| // that started the whole process (i.e. the triggering node). |
| // |
| // The method scans the given tree in in-oreder style, i.e. left child, |
| // current node and then right child. The method looks for the first |
| // GenericUpdate node encountered in this order of scanning. First there's |
| // a dril down into the left most child. From there on, the method goes |
| // back in the tree. If the current inspected node is a GeericUpdate, it |
| // is returned. Otherwise, it's right child is scanned also, and if no |
| // GenericUpdate is found yet, there's a withdrawing to the parent node. |
| // |
| // The reason for looking into the right childs also is that when before |
| // triggers exist, the GenericUpdate is the right child of the EffectiveGU |
| // node. |
| //----------------------------------------------------------------------------- |
| |
| GenericUpdate * |
| OptTriggersBackbone::findLeftmostGU(ExprNode *expr) |
| { |
| GenericUpdate *guNode = NULL; |
| CMPASSERT(expr) |
| |
| if (expr->getOperator().match(REL_ANY_GEN_UPDATE)) |
| { |
| if (((GenericUpdate *) expr)->getUpdateCKorUniqueIndexKey()) |
| return (GenericUpdate *) expr; |
| |
| if (expr->getOperator().match(REL_UNARY_INSERT)) |
| { |
| Insert * ins = (Insert *)((GenericUpdate *) expr); |
| if (ins->systemGeneratesIdentityValue()) |
| return (GenericUpdate *) expr; |
| } |
| |
| if (iudNode_ && iudNode_->getTableName().getSpecialType() == ExtendedQualName::SG_TABLE) |
| return (GenericUpdate *) expr; |
| } |
| |
| if (expr->getChild(0)) |
| { |
| // recursive call to left child |
| if ((guNode = findLeftmostGU(expr->getChild(0))) != NULL) |
| { |
| return guNode; |
| } |
| } |
| |
| if (expr->getOperator().match(REL_ANY_GEN_UPDATE)) |
| { |
| return (GenericUpdate *) expr; |
| } |
| |
| if (expr->getChild(1)) |
| { |
| // recursive call to right child |
| return findLeftmostGU(expr->getChild(1)); |
| } |
| |
| return NULL; // GenericUpdate node not found in entire tree |
| } |
| |
| //----------------------------------------------------------------------------- |
| // advanceInTree |
| // |
| // advance the current node (and its parent) in the tree. The advancing is |
| // always done leftwards. |
| //----------------------------------------------------------------------------- |
| |
| void |
| OptTriggersBackbone::advanceInTree() |
| { |
| pParent_ = pNext_; |
| pNext_ = pNext_->getChild(0)->castToRelExpr(); |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // Methods For Class OptTriggerGroup |
| //----------------------------------------------------------------------------------- |
| |
| |
| //----------------------------------------------------------------------------------- |
| //----------------------------------------------------------------------------------- |
| OptColumnMapping::OptColumnMapping(TriggerBindInfo *bindInfo, |
| OperatorTypeEnum iudOpType, |
| CollHeap *heap) |
| : bindInfo_(bindInfo), |
| iudOpType_(iudOpType), |
| mappingArray_(heap), |
| newCols_(heap), |
| oldCols_(heap), |
| newCommonCols_(heap), |
| oldCommonCols_(heap), |
| oldNeeded_(FALSE), |
| newNeeded_(FALSE), |
| atNew_(NEWCorr, heap), |
| atOld_(OLDCorr, heap), |
| dummyFeed_(FALSE), |
| heap_(heap) |
| { |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // Initialize and minimize the mapping between the ValueIds expected by the |
| // triggers, and the OLD and NEW columns supplied by the new Scans on the temp table. |
| // At this stage, the new temp scan tree was not constructed yet, so the mappings are |
| // only done to the OLD and NEW column names. |
| //----------------------------------------------------------------------------------- |
| void OptColumnMapping::initializeMappings(ValueIdSet &reqOutput) |
| { |
| // First phase: insert mappings for all the OLD/NEW columns that match the inputs |
| // required by the triggers into the newCols_ and oldCols_ lists. |
| createInitialMappingLists(reqOutput); |
| |
| // Skip the rest of it if the trigger is not using any of these values anyway. |
| if (dummyFeed_) |
| return; |
| |
| // Second phase: remove redundant columns from each lists (columns that are part |
| // of the same VEG). Then find OLD columns that are equal to NEW columns (are part |
| // of the same VEG) and move them to the oldCommonCols_ and newCommonCols_ lists. |
| findCommonColumns(); |
| |
| // Third phase: find out if we really need both the OLD and the NEW tables. |
| checkWhichTablesAreNeeded(); |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // First phase: insert mappings for all the OLD/NEW columns that match the inputs |
| // required by the triggers into the newCols_ and oldCols_ lists. |
| // For each column of the original IUD RETDesc, do: |
| // If it's ValueId is referenced by the required Inputs of the trigger, do |
| // BEGIN |
| // Create a mapping object for it. |
| // If it's an OLD column - insert it into the oldCols_ list. |
| // If it's a NEW column - insert it into the newCols_ list. |
| // END |
| //----------------------------------------------------------------------------------- |
| void OptColumnMapping::createInitialMappingLists(ValueIdSet &reqOutput) |
| { |
| // Get the OLD and NEW column list from the original RETDesc. |
| const ColumnDescList *topColumnList = bindInfo_->getIudColumnList(); |
| // These are the inputs required by the triggers of this group. |
| ValueIdSet NeededOutputs(reqOutput); |
| |
| for (CollIndex i=0; i<topColumnList->entries(); i++) |
| { |
| const ColumnDesc *topColDesc = topColumnList->at(i); |
| ValueId topValueId = topColDesc->getValueId(); |
| |
| // Find the VEG in the required inputs that references this column. |
| ValueId topVeg; |
| if (NeededOutputs.referencesTheGivenValue(topValueId, topVeg)) |
| { |
| const ColRefName &colName = topColDesc->getColRefNameObj(); |
| |
| // Create a mapping object. |
| ColumnMapping *newMapping = new(heap_) ColumnMapping; |
| newMapping->colName_ = &colName; |
| newMapping->topValueId_ = topValueId; |
| newMapping->topVeg_ = topVeg; |
| |
| // Check which list to insert the new object into. |
| const NAString &corrName = colName.getCorrNameObj().getCorrNameAsString(); |
| if (corrName == atNew_) |
| newCols_.insert(newMapping); |
| else if (corrName == atOld_) |
| oldCols_.insert(newMapping); |
| } |
| } |
| |
| if (oldCols_.isEmpty() && newCols_.isEmpty()) |
| { |
| // The trigger is not using any of the OLD and NEW values. |
| dummyFeed_ = TRUE; |
| |
| // Check the IUD operator type. If the Scan is on the wrong table, there |
| // will be no rows there to drive the trigger. |
| if (iudOpType_ == REL_UNARY_INSERT || iudOpType_ == REL_LEAF_INSERT) |
| newNeeded_ = TRUE; |
| else |
| oldNeeded_ = TRUE; |
| } |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // A utility method used by findCommonColumns() to find a mapping in list colsList |
| // that has the same topVeg_ as colToFind. startAt can be specified to iterativly |
| // find all such mappings, or to find mappings in the same list as colToFind. |
| //----------------------------------------------------------------------------------- |
| OptColumnMapping::ColumnMapping * |
| OptColumnMapping::findEqualCols(MappingList &colsList, |
| ColumnMapping *colToFind, |
| CollIndex startAt) |
| { |
| for (CollIndex i=startAt; i<colsList.entries(); i++) |
| { |
| ColumnMapping *currentCol = colsList[i]; |
| if (colToFind->topVeg_ == currentCol->topVeg_) |
| return currentCol; |
| } |
| |
| return NULL; |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // Second phase: remove redundant columns from each lists (columns that are part |
| // of the same VEG). Then find OLD columns that are equal to NEW columns (are part |
| // of the same VEG) and move them to the oldCommonCols_ and newCommonCols_ lists. |
| // Both sets are saved for now, because we don't yet know if they will be used |
| // as OLD or NEW columns. |
| //----------------------------------------------------------------------------------- |
| void OptColumnMapping::findCommonColumns() |
| { |
| CollIndex i; |
| ColumnMapping *redundantCol = NULL; |
| ColumnMapping *currentCol = NULL; |
| |
| // Remove redundant OLD columns |
| if (!oldCols_.isEmpty()) |
| { |
| for (i=0; i<oldCols_.entries()-1; i++) |
| { |
| currentCol = oldCols_[i]; |
| |
| // Search the mappings after currentCol in the list |
| while ((redundantCol = findEqualCols(oldCols_, currentCol, i+1)) != NULL) |
| oldCols_.remove(redundantCol); |
| } |
| } |
| |
| // Remove redundant NEW columns |
| if (!newCols_.isEmpty()) |
| { |
| for (i=0; i<newCols_.entries()-1; i++) |
| { |
| currentCol = newCols_[i]; |
| |
| // Search the mappings after currentCol in the list |
| while ((redundantCol = findEqualCols(newCols_, currentCol, i+1)) != NULL) |
| newCols_.remove(redundantCol); |
| } |
| } |
| |
| // Move common columns to commonCols |
| if (!oldCols_.isEmpty() && !newCols_.isEmpty()) |
| { |
| for (i=0; i<oldCols_.entries(); i++) |
| { |
| ColumnMapping *oldCol = oldCols_[i]; |
| |
| // Search the entire other list. |
| ColumnMapping *newCol = findEqualCols(newCols_, oldCol, 0); |
| if (newCol != NULL) |
| { |
| // We keep both common columns for now, because even though they are |
| // equal, their mapping object is not identical - the name and ValueId |
| // are different, and are used later. Only one of the two common lists |
| // will actually be used later, but we don't yet know which one. |
| newCommonCols_.insert(newCol); |
| oldCommonCols_.insert(oldCol); |
| newCols_.remove(newCol); |
| oldCols_.remove(oldCol); |
| |
| // Compensate for entry number i, that was just deleted from this list. |
| i--; |
| } |
| } |
| } |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // Third phase: find out if we really need both the OLD and the NEW tables. |
| // Even an Update trigger that uses both OLD and NEW values can be optimized to |
| // use just one temp Scan node, if all the columns used from one of the tables |
| // are common (are in the same VEG) because they were not changed. |
| //----------------------------------------------------------------------------------- |
| void OptColumnMapping::checkWhichTablesAreNeeded() |
| { |
| if (!oldCommonCols_.isEmpty()) |
| { |
| // There are common columns - check if one of the other lists is empty. |
| |
| if (newCols_.isEmpty()) |
| { |
| // No new cols - use the common cols as old. |
| oldCols_.insert(oldCommonCols_); |
| } |
| else |
| { |
| // Use the common cols as new. |
| newCols_.insert(newCommonCols_); |
| } |
| |
| oldCommonCols_.clear(); |
| newCommonCols_.clear(); |
| } |
| |
| CMPASSERT(oldCommonCols_.isEmpty()); |
| CMPASSERT(newCommonCols_.isEmpty()); |
| |
| // OK, now that we fixed the commonCols, let's see what we have left. |
| if (!newCols_.isEmpty()) |
| { |
| // If we get here, we definetly need the NEW columns. |
| newNeeded_ = TRUE; |
| mappingArray_.insert(newCols_); |
| newCols_.clear(); |
| } |
| |
| if (!oldCols_.isEmpty()) |
| { |
| // If we get here, we definetly need the OLD columns. |
| oldNeeded_ = TRUE; |
| mappingArray_.insert(oldCols_); |
| oldCols_.clear(); |
| } |
| |
| CMPASSERT(newNeeded_ || oldNeeded_); |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // Build a select list on top of the temp Scan or Join of temp Scans. |
| // This select list has only the minimal columns needed by the triggers. Binding |
| // and normalizing the tree with this select list will guarantee minimal outputs. |
| //----------------------------------------------------------------------------------- |
| ItemExpr *OptColumnMapping::buildSelectListFromNeededInputs() |
| { |
| ItemExpr *selectList = NULL; |
| |
| for (CollIndex i=0; i<mappingArray_.entries(); i++) |
| { |
| ColumnMapping *currentCol = mappingArray_[i]; |
| |
| // Create a ColReference to the OLD or NEW column. |
| ItemExpr *colRef = new(heap_) |
| ColReference(new(heap_) ColRefName(*currentCol->colName_, heap_)); |
| |
| // Add it to the list. |
| if (selectList == NULL) |
| selectList = colRef; |
| else |
| selectList = new(heap_) ItemList(selectList, colRef); |
| } |
| |
| return selectList; |
| } |
| |
| //----------------------------------------------------------------------------------- |
| // Build the MapValueIds node above the bound temp Scan tree. |
| // For each entry in the (minimized) mapping list, find the corresponding bottom |
| // ValueId in the RETDesc of the bound tree, and add a mapEntry using the top and |
| // bottom ValueIds. |
| // Also update the outputs of the MapValueIds node accordingly. |
| //----------------------------------------------------------------------------------- |
| RelExpr *OptColumnMapping::buildMapValueIdNode(RelExpr *tempScanTree) |
| { |
| ValueIdSet OutputsOfMap; |
| ValueIdSet tempScanOutputs(tempScanTree->getGroupAttr()->getCharacteristicOutputs()); |
| ValueIdMap *map = new(heap_) ValueIdMap; |
| const RETDesc *bottomRetDesc = tempScanTree->getRETDesc(); |
| |
| for (CollIndex i=0; i<mappingArray_.entries(); i++) |
| { |
| ColumnMapping *currentCol = mappingArray_[i]; |
| |
| // Add the topValueId to the outputs of the MapValueIds node. |
| OutputsOfMap += currentCol->topValueId_; |
| |
| // Find the bottom ValueId from the RETDesc. |
| ValueId bottomValueId = |
| bottomRetDesc->findColumn(*currentCol->colName_)->getValueId(); |
| |
| // Find the VEG from the outputs of the temp Scan tree, that references |
| // the ValueId we found in the RETDesc. |
| ValueId bottomVeg; |
| if (!tempScanOutputs.referencesTheGivenValue(bottomValueId, bottomVeg)) |
| CMPASSERT(FALSE); |
| |
| // Add a new mapping entry for the MapValeIds node. |
| map->addMapEntry(currentCol->topValueId_, bottomVeg); |
| } |
| |
| // Create the MapValueIds node with the mapping. |
| MapValueIds *mapNode = new(heap_) MapValueIds(tempScanTree, *map); |
| |
| // The outputs are the top ValueIds. |
| mapNode->getGroupAttr()->setCharacteristicOutputs(OutputsOfMap); |
| |
| // The only input is the ExecId. |
| ValueId topExecId = bindInfo_->getExecuteId()->getValueId(); |
| mapNode->getGroupAttr()->addCharacteristicInputs(topExecId); |
| |
| return mapNode; |
| } |
| |