| /********************************************************************** |
| // @@@ 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: Inlining.cpp |
| * Description: Methods for the inlining of triggers. |
| * |
| * Created: 6/23/98 |
| * Language: C++ |
| * Status: $State: Exp $ |
| * |
| */ |
| |
| #define INITIALIZE_OLD_AND_NEW_NAMES // used in Triggers.h |
| #define SQLPARSERGLOBALS_FLAGS // must precede all #include's |
| #define SQLPARSERGLOBALS_CONTEXT_AND_DIAGS |
| |
| #include "Sqlcomp.h" |
| #include "AllItemExpr.h" |
| #include "AllRelExpr.h" |
| #include "BindWA.h" |
| #include "GroupAttr.h" |
| #include "parser.h" |
| #include "StmtNode.h" |
| #include "Inlining.h" |
| #include "ex_error.h" |
| #include "Triggers.h" |
| #include "TriggerDB.h" |
| #include "TriggerEnable.h" |
| #include "StmtDDLCreateTrigger.h" |
| #include "MVInfo.h" |
| #include "Refresh.h" |
| #include "ChangesTable.h" |
| #include "MvRefreshBuilder.h" |
| #include "MjvBuilder.h" |
| #include "ItmFlowControlFunction.h" |
| #include <CmpMain.h> |
| #include "RelSequence.h" |
| |
| #ifdef NA_DEBUG_GUI |
| #include "ComSqlcmpdbg.h" |
| #endif |
| |
| #include "SqlParserGlobals.h" // must be last #include |
| |
| #define DISABLE_TRIGGERS 0 |
| #define DISABLE_RI 0 |
| |
| extern THREAD_P NABoolean GU_DEBUG; |
| |
| static const char NEWTable [] = "NEW"; // QSTUFF: corr for embedded d/u |
| static const char OLDTable [] = "OLD"; // QSTUFF: corr for embedded d/u |
| |
| /******************************************************************************* |
| **** Independant Utility Functions |
| ****************************************************************************** |
| *****************************************************************************/ |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Create a CorrName to the temp table from the subject table name. |
| ////////////////////////////////////////////////////////////////////////////// |
| /*static CorrName *calcTempTableName(const CorrName &theTable, CollHeap *heap) |
| { |
| const QualifiedName &tableName = theTable.getQualifiedNameObj(); |
| |
| CorrName *result = new(heap) |
| CorrName(subjectNameToTrigTemp(tableName.getObjectName()), |
| heap, |
| tableName.getSchemaName(), |
| tableName.getCatalogName()); |
| |
| // Specify the trigger temporary table namespace. |
| result->setSpecialType(ExtendedQualName::TRIGTEMP_TABLE); |
| return result; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Does this column name start with NEW_COLUMN_PREFIX? |
| ////////////////////////////////////////////////////////////////////////////// |
| static NABoolean isNewCol(const NAString& colName) |
| { |
| if (colName.length() < sizeof(NEW_COLUMN_PREFIX)) |
| return FALSE; |
| |
| return (colName(0,sizeof(NEW_COLUMN_PREFIX)-1) == NEW_COLUMN_PREFIX); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Does this column name start with OLD_COLUMN_PREFIX? |
| ////////////////////////////////////////////////////////////////////////////// |
| static NABoolean isOldCol(const NAString& colName) |
| { |
| if (colName.length() < sizeof(OLD_COLUMN_PREFIX)) |
| return FALSE; |
| |
| return (colName(0,sizeof(OLD_COLUMN_PREFIX)-1) == OLD_COLUMN_PREFIX); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Remove the temp table column name prefix. |
| ////////////////////////////////////////////////////////////////////////////// |
| static void FixTempColName(NAString *colName) |
| { |
| CMPASSERT (sizeof(OLD_COLUMN_PREFIX) == sizeof(NEW_COLUMN_PREFIX)); |
| |
| colName->remove(0,sizeof(OLD_COLUMN_PREFIX)-1); // remove the prefix |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Does this column name from the temp table contain the @ sign? |
| // If so - it must be either a NEW@ or an OLD@ column. |
| // If not - it must be part of either the primary or clustering keys. |
| ////////////////////////////////////////////////////////////////////////////// |
| static NABoolean isSingleCopyColumn(const NAString& colName) |
| { |
| return !colName.contains(NON_SQL_TEXT_CHAR); |
| }*/ |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Utility function used by the fixTentativeRETDesc() method below. |
| // Creates a Cast ItemExpr around the parameter, so it stays the same but |
| // gets a new ValueId. |
| ////////////////////////////////////////////////////////////////////////////// |
| static ValueId wrapWithCastExpr(BindWA *bindWA, ValueId col, CollHeap *heap) |
| { |
| ItemExpr *expr = col.getItemExpr(); |
| ItemExpr *cast = new(heap) Cast(expr, col.getType().newCopy(heap)); |
| cast->bindNode(bindWA); |
| return cast->getValueId(); |
| } |
| |
| /***************************************************************************** |
| ****************************************************************************** |
| **** Inlining functions of classes other than GenericUpdate |
| ****************************************************************************** |
| *****************************************************************************/ |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Is this CorrName using a name of a trigger transition table (from the |
| // REFERENCING clause)? |
| // Find if we are in the scope of a trigger action, and if the names match. |
| // onlyNew has a default value of FALSE, which is overridden only for DDL |
| // semantic checks of before triggers. |
| ////////////////////////////////////////////////////////////////////////////// |
| NABoolean CorrName::isATriggerTransitionName(BindWA *bindWA, NABoolean onlyNew) const |
| { |
| BindScope *scope = bindWA->findNextScopeWithTriggerInfo(); |
| |
| if ((scope==NULL) || (scope->context()->triggerObj() == NULL)) |
| return FALSE; |
| |
| const NAString& objName = getQualifiedNameObj().getObjectName(); |
| if (onlyNew) |
| return scope->context()->triggerObj()->isNewTransitionName(objName); |
| else |
| return scope->context()->triggerObj()->isTransitionName(objName); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // This is a Scan on a temp table inside the action of a statement trigger. |
| // Add the uniqifier WHERE expression for it. |
| // Put a RelRoot node with a select list on top of it, to select only the |
| // needed columns. Above the RelRoot, put a RenameTable node to change the |
| // scanned temp-table to the corrlation name used in the trigger action. |
| ////////////////////////////////////////////////////////////////////////////// |
| RelExpr *Scan::buildTriggerTransitionTableView(BindWA *bindWA) |
| { |
| BindScope *scope = bindWA->findNextScopeWithTriggerInfo(); |
| |
| CMPASSERT((scope!=NULL) && (scope->context()->triggerObj() != NULL)); |
| |
| StmtDDLCreateTrigger *createTriggerNode = |
| scope->context()->triggerObj(); |
| |
| if (!createTriggerNode->isStatement()) |
| { |
| // 11019 Only statement triggers can select from the transition table. |
| *CmpCommon::diags() << DgSqlCode(-11019); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // The transition table name must not have catalog/schema. |
| QualifiedName& objName = userTableName_.getQualifiedNameObj(); |
| if (objName.getSchemaName() != "") |
| { |
| // *** 4057 Correlation name MYNEW conflicts with qualified identifier of table CAT.SCHM.MYNEW. |
| *CmpCommon::diags() << DgSqlCode(-4057) |
| << DgString0(objName.getObjectName()) |
| << DgTableName(objName.getQualifiedNameAsAnsiString()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| if (bindWA->inDDL()) |
| { |
| // This bind is for DDL semantic checks during a CREATE TRIGGER statement. |
| // When the first trigger is created, the temp table does not yet exist, |
| // so we replace the scan on the temp table by a scan on the subject |
| // table. This is actually simpler since the column names stay the same. |
| |
| // Set the name of the table to be scanned to be the fully qualified |
| // subject table. |
| if (userTableName_.getCorrNameAsString() == "") |
| userTableName_.setCorrName(objName.getObjectName()); |
| objName = createTriggerNode->getTableNameObject(); |
| |
| // Don't fix the name in the LocList. |
| userTableName_.getQualifiedNameObj().setNamePosition(0); |
| |
| // Use the RI flag to skip incrementing the NATable reference counter. |
| NABoolean inRIFlag = bindWA->getCurrentScope()->context()->inRIConstraint(); |
| bindWA->getCurrentScope()->context()->inRIConstraint() = TRUE; |
| |
| RelExpr *boundNode = bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| // Remove the SYSKEY column from the current RETDesc. It is illegal |
| // for a trigger action to select the SYSKEY from the transition table. |
| ColRefName syskeyName("SYSKEY"); |
| bindWA->getCurrentScope()->getRETDesc()->delColumn(bindWA, syskeyName, SYSTEM_COLUMN); |
| |
| // Restore the previous BindWA state. |
| bindWA->getCurrentScope()->context()->inRIConstraint() = inRIFlag; |
| |
| return boundNode; |
| } |
| else |
| { |
| // OK - this is DML time - build the select list for the root and rename. |
| |
| QualifiedName& thisTable = userTableName_.getQualifiedNameObj(); |
| |
| ChangesTable::RowsType scanType; |
| if (createTriggerNode->isOldTransitionName(thisTable.getObjectName())) |
| { |
| scanType = ChangesTable::DELETED_ROWS; |
| } |
| else |
| { |
| CMPASSERT(createTriggerNode->isNewTransitionName(thisTable.getObjectName())); |
| scanType = ChangesTable::INSERTED_ROWS; |
| } |
| |
| CorrName subjectTable = createTriggerNode->getTableNameObject(); |
| subjectTable.setCorrName(userTableName_.getExposedNameAsString()); |
| |
| TriggersTempTable tempTableObj(subjectTable, this, scanType, bindWA); |
| |
| RelExpr *transformedScan = tempTableObj.transformScan(); |
| |
| return transformedScan->bindNode(bindWA); // Bind the result |
| } |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // This method does the insertion of a MVImmediate trigger in order to |
| // refresh the specified ON STATEMENT MJV after an insert operation. |
| // |
| // The refresh action is by default implemented as a statement after trigger. |
| // This implies that the trigger is always driven by a scan from the |
| // temp-table. The overhead of inserting into the temp-table, scanning it, and |
| // deleting from it afterwards is relatively high when the number of rows is |
| // quite small. So, when the insert is driven by a Tuple or TupleList node |
| // (with up to a certain number of tuples in it), we implement the refresh |
| // action as a row after trigger, and thus avoid using the temp-table at whole. |
| // When before triggers exist we don't consider the optimization, since the |
| // values in the Tuple (TupleList) are not the actual values inserted into the |
| // table (they are modified by the before trigger). |
| // |
| ////////////////////////////////////////////////////////////////////////////// |
| void Insert::insertMvToTriggerList(BeforeAndAfterTriggers *list, |
| BindWA *bindWA, |
| CollHeap *heap, |
| const QualifiedName &mvName, |
| MVInfoForDML *mvInfo, |
| const QualifiedName &subjectTable, |
| UpdateColumns *updatedCols) |
| { |
| CMPASSERT(getOperatorType() == REL_UNARY_INSERT); |
| |
| CorrName mvCorrName(mvName, heap); |
| // The namespace is set in order to allow special update directly on the MV |
| mvCorrName.setSpecialType(ExtendedQualName::MV_TABLE); |
| |
| // Instansiate the apprpriate builder |
| MjvImmInsertBuilder *triggerBuilder = new(heap) |
| MjvImmInsertBuilder(mvCorrName, mvInfo, this, bindWA); |
| |
| // By default, the refresh is implemented as an after statement trigger |
| ComGranularity granularity = COM_STATEMENT; |
| |
| // If MV_AS_ROW_TRIGGER default value is ON or if we deal with insert of only |
| // 1 row (i.e. the child is REL_TUPLE), generate the optimized refresh tree, |
| // in which case the refresh is implemented as a row after trigger. |
| if (child(0)->getOperatorType() == REL_TUPLE || |
| CmpCommon::getDefault(MV_AS_ROW_TRIGGER) == DF_ON) |
| { |
| // build the optimized version of the tree as requested |
| granularity = COM_ROW; |
| triggerBuilder->optimizeForFewRows(); |
| } |
| |
| // Instansiate the MVImmediate trigger |
| MVImmediate *mvTrigger = new(heap) MVImmediate(bindWA, |
| triggerBuilder, |
| mvName, |
| subjectTable, |
| COM_INSERT, |
| granularity, |
| updatedCols); |
| |
| // Register the trigger in the general list of triggers |
| |
| if (granularity == COM_STATEMENT) |
| { |
| list->addNewAfterStatementTrigger(mvTrigger); |
| } |
| else |
| { |
| list->addNewAfterRowTrigger(mvTrigger); |
| } |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // This method does the insertion of a MVImmediate trigger in order to |
| // refresh the specified ON STATEMENT MJV after an update operation. |
| // |
| // The update operation may be one of two types: |
| // |
| // 1. NONE of the updated columns participate in the clustering index of |
| // the table and/or join predicate of the MJV. This is called direct update. |
| // 2. Some of the above columns are updated. This is called indirect update. |
| // |
| // For direct update, a single row trigger that directly updates the |
| // appropriate columns is sufficient to refresh the MJV. |
| // |
| // For indirect update, we should have two MVImmediate triggers defined: |
| // 1) a row after trigger that deletes each row in the MJV that corresponds to |
| // the updated ones. |
| // 2) a statement after trigger that inserts all the rows that result from |
| // applying the join expression of the MJV between the delta on the subject |
| // table (the updated rows sotred in the temp-table) and the other tables |
| // participating in the MJV (the ones that were not changed). |
| // |
| ////////////////////////////////////////////////////////////////////////////// |
| void Update::insertMvToTriggerList(BeforeAndAfterTriggers *list, |
| BindWA *bindWA, |
| CollHeap *heap, |
| const QualifiedName &mvName, |
| MVInfoForDML *mvInfo, |
| const QualifiedName &subjectTable, |
| UpdateColumns *updatedCols) |
| { |
| CMPASSERT(getOperatorType() == REL_UNARY_UPDATE); |
| |
| CorrName mvCorrName(mvName, heap); |
| // The namespace is set in order to allow special update directly on the MV |
| mvCorrName.setSpecialType(ExtendedQualName::MV_TABLE); |
| |
| switch(checkUpdateType(mvInfo, subjectTable, updatedCols)) |
| { |
| case DIRECT: |
| { |
| ///////////////////////////////////////////////////////// |
| // adding a row trigger to directly update rows in the MV |
| ///////////////////////////////////////////////////////// |
| |
| // Instansiate the apprpriate builder |
| MvRefreshBuilder *triggerBuilder = new(heap) |
| MjvImmDirectUpdateBuilder(mvCorrName, mvInfo, this, bindWA); |
| |
| // Instansiate the MVImmediate trigger |
| MVImmediate *mvTrigger = new(heap) MVImmediate(bindWA, |
| triggerBuilder, |
| mvName, |
| subjectTable, |
| COM_UPDATE, |
| COM_ROW, |
| updatedCols); |
| |
| // Register the trigger in the general list of triggers |
| list->addNewAfterRowTrigger(mvTrigger); |
| break; |
| } |
| case INDIRECT: |
| { |
| /////////////////////////////////////////////////////////////////// |
| // PART I: adding a row trigger to delete updated rows from the MJV |
| /////////////////////////////////////////////////////////////////// |
| |
| // Instansiate the apprpriate builder |
| MvRefreshBuilder *rowTriggerBuilder = new(heap) |
| MjvImmDeleteBuilder(mvCorrName, mvInfo, this, bindWA); |
| |
| // Instansiate the MVImmediate trigger |
| MVImmediate *mvRowTrigger = new(heap) MVImmediate(bindWA, |
| rowTriggerBuilder, |
| mvName, |
| subjectTable, |
| COM_DELETE, |
| COM_ROW, |
| updatedCols); |
| |
| // Register the trigger in the general list of triggers |
| list->addNewAfterRowTrigger(mvRowTrigger); |
| |
| ////////////////////////////////////////////////////////////////////////// |
| // PART II: adding a statement trigger to insert the new rows into the MJV |
| ////////////////////////////////////////////////////////////////////////// |
| |
| // Instansiate the apprpriate builder |
| MvRefreshBuilder *stmtTriggerBuilder = new(heap) |
| MjvImmInsertBuilder(mvCorrName, mvInfo, this, bindWA); |
| |
| // Instansiate the MVImmediate trigger |
| MVImmediate *mvStmtTrigger = new(heap) MVImmediate(bindWA, |
| stmtTriggerBuilder, |
| mvName, |
| subjectTable, |
| COM_INSERT, |
| COM_STATEMENT, |
| updatedCols); |
| |
| // Register the trigger in the general list of triggers |
| list->addNewAfterStatementTrigger(mvStmtTrigger); |
| break; |
| } |
| default: |
| break; // update is IRELEVANT - nothing to do |
| } |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // This method checks which type of update is it: |
| // IRELEVANT, DIRECT or INDIRECT. |
| // |
| // An update is considered as IRELEVANT until proven otherwise. The list of |
| // columns of the subject table that are in use by the MJV is scanned. For |
| // each column we check if it is updated in the current update operation. If |
| // so, the update is not IRELEVANT anymore, and is considered DIRECT until |
| // proven otherwise. Once we find an indirect-update column that is updated |
| // in the current operation, the update is considered INDIRECT and the loop |
| // is stopped at once. MJV columns of type complex cause INDIRECT update also. |
| // |
| ////////////////////////////////////////////////////////////////////////////// |
| Update::MvUpdateType Update::checkUpdateType(MVInfoForDML *mvInfo, |
| const QualifiedName &subjectTable, |
| UpdateColumns *updatedCols) const |
| { |
| MvUpdateType updateType = IRELEVANT; |
| mvInfo->initUsedObjectsHash(); // initialization for the searching |
| const MVUsedObjectInfo *mvUseInfo = mvInfo->findUsedInfoForTable(subjectTable); |
| const LIST (Lng32) &colsUsedByMv = mvUseInfo->getUsedColumnList(); |
| for (CollIndex i = 0; i < colsUsedByMv.entries(); i++ ) |
| { |
| // Check whether the column was updated in the current update operation. |
| // If so, this update is not IRELEVANT anymore. Otherwise, skip this column. |
| if (updatedCols->contains(colsUsedByMv[i])) |
| { |
| updateType = DIRECT; |
| } |
| else |
| { |
| continue; |
| } |
| |
| // Check whether the column is an indirect-update column in the MJV, which |
| // means that updating it may not only affect the corresponding row in the |
| // MJV, but also affect other rows in it. |
| if (mvUseInfo->isIndirectUpdateCol(colsUsedByMv[i])) |
| { |
| updateType = INDIRECT; |
| break; // no further search is needed |
| } |
| } |
| |
| return updateType; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // This method does the insertion of a MVImmediate trigger in order to |
| // refresh the specified ON STATEMENT MJV after a delete operation. |
| // |
| // The refresh action is implemented as a row after trigger. |
| // |
| ////////////////////////////////////////////////////////////////////////////// |
| void Delete::insertMvToTriggerList(BeforeAndAfterTriggers *list, |
| BindWA *bindWA, |
| CollHeap *heap, |
| const QualifiedName &mvName, |
| MVInfoForDML *mvInfo, |
| const QualifiedName &subjectTable, |
| UpdateColumns *updatedCols) |
| { |
| CMPASSERT(getOperatorType() == REL_UNARY_DELETE); |
| |
| CorrName mvCorrName(mvName, heap); |
| // The namespace is set in order to allow special update directly on the MV |
| mvCorrName.setSpecialType(ExtendedQualName::MV_TABLE); |
| |
| // Instansiate the apprpriate builder |
| MvRefreshBuilder *triggerBuilder = new(heap) |
| MjvImmDeleteBuilder(mvCorrName, mvInfo, this, bindWA); |
| |
| // Instansiate the MVImmediate trigger |
| MVImmediate *mvTrigger = new(heap) MVImmediate(bindWA, |
| triggerBuilder, |
| mvName, |
| subjectTable, |
| COM_DELETE, |
| COM_ROW, |
| updatedCols); |
| |
| // Register the trigger in the general list of triggers |
| list->addNewAfterRowTrigger(mvTrigger); |
| } |
| |
| /***************************************************************************** |
| ****************************************************************************** |
| **** Inlining methods of the GenericUpdate class |
| ****************************************************************************** |
| *****************************************************************************/ |
| |
| // This method was originaly called |
| // setRETDescForTSJTree(). |
| // |
| // For GenericUpdate Referential Integrity, Index Maintenance and Triggers |
| // (for IM, compare the createIM*() functions). |
| // |
| // Note that, if a RETDesc needs to be created, correlation names of |
| // "OLD@" and/or "NEW@" are given to the tables and columns. |
| // These names are safe to use because they contain a special non-Ansi |
| // character (the "@"), which is only accepted by Parser if a special |
| // "internal-only" flag is set as described for RI below. |
| // |
| // (Without the "@", the names "OLD" and "NEW" would NOT be safe to use: |
| // although they are in ReservedWords.h's PotentialAnsiReservedWords list, |
| // RI and IM could have a name collision ambiguity or misbinding due to a |
| // user table or index named "OLD" or "NEW" as a *delimited identifier*. |
| // Note also that since RI does need to use the Parser, we can't use |
| // CorrName::isFabricated()...) |
| // |
| // This function always sets gu's RETDesc to *non-empty*, signifying that |
| // this GenericUpdate produces outputs. This RETDesc is also context |
| // for our being called multiple times. |
| // |
| // Concatenate **internal** correlation names (used by Binder) |
| // and **external** Ansi delimited-identifier names (used by Parser only). |
| // |
| // RI will use the latter set of names when building parseable predicate text; |
| // i.e. it should pass them to RefConstraint::getPredicateText(). |
| // |
| |
| RETDesc *GenericUpdate::createOldAndNewCorrelationNames(BindWA *bindWA, NABoolean createRETDescOnly) |
| { |
| CMPASSERT(getOperatorType() == REL_UNARY_INSERT || |
| getOperatorType() == REL_UNARY_UPDATE || |
| getOperatorType() == REL_UNARY_DELETE); |
| |
| CMPASSERT(getRETDesc()); |
| CMPASSERT(getRETDesc()->isEmpty()); |
| |
| RETDesc *rd; |
| |
| if (getOperatorType() != REL_UNARY_DELETE) |
| { |
| // INSERT or UPDATE -- |
| // GenericUpdate::bindNode has previously set its TableDesc to the |
| // desc whose column valueid's represent the new/source/after values. |
| // Put these into a new RETDesc with columns all named "NEW@.<colname>" |
| // |
| CorrName corrName(getTableDesc()->getCorrNameObj().getQualifiedNameObj(), |
| bindWA->wHeap(), |
| NEWCorr); |
| rd = new (bindWA->wHeap()) RETDesc(bindWA, getTableDesc(), &corrName); |
| |
| // IM: Add all columns in rd as local reference to current scope. |
| // This is needed if the current scope describes a view which selects |
| // a proper subset of all the basetable columns -- here we say that |
| // yes, this view can produce *all* the b.t.cols as outputs... |
| // |
| ValueIdList vidList; |
| rd->getValueIdList(vidList, USER_AND_SYSTEM_COLUMNS); |
| for (CollIndex i = 0; i < vidList.entries(); i++) |
| bindWA->getCurrentScope()->addLocalRef(vidList[i]); |
| |
| // ##IM:## Having done the above for local refs, oughtn't we also now |
| // ##recompute inputs? |
| // ## bindWA->getCurrentScope()->mergeOuterRefs(bindWA->getCurrentScope()->getOuterRefs()); |
| // ## gu->getGroupAttr()->addCharacteristicInputs(bindWA->getCurrentScope()->getOuterRefs()); |
| } |
| else |
| { |
| // DELETE -- init an empty RETDesc, and next merge in "OLD@.<col>"'s |
| rd = new (bindWA->wHeap()) RETDesc(bindWA); |
| } |
| |
| |
| if ((getOperatorType() != REL_UNARY_INSERT) || |
| getUpdateCKorUniqueIndexKey() || |
| ((getOperatorType() == REL_UNARY_INSERT) &&((Insert *)this)->isMerge()) || |
| ((getOperatorType() == REL_UNARY_INSERT) &&((Insert *)this)->xformedEffUpsert())) |
| { |
| // DELETE or UPDATE -- |
| // Now merge the old/target/before valueid's (the Scan child RETDesc) |
| // into this RETDesc such that these cols are all named "OLD@.<col>" |
| // |
| Scan *scan ; |
| if (getOperatorType() != REL_UNARY_INSERT) |
| scan = getScanNode(); |
| else |
| scan = getLeftmostScanNode(); |
| |
| if ((getOperatorType() == REL_UNARY_INSERT) &&((Insert *)this)->xformedEffUpsert()) |
| { |
| RelSequence *olapChild = getOlapChild(); |
| CorrName corrName(getTableDesc()->getCorrNameObj().getQualifiedNameObj(), |
| bindWA->wHeap(), |
| OLDCorr); |
| |
| for (short i = 0; i< olapChild->getRETDesc()->getDegree();i++) |
| { |
| // we remembered if the original columns was from the right side of |
| // this olap node so add those to the RetDesc since those are the |
| //ones we want to delete from the dependent indexes. |
| if ((olapChild->getRETDesc()->getValueId(i)).getItemExpr()->origOpType() == ITM_INSTANTIATE_NULL) |
| { |
| rd->addColumn(bindWA, |
| ColRefName(olapChild->getRETDesc()->getColRefNameObj(i).getColName(), corrName), |
| olapChild->getRETDesc()->getValueId(i), |
| USER_COLUMN, |
| olapChild->getRETDesc()->getHeading(i)); |
| |
| } |
| } |
| rd->addColumns(bindWA, *olapChild->getRETDesc()->getSystemColumnList(), SYSTEM_COLUMN,&corrName); |
| |
| |
| } |
| |
| else |
| { |
| CMPASSERT(scan); |
| CorrName corrName(scan->getTableDesc()->getCorrNameObj().getQualifiedNameObj(), |
| bindWA->wHeap(), |
| OLDCorr); |
| |
| rd->addColumns(bindWA, *scan->getRETDesc(), &corrName); |
| } |
| } |
| |
| |
| Set_SqlParser_Flags(ALLOW_FUNNY_IDENTIFIER); // allow "@" processing |
| Set_SqlParser_Flags(DELAYED_RESET); // allow multiple parser calls. |
| |
| CMPASSERT(!rd->isEmpty()); |
| |
| // we only need the RETDesc to use it later for the transformation. Don't change the |
| // RETDesc of the current operator or the current bind scope. |
| if (!createRETDescOnly) |
| { |
| delete getRETDesc(); // safe because empty |
| setRETDesc(rd); |
| bindWA->getCurrentScope()->setRETDesc(rd); |
| } |
| |
| return rd; |
| |
| } // GenericUpdate::createOldAndNewCorrelationNames() |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Add a virtual column to the RETDesc of this GenericUpdate node. |
| // Currently used for the Unique Execute ID column (before triggers), |
| // and for MV logging: the current Epoch, row type and row count. |
| ////////////////////////////////////////////////////////////////////////////// |
| ValueId GenericUpdate::addVirtualColumn(BindWA *bindWA, |
| ItemExpr *colExpr, |
| const char *colName, |
| CollHeap *heap) |
| { |
| RETDesc *retDesc = getRETDesc(); |
| |
| colExpr->bindNode(bindWA); |
| ValueId exprValueId = wrapWithCastExpr(bindWA, colExpr->getValueId(), heap); |
| ColRefName virtualColName(colName); |
| retDesc->addColumn(bindWA, virtualColName, exprValueId); |
| return exprValueId; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Inline the temp insert sub-tree directly into the backbone. |
| // The resulting tree looks like this: |
| // TSJ |
| // / \ |
| // topNode RelRoot |
| // | |
| // LeafInsert |
| ////////////////////////////////////////////////////////////////////////////// |
| RelExpr *GenericUpdate::inlineTempInsert(RelExpr *topNode, |
| BindWA *bindWA, |
| TriggersTempTable& tempTableObj, |
| NABoolean isDrivenByBeforeTriggers, |
| NABoolean isTopMostTSJ, |
| CollHeap *heap) |
| { |
| // The new top node should be a left TSJ for after triggers, |
| // and a normal TSJ for before triggers. |
| OperatorTypeEnum JoinType = |
| ( (isDrivenByBeforeTriggers || isTopMostTSJ) |
| ? REL_TSJ |
| : REL_LEFT_TSJ); |
| |
| if (isDrivenByBeforeTriggers) |
| tempTableObj.setBeforeTriggersExist(); |
| |
| RelExpr *insertNode = tempTableObj.buildInsert(!isDrivenByBeforeTriggers); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| RelRoot *rootNode = new (heap) RelRoot(insertNode); |
| rootNode->setRootFlag(FALSE); |
| rootNode->setEmptySelectList(); |
| |
| topNode = new(heap) Join(topNode, rootNode, JoinType); |
| topNode->getInliningInfo().setFlags(II_DrivingTempInsert | |
| II_SingleExecutionForTriggersTSJ | |
| II_AccessSetNeeded); |
| // Indicate to the Normalizer that this TSJ cannot be optimized away, |
| // and that if the write operation is implemented via a cursor then |
| // it cannot be a "flow" cursor operation - i.e. it cannot utilize |
| // a TSJFlow node. |
| // Genesis case #10-990116-7164 |
| ((Join *)topNode)->setTSJForWrite(TRUE); |
| if (bindWA->getHostArraysArea() && bindWA->getHostArraysArea()->getTolerateNonFatalError()) |
| { |
| ((Join *)topNode)->setTolerateNonFatalError(RelExpr::NOT_ATOMIC_); |
| ((Join *)topNode)->setTSJForSetNFError(TRUE); |
| } |
| return topNode; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Do we need Index Maintenance on this GenericUpdate node? |
| // We do if there are indexes except the clustering index. In case this is |
| // an Update, at least one such index must match ant of the columns actually |
| // being updated. |
| ////////////////////////////////////////////////////////////////////////////// |
| NABoolean GenericUpdate::isIMNeeded(UpdateColumns *updatedColumns) |
| { |
| NABoolean imNeeded = FALSE; |
| |
| if (!getTableDesc()->hasSecondaryIndexes()) |
| return FALSE; |
| |
| const LIST(IndexDesc *) indexList = getTableDesc()->getIndexes(); |
| for (CollIndex i=0; (i<indexList.entries()) && !imNeeded; i++) |
| { |
| IndexDesc *index = indexList[i]; |
| |
| // The base table itself is an index (the clustering index); |
| // obviously IM need not deal with it. |
| if (index->isClusteringIndex()) |
| continue; |
| |
| // An index always needs maintenance on an Insert or Delete... |
| if((getOperatorType() != REL_UNARY_UPDATE) || |
| (isMerge())) |
| imNeeded = TRUE; |
| else |
| { |
| // This is Update - check if columns match. |
| CMPASSERT(updatedColumns!=NULL); |
| const ValueIdList &indexColumns = index->getIndexColumns(); |
| for (CollIndex j=0; j < indexColumns.entries() && !imNeeded; j++) |
| { |
| Lng32 indexCol = indexColumns[j].getNAColumn()->getPosition(); |
| if (updatedColumns->contains(indexCol)) |
| { |
| imNeeded = TRUE; |
| break; |
| } |
| } // for k |
| } // else |
| } // for j |
| |
| return imNeeded; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Inline Index Maintainance. |
| // We get here only if isIMNeeded() returned TRUE. |
| // The result looks like this: |
| // TSJ |
| // / \ |
| // topNode IM |
| ////////////////////////////////////////////////////////////////////////////// |
| RelExpr *GenericUpdate::inlineIM(RelExpr *topNode, |
| BindWA *bindWA, |
| NABoolean isLastTSJ, |
| UpdateColumns *updatedColumns, |
| CollHeap *heap, |
| NABoolean useInternalSyskey, |
| NABoolean rowTriggersPresents) |
| { |
| // Create the tree that handles Index Maintainance. |
| RelExpr *imTree = createIMTree(bindWA, updatedColumns, useInternalSyskey); |
| |
| // If no IM tree was created than we have a bug! |
| CMPASSERT(imTree != NULL); |
| |
| if (bindWA->isTrafLoadPrep()) |
| return imTree; |
| |
| // Drive IM and RI trees using a TSJ on top of the GenericUpdate node. |
| topNode = new(bindWA->wHeap()) |
| Join(topNode, |
| imTree, |
| (isLastTSJ ? REL_TSJ : REL_LEFT_TSJ)); |
| topNode->getInliningInfo().setFlags(II_DrivingIM | II_AccessSetNeeded); |
| if (rowTriggersPresents) |
| topNode->getInliningInfo().setFlags(II_SingleExecutionForTriggersTSJ); |
| // Indicate to the Normalizer that this TSJ cannot be optimized away, |
| // and that if the write operation is implemented via a cursor then |
| // it cannot be a "flow" cursor operation - i.e. it cannot utilize |
| // a TSJFlow node. |
| // Genesis case #10-990116-7164 |
| ((Join *)topNode)->setTSJForWrite(TRUE); |
| |
| return topNode; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Add to a trigger WHEN clause, the check if the trigger is enabled. |
| // Result condition ItemExpr tree looks like this: |
| // |
| // AND |
| // / \ |
| // GetBitValueAt WHEN clause |
| // / \ |
| // GetTriggerStatus TriggerIndex |
| // |
| ////////////////////////////////////////////////////////////////////////////// |
| static ItemExpr *addCheckForTriggerEnabled(BindWA *bindWA, |
| ItemExpr *whenClause, |
| Trigger *triggerObj, |
| CollHeap *heap) |
| { |
| // Register the trigger timestamp in the list managed by bindWA. The |
| // returned value is the index into the trigger array for this RelExpr |
| CollIndex triggerIndex = bindWA->addTrigger(triggerObj->getTimeStamp()); |
| |
| CollIndex MaxTriggersPerStatement = MAX_TRIGGERS_PER_STATEMENT; |
| |
| // debugging for coverage |
| #ifndef NDEBUG |
| char* env = getenv("TESTING_MAX_TRIGGERS_PER_STATEMENT"); |
| if (env) |
| { |
| MaxTriggersPerStatement = atol(env); |
| } |
| #endif |
| |
| if (triggerIndex >= MaxTriggersPerStatement) |
| { |
| // There are more than 256 triggers in this statement |
| *CmpCommon::diags() << DgSqlCode(-11001); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| ItemExpr *enableCheck = new(heap) |
| GetBitValueAt(new(heap) GetTriggersStatus(), |
| new(heap) ConstValue(triggerIndex) ); |
| |
| // Check if whenClause is empty or TRUE |
| if (whenClause == NULL || whenClause->getOperatorType() == ITM_RETURN_TRUE) |
| return enableCheck; |
| else { |
| |
| /* |
| * for proper typing and transformation use correct AND subtree as follows: |
| * (AND (AND (NOT_NULL GBVA) (NOT_EQUALS GBVA 0)) whenClause) |
| * Here enableCheck is GBVA (GetBitValueAt) |
| */ |
| |
| ItemExpr *notNullNode = new(heap) UnLogic (ITM_IS_NOT_NULL, enableCheck); |
| |
| ItemExpr *constZeroNode = new(heap) ConstValue(0); |
| |
| ItemExpr *notZeroNode = new(heap) BiRelat(ITM_NOT_EQUAL, enableCheck, constZeroNode); |
| |
| enableCheck = new(heap) BiLogic(ITM_AND, notNullNode, notZeroNode); |
| |
| return new(heap) BiLogic(ITM_AND, enableCheck, whenClause); |
| } |
| } |
| |
| |
| // auxiliary function that reutrns an expression that wraps a RaiseError |
| // built-in function. |
| static ItemExpr *addTrigActionExcept (Trigger *trigObj, CollHeap *heap) |
| { |
| ItemExpr *trigActionExceptExprPred = |
| new (heap) RaiseError(ComDiags_TrigActionExceptionSQLCODE, |
| trigObj->getTriggerName(), |
| trigObj->getSubjectTableName()); |
| |
| return trigActionExceptExprPred; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // This function is only called for row triggers. Look for all update |
| // nodes in the action of the trigger and flag them as being in the action |
| // of a row trigger |
| ////////////////////////////////////////////////////////////////////////////// |
| static void flagUpdateAsRowTrigger(RelExpr *topNode) |
| { |
| if (topNode) |
| { |
| if (topNode->getOperatorType() == REL_UNARY_UPDATE) |
| { |
| topNode->getInliningInfo().setFlags(II_InActionOfRowTrigger); |
| } |
| |
| flagUpdateAsRowTrigger((RelExpr *)topNode->child(0)); |
| flagUpdateAsRowTrigger((RelExpr *)topNode->child(1)); |
| } |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Statement triggers are blocked by an ordered union node, but then |
| // driven to execute in parallel, by connecting them using a left linear tree |
| // of union nodes. Row triggers are similarly attached to each other, but are |
| // connected to the trigger backbone as part of the "Pipelined actions". |
| // The result looks like this for three triggers: |
| // The trigger transformation code expects this trigger group to be |
| // in a left linear tree structure. |
| // |
| // Statement triggers: Row triggers: |
| // OU |
| // / \ |
| // topNode U U |
| // / \ / \ |
| // U ST3 U RT3 |
| // / \ / \ |
| // ST1 ST2 RT1 RT2 |
| ////////////////////////////////////////////////////////////////////////////// |
| static RelExpr *inlineTriggerGroup(RelExpr *topNode, |
| const TriggerList *triggers, |
| NABoolean isRow, |
| CollHeap *heap, |
| BindWA *bindWA) |
| { |
| RelExpr *topUnion = NULL; |
| |
| if ((triggers == NULL) || (triggers->entries() == 0)) |
| return topNode; |
| |
| // Now do the rest, and connect them with Union nodes. |
| for (CollIndex i=0; i<triggers->entries(); i++) |
| { |
| // Get the Trigger object. |
| Trigger *current = (*triggers)[i]; |
| // Get the trigger action tree. |
| RelExpr *triggerTree = current->getParsedTrigger(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| triggerTree->getInliningInfo().setFlags(II_TriggerRoot); |
| triggerTree->getInliningInfo().setTriggerObject(current); |
| |
| // The check whether the trigger is enabled is applicable only for regular |
| // triggers. ON STATEMENT MVs (MVImmediate triggers) are always enabled. |
| if ( !(current->isMVImmediate()) ) |
| { |
| Union *triggerRoot = (Union *)triggerTree->getChild(0); // Get past the RelRoot |
| if (isRow) |
| triggerRoot = (Union *)triggerRoot->getChild(0); // Get past the RenameReference |
| if (triggerRoot != NULL && triggerRoot->getOperatorType() == REL_UNION) |
| { |
| // mark the update nodes as being in the action of a row trigger |
| // so IM can be set appropriately of this case to avoid a data |
| // corruption - refer to method createIMNodes for more info on this issue |
| flagUpdateAsRowTrigger((RelExpr *)triggerRoot->getChild(0)); |
| ItemExpr *whenClause = triggerRoot->removeCondExprTree(); |
| triggerRoot->addCondExprTree( |
| addCheckForTriggerEnabled(bindWA, whenClause, current, heap)); |
| triggerRoot->addTrigExceptExprTree( |
| addTrigActionExcept(current, heap)); |
| |
| // if we are in a not atomic statement, set a flag in the unary_union node |
| // this flag will passed to the executor through the union tdb. During execution |
| // if condExpr() evaluates to truw and this flag is set, then error -30029 is raised. |
| if (bindWA->getHostArraysArea() && |
| bindWA->getHostArraysArea()->getTolerateNonFatalError()) |
| { |
| triggerRoot->setInNotAtomicStatement(); |
| } |
| } |
| } |
| |
| if (i == 0) // topUnion is still NULL? |
| topUnion = triggerTree; |
| else |
| { |
| topUnion = new(heap) Union(topUnion, triggerTree, NULL, NULL, REL_UNION, |
| CmpCommon::statementHeap(), TRUE); |
| |
| if (bindWA->getHostArraysArea() && |
| bindWA->getHostArraysArea()->getTolerateNonFatalError()) |
| { |
| ((Union *)topUnion)->setInNotAtomicStatement(); |
| } |
| |
| } |
| |
| topUnion->getInliningInfo().setFlags(II_AccessSetNeeded); |
| |
| } |
| |
| if (isRow) |
| return topUnion; |
| else |
| { |
| Union *newOU = new(heap) Union(topNode, topUnion, NULL, NULL, REL_UNION, |
| CmpCommon::statementHeap(), TRUE); |
| newOU->setOrderedUnion(); |
| newOU->setNoOutputs(); |
| newOU->getInliningInfo().setFlags(II_DrivingStatementTrigger | |
| II_AccessSetNeeded); |
| |
| if (bindWA->getHostArraysArea() && |
| bindWA->getHostArraysArea()->getTolerateNonFatalError()) |
| { |
| newOU->setInNotAtomicStatement(); |
| } |
| return newOU; |
| } |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Create a dummy sub-tree as a place-holder for missing pipelined actions |
| // (RI or row triggers), in order to maintain a regular tree. |
| // Such dummy statements are later removed by the trigger transformation code. |
| ////////////////////////////////////////////////////////////////////////////// |
| static RelExpr *createDummyStatement(CollHeap *heap) |
| { |
| Tuple *tupleNode = new(heap) Tuple(new(heap) ConstValue(0)); |
| tupleNode->getInliningInfo().setFlags(II_DummyStatement); |
| |
| RelRoot *result = new RelRoot(tupleNode); |
| result->setRootFlag(FALSE); |
| result->setEmptySelectList(); |
| return result; |
| } |
| |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Inline the pipelined actions: RI and row triggers. |
| // The root node above the TSJ has an empty select-list, so that the Union |
| // node above it will accept it as compatible with it's other child. It also |
| // will not open a new BindScope when bound, so that when the binding will |
| // reach the already bound 'this' node - it will be in the same scope. This |
| // avoids problems when calculating inputs. |
| // |
| // The resulting tree looks like this: |
| // RelRoot |
| // | |
| // TSJ |
| // / \ |
| // topNode U |
| // / \ |
| // RI Row |
| // Triggers |
| ////////////////////////////////////////////////////////////////////////////// |
| RelExpr *GenericUpdate::inlinePipelinedActions(RelExpr *topNode, |
| BindWA *bindWA, |
| TriggerList *rowTriggers, |
| RefConstraintList *riList, |
| CollHeap *heap) |
| { |
| RelExpr *resultTree=topNode; |
| |
| if ((rowTriggers != NULL) || (riList != NULL)) |
| { |
| RelExpr *rowTriggersTree=NULL; |
| RelExpr *riTree=NULL; |
| |
| // Create the tree that handles all row triggers |
| if (rowTriggers != NULL) |
| { |
| rowTriggersTree = inlineTriggerGroup(NULL, |
| rowTriggers, |
| TRUE, |
| heap, |
| bindWA); |
| } |
| else |
| { |
| rowTriggersTree = createDummyStatement(heap); |
| } |
| |
| // Create the tree that handles Referencial Integrity. |
| if (riList != NULL) |
| riTree = inlineRI(bindWA, riList, heap); |
| else |
| riTree = createDummyStatement(heap); |
| |
| resultTree = new(heap) Union(riTree, rowTriggersTree, NULL, NULL, |
| REL_UNION, CmpCommon::statementHeap(), TRUE); |
| resultTree->getInliningInfo().setFlags(II_AccessSetNeeded); |
| if (riList!=NULL) |
| resultTree->getInliningInfo().setFlags(II_DrivingRI); |
| if (rowTriggers != NULL) |
| resultTree->getInliningInfo().setFlags(II_DrivingRowTrigger); |
| |
| // Put a RelRoot on top of the Union to avoid trashing the current scope. |
| resultTree = new(heap) RelRoot(resultTree); |
| ((RelRoot *)resultTree)->setRootFlag(FALSE); |
| ((RelRoot *)resultTree)->setEmptySelectList(); |
| |
| // Drive IM and RI trees using a TSJ on top of the GenericUpdate node. |
| NABoolean needsOutputs = isMtsStatement() || |
| (getUpdateCKorUniqueIndexKey() && (getOperatorType() == REL_UNARY_DELETE)); |
| OperatorTypeEnum joinType = needsOutputs ? REL_ANTI_SEMITSJ : REL_TSJ; |
| resultTree = new(bindWA->wHeap()) Join(topNode, resultTree, joinType); |
| resultTree->getInliningInfo().setFlags(II_AccessSetNeeded); |
| resultTree->getInliningInfo().setFlags(II_DrivingPipelinedActions); |
| |
| // disable parallele execution for TSJs that control row triggers |
| // execution. Parallel execution for triggers TSJ introduces the |
| // potential for non-deterministic execution |
| if (rowTriggers) |
| resultTree->getInliningInfo().setFlags(II_SingleExecutionForTriggersTSJ); |
| |
| // Indicate to the Normalizer that this TSJ cannot be optimized away, |
| // and that if the write operation is implemented via a cursor then |
| // it cannot be a "flow" cursor operation - i.e. it cannot utilize |
| // a TSJFlow node. |
| // Genesis case #10-990116-7164 |
| ((Join *)resultTree)->setTSJForWrite(TRUE); |
| |
| } |
| |
| RelRoot *rootNode = new(heap) RelRoot(resultTree); |
| rootNode->setRootFlag(FALSE); |
| rootNode->setEmptySelectList(); |
| rootNode->setDontOpenNewScope(); |
| |
| return rootNode; |
| } // GenericUpdate::inlinePipelinedActions |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Create the sub-tree that deletes the affected set from the temporary table. |
| // The corresponding SQL text is |
| // DELETE FROM <temp table> WHERE <Uniquifier> = <Uniquifier value>; |
| // |
| // The result looks like this: |
| // OU |
| // / \ |
| // topNode Delete |
| // | |
| // Scan (temp table) |
| ////////////////////////////////////////////////////////////////////////////// |
| RelExpr *GenericUpdate::inlineTempDelete(BindWA *bindWA, |
| RelExpr *topNode, |
| TriggersTempTable& tempTableObj, |
| CollHeap *heap) |
| { |
| RelExpr *deleteSubTree = tempTableObj.buildDelete(); |
| |
| Union *newOU = new(heap) Union(topNode, deleteSubTree, NULL, NULL, |
| REL_UNION,CmpCommon::statementHeap(), |
| TRUE); |
| newOU->setOrderedUnion(); |
| newOU->getInliningInfo().setFlags(II_DrivingTempDelete | |
| II_AccessSetNeeded); |
| return newOU; |
| } |
| |
| /***************************************************************************** |
| ****************************************************************************** |
| **** Methods for handling of before triggers. |
| ****************************************************************************** |
| *****************************************************************************/ |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Find all the NEW@columns in the RETDesc of the GU that is about to be |
| // replaced by a "tentative execution" node. These columns point to columns |
| // in the IUD TableDesc that will soon be not valid anymore. The names NEW@.* |
| // should instead reference the appropriate expresions from the new record |
| // expression. |
| ////////////////////////////////////////////////////////////////////////////// |
| void GenericUpdate::fixTentativeRETDesc(BindWA *bindWA, CollHeap *heap) |
| { |
| RETDesc *retDesc = getRETDesc(); |
| |
| // The name of the subject table should no longer be in the XTNM. |
| bindWA->getCurrentScope()->getXTNM()->remove(&getTableName()); |
| |
| // Create a "virtual column" called @EXECID for the value of the Unique |
| // Execute ID. All the before trigger SIGNAL expressions will be piggy- |
| // backed on this "column" before it is inserted into the temp table. |
| ItemExpr *execId = new(heap) UniqueExecuteId(); |
| addVirtualColumn(bindWA, execId, InliningInfo::getExecIdVirtualColName(), heap); |
| |
| if (getOperatorType() == REL_UNARY_DELETE) |
| return; // No NEW@ values for Delete. |
| |
| CorrName newCorr(NEWCorr); |
| ColumnDescList newCols(heap); |
| ValueIdArray& newRecordExpr = newRecExprArray(); |
| ValueId *foundAssignToCol; |
| |
| // Get all the NEW@ columns from the GU's RETDesc. |
| newCols.insert(*(retDesc->getQualColumnList(newCorr))); |
| |
| // The getQualColumnList() call does not return SYSKEY, so if it's there |
| // find it and add it to the list. |
| NAString syskeyName("SYSKEY", heap); |
| ColRefName newSyskeyName(syskeyName, newCorr, heap); |
| ColumnNameMap *newSyskeyMap = retDesc->findColumn(newSyskeyName); |
| if (newSyskeyMap != NULL) |
| { |
| ColumnDesc *newSyskey = newSyskeyMap->getColumnDesc(); |
| // Make sure SYSKEY is not already in the list. |
| CMPASSERT(!newCols.contains(newSyskey)); |
| newCols.insert(newSyskey); |
| } |
| |
| for (CollIndex i=0; i<newCols.entries(); i++) // For each NEW@ column, |
| { |
| ValueId tempValueId; |
| foundAssignToCol = NULL; |
| const ColRefName& colName = newCols[i]->getColRefNameObj(); |
| |
| // Lookup the column name in the target of an Assign node in the |
| // new record expression of the GU. |
| for (CollIndex j=0; j<newRecordExpr.entries(); j++) |
| { |
| ItemExpr *currentExpr = newRecordExpr[j].getItemExpr(); |
| CMPASSERT(currentExpr->getOperatorType() == ITM_ASSIGN); |
| Assign *currentAssign = (Assign *)currentExpr; |
| |
| ItemExpr *target = currentAssign->getTarget().getItemExpr(); |
| if (target->getOperatorType() == ITM_BASECOLUMN) |
| { |
| // Do the column names match? |
| const NAString& trgtCol = ((BaseColumn *)target)->getColName(); |
| if (!trgtCol.compareTo(colName.getColName())) |
| { |
| // Save the ValueId of the Assign source expression. |
| tempValueId = currentAssign->getSource(); |
| foundAssignToCol = &tempValueId; |
| // No need to check the rest of the new record expression. |
| break; |
| } |
| } |
| } // for each Assign in the newRecExpr |
| |
| // Delete the current NEW@.* column from the RETDesc. |
| if (colName.getColName() == "SYSKEY") |
| { |
| retDesc->delColumn(bindWA, colName, SYSTEM_COLUMN); |
| } |
| else |
| { |
| retDesc->delColumn(bindWA, colName, USER_COLUMN); |
| } |
| // it is no longer a local reference in the current scope. |
| ValueId colValId = newCols[i]->getValueId(); |
| bindWA->getCurrentScope()->removeLocalRef(colValId); |
| |
| // Did we find a matching Assign expression? |
| if (foundAssignToCol != NULL) |
| { |
| ValueId newExpr = *foundAssignToCol; |
| |
| // Yes - Make NEW@.* reference it. |
| if (getGroupAttr()->isCharacteristicInput(newExpr)) |
| newExpr = wrapWithCastExpr(bindWA, newExpr, heap); |
| |
| retDesc->addColumn(bindWA, colName, newExpr); |
| } |
| else |
| { |
| // No - this must be a column that is not SET into, in an Update node. |
| CMPASSERT (getOperatorType() == REL_UNARY_UPDATE); |
| |
| // OK - just reference the appropriate OLD@.* value. |
| CorrName oldCorr(OLDCorr); |
| ColRefName oldColName(colName.getColName(), oldCorr); |
| ValueId oldCol = retDesc->findColumn(oldColName)->getValueId(); |
| |
| // Wrap it with a Cast expression to give it a different ValueId |
| // than the original OLD@.* column, thus marking it as a |
| // duplicate entry in the XCNM. |
| retDesc->addColumn(bindWA, colName, wrapWithCastExpr(bindWA, oldCol, heap)); |
| } |
| } // for each NEW@ column |
| |
| // The next block handles the value of the SYSKEY column in the |
| // temporary table for Insert operations. The problem is that the row |
| // is inserted into the temp table before it is inserted into the |
| // subject table, and so - before the actual SYSKEY value was |
| // generated for it. Since this column is part of the primary key of |
| // the temp table, and the rest of the primary key columns may be |
| // identical, this SYSKEY value must be unique for each row. |
| // Solution - give it the value of JulianTimestamp. |
| // The Timestamp ItemExpr is a new BuiltInFunction that is evaluated |
| // for each row. |
| if (getOperatorType() == REL_UNARY_INSERT) |
| { |
| // Does this RETDesc have a NEW@.SYSKEY column? |
| CorrName newCorr(NEWCorr); |
| ColRefName syskey("SYSKEY", newCorr); |
| if (retDesc->findColumn(syskey) != NULL) |
| { |
| // code added to make sure that the generated julian |
| // timestamp is unique. This change is added to support |
| // bulk inserts (including rowset inserts) when before |
| // row triggers are used. |
| ItemExpr* counterExpr, *incrementExpr; |
| counterExpr = new (heap) ItmPersistentExpressionVar(0); |
| incrementExpr = new (heap) ItmBlockFunction |
| (counterExpr, new (heap) Assign (counterExpr, |
| new (heap) BiArith(ITM_PLUS, counterExpr, |
| new (heap) ConstValue(1)))); |
| |
| // Synthesize the types and value IDs for the new items |
| incrementExpr->synthTypeAndValueId(TRUE); |
| |
| // Construct a new JulianTimestamp expression |
| ItemExpr *fakeSyskey = new (heap) |
| BiArith(ITM_PLUS, incrementExpr, new (heap) |
| JulianTimestamp(new (heap) InternalTimestamp)); |
| |
| fakeSyskey->bindNode(bindWA); |
| |
| // Make NEW@.SYSKEY point to the timestamp expression. |
| retDesc->delColumn(bindWA, syskey, SYSTEM_COLUMN); |
| retDesc->addColumn(bindWA, syskey, fakeSyskey->getValueId()); |
| } |
| } // if REL_UNARY_INSERT |
| } // End of GenericUpdate::fixTentativeRETDesc() |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // For Update nodes, add the predicate: OLD@.<ci> = NEW@.<ci> |
| ////////////////////////////////////////////////////////////////////////////// |
| void GenericUpdate::addPredicateOnClusteringKey(BindWA *bindWA, |
| RelExpr *tentativeNode, |
| CollHeap *heap) |
| { |
| TableDesc *newTableDesc = getTableDesc(); |
| const IndexDesc *newCI = newTableDesc->getClusteringIndex(); |
| const ValueIdList& newCICols = newCI->getIndexKey(); |
| CorrName newCorrName(NEWCorr); |
| CorrName oldCorrName(OLDCorr); |
| |
| ItemExpr *predicate = NULL; |
| for (CollIndex i=0; i<newCICols.entries(); i++) |
| { |
| ItemExpr *currentCol = newCICols[i].getItemExpr(); |
| CMPASSERT(currentCol->getOperatorType() == ITM_INDEXCOLUMN); |
| IndexColumn *currentBaseCol = (IndexColumn *) currentCol; |
| const NAString& colName = currentBaseCol->getNAColumn()->getColName(); |
| |
| ColRefName *newColName = new(heap) ColRefName(colName, newCorrName, heap); |
| ColRefName *oldColName = new(heap) ColRefName(colName, oldCorrName, heap); |
| |
| ItemExpr *currentPredicate = new(heap) |
| BiRelat(ITM_EQUAL, |
| new(heap) ColReference(newColName), |
| new(heap) ColReference(oldColName)); |
| |
| if (predicate == NULL) |
| predicate = currentPredicate; |
| else |
| predicate = new(heap) BiLogic(ITM_AND, predicate, currentPredicate); |
| } |
| |
| CMPASSERT(predicate != NULL); |
| predicate->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return; |
| |
| tentativeNode->selectionPred().insert(predicate->getValueId()); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // This method replaces the GenericUpdate node that fired the triggers (this) |
| // with a "tentative execution" node. |
| ////////////////////////////////////////////////////////////////////////////// |
| RelExpr *GenericUpdate::createTentativeGU(BindWA *bindWA, CollHeap *heap) |
| { |
| // 'this' is the GU that started the whole mess. Let's kill it! |
| // Create a dummy Rename node. |
| RelExpr *tentativeNode = new(heap) |
| RenameTable(child(0), "TentativeGU"); |
| // Fix the NEW@ cols to point to the right expressions. |
| fixTentativeRETDesc(bindWA, heap); |
| // Give it my RETDesc with all the NEW and OLD stuff |
| tentativeNode->setRETDesc(getRETDesc()); |
| tentativeNode->setGroupAttr(getGroupAttr()); |
| |
| ColumnNameMap *execIdCol = |
| getRETDesc()->findColumn(InliningInfo::getExecIdVirtualColName()); |
| CMPASSERT(execIdCol != NULL); |
| |
| ValueIdList outputs; |
| getRETDesc()->getValueIdList(outputs, USER_AND_SYSTEM_COLUMNS); |
| outputs.remove(execIdCol->getValueId()); |
| tentativeNode->getGroupAttr()->addCharacteristicOutputs(outputs); |
| |
| // For Update nodes, add the predicate: OLD@.<ci> = NEW@.<ci>s |
| if (getOperatorType() == REL_UNARY_UPDATE) |
| { |
| addPredicateOnClusteringKey(bindWA, tentativeNode, heap); |
| if (bindWA->errStatus()) |
| return NULL; |
| } |
| |
| tentativeNode->markAsBound(); // No more binding is needed here. |
| return tentativeNode; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Find the position of all columns SET to by this before Trigger, and add |
| // them to colToSet. This way, when we call TriggerDB for after triggers we |
| // will get triggers that fire on these columns too. |
| ////////////////////////////////////////////////////////////////////////////// |
| void BeforeTrigger::addUpdatedColumns(UpdateColumns *colsToSet, |
| const NATable *naTable) |
| { |
| if (setList_ == NULL) // Does this trigger have a SET clause? |
| return; |
| |
| // For each Assign expression, add the col position to ColsToSet. |
| for (CollIndex i=0; i<setList_->entries(); i++) |
| { |
| Lng32 targetColPosition = getTargetColumn(i, NULL, naTable); |
| CMPASSERT(targetColPosition != -1); |
| colsToSet->addColumn(targetColPosition); |
| } |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // This method is called only when before triggers exist, and it builds the |
| // left side of the inlining tree - the tentative execution tree. |
| // 1. Create the tentative GU node, that replaces this node. |
| // 2. Build a totem of before triggers on top of the tentative GU node. |
| // Columns updated by before triggers are added to the list of columns |
| // updated by the triggering statement, passed in colsToSet. |
| // 3. Inline the temp insert above the before triggers. |
| // 4. Add a RelRoot. This root has an empty select-list, so that the Union |
| // node above it will accept it as compatible with it's other child. It |
| // also will not open a new BindScope when bound, so that when the binding |
| // will reach the already bound tentative node - it will be in the same |
| // scope. This avoids problems when calculating inputs. |
| // |
| // The result looks like this, with 3 before triggers BT1, BT2 and BT3: |
| // (the triggers are already sorted by timestamp, BT1 is the oldest) |
| // RelRoot |
| // | |
| // TSJ |
| // / \ |
| // BT3 Temp Insert |
| // | |
| // BT2 |
| // | |
| // BT1 |
| // | |
| // TentativeGU (replacing this) |
| // | |
| // this->child(0) |
| ////////////////////////////////////////////////////////////////////////////// |
| RelExpr *GenericUpdate::createTentativeSubTree(BindWA *bindWA, |
| TriggerList *beforeTriggers, |
| UpdateColumns *updatedColumns, |
| TriggersTempTable& tempTableObj, |
| CollHeap *heap) |
| { |
| const NATable *naTable = getTableDesc()->getNATable(); |
| |
| // Step 1. |
| RelExpr *topNode = createTentativeGU(bindWA, heap); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| // Step 2. |
| Trigger *current; |
| BeforeTrigger *triggerNode=0; |
| for (CollIndex i=0; i<beforeTriggers->entries(); i++) |
| { |
| current = (*beforeTriggers)[i]; |
| triggerNode = (BeforeTrigger *)current->getParsedTrigger(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| CMPASSERT(triggerNode->getOperatorType() == REL_BEFORE_TRIGGER); |
| |
| // BeforeTrigger nodes are created childless, |
| // And the child is always added before binding it. |
| triggerNode->child(0) = topNode; |
| topNode = triggerNode; |
| |
| if (updatedColumns != NULL) // Is this an UPDATE op? |
| { |
| // Add columns changed by the trigger to the columns changed |
| // by the original UPDATE operation. |
| triggerNode->addUpdatedColumns(updatedColumns, naTable); |
| } |
| ItemExpr *whenClause = triggerNode->getWhenClause(); |
| whenClause = addCheckForTriggerEnabled(bindWA, whenClause, current, heap); |
| triggerNode->setWhenClause(whenClause); |
| } |
| |
| // Step 3. |
| topNode = inlineTempInsert(topNode, bindWA, tempTableObj, TRUE, TRUE, heap); |
| if (bindWA->errStatus()) |
| return NULL; |
| // Save a pointer to the TSJ for tansformNod(). |
| CMPASSERT(triggerNode); |
| triggerNode->setParentTSJ(topNode); |
| |
| // Step 4 |
| RelRoot *rootNode = new(heap) RelRoot(topNode); |
| rootNode->setRootFlag(FALSE); |
| rootNode->setEmptySelectList(); |
| rootNode->setDontOpenNewScope(); |
| return rootNode; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // The effective GU affects the rows in the affected set into the subject |
| // table itself. Since this operation is different in Insert, Update and |
| // Delete, I implemented it as a virtual method, that is implemented by the |
| // child classes only. |
| ////////////////////////////////////////////////////////////////////////////// |
| // we are not supposed to get here |
| RelExpr *GenericUpdate::createEffectiveGU(BindWA *bindWA, |
| CollHeap *heap, |
| TriggersTempTable& tempTableObj, |
| GenericUpdate **effectiveGUNode, |
| UpdateColumns *colsToSet) |
| { |
| CMPASSERT(FALSE); // Not supposed to get here !!! |
| return NULL; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Create an Insert node that inserts the NEW@ values into the subject table. |
| ////////////////////////////////////////////////////////////////////////////// |
| RelExpr *Insert::createEffectiveGU(BindWA *bindWA, |
| CollHeap *heap, |
| TriggersTempTable& tempTableObj, |
| GenericUpdate **effectiveGUNode, |
| UpdateColumns *colsToSet) |
| { |
| // Create the Scan on the temporary table. |
| Scan *tempScanNode = |
| tempTableObj.buildScan(ChangesTable::INSERTED_ROWS); |
| |
| CorrName& tempTable = tempScanNode->getTableName(); |
| ItemExpr *selectList = tempTableObj.buildBaseColsSelectList(tempTable); |
| |
| // Build a RelRoot on top of the Scan node. |
| RelRoot *rootNode = new(heap) RelRoot(tempScanNode, REL_ROOT, selectList); |
| rootNode->setRootFlag(FALSE); |
| |
| // Create an Insert node above the RelRoot. |
| // The newRecExpr will be created during binding if the Insert node. |
| GenericUpdate *gu = new(heap) |
| Insert(getTableName(), NULL, REL_UNARY_INSERT, rootNode); |
| |
| // If this GU is an action of a trigger - don't count rows affected. |
| gu->rowsAffected() = rowsAffected(); |
| gu->getInliningInfo().setFlags(II_EffectiveGU); |
| |
| *effectiveGUNode = gu; |
| return gu; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Create an Update node that updates rows in the subject table according to |
| // primary key, and sets them to the NEW@ values. |
| // Normally, the Update node itself does not have any outputs, because the |
| // OLD and NEW values are taken from the temp table. However, when MV |
| // logging is needed, the Update node must project the CurrentEpoch column. |
| ////////////////////////////////////////////////////////////////////////////// |
| RelExpr *Update::createEffectiveGU(BindWA *bindWA, |
| CollHeap *heap, |
| TriggersTempTable& tempTableObj, |
| GenericUpdate **effectiveGUNode, |
| UpdateColumns *colsToSet) |
| { |
| ItemExpr *assignList=NULL; |
| Assign *assignExpr=NULL; |
| CorrName newCorrName(NEWCorr); |
| |
| NABoolean mvLoggingRequired = isMvLoggingRequired(); |
| |
| // Get the columns of the subject table. |
| const NAColumnArray &subjectColumns = |
| getTableDesc()->getNATable()->getNAColumnArray(); |
| |
| for (CollIndex i=0; i<subjectColumns.entries(); i++) |
| { |
| // If this column was not SET into, no need to change it. |
| if (!colsToSet->contains(i)) |
| continue; |
| |
| NAColumn *currentColumn = subjectColumns.getColumn(i); |
| const NAString &colName = currentColumn->getColName(); |
| |
| // Cannot update a clustering/primary key column! |
| // This error is caught during binding in DDL. |
| // see BeforeTrigger::bindSetClause() in BindRelExpr.cpp |
| CMPASSERT(!currentColumn->isClusteringKey() && !currentColumn->isPrimaryKey()); |
| |
| NAString tempColName(colName); |
| assignExpr = new(heap) |
| Assign(new(heap) ColReference( |
| new(heap) ColRefName(colName)), |
| new(heap) ColReference(new(heap) ColRefName(tempColName, newCorrName)), |
| FALSE); |
| if (assignList==NULL) |
| assignList = assignExpr; |
| else |
| assignList = new(heap) ItemList(assignExpr, assignList); |
| } |
| |
| // The selection predicate on the Scan is on the NEW@ clustering index cols. |
| // The left side of the equation is using the "@SYSKEY" column while the right |
| // side uses the SYSKEY column. |
| NAString newName(NEWCorr); |
| ItemExpr *selectionPredicate = new(heap) |
| BiRelat(ITM_EQUAL, |
| tempTableObj.buildClusteringIndexVector(&newName,TRUE), |
| tempTableObj.buildClusteringIndexVector() ); |
| |
| Scan *baseScanNode = new(heap) Scan(getTableName()); |
| baseScanNode->addSelPredTree(selectionPredicate); |
| GenericUpdate *effectiveGu = new(heap) |
| Update(getTableName(), NULL, REL_UNARY_UPDATE, baseScanNode, assignList); |
| |
| // If this GU is an action of a trigger - don't count rows affected. |
| effectiveGu->rowsAffected() = rowsAffected(); |
| effectiveGu->getInliningInfo().setFlags(II_EffectiveGU); |
| |
| RelRoot *effectiveRoot = new(heap) RelRoot(effectiveGu); |
| if (!mvLoggingRequired) |
| effectiveRoot->setEmptySelectList(); |
| |
| RelExpr *joinTemps = tempTableObj.buildOldAndNewJoin(); |
| |
| OperatorTypeEnum joinType = mvLoggingRequired ? REL_TSJ : REL_LEFT_TSJ; |
| Join *tsjNode = new(heap) Join(joinTemps, effectiveRoot, joinType); |
| tsjNode->setTSJForWrite(TRUE); |
| |
| *effectiveGUNode = effectiveGu; |
| return tsjNode; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Create a Delete node that deletes rows from the subject table according |
| // to the primary from the row read by the temp Scan node. |
| ////////////////////////////////////////////////////////////////////////////// |
| RelExpr *Delete::createEffectiveGU(BindWA *bindWA, |
| CollHeap *heap, |
| TriggersTempTable& tempTableObj, |
| GenericUpdate **effectiveGUNode, |
| UpdateColumns *colsToSet) |
| { |
| // Create the Scan on the temporary table. |
| Scan *tempScanNode = tempTableObj.buildScan(ChangesTable::DELETED_ROWS); |
| |
| Delete *gu = new(heap) |
| Delete(getTableName(), NULL, REL_UNARY_DELETE, tempScanNode); |
| |
| // If this GU is an action of a trigger - don't count rows affected. |
| gu->rowsAffected() = rowsAffected(); |
| gu->getInliningInfo().setFlags(II_EffectiveGU); |
| |
| *effectiveGUNode = gu; |
| return gu; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| ////////////////////////////////////////////////////////////////////////////// |
| RelExpr *GenericUpdate::bindEffectiveGU(BindWA *bindWA) |
| { |
| if (getInliningInfo().hasPipelinedActions()) |
| { |
| setNoFlow(TRUE); |
| |
| if (getOperatorType() != REL_UNARY_UPDATE) |
| { |
| createOldAndNewCorrelationNames(bindWA); |
| } |
| |
| if (isMvLoggingRequired()) |
| { |
| prepareForMvLogging(bindWA, bindWA->wHeap()); |
| } |
| |
| ValueIdList outputs; |
| getRETDesc()->getValueIdList(outputs, USER_AND_SYSTEM_COLUMNS); |
| setPotentialOutputValues(outputs); |
| } |
| // case of before triggers and after statement triggers where the after triggers are in conflict |
| // and the effective GU is either an insert or a delete. |
| else if (getOperatorType() != REL_UNARY_UPDATE) |
| { |
| RETDesc *rd = createOldAndNewCorrelationNames(bindWA, TRUE /* only create RETDesc */); |
| getInliningInfo().buildTriggerBindInfo(bindWA, rd, bindWA->wHeap()); |
| delete rd; |
| } |
| // indicate that this is a subject table for enable/disable |
| getOptStoi()->getStoi()->setSubjectTable(TRUE); |
| |
| return this; |
| } |
| |
| /***************************************************************************** |
| ****************************************************************************** |
| **** Methods for handling Index Maintenance. |
| ****************************************************************************** |
| *****************************************************************************/ |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // See comments in common/ComTransInfo.h |
| ////////////////////////////////////////////////////////////////////////////// |
| static void setScanLockForIM(const RelExpr *re) |
| { |
| if (re->getOperator().match(REL_SCAN)) { |
| Scan *rs = (Scan *)re; |
| rs->accessOptions().setScanLockForIM(TRUE); |
| } |
| for (Int32 i = 0; i < re->getArity(); ++i ) { |
| if (re->child(i)) |
| setScanLockForIM(re->child(i)); |
| } |
| } |
| |
| |
| // All table info in these createIM*() methods must come from the TableDesc. |
| // In particular, use of getTableName() is wrong: that is the name of the |
| // topmost view if the target table is a view. The TableDesc always represents |
| // the underlying *base* table. |
| // |
| RelExpr *GenericUpdate::createIMTree(BindWA *bindWA, |
| UpdateColumns *updatedColumns, |
| NABoolean useInternalSyskey) |
| { |
| RelExpr *imTree = NULL; |
| |
| NAString origCorr(getTableDesc()->getCorrNameObj().getCorrNameAsString(), |
| bindWA->wHeap()); |
| |
| const LIST(IndexDesc *) indexList = getTableDesc()->getIndexes(); |
| for (CollIndex i=0; i < indexList.entries(); i++) { |
| |
| IndexDesc *index = indexList[i]; |
| |
| // The base table itself is an index (the clustering index); |
| // obviously IM need not deal with it. |
| // |
| if (!index->isClusteringIndex()) { |
| |
| // An index always needs maintenance on an Insert or Delete... |
| // |
| NABoolean imNeeded = |
| ((getOperatorType() != REL_UNARY_UPDATE) || |
| (isMerge())); |
| |
| // ...but for an Update, it needs maint if it contains any of the columns |
| // being updated. The test for intersection must use column position |
| // (which is always the position in the *base table*, so comparisons |
| // *are* meaningful!). Someday should be abstracted into a shared ## |
| // method also used by ItemConstr.h ColSignature stuff... ## |
| // |
| if (!imNeeded) { |
| const ValueIdList &indexColumns = index->getIndexColumns(); |
| |
| for (CollIndex j=0; j < indexColumns.entries() && !imNeeded; j++) { |
| Lng32 indexCol = indexColumns[j].getNAColumn()->getPosition(); |
| |
| if (updatedColumns != NULL) // -- Triggers |
| imNeeded |= updatedColumns->contains(indexCol); |
| else { |
| for (CollIndex k=0; k < newRecExprArray().entries(); k++) { |
| Lng32 tableCol = newRecExprArray()[k].getItemExpr()->child(0). |
| getNAColumn()->getPosition(); |
| |
| if (indexCol <= tableCol) { |
| if (indexCol == tableCol) imNeeded = TRUE; |
| break; // newRecExprArray is ordered by position, so break if <= |
| } |
| } // for k |
| } // else |
| } // for j |
| |
| #ifndef NDEBUG |
| if (imNeeded && GU_DEBUG) |
| cerr << "imNeeded: " << index->getNAFileSet()->getExtFileSetName() |
| << endl; |
| #endif |
| } // Update, need to test whether IM is needed for this index |
| |
| if (imNeeded) |
| if (!imTree) |
| { |
| imTree = createIMNodes(bindWA, useInternalSyskey, |
| index, producedMergeIUDIndicator_); |
| if (getOperatorType() == REL_UNARY_UPDATE || |
| getOperatorType() == REL_UNARY_DELETE) |
| setScanLockForIM(child(0)); |
| if (bindWA->isTrafLoadPrep()) |
| imTree->setChild(0,this); |
| } |
| else |
| { |
| if (!bindWA->isTrafLoadPrep()) |
| { |
| imTree = new (bindWA->wHeap()) |
| Union(imTree, createIMNodes(bindWA, useInternalSyskey, |
| index, producedMergeIUDIndicator_), |
| NULL, NULL, REL_UNION, CmpCommon::statementHeap(), TRUE, TRUE); |
| imTree->setBlockStmt(isinBlockStmt()); |
| imTree->getInliningInfo().setFlags(II_isIMUnion); |
| } // not bulk load |
| else { |
| RelExpr * oldIMTree = imTree; |
| imTree = createIMNodes(bindWA, useInternalSyskey, index, |
| producedMergeIUDIndicator_); |
| imTree->setChild(0,oldIMTree); |
| } // is bulk load |
| } |
| |
| } // !clusteringIndex |
| } // loop over all indexes |
| |
| // ##IM: This extra RelRoot is probably unnecessary (wasteful), |
| // ## due to createIMNode*() always returning a RelRoot-topped tree -- |
| // ## but I didn't have time to remove it and re-test. |
| if (imTree && imTree->getOperatorType() != REL_ROOT) |
| imTree = new (bindWA->wHeap()) RelRoot(imTree); |
| |
| getTableDesc()->getCorrNameObj().setCorrName(origCorr); |
| |
| return imTree; |
| } // GenericUpdate::createIMTree() |
| |
| |
| // Here, we make virgin ColReferences, which when bound will be found |
| // in an outer scope engendered by our (and/or GenericUpdate's) interposing |
| // a RelRoot between us and our parent GenericUpdate -- |
| // and the LeafXxx will add these outer refs to its characteristic inputs. |
| static RelExpr *createIMNode(BindWA *bindWA, |
| CorrName &tableCorrName, |
| const CorrName &indexCorrName, |
| IndexDesc *index, |
| const ValueId &mergeIUDIndicator, |
| NABoolean isIMInsert, |
| NABoolean useInternalSyskey, |
| NABoolean isForUpdateOrMergeUpdate, |
| NABoolean mergeDeleteWithInsertOrMergeUpdate, |
| NABoolean isEffUpsert) |
| { |
| |
| // See createOldAndNewCorrelationNames() for info on OLDCorr/NEWCorr |
| |
| // A merge statement can perform an update or an insert if a matching row |
| // is detected or not, correspondingly. In either case, a update operation is |
| // performed on the index table. If a unique index is involved, a update |
| // operation would be able to delete a corresponding row in the index table |
| // using the index "key" column before inserting a new row. If it is a insert |
| // operation on the base table, then a update on the index table should not |
| // delete a row that corresponds to a different row in the base table(index |
| // key belonging to a different row). Hence in this case, it is better to always |
| // match not only the index key but also remaining columns in the index table |
| // that correspond to the base table. Hence we introduce |
| // robustDelete below. This flag could also be called |
| // isIMOnAUniqueIndexForMerge |
| NABoolean robustDelete = (mergeDeleteWithInsertOrMergeUpdate && index->isUniqueIndex()) || |
| (isEffUpsert && index->isUniqueIndex()); |
| |
| tableCorrName.setCorrName(isIMInsert ? NEWCorr : OLDCorr); |
| |
| ItemExprList *colRefList = new(bindWA->wHeap()) ItemExprList(bindWA->wHeap()); |
| |
| const ValueIdList &indexColVids = ((isIMInsert || robustDelete )? |
| index->getIndexColumns() : |
| index->getIndexKey()); |
| ItemExpr *preCond = NULL; // pre-condition for delete, insert if any |
| for (CollIndex i=0; i < indexColVids.entries(); i++) { |
| |
| const NAString &colName = indexColVids[i].getNAColumn()->getColName(); |
| |
| NAString realColName = colName; |
| if (useInternalSyskey && colName == "SYSKEY") |
| { |
| realColName = "@SYSKEY"; |
| } |
| |
| ColReference *colRef = |
| new (bindWA->wHeap()) ColReference |
| (new (bindWA->wHeap()) ColRefName (realColName, tableCorrName, bindWA->wHeap())); |
| |
| colRefList->insert(colRef); |
| } |
| |
| // There are 4 cases here. Following table shows when precondition |
| // expression is addded |
| // Index Type/IM operation-> Delete | Insert |
| // Non-unique Index Yes No |
| // Unique Index Yes Yes |
| if ((!isIMInsert && isForUpdateOrMergeUpdate)||robustDelete || (!isIMInsert && isEffUpsert)) |
| { |
| // For delete nodes that are part of an update or merge update or upsert, |
| // generate a comparison expression between old and new index column |
| // values and suppress the delete if no columns change. This avoids the |
| // situation where we delete and then re-insert the same index |
| // row within one millisecond and get the same HBase timestamp |
| // value assigned. In that case, the delete will win out over |
| // the insert, even though the insert happens later in time. The |
| // HBase-trx folks are also working on a change to avoid that. |
| // similar checks are added for unique index insert (into the index |
| // table only, using the robustDelete flag above). |
| // Since we do checkandput for unique indexes, putting |
| // an existing row (key + value) will raise a uniqueness violation, |
| // while from the user's point of view no change in the uninque index |
| // table is expected. |
| |
| CorrName predValues(tableCorrName); |
| |
| if (isIMInsert) |
| predValues.setCorrName(OLDCorr); |
| else |
| predValues.setCorrName(NEWCorr); |
| |
| for (CollIndex cc=0; cc<colRefList->entries(); cc++) |
| { |
| ColReference *predColRef = static_cast<ColReference *> |
| ((*colRefList)[cc]); |
| |
| BiRelat *comp1Col = NULL; |
| // create a predicate OLD@.<col> = NEW@.<col> |
| comp1Col = new (bindWA->wHeap()) |
| BiRelat(ITM_EQUAL, |
| predColRef, |
| new (bindWA->wHeap()) ColReference( |
| new (bindWA->wHeap()) ColRefName( |
| predColRef->getColRefNameObj().getColName(), |
| predValues, |
| bindWA->wHeap())), |
| TRUE); // special NULLs, treat NULL == NULL as true |
| |
| if (preCond == NULL) |
| preCond = comp1Col; |
| else |
| preCond = new (bindWA->wHeap()) BiLogic(ITM_AND, preCond, comp1Col); |
| } |
| |
| // the actual condition is that the values are NOT the same |
| preCond = new (bindWA->wHeap()) UnLogic(ITM_NOT, preCond); |
| } |
| |
| // If we got an IUD indicator passed in, that means that we have |
| // an IM tree for update, but the actual operation may be an |
| // insert or a delete, and therefore we may need to suppress the |
| // index delete or insert, respectively, with a precondition. |
| if (mergeIUDIndicator != NULL_VALUE_ID) |
| { |
| ItemExpr *iudCond = |
| new(bindWA->wHeap()) BiRelat( |
| ITM_NOT_EQUAL, |
| mergeIUDIndicator.getItemExpr(), |
| new(bindWA->wHeap()) SystemLiteral( |
| (isIMInsert ? "D" : "I"), |
| CharInfo::ISO88591)); |
| |
| if (preCond == NULL) |
| preCond = iudCond; |
| else |
| preCond = new (bindWA->wHeap()) BiLogic( |
| ITM_AND, |
| iudCond, |
| preCond); |
| } |
| |
| // NULL tableDesc here, like all Insert/Update/Delete ctors in SqlParser, |
| // because the LeafXxx::bindNode will call GenericUpdate::bindNode |
| // which will do the appropriate createTableDesc. |
| GenericUpdate *imNode; |
| if (isIMInsert) |
| { |
| if (!bindWA->isTrafLoadPrep()) |
| { |
| imNode = new (bindWA->wHeap()) |
| LeafInsert(indexCorrName, NULL, colRefList, REL_LEAF_INSERT, |
| preCond, bindWA->wHeap()); |
| HostArraysWA * arrayWA = bindWA->getHostArraysArea() ; |
| if (arrayWA && arrayWA->hasHostArraysInTuple()) { |
| if (arrayWA->getTolerateNonFatalError() == TRUE) |
| imNode->setTolerateNonFatalError(RelExpr::NOT_ATOMIC_); |
| } |
| // For index maintenance on non-unique HBase indexes we can use a put. |
| // In fact we must use a put, otherwise we'll get a uniqueness constraint |
| // violation for those rows that didn't change and therefore didn't get |
| // deleted, due to the precondition (see below). |
| if (!index->isUniqueIndex()) |
| imNode->setNoCheck(TRUE); |
| } // regular insert |
| else { |
| imNode = new (bindWA->wHeap()) Insert(indexCorrName, |
| NULL, |
| REL_UNARY_INSERT, |
| NULL); |
| ((Insert *)imNode)->setBaseColRefs(colRefList); |
| ((Insert *)imNode)->setInsertType(Insert::UPSERT_LOAD); |
| ((Insert *)imNode)->setIsTrafLoadPrep(true); |
| ((Insert *)imNode)->setNoIMneeded(TRUE); |
| } // traf load prep |
| } |
| else |
| { |
| imNode = new (bindWA->wHeap()) LeafDelete(indexCorrName, |
| NULL, |
| colRefList, |
| (robustDelete )?TRUE:FALSE, |
| REL_LEAF_DELETE, |
| preCond, |
| bindWA->wHeap()); |
| imNode->setReferencedMergeIUDIndicator(mergeIUDIndicator); |
| } |
| |
| // The base table's rowsAffected() will get set in ImplRule.cpp, |
| // but we don't want any of these indexes' rowsAffected to be computed |
| // (if I insert one row into a table, I want to see "1 row(s) inserted", |
| // not 1 + number of indexes being maintained!). |
| imNode->rowsAffected() = GenericUpdate::DO_NOT_COMPUTE_ROWSAFFECTED; |
| |
| // Do not collect STOI info for security checks. |
| imNode->getInliningInfo().setFlags(II_AvoidSecurityChecks); |
| |
| // Set the flag that this GU is part of IM |
| imNode->getInliningInfo().setFlags(II_isIMGU); |
| |
| if (bindWA->isTrafLoadPrep()) |
| return imNode; |
| |
| // Add a root here to prevent error 4056 when binding the LeafDelete+Insert |
| // pair for an Update. |
| return new (bindWA->wHeap()) RelRoot(imNode); |
| } // static createIMNode() |
| |
| RelExpr *GenericUpdate::createIMNodes(BindWA *bindWA, |
| NABoolean useInternalSyskey, |
| IndexDesc *index, |
| const ValueId &mergeIUDIndicator) |
| { |
| // We call getExtFileSetObj (returns QualifiedName), |
| // NOT getExtFileSetName (returns NAString), |
| // hence the CorrName ctor sets up entire QualifiedName, |
| // NOT just an (erroneously delimited) objectName part of one. |
| // |
| CorrName indexCorrName(index->getNAFileSet()->getExtFileSetObj()); |
| indexCorrName.setSpecialType(ExtendedQualName::INDEX_TABLE); |
| |
| CorrName &tableCorrName = getTableDesc()->getCorrNameObj(); |
| if(tableCorrName.isVolatile()) |
| indexCorrName.setIsVolatile(TRUE); |
| |
| RelExpr *indexInsert = NULL, *indexDelete = NULL, *indexOp = NULL; |
| NABoolean isForUpdateOrMergeUpdate = (getOperatorType() == REL_UNARY_UPDATE || |
| isMergeUpdate()); |
| |
| NABoolean isEffUpsert = ((getOperatorType() == REL_UNARY_INSERT) && ((Insert *)this)->xformedEffUpsert()); |
| if (indexCorrName.getUgivenName().isNull()) |
| { |
| indexCorrName.setUgivenName(tableCorrName.getUgivenName()); |
| } |
| |
| // Create a list of base columns ColReferences for |
| // ALL the index columns as AFTER/NEW columns. |
| // |
| if (getOperatorType() == REL_UNARY_INSERT || |
| getOperatorType() == REL_UNARY_UPDATE || isEffUpsert) |
| indexInsert = indexOp = createIMNode(bindWA, |
| tableCorrName, |
| indexCorrName, |
| index, |
| mergeIUDIndicator, |
| TRUE, |
| useInternalSyskey, |
| isForUpdateOrMergeUpdate, |
| isMerge(), |
| isEffUpsert); |
| |
| // Create a list of base columns ColReferences for |
| // ONLY the index KEY columns as BEFORE/OLD columns. |
| // |
| if (getOperatorType() == REL_UNARY_DELETE || |
| getOperatorType() == REL_UNARY_UPDATE || |
| isEffUpsert) |
| { |
| NABoolean mergeDeleteWithInsertOrMergeUpdate = isMerge(); |
| if (mergeDeleteWithInsertOrMergeUpdate && |
| (getOperatorType() == REL_UNARY_DELETE) && |
| (!insertValues())) |
| // merge delete without an insert |
| mergeDeleteWithInsertOrMergeUpdate = FALSE; |
| |
| indexDelete = indexOp = createIMNode(bindWA, |
| tableCorrName, |
| indexCorrName, |
| index, |
| mergeIUDIndicator, |
| FALSE, |
| useInternalSyskey, |
| isForUpdateOrMergeUpdate, |
| mergeDeleteWithInsertOrMergeUpdate, |
| isEffUpsert); |
| } |
| |
| if ((getOperatorType() == REL_UNARY_UPDATE) || isEffUpsert){ |
| indexOp = new (bindWA->wHeap()) Union(indexDelete, indexInsert, |
| NULL, NULL, REL_UNION, |
| CmpCommon::statementHeap(),TRUE,TRUE); |
| |
| // is this in a compound statement? |
| indexOp->setBlockStmt(isinBlockStmt()); |
| |
| // is this GU driven by a row trigger |
| // this a temporary fix to prevent data corruption when |
| // the update operation is in the action of a row |
| // trigger. The data corruption is the result of the |
| // insert side of the IM tree lagging behind the delete |
| // in the case where multiple request to update the same rows |
| // are flowing to this update node. |
| // This is also the case when updates are being driven |
| // by rowsets. |
| // The fix is to unconditionally block the ordered union |
| // to handle all cases of IM updates. |
| // Note that this may cause performance issues. Improving |
| // the performance is an RFE at the moment. |
| //if (this->getInliningInfo().isInActionOfRowTrigger() || |
| // bindWA->getHostArraysArea()) |
| //{ |
| ((Union *)indexOp)->setBlockedUnion(); |
| //} |
| //else |
| //{ |
| // ((Union *)indexOp)->setOrderedUnion(); |
| //} |
| |
| // Add a root just to be consistent, so all returns from this method |
| // are topped with a RelRoot. |
| |
| // Set this Union is part of IM |
| indexOp->getInliningInfo().setFlags(II_isIMUnion); |
| indexOp = new (bindWA->wHeap()) RelRoot(indexOp); |
| } |
| |
| return indexOp; |
| } // GenericUpdate::createIMNodes() |
| |
| |
| /***************************************************************************** |
| ****************************************************************************** |
| **** Methods for handling Undo (for an insert) . |
| ****************************************************************************** |
| *****************************************************************************/ |
| |
| // All table info in these createUndo*() methods must come from the TableDesc. |
| // In particular, use of getTableName() is wrong: that is the name of the |
| // topmost view if the target table is a view. The TableDesc always represents |
| // the underlying *base* table. |
| // |
| RelExpr *GenericUpdate::createUndoTree(BindWA *bindWA, |
| UpdateColumns *updatedColumns, |
| NABoolean useInternalSyskey, |
| NABoolean imOrRiPresent, |
| NABoolean ormvPresent, |
| TriggersTempTable *tempTableObj) |
| { |
| RelExpr *undoTree = NULL; |
| |
| NAString origCorr(getTableDesc()->getCorrNameObj().getCorrNameAsString(), |
| bindWA->wHeap()); |
| |
| const LIST(IndexDesc *) indexList = getTableDesc()->getIndexes(); |
| |
| |
| |
| NABoolean undoNeeded = getOperatorType() == REL_UNARY_INSERT; |
| if (!undoNeeded) |
| return NULL; |
| if (imOrRiPresent) |
| { |
| for (CollIndex i=0; i < indexList.entries(); i++) |
| { |
| |
| IndexDesc *index = indexList[i]; |
| if (undoNeeded) |
| if (!undoTree) |
| undoTree = createUndoNodes(bindWA, useInternalSyskey, index); |
| else |
| { |
| undoTree = new (bindWA->wHeap()) |
| Union(undoTree, createUndoNodes(bindWA, useInternalSyskey, index), |
| NULL, NULL, REL_UNION, CmpCommon::statementHeap(), TRUE, TRUE); |
| undoTree->setBlockStmt(isinBlockStmt()); |
| } |
| |
| |
| } // loop over all indexes |
| } |
| if (ormvPresent) |
| { |
| if (!undoTree) |
| undoTree = createUndoIUDLog(bindWA); |
| else |
| undoTree = new (bindWA->wHeap()) |
| Union(undoTree, createUndoIUDLog(bindWA), |
| NULL, NULL, REL_UNION, CmpCommon::statementHeap(), TRUE, TRUE); |
| } |
| if (tempTableObj) |
| { |
| if (!undoTree) |
| undoTree = createUndoTempTable(tempTableObj,bindWA); |
| else |
| undoTree = new (bindWA->wHeap()) |
| Union(undoTree, createUndoTempTable(tempTableObj,bindWA), |
| NULL, NULL, REL_UNION, CmpCommon::statementHeap(), TRUE, TRUE); |
| } |
| |
| // ##IM: This extra RelRoot is probably unnecessary (wasteful), |
| // ## due to createIMNode*() always returning a RelRoot-topped tree -- |
| // ## but I didn't have time to remove it and re-test. |
| if (undoTree && undoTree->getOperatorType() != REL_ROOT) |
| undoTree = new (bindWA->wHeap()) RelRoot(undoTree); |
| |
| getTableDesc()->getCorrNameObj().setCorrName(origCorr); |
| |
| return undoTree; |
| } // GenericUpdate::createUndoTree() |
| |
| |
| RelExpr * GenericUpdate::createUndoTempTable(TriggersTempTable *tempTableObj,BindWA *bindWA) |
| { |
| |
| // TriggersTempTable *tempTableObj = new(bindWA->wHeap()) TriggersTempTable(this, bindWA); |
| const NAColumnArray &tempColumns = tempTableObj->getNaTable()->getNAColumnArray(); |
| ItemExprList *tempColRefList = new(bindWA->wHeap()) ItemExprList(bindWA->wHeap()); |
| /* CorrName tempCorrName = *(tempTableObj->calcTargetTableName(tempTableObj->getSubjectTableName().getQualifiedNameObj()));*/ |
| CorrName tempCorrName = *(tempTableObj->getTableName()); |
| CorrName origTempCorrName = tempCorrName; |
| tempCorrName.setCorrName( NEWCorr); |
| for (CollIndex i=0; i<tempColumns.entries(); i++) |
| { |
| NAString tempColName(tempColumns.getColumn(i)->getColName()); |
| |
| |
| ColReference *tempColRef = new(bindWA->wHeap()) |
| ColReference(new(bindWA->wHeap()) ColRefName(tempColName, tempCorrName)); |
| |
| tempColRefList->insert(tempColRef); |
| } |
| |
| RelExpr *delTemp = new (bindWA->wHeap()) LeafDelete(origTempCorrName, NULL, |
| tempColRefList,FALSE); |
| ((LeafDelete *)delTemp)->setTrigTemp(tempTableObj); |
| ((GenericUpdate *)delTemp)->rowsAffected() = GenericUpdate::DO_NOT_COMPUTE_ROWSAFFECTED; |
| |
| return new (bindWA->wHeap()) RelRoot(delTemp); |
| |
| |
| } |
| |
| // Here, we make virgin ColReferences, which when bound will be found |
| // in an outer scope engendered by our (and/or GenericUpdate's) interposing |
| // a RelRoot between us and our parent GenericUpdate -- |
| // and the LeafXxx will add these outer refs to its characteristic inputs. |
| static RelExpr *createUndoNode(BindWA *bindWA, |
| CorrName &tableCorrName, |
| const CorrName &indexCorrName, |
| IndexDesc *index, |
| NABoolean useInternalSyskey) |
| { |
| |
| // See createOldAndNewCorrelationNames() for info on OLDCorr/NEWCorr |
| CorrName btCorrName = tableCorrName; |
| |
| tableCorrName.setCorrName( NEWCorr); |
| |
| |
| ItemExprList *colRefList = new(bindWA->wHeap()) ItemExprList(bindWA->wHeap()); |
| |
| const ValueIdList &indexColVids = |
| (index->isUniqueIndex()? index->getIndexColumns(): index->getIndexKey()); |
| |
| |
| |
| |
| for (CollIndex i=0; i < indexColVids.entries(); i++) { |
| |
| const NAString &colName = indexColVids[i].getNAColumn()->getColName(); |
| |
| NAString realColName = colName; |
| if (useInternalSyskey && colName == "SYSKEY") |
| { |
| realColName = "@SYSKEY"; |
| } |
| |
| ColReference *colRef = |
| new (bindWA->wHeap()) ColReference |
| (new (bindWA->wHeap()) ColRefName (realColName, tableCorrName, bindWA->wHeap())); |
| |
| colRefList->insert(colRef); |
| } |
| |
| |
| |
| // NULL tableDesc here, like all Insert/Update/Delete ctors in SqlParser, |
| // because the LeafXxx::bindNode will call GenericUpdate::bindNode |
| // which will do the appropriate createTableDesc. |
| GenericUpdate *delIndex; |
| |
| |
| |
| |
| |
| if (index->isClusteringIndex()) |
| delIndex = new (bindWA->wHeap()) LeafDelete(btCorrName, NULL, |
| colRefList); |
| else |
| delIndex = new (bindWA->wHeap()) LeafDelete(indexCorrName, NULL |
| ,colRefList |
| ,index->isUniqueIndex()); |
| |
| |
| // The base table's rowsAffected() will get set in ImplRule.cpp, |
| // but we don't want any of these indexes' rowsAffected to be computed |
| // (if I insert one row into a table, I want to see "1 row(s) inserted", |
| // not 1 + number of indexes being maintained!). |
| delIndex->rowsAffected() = GenericUpdate::DO_NOT_COMPUTE_ROWSAFFECTED; |
| |
| // Do not collect STOI info for security checks. |
| delIndex->getInliningInfo().setFlags(II_AvoidSecurityChecks); |
| |
| |
| // Add a root here to prevent error 4056 when binding the LeafDelete+Insert |
| // pair for an Update. |
| return new (bindWA->wHeap()) RelRoot(delIndex); |
| |
| |
| return NULL; |
| } // static createUndoNode() |
| |
| RelExpr *GenericUpdate::createUndoNodes(BindWA *bindWA, |
| NABoolean useInternalSyskey, |
| IndexDesc *index) |
| { |
| // We call getExtFileSetObj (returns QualifiedName), |
| // NOT getExtFileSetName (returns NAString), |
| // hence the CorrName ctor sets up entire QualifiedName, |
| // NOT just an (erroneously delimited) objectName part of one. |
| // |
| |
| CorrName indexCorrName(index->getNAFileSet()->getExtFileSetObj()); |
| indexCorrName.setSpecialType(ExtendedQualName::INDEX_TABLE); |
| |
| CorrName &tableCorrName = getTableDesc()->getCorrNameObj(); |
| RelExpr *undoInsert = NULL; |
| |
| if (indexCorrName.getUgivenName().isNull()) |
| { |
| indexCorrName.setUgivenName(tableCorrName.getUgivenName()); |
| } |
| |
| if(tableCorrName.isVolatile()) |
| indexCorrName.setIsVolatile(TRUE); |
| |
| // Create a list of base columns ColReferences for |
| // ALL the index columns as AFTER/NEW columns. |
| // |
| |
| undoInsert = createUndoNode(bindWA, |
| tableCorrName, |
| indexCorrName, |
| index, |
| useInternalSyskey); |
| |
| |
| |
| return undoInsert; |
| } // GenericUpdate::createUndoNodes() |
| /***************************************************************************** |
| ****************************************************************************** |
| **** Methods for handling undo from IUD log |
| ****************************************************************************** |
| *****************************************************************************/ |
| |
| |
| |
| RelExpr *GenericUpdate::createUndoIUDLog(BindWA *bindWA) |
| { |
| |
| MvIudLog logTableObj(this, bindWA); |
| RelExpr *undoIUDNode = logTableObj.buildInsert(TRUE, |
| ChangesTable::ALL_ROWS, |
| TRUE, |
| TRUE); |
| return undoIUDNode; |
| } |
| |
| /***************************************************************************** |
| ****************************************************************************** |
| **** Methods for handling RI constraints |
| ****************************************************************************** |
| *****************************************************************************/ |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| ////////////////////////////////////////////////////////////////////////////// |
| RefConstraintList *GenericUpdate::getRIs(BindWA *bindWA, |
| const NATable *naTable) |
| { |
| RefConstraintList *allRIConstraints = new(bindWA->wHeap()) |
| RefConstraintList(bindWA->wHeap()); |
| |
| if ((getOperatorType() == REL_UNARY_INSERT) || |
| (getOperatorType()== REL_UNARY_UPDATE)) |
| naTable->getRefConstraints().getRefConstraints(bindWA, |
| newRecExpr(), |
| *allRIConstraints); |
| |
| if ((getOperatorType() == REL_UNARY_DELETE) || |
| (getOperatorType()== REL_UNARY_UPDATE) || |
| ((getOperatorType() == REL_UNARY_INSERT) && ((Insert *)this)->xformedEffUpsert())) |
| naTable->getUniqueConstraints().getRefConstraints(bindWA, |
| newRecExpr(), |
| *allRIConstraints); |
| |
| CollIndex numConstraints = allRIConstraints->entries(); |
| |
| for(CollIndex index = 0; index < numConstraints; index++) |
| { |
| if(NOT (allRIConstraints->at(index)->getIsEnforced())) |
| { |
| allRIConstraints->removeAt(index); |
| numConstraints--; |
| index--; |
| } |
| } |
| |
| if(allRIConstraints->isEmpty()) |
| { |
| delete allRIConstraints; |
| allRIConstraints = NULL; |
| } |
| |
| return allRIConstraints; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Given the columns being updated, do we need to enforce this RI constraint? |
| // Check if there is any matching columns in both the RI key columns and |
| // in the set of columns being updated. |
| ////////////////////////////////////////////////////////////////////////////// |
| NABoolean RefConstraint::isRINeededForUpdatedColumns(UpdateColumns *UpdatedColumns) |
| { |
| // UpdatedColumns is NULL for INSERT and DELETE. Columns should be matched |
| // for UPDATE operations only. |
| |
| if (NOT getIsEnforced()) |
| return FALSE; |
| |
| if (UpdatedColumns == NULL) |
| return TRUE; |
| |
| NABoolean isAReferencingConstraint = isaForeignKeyinTableBeingUpdated() |
| && getIsEnforced(); |
| const KeyColumns *riColumns; |
| |
| if (isAReferencingConstraint) |
| riColumns = &keyColumns(); |
| else |
| riColumns = uniqueConstraintReferencedByMe_.keyColumns_; |
| |
| for (CollIndex i=0; i<riColumns->entries(); i++) |
| { |
| if (UpdatedColumns->contains(riColumns->at(i)->getPosition())) |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Return a list of only the RI constraints needed when updating the |
| // specified columns. |
| ////////////////////////////////////////////////////////////////////////////// |
| RefConstraintList *RefConstraintList::getNeededRIs(UpdateColumns *updatedColumns, |
| CollHeap *heap) |
| { |
| // Insert into neededRIs only the constraints that match the columns |
| // being updated. |
| RefConstraintList *neededRIs = new(heap) RefConstraintList(heap); |
| for (CollIndex i=0; i<entries(); i++) |
| if (at(i)->isRINeededForUpdatedColumns(updatedColumns)) |
| neededRIs->insert(at(i)); |
| |
| return neededRIs; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // The tree below with a refConstraint after its bound. |
| // TSJ |
| // / \ |
| // Insert RelRoot |
| // | | |
| // Tuple GroupByAgg |
| // | |
| // Scan |
| // |
| // This function creates the rightSubtree of the TSJ node. |
| // Step 1. create the scan node with the predicate. |
| // Step 2. Create the GroupBy Node with the aggregate expression. |
| // Step 3. create the RelRoot node. |
| ////////////////////////////////////////////////////////////////////////////// |
| RelExpr* GenericUpdate::createRISubtree(BindWA *bindWA, |
| const NATable *naTable, |
| const RefConstraint& refConstraint, |
| CollHeap *heap) |
| { |
| RelExpr *newScan = NULL; |
| NAString constraintName(bindWA->wHeap()); |
| NAString tableName(bindWA->wHeap()); |
| Parser parser(bindWA->currentCmpContext()); |
| |
| NABoolean isReferencingConstraint = |
| (refConstraint.isaForeignKeyinTableBeingUpdated() && |
| refConstraint.getIsEnforced()); |
| |
| // Step 1: Create a scan node with predicate given by the refConstraint. |
| const QualifiedName parentQualName = refConstraint.getOtherTableName(); |
| |
| NAString scanPredicateTxt; |
| CorrName corrName(naTable->getTableName(), heap, |
| (isReferencingConstraint ? NEWAnsi : OLDAnsi)); |
| |
| NAString corrNameString = corrName.getCorrNameAsString(); |
| refConstraint.getPredicateText(scanPredicateTxt, &corrNameString); |
| |
| constraintName = refConstraint.getConstraintName().getQualifiedNameAsAnsiString(); |
| tableName = naTable->getTableName().getQualifiedNameAsAnsiString(); |
| |
| // Create the Scan node. |
| newScan = new (heap) Scan (CorrName(parentQualName)); |
| ItemExpr *newScanPredicate = parser.getItemExprTree |
| ((char *)scanPredicateTxt.data()); |
| newScan->addSelPredTree(newScanPredicate); |
| ((Scan *)newScan)->accessOptions().accessType() = TransMode::REPEATABLE_READ_ACCESS_; |
| // Do not collect STOI info for security checks. |
| newScan->getInliningInfo().setFlags(II_AvoidSecurityChecks); |
| |
| |
| // Step 2: Create GroupBy Node. |
| // Create a selection predicate and attach it to the |
| // GroupBy Node. in the |
| // case |
| // | |
| // IfThenElse |
| // ________|__________ |
| // | | | |
| // | | | |
| // | | | |
| // OR False RaiseError |
| // / \ |
| // / \ |
| // OneTrue (FK1 IS NULL |
| // | OR |
| // = FK2 IS NULL..) |
| // / \ |
| // 1 1 |
| |
| ItemExpr *aggSelPredicate = parser.getItemExprTree ("1 = 1"); |
| |
| ItemExpr *newAggExpr = NULL; |
| |
| // For inserts we have to see if a value exists in the referenced table |
| // and for deletes we have to check that the deleted value does not exist |
| // in the referencing table. Update is a delete and an insert, so we |
| // have to check both. |
| // You can tell whether to use "EXISTS" or "NOT EXISTS" by looking at |
| // RefConstraint::isaForeignKeyinTableBeingUpdated |
| |
| if (isReferencingConstraint) |
| { |
| // According to ANSI SQL99 (4.17.2), RI constraint is satisfied if one of the |
| // following conditions is true, depending on the <match option> specified in the |
| // <referential constraint definition>: |
| // If no < match type> was specified then, for each row R1 of the referencing |
| // table, either at least one of the values of the referencing columns in |
| // R1 shall be a null value, or the value of each referencing column in R1 shall |
| // be equal to the value of the corresponding referenced column in |
| // some row of the referenced table. |
| |
| // The MX 2.0 does not support <match type>, so it would be equivalent to |
| // not specifying the <match type>. |
| |
| // This MatchOptionPredicate of the form (fk1 IS NULL or fk2 IS NULL) |
| // is added to let the FKs with NULL values pass the RI constraint. |
| |
| // Also note that this MatchOptionPredicate is evaluated in the GroupBy |
| // instead of Scan node. This is because when this predicate is added |
| // to the row value constructor in the Scan node, the Optimizer fails to |
| // recognise it as a key predicate, hence affecting performance. |
| |
| NAString matchOptionPredicateTxt; |
| refConstraint.getMatchOptionPredicateText(matchOptionPredicateTxt, |
| &corrNameString); |
| |
| ItemExpr *matchOptionPred = parser.getItemExprTree |
| ((char *)matchOptionPredicateTxt.data()); |
| newAggExpr = new (heap) BiLogic(ITM_OR, |
| new (heap) |
| Aggregate(ITM_ONE_TRUE, aggSelPredicate), |
| matchOptionPred |
| ); |
| |
| } |
| else |
| { |
| newAggExpr = new (heap) |
| UnLogic(ITM_NOT, new (heap) Aggregate(ITM_ONE_TRUE, aggSelPredicate)); |
| } |
| |
| ItemExpr *grbySelectionPred = new (heap) |
| Case(NULL, |
| new (heap) |
| IfThenElse(newAggExpr, |
| new (heap) BoolVal(ITM_RETURN_FALSE), |
| new (heap) |
| RaiseError((Lng32)EXE_RI_CONSTRAINT_VIOLATION, constraintName, |
| tableName))); |
| |
| |
| // Create a GroupBy on the newScan, and attach the new case as "having" predicate. |
| RelExpr * newGrby = new(heap) |
| GroupByAgg(newScan, REL_GROUPBY); |
| newGrby->addSelPredTree(grbySelectionPred); |
| |
| // Create the Root Node. |
| RelExpr *newRoot = new (heap) |
| RelRoot(newGrby, |
| TransMode::REPEATABLE_READ_ACCESS_, |
| SHARE_); |
| |
| ((RelRoot *)newRoot)->setEmptySelectList(); |
| |
| return newRoot; |
| } // createRISubtree() |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Given the ConstraintList, this function creates a RI subtree and returns |
| // the root of the subtree to the caller. |
| ////////////////////////////////////////////////////////////////////////////// |
| RelExpr* GenericUpdate::inlineRI (BindWA *bindWA, |
| const RefConstraintList *refConstraints, |
| CollHeap *heap) |
| { |
| Int32 entries = 0; |
| RelExpr *riSubtree = NULL; |
| const NATable *naTable = getTableDesc()->getNATable(); |
| |
| CMPASSERT (!refConstraints->isEmpty()) |
| |
| if ((entries = refConstraints->entries())) |
| { |
| riSubtree = createRISubtree(bindWA, naTable, *(refConstraints->at(0)), heap); |
| for (Int32 i=1; i < entries; i++) |
| { |
| riSubtree = new(heap) |
| Union(createRISubtree(bindWA, naTable, *(refConstraints->at(i)), heap), |
| riSubtree, NULL, NULL, REL_UNION, CmpCommon::statementHeap(), TRUE); |
| } //end of for |
| } |
| |
| CMPASSERT(riSubtree); |
| |
| // Create the Root Node, only if the "riSubtree" is not a RelRoot. |
| // "riSubtree" will be a relroot when there is only one RI constraint. |
| // Avoding duplicate roots. |
| if (riSubtree->getOperatorType() != REL_ROOT) { |
| riSubtree = new (heap) |
| RelRoot(riSubtree, |
| TransMode::REPEATABLE_READ_ACCESS_, |
| SHARE_); |
| ((RelRoot *)riSubtree)->setEmptySelectList(); |
| } |
| |
| riSubtree->getInliningInfo().setFlags(II_ActionOfRI); |
| return riSubtree; |
| } // inlineRI. |
| |
| /***************************************************************************** |
| ****************************************************************************** |
| **** Methods for handling MV Logging |
| ****************************************************************************** |
| *****************************************************************************/ |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Logging should NOT be done in the following cases: |
| // 1. DELETE FROM MV |
| // 2. Update/Delete on INSERTLOG table. |
| // 3. NOLOG option specified |
| // 4. Pipelined refresh. |
| // 5. Recompute. |
| // In the first 3 cases, the table should be marked as inconsistent, and |
| // getIsInsertLog() returns INCONSISTENT_NOLOG. |
| // In the cases of pipelined refresh and recompute, logging is |
| // disabled, but the table is NOT marked as inconsistent (CONSISTENT_NOLOG). |
| // P.S. Marking the table as inconsistent is not implemented yet. |
| ////////////////////////////////////////////////////////////////////////////// |
| NABoolean GenericUpdate::isMvLoggingRequired() |
| { |
| const ComMvAttributeBitmap& bitmap = |
| getTableDesc()->getNATable()->getMvAttributeBitmap(); |
| |
| if (!bitmap.getLoggingRequired()) |
| { |
| // Just in case the user used NOLOG when logging is not required. We don't |
| // want to mark the table as inconsistent. |
| isNoLogOperation_ = NORMAL_LOGGING; |
| return FALSE; |
| } |
| |
| #if 0 |
| // A set loggingRequired flag does not mean the MVs on this table |
| // are initialized. If all the ON REQUEST MVs using this table |
| // are not initialized, no logging is required. |
| // This is commented out until we update the redefinition timestamp |
| // of the base table when we initialize the MV. |
| const UsingMvInfoList& mvsUsingMe = getTableDesc()->getNATable()->getMvsUsingMe(); |
| NABoolean foundInitializedMVs = FALSE; |
| CollIndex maxEntries = mvsUsingMe.entries(); |
| for (CollIndex i=0; i<maxEntries; i++) |
| { |
| const UsingMvInfo* mv = mvsUsingMe[i]; |
| |
| // Ignore MVs that are not ON REQUEST. |
| if ( mv->getRefreshType() == COM_ON_REQUEST && |
| mv->isInitialized() ) |
| { |
| // The MV is initialized. |
| // No need to continue the loop - logging is required. |
| foundInitializedMVs = TRUE; |
| break; |
| } |
| } |
| if (foundInitializedMVs == FALSE) |
| { |
| // No initialized ON REQUEST MVs were found, so no reason to log. |
| // Do not cache this query, to avoid breaking such statements |
| // after the MV has been initialized. |
| setNonCacheable(); |
| return FALSE; |
| } |
| #endif |
| |
| // Check for Update/Delete on an INSERTLOG table |
| if (getOperatorType() != REL_UNARY_INSERT && |
| bitmap.getIsInsertLog() ) |
| { |
| setNoLogOperation(FALSE); // This is an inconsistent operation. |
| } |
| |
| // Marking the table as inconsistent is not implemented yet, |
| // therefore the INCONSISTENT_NOLOG mode is not considered logging. |
| // When implemented, it will mean writing the CurrentEpoch to the UMD |
| // table, and will be handled just like logging. |
| return (isNoLogOperation() == NORMAL_LOGGING); |
| //return (isNoLogOperation() == NORMAL_LOGGING || |
| // isNoLogOperation() == INCONSISTENT_NOLOG); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Add to the RETDesc the virtual columns needed for MV logging. |
| ////////////////////////////////////////////////////////////////////////////// |
| void GenericUpdate::prepareForMvLogging(BindWA *bindWA, |
| CollHeap *heap) |
| { |
| // Create a "virtual column" called @CURRENT_EPOCH for the CurrentEpoch |
| // function. This function must be evaluated on the GU, and pipelined to |
| // the Log Insert node. |
| ItemExpr *currEpoch = new(heap) GenericUpdateOutputFunction(ITM_CURRENTEPOCH); |
| ValueId epochId = |
| addVirtualColumn(bindWA, currEpoch, InliningInfo::getEpochVirtualColName(), heap); |
| ValueId rowTypeId, rowCountId; |
| if (getOperatorType() == REL_UNARY_INSERT && |
| getTableDesc()->getNATable()->getMvAttributeBitmap().getAutomaticRangeLoggingRequired()) |
| { |
| // dead code, range logging is not supported |
| ItemExpr *rowType = new(heap) GenericUpdateOutputFunction(ITM_VSBBROWTYPE); |
| ItemExpr *rowCount = new(heap) GenericUpdateOutputFunction(ITM_VSBBROWCOUNT); |
| rowTypeId = |
| addVirtualColumn(bindWA, rowType, InliningInfo::getRowTypeVirtualColName(), heap); |
| rowCountId = |
| addVirtualColumn(bindWA, rowCount, InliningInfo::getRowCountVirtualColName(), heap); |
| } |
| |
| ItemExpr *tsOutExpr = new (heap) |
| GenericUpdateOutputFunction(ITM_JULIANTIMESTAMP, |
| 1, |
| new (heap) InternalTimestamp); |
| |
| ValueId tsId = addVirtualColumn(bindWA, |
| tsOutExpr, |
| InliningInfo::getMvLogTsColName(), |
| heap); |
| |
| |
| ValueIdSet potentialOutputs; |
| getPotentialOutputValues(potentialOutputs); |
| potentialOutputs += epochId; |
| potentialOutputs += tsOutExpr->getValueId(); |
| setPotentialOutputValues(potentialOutputs); |
| getInliningInfo().setFlags(II_isMVLoggingInlined); |
| |
| // for push down |
| if ( ((getOperatorType() == REL_UNARY_UPDATE) && CmpCommon::getDefault(MV_LOG_PUSH_DOWN_DP2_UPDATE) == DF_ON) || |
| ((getOperatorType() == REL_UNARY_DELETE) && CmpCommon::getDefault(MV_LOG_PUSH_DOWN_DP2_DELETE) == DF_ON) || |
| ((getOperatorType() == REL_UNARY_INSERT) && CmpCommon::getDefault(MV_LOG_PUSH_DOWN_DP2_INSERT) == DF_ON) ) |
| getInliningInfo().setFlags(II_isUsedForMvLogging); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Insert the OLD and NEW values into the MV IUD Log |
| ////////////////////////////////////////////////////////////////////////////// |
| RelExpr *GenericUpdate::createMvLogInsert(BindWA *bindWA, |
| CollHeap *heap, |
| UpdateColumns *updatedColumns, |
| NABoolean projectMidRangeRows) |
| { |
| MvIudLog logTableObj(this, bindWA); |
| logTableObj.setUpdatedColumns(updatedColumns); |
| RelExpr *insertNode = logTableObj.buildInsert(TRUE, |
| ChangesTable::ALL_ROWS, |
| FALSE, |
| TRUE); |
| |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| prepareForMvLogging(bindWA, heap); |
| |
| if (projectMidRangeRows) |
| { |
| // Set the flag on this node. |
| // This flag should be set even when range logging is off. |
| getInliningInfo().setFlags(II_ProjectMidRangeRows); |
| } |
| |
| RelExpr *topNode = insertNode; |
| if (logTableObj.needsRangeLogging() && projectMidRangeRows) |
| { |
| // dead code, range logging is not supported |
| RelRoot *rootNode = new (heap) RelRoot(insertNode); |
| rootNode->setEmptySelectList(); |
| |
| ItemExpr *noIgnoreCondition = new(heap) |
| BiRelat(ITM_NOT_EQUAL, |
| new(heap) ConstValue(ComMvRowType_MidRange), |
| new(heap) ColReference(new(heap) |
| ColRefName(InliningInfo::getRowTypeVirtualColName())) ); |
| |
| // for the "else" of the 'when clause' |
| ItemExpr *noOpArg = new (heap) ConstValue(0); |
| RelExpr *noOp = new (heap) Tuple(noOpArg); |
| RelRoot *noOpRoot = new (heap) RelRoot(noOp); |
| noOpRoot->setEmptySelectList(); |
| |
| Union *ifNode = new(heap) Union |
| (rootNode, noOpRoot, NULL, noIgnoreCondition, REL_UNION, |
| CmpCommon::statementHeap(), TRUE); |
| ifNode->setCondUnary(); |
| |
| topNode = ifNode; |
| } |
| |
| RelRoot *rootNode = new (heap) RelRoot(topNode); |
| rootNode->setEmptySelectList(); |
| rootNode->getInliningInfo().setFlags(II_ActionOfRI); |
| |
| return rootNode; |
| } |
| |
| // This helper function is called only from GenericUpdate::handleInlining and GenericUpdate::getTriggeredMVs |
| // It checks if we are compiling a NOT ATOMIC statement as raises the appropriate |
| // error/warning. A warning is raised for ODBC and the statement will be compiled as an |
| // ATOMIC statement. This method retuns TRUE if an error is raised and returns FALSE otherwise |
| NABoolean GenericUpdate::checkForNotAtomicStatement(BindWA *bindWA, Lng32 sqlcode, NAString objname, NAString tabname) |
| { |
| if (bindWA->getHostArraysArea() && |
| bindWA->getHostArraysArea()->getTolerateNonFatalError()) { |
| |
| if (CmpCommon::getDefault(ODBC_PROCESS) != DF_ON) { |
| *CmpCommon::diags() << DgSqlCode(-sqlcode) |
| << DgString0(objname) |
| << DgString1(tabname); |
| bindWA->setErrStatus(); |
| return TRUE; |
| } |
| else { |
| bindWA->getHostArraysArea()->setTolerateNonFatalError(FALSE); |
| setTolerateNonFatalError(RelExpr::UNSPECIFIED_); |
| *CmpCommon::diags() << DgSqlCode(sqlcode) |
| << DgString0(objname) |
| << DgString1(tabname); |
| } |
| |
| } |
| return FALSE ; |
| } |
| |
| /***************************************************************************** |
| ****************************************************************************** |
| **** Methods for handling ON STATEMENT MVs |
| ****************************************************************************** |
| *****************************************************************************/ |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // This method inserts for each ON STATEMENT MV, that is affected by the |
| // action at the IUD node, a MVImmediate trigger(s) to the list of triggers to |
| // be fired on the subject table. These MVImmediate triggers are responsible for |
| // refreshing the MVs. |
| // |
| // The algorithm is as follows: |
| // |
| // 1. get list of MVs defined on the subject table |
| // 2. for each MV in the list do |
| // 2.1 ensure refresh type is "ON STATEMENT" - if not, skip MV |
| // 2.2 verify that the MV is initialized - if not, skip MV |
| // 2.3 add MV to the list of triggers (call insertMvToTriggerList) |
| // |
| // For now, only ON STATEMENT MJVs are supported!!! |
| // |
| ////////////////////////////////////////////////////////////////////////////// |
| BeforeAndAfterTriggers * |
| GenericUpdate::getTriggeredMvs(BindWA *bindWA, |
| BeforeAndAfterTriggers *list, |
| UpdateColumns *updatedColumns) |
| { |
| CollHeap *heap = bindWA->wHeap(); |
| |
| const NATable *subjectNA = tabId_->getNATable(); |
| CMPASSERT(subjectNA != NULL); |
| |
| const UsingMvInfoList &mvList = subjectNA->getMvsUsingMe(); |
| if (mvList.isEmpty()) |
| { |
| // No MVs are to be refreshed - return the given list as is |
| return list; |
| } |
| else { //check for any non-atomic statements |
| const PartitioningFunction *partFunc = |
| subjectNA->getClusteringIndex()->getPartitioningFunction(); |
| |
| |
| for (CollIndex i = 0; i < mvList.entries(); i++) |
| { |
| if ((CmpCommon::getDefault(NAR_DEPOBJ_ENABLE2) == DF_OFF) || |
| ((partFunc->isARangePartitioningFunction()))) |
| |
| { |
| if (checkForNotAtomicStatement(bindWA,30033, |
| (mvList[i]->getMvName()).getQualifiedNameAsAnsiString(), |
| (subjectNA->getTableName()).getQualifiedNameAsAnsiString())) |
| { |
| return list; |
| } |
| } |
| |
| if (isNoRollback() || |
| (CmpCommon::transMode()->getRollbackMode() == TransMode::NO_ROLLBACK_)) { |
| *CmpCommon::diags() << DgSqlCode(-3232) |
| << DgString0((subjectNA->getTableName()).getQualifiedNameAsAnsiString()) |
| << DgString1("Materialized View :") |
| << DgString2((mvList[i]->getMvName()).getQualifiedNameAsAnsiString()); |
| bindWA->setErrStatus(); |
| |
| return list ; |
| } |
| } |
| } |
| |
| |
| // If the given list of triggers is still empty, allocate a new one for the |
| // new MVImmediate trigger(s) that might be added |
| BeforeAndAfterTriggers *newList = list; |
| if (newList == NULL) |
| { |
| newList = new(heap) |
| BeforeAndAfterTriggers(NULL, NULL, NULL, subjectNA->getRedefTime()); |
| } |
| |
| // Search for MVs that should be refreshed |
| for (CollIndex i = 0; i < mvList.entries(); i++) |
| { |
| // If MV is not ON STATEMENT - skip it |
| if (mvList[i]->getRefreshType() != COM_ON_STATEMENT) |
| { |
| continue; |
| } |
| |
| // If MV not intialized - skip it |
| if (!mvList[i]->isInitialized()) |
| { |
| continue; |
| } |
| |
| // Retreive NATable of the MV. Return on error. |
| CorrName mvCorr = CorrName(mvList[i]->getMvName(), heap); |
| NATable *naTableMv = bindWA->getNATable(mvCorr); |
| if (bindWA->errStatus()) |
| { |
| return list; |
| } |
| |
| // Retreive MVInfo for the MV. Return on error. |
| MVInfoForDML *mvInfo = naTableMv->getMVInfo(bindWA); |
| if (mvInfo == NULL) |
| { |
| return list; |
| } |
| |
| // For now, only ON STATMENET MJVs are supported! |
| if (mvInfo->getMVType() != COM_MJV) |
| { |
| continue; |
| } |
| |
| if (bindWA->getTopRoot() != NULL) |
| bindWA->getTopRoot()->setContainsOnStatementMV(TRUE); |
| |
| // The MV have passed all the general pre-conditions. Do some specific |
| // checks and if it passes, add it to the triggers list. |
| insertMvToTriggerList(newList, |
| bindWA, |
| heap, |
| mvList[i]->getMvName(), |
| mvInfo, |
| getTableName().getQualifiedNameObj(), |
| updatedColumns); |
| } |
| |
| if (newList->entries() == 0) |
| { |
| //"newList" will be freed by statementHeap |
| // add code annotation to prevent Coverity RESORUCE_LEAK checking error |
| // coverity[leaked_storage] |
| return NULL; // the list doesn't contain any triggers |
| } |
| |
| // If there are only immediate MVs but not triggers, and this is an |
| // embedded IUD statement, abort with an error. |
| if (list == NULL && |
| (getGroupAttr()->isEmbeddedUpdateOrDelete() || getGroupAttr()->isEmbeddedInsert())) |
| { |
| *CmpCommon::diags() << DgSqlCode(-12118); |
| bindWA->setErrStatus(); |
| return NULL; |
| } |
| |
| return newList; // return the updated list (including added MVs, if any) |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // This method does the actual insertion of the MVImmediate trigger to the |
| // list of triggers to be fired. Implemented only for derived classes! |
| // |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| // we are not supposed to get here |
| void GenericUpdate::insertMvToTriggerList(BeforeAndAfterTriggers *list, |
| BindWA *bindWA, |
| CollHeap *heap, |
| const QualifiedName &mvName, |
| MVInfoForDML *mvInfo, |
| const QualifiedName &subjectTable, |
| UpdateColumns *updateCols) |
| { |
| CMPASSERT(false); // not implemented in GenericUpdate |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| ////////////////////////////////////////////////////////////////////////////// |
| RelExpr *GenericUpdate::inlineOnlyRIandIMandMVLogging(BindWA *bindWA, |
| RelExpr *topNode, |
| NABoolean needIM, |
| RefConstraintList *riConstraints, |
| NABoolean isMVLoggingRequired, |
| UpdateColumns *columns, |
| CollHeap *heap) |
| { |
| RelExpr *imTree = NULL; |
| RelExpr *undoTree = NULL; |
| RelExpr *riTree = NULL; |
| RelExpr *mvTree = NULL; |
| RelExpr *result = NULL; |
| |
| if (needIM || (riConstraints!=NULL) || isMVLoggingRequired) |
| { |
| if (topNode->getFirstNRows() >= 0) |
| { |
| // create a firstN node to delete N rows. |
| FirstN * firstn = new(bindWA->wHeap()) |
| FirstN(topNode, topNode->getFirstNRows(), FALSE /* No ordering requirement */); |
| firstn->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| topNode->setFirstNRows(-1); |
| topNode = firstn; |
| } |
| } |
| // Create the tree that handles Index Maintainance. |
| if (needIM) |
| { |
| // here we don't use the internal syskey column name ("@SYSKEY") since the |
| // whole backbone is driven by the IUD node on the subject table |
| imTree = createIMTree(bindWA, columns, FALSE); |
| |
| } |
| |
| |
| // Create the tree that handles RI |
| if (riConstraints!=NULL) |
| riTree = inlineRI(bindWA, riConstraints, heap); |
| |
| // Create the tree for MV Logging. |
| if (isMVLoggingRequired) |
| { |
| // When RI/IM/Triggers are not inlined, we can skip the projection of |
| // the rows that are not Single/BeginRange/EndRange. |
| NABoolean projectMidRangeRows = TRUE; |
| if (imTree==NULL && riTree==NULL) |
| projectMidRangeRows = FALSE; |
| |
| mvTree = createMvLogInsert(bindWA, heap, columns, projectMidRangeRows); |
| if (mvTree != NULL) |
| { |
| // Use REL_SEMITSJ if it should not project any outputs |
| // to the IM or RI trees. |
| OperatorTypeEnum |
| joinType = (imTree || riTree) ? REL_LEFT_TSJ : REL_TSJ; |
| NABoolean needsOutputs = isMtsStatement() || |
| (getUpdateCKorUniqueIndexKey() && (getOperatorType() == REL_UNARY_DELETE)); |
| if (needsOutputs && joinType == REL_TSJ) |
| joinType = REL_ANTI_SEMITSJ; |
| // joinType = (imTree || riTree) ? REL_ANTI_SEMITSJ : REL_SEMITSJ; |
| |
| Join *logTSJ = new(heap) Join (topNode, mvTree, joinType); |
| logTSJ->getInliningInfo().setFlags(II_DrivingMvLogInsert); |
| logTSJ->setTSJForWrite(TRUE); |
| |
| if ( ((getOperatorType() == REL_UNARY_UPDATE) && CmpCommon::getDefault(MV_LOG_PUSH_DOWN_DP2_UPDATE) != DF_ON) || |
| ((getOperatorType() == REL_UNARY_DELETE) && CmpCommon::getDefault(MV_LOG_PUSH_DOWN_DP2_DELETE) != DF_ON) || |
| ((getOperatorType() == REL_UNARY_INSERT) && CmpCommon::getDefault(MV_LOG_PUSH_DOWN_DP2_INSERT) != DF_ON) ) |
| logTSJ->setAllowPushDown (FALSE); |
| |
| if (bindWA->getHostArraysArea() && bindWA->getHostArraysArea()->getTolerateNonFatalError()) |
| logTSJ->setTolerateNonFatalError(RelExpr::NOT_ATOMIC_); |
| topNode = logTSJ; |
| } |
| } |
| |
| result = imTree; |
| if (riTree != NULL) |
| { |
| if (result == NULL) |
| { |
| result = riTree; |
| } |
| else |
| { |
| result = new (heap) Union(imTree, riTree, NULL, NULL, REL_UNION, |
| CmpCommon::statementHeap(), TRUE); |
| } |
| } |
| |
| // For NAR , generate an undo tree as well |
| if (bindWA->getHostArraysArea() && bindWA->getHostArraysArea()->getTolerateNonFatalError() && (imTree || riTree)) |
| undoTree = createUndoTree(bindWA,columns,FALSE,(imTree||riTree),isMVLoggingRequired,NULL); |
| if (undoTree && result) |
| { |
| OperatorTypeEnum joinType= REL_TSJ; |
| Join * joinResultUndo = new (heap) Join(result,undoTree,joinType); |
| joinResultUndo->setTSJForWrite(TRUE); |
| joinResultUndo->setTolerateNonFatalError(RelExpr::NOT_ATOMIC_); |
| joinResultUndo->setTSJForUndo(TRUE); |
| result = joinResultUndo; |
| } |
| |
| |
| if (bindWA->isTrafLoadPrep()) |
| return result ; |
| |
| |
| if (result!=NULL) |
| { |
| // This RelRoot opens a new BindScope, so that Union::bindChildern() |
| // will not overwrite the RETDesc of the current scope with the NEW |
| // and OLD values. |
| RelRoot *rootNode = new(heap) RelRoot(result); |
| rootNode->setRootFlag(FALSE); |
| rootNode->setEmptySelectList(); |
| |
| OperatorTypeEnum joinOp; |
| if ((topNode->child(0).getGroupAttr()->getEmbeddedIUD()) || |
| isMtsStatement() || // This is an embedded IUD statement |
| // (i.e. an IUD statement that has an outer select) |
| (getUpdateCKorUniqueIndexKey() && (getOperatorType() == REL_UNARY_DELETE))) |
| { |
| // originally index maintenance was using a TSJ joining the |
| // tuples to be deleted from the base table with the tuples to |
| // be deleted in the invidual indices. When returning tuples to |
| // the user this causes the tuples to be deleted multiplied by |
| // the number of indices. Therefore we now use an ANTI_SEMITSJ |
| // which does not require LeafDeletes to return tuples...i.e. it |
| // only return the leaf tuple if nothing is returned from the right |
| // child...this is what we want. |
| joinOp = REL_ANTI_SEMITSJ; |
| } |
| else |
| { |
| joinOp = REL_TSJ; |
| } |
| |
| Join *newTSJ = new(heap) Join (topNode, rootNode, joinOp); |
| topNode = newTSJ; |
| } |
| |
| ((Join *)topNode)->setTSJForWrite(TRUE); |
| |
| if (isMerge()) |
| { |
| ((Join *)topNode)->setTSJForMerge(TRUE); |
| |
| if (isMergeUpdate()) |
| { |
| if (((MergeUpdate*)this)->insertValues()) |
| ((Join *)topNode)->setTSJForMergeWithInsert(TRUE); |
| } |
| else |
| { |
| if (((MergeDelete*)this)->insertValues()) |
| ((Join *)topNode)->setTSJForMergeWithInsert(TRUE); |
| } |
| } |
| |
| if (bindWA->getHostArraysArea() && bindWA->getHostArraysArea()->getTolerateNonFatalError()) |
| { |
| ((Join *)topNode)->setTolerateNonFatalError(RelExpr::NOT_ATOMIC_); |
| ((Join *)topNode)->setTSJForSetNFError(TRUE); |
| } |
| return topNode; |
| |
| } |
| |
| /***************************************************************************** |
| ****************************************************************************** |
| **** The "main" methods of inlining. |
| **** Build the trigger backbone and handle all the special cases. |
| ****************************************************************************** |
| *****************************************************************************/ |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| ////////////////////////////////////////////////////////////////////////////// |
| RelExpr *GenericUpdate::inlineAfterOnlyBackbone(BindWA *bindWA, |
| TriggersTempTable& tempTableObj, |
| TriggerList *rowTriggers, |
| TriggerList *stmtTriggers, |
| RefConstraintList *riConstraints, |
| NABoolean needIM, |
| NABoolean isMVLoggingRequired, |
| UpdateColumns *updatedColumns, |
| CollHeap *heap) |
| { |
| |
| |
| |
| |
| |
| RelExpr *topNode = this; |
| if (topNode->getFirstNRows() > 0) |
| { |
| // create a firstN node to delete N rows. |
| FirstN * firstn = new(bindWA->wHeap()) |
| FirstN(topNode, topNode->getFirstNRows(), FALSE /* No ordering requirement */); |
| firstn->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| topNode->setFirstNRows(-1); |
| topNode = firstn; |
| } |
| |
| NABoolean noPipelinedActions = |
| (rowTriggers == NULL) && (riConstraints == NULL); |
| |
| NABoolean rowTriggersPresent = (rowTriggers != NULL); |
| |
| // First inline MV logging, so it will be pushed to DP2 with the IUD. |
| if (isMVLoggingRequired) |
| { |
| RelExpr *mvTree = createMvLogInsert(bindWA, heap, updatedColumns, TRUE); |
| if (mvTree != NULL) |
| { |
| Join *logTSJ = new(heap) Join(topNode, mvTree, REL_LEFT_TSJ); |
| logTSJ->setTSJForWrite(TRUE); |
| logTSJ->getInliningInfo().setFlags(II_DrivingMvLogInsert); |
| if (bindWA->getHostArraysArea() && bindWA->getHostArraysArea()->getTolerateNonFatalError()) |
| logTSJ->setTolerateNonFatalError(RelExpr::NOT_ATOMIC_); |
| topNode = logTSJ; |
| } |
| } |
| |
| // Next inline Index Maintainance |
| if (needIM) |
| { |
| // here we don't use the internal syskey column name ("@SYSKEY") since the |
| // whole backbone is driven by the IUD node on the subject table |
| topNode = inlineIM(topNode, bindWA, FALSE, updatedColumns, heap, FALSE, rowTriggersPresent); |
| } |
| |
| // Next inline the temp Insert |
| topNode = inlineTempInsert(topNode, |
| bindWA, |
| tempTableObj, |
| FALSE, |
| noPipelinedActions, |
| heap); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| Insert *pTempInsert = (Insert *)(RelExpr *)(topNode->child(1)->child(0)); |
| CMPASSERT(pTempInsert->getOperatorType() == REL_LEAF_INSERT || |
| pTempInsert->getOperatorType() == REL_UNARY_INSERT ); |
| |
| // Inline RI and row triggers. |
| topNode = inlinePipelinedActions(topNode, bindWA, |
| rowTriggers, |
| riConstraints, |
| heap); |
| |
| // Inline statement triggers |
| topNode = inlineTriggerGroup(topNode, stmtTriggers, FALSE, heap, bindWA); |
| |
| // Inline the Temp delete. |
| topNode = inlineTempDelete(bindWA, topNode, tempTableObj, heap); |
| ((Union *)topNode)->setBlockedUnion(); |
| ((Union *)topNode)->setNoOutputs(); |
| if (isMtsStatement()) |
| ((Union *)topNode)->setIsTemporary(); |
| |
| // If we are in an NAR, then set the InNotAtomicStatement flag |
| if (bindWA->getHostArraysArea() && |
| bindWA->getHostArraysArea()->getTolerateNonFatalError()) |
| { |
| ((Union *)topNode)->setInNotAtomicStatement(); |
| } |
| |
| if (bindWA->errStatus()) |
| { |
| return this; |
| } |
| |
| // Now bind the resulting tree |
| topNode = topNode->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| { |
| return this; |
| } |
| |
| // store information for triggers transformation phase |
| getInliningInfo().buildTriggerBindInfo(bindWA, getRETDesc(), heap); |
| |
| return topNode; |
| } |
| |
| |
| |
| |
| RelExpr *GenericUpdate::inlineAfterOnlyBackboneForUndo(BindWA *bindWA, |
| TriggersTempTable& tempTableObj, |
| TriggerList *rowTriggers, |
| TriggerList *stmtTriggers, |
| RefConstraintList *riConstraints, |
| NABoolean needIM, |
| NABoolean isMVLoggingRequired, |
| UpdateColumns *updatedColumns, |
| CollHeap *heap) |
| { |
| RelExpr *imTree = NULL; |
| RelExpr *undoTree = NULL; |
| RelExpr *riTree = NULL; |
| RelExpr *mvTree = NULL; |
| RelExpr *result = NULL; |
| RelExpr *topNode = this; |
| if (topNode->getFirstNRows() > 0) |
| { |
| // create a firstN node to delete N rows. |
| FirstN * firstn = new(bindWA->wHeap()) |
| FirstN(topNode, topNode->getFirstNRows(), FALSE /* No ordering requirement */); |
| firstn->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| topNode->setFirstNRows(-1); |
| topNode = firstn; |
| } |
| |
| NABoolean noPipelinedActions = |
| (rowTriggers == NULL) && (riConstraints == NULL); |
| |
| NABoolean rowTriggersPresent = (rowTriggers != NULL); |
| |
| // create the temp insert tree here |
| |
| RelExpr *tempInsertNode = tempTableObj.buildInsert(TRUE); |
| RelRoot *tempInsRoot = new (heap) RelRoot(tempInsertNode); |
| tempInsRoot->setRootFlag(FALSE); |
| tempInsRoot->setEmptySelectList(); |
| |
| if (isMVLoggingRequired) |
| mvTree = createMvLogInsert(bindWA, heap, updatedColumns, TRUE); |
| |
| |
| if (mvTree != NULL) |
| { |
| |
| RelRoot *mvRoot = new (heap) RelRoot(mvTree); |
| if (mvRoot != NULL) |
| { |
| Join *logTSJ = new(heap) Join(topNode, mvRoot, REL_LEFT_TSJ); |
| logTSJ->setTSJForWrite(TRUE); |
| logTSJ->setTolerateNonFatalError(RelExpr::NOT_ATOMIC_); |
| logTSJ->getInliningInfo().setFlags(II_DrivingMvLogInsert); |
| topNode = logTSJ; |
| } |
| } |
| |
| if (tempInsRoot != NULL) |
| { |
| Join *logTSJ = new(heap) Join(topNode, tempInsRoot, REL_LEFT_TSJ); |
| logTSJ->setTSJForWrite(TRUE); |
| logTSJ->setTolerateNonFatalError(RelExpr::NOT_ATOMIC_); |
| topNode = logTSJ; |
| } |
| |
| |
| |
| |
| // Create the tree that handles Index Maintainance. |
| if (needIM) |
| { |
| // here we don't use the internal syskey column name ("@SYSKEY") since the |
| // whole backbone is driven by the IUD node on the subject table |
| imTree = createIMTree(bindWA, updatedColumns, FALSE); |
| |
| } |
| // Create the tree that handles RI |
| if (riConstraints!=NULL) |
| riTree = inlineRI(bindWA, riConstraints, heap); |
| |
| result = imTree; |
| if (riTree != NULL) |
| { |
| if (result == NULL) |
| { |
| result = riTree; |
| } |
| else |
| { |
| result = new (heap) Union(imTree, riTree, NULL, NULL, REL_UNION, |
| CmpCommon::statementHeap(), TRUE); |
| } |
| } |
| undoTree = createUndoTree(bindWA,updatedColumns,FALSE,(imTree||riTree),isMVLoggingRequired, &tempTableObj); |
| if (undoTree && result) |
| { |
| OperatorTypeEnum joinType= REL_TSJ; |
| Join * joinResultUndo = new (heap) Join(result,undoTree,joinType); |
| joinResultUndo->setTSJForWrite(TRUE); |
| joinResultUndo->setTolerateNonFatalError(RelExpr::NOT_ATOMIC_); |
| joinResultUndo->setTSJForUndo(TRUE); |
| result = joinResultUndo; |
| |
| } |
| |
| |
| |
| if (result!=NULL) |
| { |
| // This RelRoot opens a new BindScope, so that Union::bindChildern() |
| // will not overwrite the RETDesc of the current scope with the NEW |
| // and OLD values. |
| RelRoot *rootNode = new(heap) RelRoot(result); |
| rootNode->setRootFlag(FALSE); |
| rootNode->setEmptySelectList(); |
| |
| OperatorTypeEnum joinOp; |
| if ((topNode->child(0).getGroupAttr()->getEmbeddedIUD()) || |
| isMtsStatement()) // This is an embedded IUD statement |
| // (i.e. an IUD statement that has an outer select) |
| { |
| // originally index maintenance was using a TSJ joining the |
| // tuples to be deleted from the base table with the tuples to |
| // be deleted in the invidual indices. When returning tuples to |
| // the user this causes the tuples to be deleted multiplied by |
| // the number of indices. Therefore we now use an ANTI_SEMITSJ |
| // which does not require LeafDeletes to return tuples...i.e. it |
| // only return the leaf tuple if nothing is returned from the right |
| // child...this is what we want. |
| joinOp = REL_ANTI_SEMITSJ; |
| } |
| else |
| { |
| joinOp = REL_TSJ; |
| } |
| |
| Join *newTSJ = new(heap) Join (topNode, rootNode, joinOp); |
| newTSJ->setTSJForWrite(TRUE); |
| |
| newTSJ->setTolerateNonFatalError(RelExpr::NOT_ATOMIC_); |
| newTSJ->setTSJForSetNFError(TRUE); |
| RelRoot *rootNode2 = new(heap) RelRoot(newTSJ); |
| rootNode2->setRootFlag(FALSE); |
| rootNode2->setEmptySelectList(); |
| |
| topNode = rootNode2; |
| } |
| // Inline statement triggers |
| topNode = inlineTriggerGroup(topNode, stmtTriggers, FALSE, heap, bindWA); |
| |
| // Inline the Temp delete. |
| topNode = inlineTempDelete(bindWA, topNode, tempTableObj, heap); |
| ((Union *)topNode)->setBlockedUnion(); |
| ((Union *)topNode)->setNoOutputs(); |
| if (isMtsStatement()) |
| ((Union *)topNode)->setIsTemporary(); |
| |
| // If we are in an NAR, then set the InNotAtomicStatement flag |
| if (bindWA->getHostArraysArea() && |
| bindWA->getHostArraysArea()->getTolerateNonFatalError()) |
| { |
| ((Union *)topNode)->setInNotAtomicStatement(); |
| } |
| |
| if (bindWA->errStatus()) |
| { |
| return this; |
| } |
| |
| // Now bind the resulting tree |
| topNode = topNode->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| { |
| return this; |
| } |
| |
| // store information for triggers transformation phase |
| getInliningInfo().buildTriggerBindInfo(bindWA, getRETDesc(), heap); |
| |
| return topNode; |
| |
| } |
| ////////////////////////////////////////////////////////////////////////////// |
| // Remove from the inputs of 'node' any ValueIds that do not reference |
| // any of the values in realInputs. |
| ////////////////////////////////////////////////////////////////////////////// |
| static void minimizeInputsForNode(RelExpr *node, ValueIdSet &realInputs) |
| { |
| ValueIdSet inputsOfNode(node->getGroupAttr()->getCharacteristicInputs()); |
| realInputs.weedOutUnreferenced(inputsOfNode); |
| node->getGroupAttr()->setCharacteristicInputs(inputsOfNode); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // For each and every node in 'subtree' call minimizeInputsForNode() |
| // to remove the redundant inputs. |
| ////////////////////////////////////////////////////////////////////////////// |
| static void minimizeInputsForSubtree(RelExpr *subtree, ValueIdSet &realInputs) |
| { |
| minimizeInputsForNode(subtree, realInputs); |
| |
| for (Int32 i=0; i<subtree->getArity(); i++) |
| minimizeInputsForSubtree(subtree->child(i), realInputs); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // The binding process adds redundant inputs to the temp insert subtree. |
| // This usually works out in the transformation and normalization, but |
| // when the triggering action is an Update with sub-queries - it does not. |
| // The real inputs for the temp insert subtree, are the OLD values, plus |
| // the UniqueExecuteId. Remove from thecharacteristic inputs of every node |
| // of the temp insert subtree, all the values that do not reference the |
| // real inputs as defined above. |
| ////////////////////////////////////////////////////////////////////////////// |
| void GenericUpdate::removeRedundantInputsFromTempInsertTree(BindWA *bindWA, |
| RelExpr *tentativeSubtree) |
| { |
| // The OLD values are the outputs of the Scan node below the triggering IUD |
| // node (this node). |
| Scan *scanNode = getScanNode(); |
| CMPASSERT(scanNode != NULL); |
| ValueIdSet realInputs(scanNode->getGroupAttr()->getCharacteristicOutputs()); |
| |
| // All instantiations of the UniqueExcuteId funcction have the same ValueId. |
| // Once the transformation code changes are merged in, we can take the ExecId |
| // ValueId from the BindInfo. |
| ItemExpr *execId = new(bindWA->wHeap()) UniqueExecuteId(); |
| execId->bindNode(bindWA); |
| realInputs += execId->getValueId(); |
| |
| // Find the top node of the temp insert subtree.(through the RelRoot and TSJ). |
| RelExpr *tempInsertNode = tentativeSubtree->child(0)->child(1); |
| CMPASSERT(tempInsertNode->getOperatorType() == REL_ROOT); |
| CMPASSERT(tempInsertNode->child(0)->getOperatorType() == REL_UNARY_INSERT); |
| |
| // Now remove the redundant inputs. |
| minimizeInputsForSubtree(tempInsertNode, realInputs); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| ////////////////////////////////////////////////////////////////////////////// |
| RelExpr *GenericUpdate::inlineBeforeAndAfterBackbone(BindWA *bindWA, |
| RelExpr *tentativeSubtree, |
| TriggersTempTable& tempTableObj, |
| TriggerList *rowTriggers, |
| TriggerList *stmtTriggers, |
| RefConstraintList *riConstraints, |
| NABoolean needIM, |
| NABoolean isMVLoggingRequired, |
| UpdateColumns *updatedColumns, |
| CollHeap *heap) |
| { |
| // Create the effective Insert, Update or Delete node. |
| GenericUpdate *effectiveGuNode = NULL; |
| RelExpr *effectiveGuRootNode = |
| createEffectiveGU(bindWA, heap, tempTableObj, &effectiveGuNode, updatedColumns); |
| |
| CMPASSERT(effectiveGuNode != NULL); |
| |
| RelExpr *topNode = effectiveGuRootNode; |
| // used by the generator to indicate that this plan has triggers so |
| // if the trigger is dropped the plan is recompiled |
| topNode->getInliningInfo().setFlags(II_hasTriggers); |
| |
| // We only use the FiringTriggers flag if this GU will have to generate |
| // the NEW and OLD values for Row triggers, RI or IM. |
| if (getInliningInfo().hasPipelinedActions()) |
| { |
| effectiveGuNode->getInliningInfo().setFlags(II_hasPipelinedActions); |
| } |
| |
| // First inline MV logging, so it will be pushed to DP2 with the IUD. |
| if (isMVLoggingRequired) |
| { |
| RelExpr *mvTree = createMvLogInsert(bindWA, heap, updatedColumns, TRUE); |
| if (mvTree != NULL) |
| { |
| OperatorTypeEnum joinType = REL_LEFT_TSJ; |
| if (!rowTriggers && !riConstraints && !needIM) |
| joinType = REL_TSJ; |
| Join *logTSJ = new(heap) Join(topNode, mvTree, joinType); |
| logTSJ->setTSJForWrite(TRUE); |
| |
| // disable parallele execution for TSJs that control row triggers |
| // execution. Parallel execution for triggers TSJ introduces the |
| // potential for non-deterministic execution |
| if (rowTriggers) |
| logTSJ->getInliningInfo().setFlags(II_SingleExecutionForTriggersTSJ); |
| |
| topNode = logTSJ; |
| } |
| } |
| |
| if (needIM) |
| { |
| // Next inline Index Maintainance |
| NABoolean imIsLastTSJ = ((rowTriggers==NULL) && (riConstraints==NULL)); |
| // here we do use the internal syskey column name ("@SYSKEY") since the |
| // whole backbone is driven by the temp-table insert node |
| |
| NABoolean rowTriggersPresent = (rowTriggers != NULL); |
| |
| if (getOperatorType() == REL_UNARY_INSERT) |
| { |
| topNode = inlineIM(topNode, bindWA, imIsLastTSJ, updatedColumns, |
| heap, FALSE, rowTriggersPresent); |
| } |
| else // REL_UNARY_UPDATE or REL_UNARY_DELETE |
| { |
| topNode = inlineIM(topNode, bindWA, imIsLastTSJ, updatedColumns, |
| heap, TRUE, rowTriggersPresent); |
| } |
| } |
| |
| // Next inline RI and row triggers (temp Insert is on the tentative side). |
| topNode = inlinePipelinedActions(topNode, |
| bindWA, |
| rowTriggers, |
| riConstraints, |
| heap); |
| |
| // Inline statement triggers |
| topNode = inlineTriggerGroup(topNode, stmtTriggers, FALSE, heap, bindWA); |
| |
| // Inline the Temp delete. |
| topNode = inlineTempDelete(bindWA, topNode, tempTableObj, heap); |
| topNode->getInliningInfo().setFlags(II_BeforeTriggersExist); |
| |
| // Open a new scope for the after-trigger part. |
| topNode = new(heap) RelRoot(topNode); |
| ((RelRoot *)topNode)->setRootFlag(FALSE); |
| |
| // Join the two parts of the backbone using a blocked Union node. |
| Union *topUnion = new(heap) Union(tentativeSubtree, topNode, NULL, NULL, |
| REL_UNION, CmpCommon::statementHeap(), |
| TRUE); |
| topUnion->setBlockedUnion(); |
| topUnion->setNoOutputs(); |
| topUnion->getInliningInfo().setFlags(II_DrivingBeforeTriggers); |
| |
| // If we are in an NAR, then set the InNotAtomicStatement flag |
| if (bindWA->getHostArraysArea() && |
| bindWA->getHostArraysArea()->getTolerateNonFatalError()) |
| { |
| topUnion->setInNotAtomicStatement(); |
| } |
| |
| topNode = topUnion; |
| |
| // Now bind the resulting tree |
| topNode = topNode->bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| // store information for triggers transformation phase |
| InliningInfo &info = effectiveGuNode->getInliningInfo(); |
| |
| if (!(info.hasPipelinedActions()) && getOperatorType() != REL_UNARY_UPDATE) |
| { |
| // case of before triggers and after statement triggers where the after triggers are in conflict |
| // and the effective GU is either an insert or a delete. |
| info.getTriggerBindInfo()->setBackboneIudNum(bindWA->getUniqueIudNum()); |
| } |
| else |
| { |
| info.buildTriggerBindInfo(bindWA, effectiveGuRootNode->getRETDesc(), heap); |
| } |
| |
| |
| // The binding process adds redundant inputs to the temp insert subtree. |
| // This usually works out in the transformation and normalization, but |
| // when the triggering action is an Update with sub-queries - it does not. |
| if (getOperatorType() == REL_UNARY_UPDATE) |
| removeRedundantInputsFromTempInsertTree(bindWA, tentativeSubtree); |
| |
| return topNode; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Is this backbone cascaded from a row trigger? |
| ////////////////////////////////////////////////////////////////////////////// |
| NABoolean GenericUpdate::shouldForbidMaterializeNodeHere(BindWA *bindWA) |
| { |
| // If this backbone is cascaded from a row after trigger, Do not allow the |
| // optimizer to use any Materialize nodes below this point. |
| BindScope *triggerScope = NULL; |
| // Skip this if the flag was already set by a trigger above us. |
| if (!getInliningInfo().isMaterializeNodeForbidden()) |
| { |
| // For each scope above us, that has a trigger object |
| while ((triggerScope = bindWA->findNextScopeWithTriggerInfo(triggerScope)) |
| != NULL) |
| { |
| StmtDDLCreateTrigger* triggerObj = triggerScope->context()->triggerObj(); |
| // If its not a row after trigger - continue searching. |
| if (triggerObj->isStatement() || !triggerObj->isAfter()) |
| continue; |
| |
| return TRUE; |
| } |
| } |
| |
| return FALSE; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| ////////////////////////////////////////////////////////////////////////////// |
| void GenericUpdate::InliningFinale(BindWA *bindWA, RelExpr *topNode, |
| RETDesc *origRETDesc) |
| { |
| // QSTUFF |
| // this expression is executed once all inlining and binding |
| // for index maintenance, RI and triggers has been done. We replace the |
| // current scope with a RETDesc containing references to old and new column |
| // values as can be referenced in the return clause of an embedded delete or |
| // and embedded update |
| |
| if (getGroupAttr()->isEmbeddedUpdateOrDelete() || |
| isMtsStatement() || |
| (getUpdateCKorUniqueIndexKey() && (getOperatorType() == REL_UNARY_DELETE))) |
| { |
| |
| // lets only return the implicit old and new table columns |
| if (!getRETDesc()) delete getRETDesc(); |
| |
| CorrName corrNEWTable |
| (getTableDesc()->getCorrNameObj().getQualifiedNameObj(), |
| bindWA->wHeap(),NEWTable); |
| |
| if (getOperatorType() == REL_UNARY_UPDATE || |
| getOperatorType() == REL_UNARY_INSERT){ |
| // expose NEW table columns |
| setRETDesc(new (bindWA->wHeap()) |
| RETDesc(bindWA,getTableDesc(),&corrNEWTable)); |
| } |
| else |
| setRETDesc(new (bindWA->wHeap()) RETDesc(bindWA)); |
| |
| if (getOperatorType() == REL_UNARY_UPDATE || |
| getOperatorType() == REL_UNARY_DELETE) |
| { |
| CorrName corrOLDTable |
| (getScanNode(TRUE)->getTableDesc()->getCorrNameObj().getQualifiedNameObj(), |
| bindWA->wHeap(),OLDTable |
| ); |
| |
| // expose OLD table columns |
| getRETDesc()-> |
| addColumns(bindWA, *getScanNode(TRUE)->getRETDesc(), &corrOLDTable); |
| |
| } |
| |
| // allow the TSJRule to be used to transform Updates/Deletes |
| setNoFlow(TRUE); |
| |
| // record the GenericUpdateRoot property in the group |
| // attributes of the root of the generic update tree. |
| // this is used by pushdowncovered expression to prevent |
| // expression to be push beyond the root of a generic update |
| // tree |
| topNode->getGroupAttr()->setGenericUpdateRoot(TRUE); |
| |
| // set current scope to contain NEW and OLD tables only |
| origRETDesc = getRETDesc(); |
| } |
| |
| if (bindWA->inDDL()) |
| return; |
| |
| //QSTUFF |
| |
| ValueIdList outputs; |
| getRETDesc()->getValueIdList(outputs, USER_AND_SYSTEM_COLUMNS); |
| addPotentialOutputValues(outputs); |
| |
| // If ever extend the SQL syntax to such non-Ansi constructs as |
| // INSERT INTO TI (DELETE FROM TD ... ); |
| // SELECT * FROM (UPDATE TU SET ... ) X; |
| // then we'll want to revisit these next two lines, |
| // (resetting our RETDesc to an empty one) because otherwise |
| // this nonempty one'll become our parent RelRoot's compExpr(), |
| // which will cause |
| // RelRoot::preCodeGen - compExpr().replaceVEGExpressions - |
| // VEGReference::replaceVEGReference |
| // to assert with "no available values hence valuesToBeBound.isEmpty". |
| // |
| // QSTUFF |
| // please see above..we made the extensions and did fixed whats |
| // referred to above |
| // select * from (delete from x)y, z where y.x = z.x; |
| // select * from (update x set x = x + 1) y, z where y.x = z.x; |
| if (!getGroupAttr()->isEmbeddedUpdateOrDelete() && |
| !isMtsStatement() && |
| !(getUpdateCKorUniqueIndexKey() && (getOperatorType() == REL_UNARY_DELETE))) |
| { |
| delete getRETDesc(); |
| setRETDesc(new (bindWA->wHeap()) RETDesc(bindWA)); |
| } |
| |
| // QSTUFF |
| topNode->setRETDesc(getRETDesc()); |
| bindWA->getCurrentScope()->setRETDesc(origRETDesc); |
| } |
| |
| // Helper function used to check the existence of at least one update |
| // trigger whose explicit columns match at least one of the columns |
| // supplied as a second parameter |
| NABoolean atLeastOneMatch(const BeforeAndAfterTriggers *allTriggers, |
| const UpdateColumns *columns) |
| { |
| TriggerList *trigs = NULL; |
| |
| assert(allTriggers != NULL); |
| |
| // check the before row triggers for a match |
| if (allTriggers->getBeforeTriggers() != NULL) |
| { |
| trigs = |
| allTriggers->getBeforeTriggers()->getColumnMatchingTriggers (columns); |
| } |
| |
| // check the after row triggers for a match |
| if ((trigs == NULL) && (allTriggers->getAfterRowTriggers() != NULL)) |
| { |
| trigs = |
| allTriggers->getAfterRowTriggers()->getColumnMatchingTriggers (columns); |
| } |
| |
| // check the after statement triggers for a match |
| if ((trigs == NULL) && (allTriggers->getAfterStatementTriggers() != NULL)) |
| { |
| trigs = |
| allTriggers->getAfterStatementTriggers()->getColumnMatchingTriggers (columns); |
| } |
| |
| // at least one match was found |
| if (trigs != NULL) |
| { |
| return TRUE; |
| } |
| |
| // no match |
| return FALSE; |
| } |
| |
| NABoolean GenericUpdate::checkNonSupportedTriggersUse(BindWA *bindWA, |
| QualifiedName &subjectTable, |
| ComOperation op, |
| BeforeAndAfterTriggers *allTriggers) |
| { |
| CollHeap *heap = bindWA->wHeap(); |
| |
| // disable embedded update and delete as trigger events |
| if ((allTriggers != NULL) && (getGroupAttr()->isEmbeddedUpdateOrDelete())) |
| { |
| // for update operations we have to make sure that there is at least |
| // one trigger defined on the column updated by the operation |
| if (op == COM_UPDATE) |
| { |
| // Get the list of updated columns. |
| UpdateColumns *columns = new(heap) UpdateColumns(stoi_->getStoi()); |
| |
| // find at least one trigger that is fired by the columns updated |
| // by this operation |
| if (atLeastOneMatch(allTriggers, columns)) |
| { |
| *CmpCommon::diags() << DgSqlCode(-11027); |
| bindWA->setErrStatus(); |
| // "columns" will be freed by statementHeap. |
| // add code annotation to prevent Coverity checking error |
| // coverity[leaked_storage] |
| return TRUE; |
| } |
| |
| } |
| else // delete - all delete triggers are considered |
| { |
| *CmpCommon::diags() << DgSqlCode(-11027); |
| bindWA->setErrStatus(); |
| return TRUE; |
| } |
| |
| } |
| |
| // disable embedded insert as trigger events |
| if ((allTriggers != NULL) && (getGroupAttr()->isEmbeddedInsert())) |
| { |
| *CmpCommon::diags() << DgSqlCode(-11027); |
| bindWA->setErrStatus(); |
| return TRUE; |
| } |
| |
| // the set clause of SET ON ROLLBACK statements may not change |
| // columns on which update triggers are defined |
| if (newRecBeforeExpr() != NULL) |
| { |
| BeforeAndAfterTriggers *allTriggers2 = allTriggers; |
| |
| // for SET ON ROLLBACK delete statements, we are not interested |
| // in the triggers fired by the delete operation but rather by the |
| // triggers defined on the columns updated by the SET ON ROLLBACK |
| // clause. These triggers are update triggers not delete triggers. |
| if (op == COM_DELETE) |
| { |
| ComOperation op2 = COM_UPDATE; |
| allTriggers2 = |
| bindWA->getSchemaDB()->getTriggerDB()->getTriggers(subjectTable, |
| op2, bindWA); |
| if (bindWA->errStatus()) |
| return TRUE; |
| } |
| |
| if (allTriggers2 != NULL) |
| { |
| ValueId exprId; |
| UpdateColumns *columns = |
| new(heap) UpdateColumns((SqlTableOpenInfo *)NULL); |
| for (exprId = newRecBeforeExpr().init(); |
| newRecBeforeExpr().next(exprId); |
| newRecBeforeExpr().advance(exprId)) |
| { |
| ItemExpr *thisIE = exprId.getItemExpr(); |
| columns->addColumn((thisIE->child(0).getNAColumn())->getPosition()); |
| } |
| |
| // if at least one of the columns updated in SET ON ROLLBACK clause |
| // of the SET ON ROLLBACK statement is a subject column of an update |
| // trigger, raise an error message |
| if (atLeastOneMatch(allTriggers2, columns)) |
| { |
| *CmpCommon::diags() << DgSqlCode(-11026); |
| bindWA->setErrStatus(); |
| // "columns" will be freed by statementHeap. |
| // add code annotation to prevent Coverity checking error |
| // coverity[leaked_storage] |
| return TRUE; |
| } |
| } |
| } |
| |
| return FALSE; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Handle the inlining of Triggers, RI, IM, MV logging and ON STATEMENT MVs. |
| // This method is called from the end of the bindNode() methods of Insert, |
| // Update and Delete. |
| // The trigger backbone is different if before triggers exist. |
| // Please read the Triggers internal documentation before trying to understand |
| // this code. |
| ////////////////////////////////////////////////////////////////////////////// |
| RelExpr * GenericUpdate::handleInlining(BindWA *bindWA, RelExpr *boundExpr) |
| { |
| RETDesc *origScopeRETDesc = bindWA->getCurrentScope()->getRETDesc(); |
| CorrName &subjectTableCorr = getTableDesc()->getCorrNameObj(); |
| |
| if (bindWA->inDDL()) |
| { |
| // some QSTUFF code in inlineingFinale() should be executed when we |
| // are in create view statement. |
| InliningFinale(bindWA, boundExpr, origScopeRETDesc); |
| return boundExpr; |
| } |
| |
| // MultiCommit is currently only valid for DELETE statements |
| if (NOT getOperator().match(REL_ANY_DELETE) |
| && |
| CmpCommon::transMode()->getMultiCommit() == TransMode::MC_ON_) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4351); |
| bindWA->setErrStatus(); |
| return boundExpr; |
| } |
| |
| // We don't do views. |
| // ignore location specified operations. |
| if ( (getTableDesc()->getNATable()->getViewText() != NULL) || |
| (subjectTableCorr.isLocationNameSpecified() )) |
| return boundExpr; |
| |
| if (subjectTableCorr.getSpecialType() == ExtendedQualName::SG_TABLE) |
| { |
| InliningFinale(bindWA, boundExpr, origScopeRETDesc); |
| return boundExpr; // Nothing for us to do here. |
| } |
| |
| // A "DELETE [FIRST n] FROM <IUD-log-table>" |
| // is the only case we allow a special table through here. |
| NABoolean firstN_OnIudLogTable = FALSE; |
| if ((subjectTableCorr.getSpecialType() == ExtendedQualName::IUD_LOG_TABLE) && |
| (getFirstNRows() > 0) ) |
| firstN_OnIudLogTable = TRUE; |
| |
| // Don't waste time on special tables like index etc. |
| // The IUD log is allowed here because we allow delete with multi commit on it. |
| if ((subjectTableCorr.getSpecialType() != ExtendedQualName::NORMAL_TABLE) && |
| (subjectTableCorr.getSpecialType() != ExtendedQualName::MV_TABLE) && |
| !firstN_OnIudLogTable && |
| !getInliningInfo().isNeedGuOutputs() ) |
| return boundExpr; |
| |
| // no inlining for the effective GU of a before trigger |
| if (getInliningInfo().isEffectiveGU()) |
| return bindEffectiveGU(bindWA); |
| |
| if (getInliningInfo().isNeedGuOutputs()) |
| { |
| // The OLD/NEW outputs of this GU node are needed for some purpose |
| // other than triggers/RI/IM etc. |
| createOldAndNewCorrelationNames(bindWA); |
| |
| ValueIdList outputs; |
| getRETDesc()->getValueIdList(outputs, USER_AND_SYSTEM_COLUMNS); |
| setPotentialOutputValues(outputs); |
| |
| setNoFlow(TRUE); |
| |
| return boundExpr; |
| } |
| |
| // If code future changes cause this assertion to fail, we need to make |
| // sure our code still works. |
| CMPASSERT(boundExpr == this); |
| |
| ComOperation op; |
| switch (getOperatorType()) |
| { |
| case REL_UNARY_INSERT: op = COM_INSERT; break; |
| case REL_UNARY_UPDATE: op = COM_UPDATE; break; |
| case REL_UNARY_DELETE: op = COM_DELETE; break; |
| default : return boundExpr; // We only handle these three operators. |
| } |
| |
| CollHeap *heap = bindWA->wHeap(); |
| QualifiedName& subjectTable = subjectTableCorr.getQualifiedNameObj(); |
| |
| // get all triggers in the triggerDB, and RIs from the SchemaDB. |
| #if DISABLE_TRIGGERS |
| BeforeAndAfterTriggers *allTriggers = NULL; |
| #else |
| BeforeAndAfterTriggers *allTriggers = 0; |
| |
| if ((isIgnoreTriggers() == FALSE) && !firstN_OnIudLogTable ) |
| { |
| if (getUpdateCKorUniqueIndexKey()) |
| { |
| // if this the delete node of updateCKorUniqueIndexKey then skip |
| // inlining triggers altogether (allTriggers remains NULL). |
| if (op == COM_INSERT) |
| { |
| op = COM_UPDATE; |
| allTriggers = bindWA->getSchemaDB()->getTriggerDB()->getTriggers(subjectTable, op, bindWA); |
| } |
| } |
| else |
| { |
| allTriggers = bindWA->getSchemaDB()->getTriggerDB()->getTriggers(subjectTable, op, bindWA); |
| |
| if ((allTriggers == NULL) && (isMerge())) |
| { |
| // Triggers are not supported with Merge statement. |
| // if update part of merge didn't cause any triggers to be inlined, |
| // check for insert triggers. |
| // These triggers will be inlined here but an error will be |
| // returned during preCodeGen. |
| allTriggers = bindWA->getSchemaDB()->getTriggerDB()->getTriggers( |
| subjectTable, COM_INSERT, bindWA); |
| } |
| } |
| } |
| #endif |
| |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| if (allTriggers) { |
| getInliningInfo().setFlags(II_hasTriggers); |
| bindWA->setInTrigger(); |
| } |
| |
| // certain triggers uses are currently not supported and errors |
| // are raised if these uses are tried |
| if (checkNonSupportedTriggersUse(bindWA, subjectTable, op, allTriggers)) |
| { |
| return this; |
| } |
| |
| #if DISABLE_RI |
| RefConstraintList *riConstraints = NULL; |
| #else |
| RefConstraintList *riConstraints = |
| getRIs(bindWA, getTableDesc()->getNATable()); |
| #endif |
| |
| if (riConstraints) |
| getInliningInfo().setFlags(II_hasRI); |
| |
| #if DISABLE_MV_LOGGING |
| NABoolean isMVLoggingRequired = FALSE; |
| #else |
| NABoolean isMVLoggingRequired = isMvLoggingRequired(); |
| #endif |
| |
| RelExpr *topNode=boundExpr; |
| |
| // Get the list of updated columns. |
| UpdateColumns *columns = NULL; |
| if (op == COM_UPDATE) |
| columns = new(heap) UpdateColumns(stoi_->getStoi()); |
| |
| // Get only the before triggers that match these columns. |
| TriggerList *beforeTriggers = NULL; |
| if ((allTriggers != NULL) && (allTriggers->getBeforeTriggers() != NULL)) |
| beforeTriggers = allTriggers->getBeforeTriggers()->getColumnMatchingTriggers(columns); |
| |
| NABoolean tsjRETDescCreated = FALSE; |
| |
| if (beforeTriggers != NULL) { |
| if (checkForNotAtomicStatement(bindWA, 30027, |
| ((*beforeTriggers)[0])->getTriggerName(), |
| subjectTable.getQualifiedNameAsAnsiString())) { |
| return this; |
| } |
| |
| tsjRETDescCreated = TRUE; |
| createOldAndNewCorrelationNames(bindWA); |
| } |
| |
| // Save the previous IudNum, it will be restored at the end of the |
| // inlining of the current backbone. |
| Lng32 prevIudNum = bindWA->getUniqueIudNum(); |
| // Set the IudNum for the current generic update backbone. |
| // This value is used later as part of the uniquifier |
| // as the temporary table column UNIQUEIUD_COLUMN |
| bindWA->setUniqueIudNum(); |
| |
| // When triggers defined on the subject table, it is assured that the |
| // temp-table exists for this table. Since during initialization of the |
| // TriggersTempTable object there's a seek for the temp-table's NATable, we |
| // must be sure this table actually exists before creating that object. |
| |
| TriggersTempTable *tempTableObj = NULL; |
| if (allTriggers != NULL) |
| { |
| tempTableObj = new(heap) TriggersTempTable(this, bindWA); |
| } |
| |
| // Build the before triggers side of the inlining backbone. |
| // This method also adds to 'columns' any column that is updated by before triggers. |
| RelExpr *beforeSubtree = NULL; |
| if (beforeTriggers != NULL) |
| beforeSubtree = createTentativeSubTree(bindWA, |
| beforeTriggers, |
| columns, |
| *tempTableObj, |
| heap); |
| |
| if (bindWA->errStatus()) |
| return NULL; |
| |
| #if DISABLE_TRIGGERS |
| // Nothing to do. |
| #else |
| if ((allTriggers != NULL) && (allTriggers->getAfterStatementTriggers() != NULL)) |
| { |
| TriggerList *pureStmtTriggers = allTriggers->getAfterStatementTriggers()->getColumnMatchingTriggers(columns); |
| // There are statement triggers which are not for statement MVs. |
| // The ones used for statement MVs will be pupulated with the |
| // getTriggeredMVs call below. |
| if (pureStmtTriggers) |
| if (checkForNotAtomicStatement(bindWA, 30034, |
| ((*pureStmtTriggers)[0])->getTriggerName(), |
| subjectTable.getQualifiedNameAsAnsiString())) |
| { |
| return this; |
| } |
| } |
| // Now that we know the final set of columns updated in the query, we |
| // can determine whether the update of the MVs is direct or indirect. |
| allTriggers = getTriggeredMvs(bindWA, allTriggers, columns); |
| if (bindWA->errStatus()) |
| return this; |
| #endif |
| |
| if ((allTriggers == NULL) && |
| (riConstraints == NULL) && |
| !isMVLoggingRequired && |
| !getTableDesc()->hasSecondaryIndexes()) |
| { |
| InliningFinale(bindWA, boundExpr, origScopeRETDesc); |
| bindWA->resetUniqueIudNum(prevIudNum); // restore the saved IudNum |
| return boundExpr; // Nothing for us to do here. |
| } |
| |
| // Get the row and statement triggers that match the updated column list. |
| TriggerList *rowTriggers = NULL; |
| if ((allTriggers != NULL) && (allTriggers->getAfterRowTriggers() != NULL)) |
| rowTriggers = allTriggers->getAfterRowTriggers()->getColumnMatchingTriggers(columns); |
| TriggerList *stmtTriggers = NULL; |
| if ((allTriggers != NULL) && (allTriggers->getAfterStatementTriggers() != NULL)) |
| stmtTriggers = allTriggers->getAfterStatementTriggers()->getColumnMatchingTriggers(columns); |
| |
| if (rowTriggers != NULL) { |
| if (checkForNotAtomicStatement(bindWA, 30034, |
| ((*rowTriggers)[0])->getTriggerName(), |
| subjectTable.getQualifiedNameAsAnsiString())) |
| { |
| return this; |
| } |
| else if (isNoRollback() || |
| (CmpCommon::transMode()->getRollbackMode() == TransMode::NO_ROLLBACK_)) |
| { |
| *CmpCommon::diags() << DgSqlCode(-3232) |
| << DgString0(subjectTable.getQualifiedNameAsAnsiString()) |
| << DgString1("After Trigger :") |
| << DgString2(((*rowTriggers)[0])->getTriggerName()); |
| bindWA->setErrStatus(); |
| |
| return this ; |
| } |
| } |
| else if (stmtTriggers != NULL) { |
| if ((CmpCommon::getDefault(NAR_DEPOBJ_ENABLE2) == DF_OFF) ) |
| { |
| if (checkForNotAtomicStatement(bindWA, 30034, |
| ((*stmtTriggers)[0])->getTriggerName(), |
| subjectTable.getQualifiedNameAsAnsiString())) |
| { |
| return this; |
| } |
| } |
| else if (isNoRollback() || |
| (CmpCommon::transMode()->getRollbackMode() == TransMode::NO_ROLLBACK_)) |
| { |
| *CmpCommon::diags() << DgSqlCode(-3232) |
| << DgString0(subjectTable.getQualifiedNameAsAnsiString()) |
| << DgString1("After Trigger :") |
| << DgString2(((*stmtTriggers)[0])->getTriggerName()); |
| bindWA->setErrStatus(); |
| |
| return this ; |
| } |
| } |
| |
| |
| if ((stmtTriggers != NULL) || (beforeTriggers != NULL)) |
| { |
| NAString trigName = stmtTriggers ? ((*stmtTriggers)[0])->getTriggerName() : ((*beforeTriggers)[0])->getTriggerName() ; |
| if (getFirstNRows() > 0) |
| { |
| // first N delete not supported with before triggers and after statement triggers. |
| *CmpCommon::diags() << DgSqlCode(-11045) |
| << DgString0(trigName) |
| << DgString1(subjectTable.getQualifiedNameAsAnsiString()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| |
| // Filter only the RI constraints that match the updated columns. |
| if (riConstraints != NULL) |
| { |
| riConstraints = riConstraints->getNeededRIs(columns, heap); |
| if (riConstraints->isEmpty()) |
| riConstraints = NULL; |
| else // There are RI constraints that need to be enforced. |
| { |
| // Disallow embedded delete on a referenced table. |
| // Disallow embedded updates on columns which are part of an RI constraint. |
| if (getGroupAttr()->isEmbeddedDelete()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4183); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| if (getGroupAttr()->isEmbeddedUpdate()) |
| { |
| *CmpCommon::diags() << DgSqlCode(-4184); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| if ((CmpCommon::getDefault(NAR_DEPOBJ_ENABLE2) == DF_OFF) ) |
| { |
| |
| if (checkForNotAtomicStatement(bindWA, 30028, |
| (riConstraints->at(0)->getConstraintName()).getQualifiedNameAsAnsiString(), |
| subjectTable.getQualifiedNameAsAnsiString())) { |
| return this ; |
| } |
| } |
| |
| if (isNoRollback() || |
| (CmpCommon::transMode()->getRollbackMode() == TransMode::NO_ROLLBACK_)) { |
| *CmpCommon::diags() << DgSqlCode(-3232) |
| << DgString0(subjectTable.getQualifiedNameAsAnsiString()) |
| << DgString1("Referential Intergrity Constraint :") |
| << DgString2((riConstraints->at(0)->getConstraintName()).getQualifiedNameAsAnsiString()); |
| bindWA->setErrStatus(); |
| |
| return this ; |
| } |
| |
| } |
| } |
| |
| NABoolean needIM = isIMNeeded(columns); |
| |
| if (needIM) { |
| if ((isNoRollback() || |
| (CmpCommon::transMode()->getRollbackMode() == TransMode::NO_ROLLBACK_)) || |
| (bindWA->getHostArraysArea() && |
| bindWA->getHostArraysArea()->getTolerateNonFatalError())) { |
| NAString indexName; |
| const LIST(IndexDesc *) indexList = getTableDesc()->getIndexes(); |
| for (CollIndex i=0; i<indexList.entries(); i++) |
| { |
| IndexDesc *index = indexList[i]; |
| if (!(index->isClusteringIndex())) { |
| indexName = index->getExtIndexName(); |
| break; |
| } |
| } |
| if ((CmpCommon::getDefault(NAR_DEPOBJ_ENABLE) == DF_OFF) ) |
| { |
| if (checkForNotAtomicStatement(bindWA, |
| 30026, |
| indexName, |
| subjectTable.getQualifiedNameAsAnsiString())) |
| { |
| return this; |
| |
| } |
| } |
| if (isNoRollback() || |
| (CmpCommon::transMode()->getRollbackMode() == TransMode::NO_ROLLBACK_)) { |
| *CmpCommon::diags() << DgSqlCode(-3232) |
| << DgString0(subjectTable.getQualifiedNameAsAnsiString()) |
| << DgString1("Index :") |
| << DgString2(indexName); |
| bindWA->setErrStatus(); |
| |
| return this ; |
| |
| } |
| } |
| getInliningInfo().setFlags(II_hasIM); |
| } |
| |
| // Do we have any triggers to work on? |
| if ((beforeTriggers == NULL) && |
| (rowTriggers == NULL) && |
| (stmtTriggers == NULL) ) |
| { |
| bindWA->resetUniqueIudNum(prevIudNum); |
| |
| if (!needIM && !isMVLoggingRequired && (riConstraints == NULL)) |
| { |
| // We get here only if this is an Update operation, and there are |
| // triggers/IM/RI defined on Update on this table, but all of them |
| // are on columns other than the ones updated. |
| // e.g. A trigger AFTER UPDATE OF (a) ON T1, |
| // and a triggering statement like: UPDATE T1 SET B=5; |
| InliningFinale(bindWA, boundExpr, origScopeRETDesc); |
| CMPASSERT (op == COM_UPDATE); |
| return boundExpr; |
| } |
| |
| // OK, so we have no triggers - just RI, IM or both. |
| createOldAndNewCorrelationNames(bindWA); |
| setNoFlow(TRUE); |
| getInliningInfo().setFlags(II_hasPipelinedActions); |
| |
| topNode = |
| inlineOnlyRIandIMandMVLogging(bindWA, |
| this, |
| needIM, |
| riConstraints, |
| isMVLoggingRequired, |
| columns, |
| heap); |
| |
| if (needIM) |
| { |
| topNode->getInliningInfo().setFlags(II_DrivingIM); |
| } |
| |
| topNode = topNode->bindNode(bindWA); |
| // Create the tree that handles Index Maintainance. |
| if (needIM) |
| { |
| // don't allow index maintenance on non-audited tables unless |
| // this is enabled by a default |
| // We allow index maintenance in an Internal refresh statement |
| if (!getTableDesc()->getClusteringIndex()->getNAFileSet()->isAudited() && |
| !bindWA->isBindingMvRefresh()) |
| { |
| NAString dummyTokString(bindWA->wHeap()); |
| const NATable *naTable = bindWA->getNATable(getTableName()); |
| DefaultToken imAllowed = |
| bindWA->getSchemaDB()->getDefaults().token(IUD_NONAUDITED_INDEX_MAINT, |
| dummyTokString); |
| |
| switch (imAllowed) |
| { |
| case DF_ON: |
| // go ahead and do it, the user asked for it |
| break; |
| case DF_WARN: |
| // emit a warning and continue |
| *CmpCommon::diags() << DgSqlCode(4203) << |
| DgTableName(naTable->getTableName().getQualifiedNameAsAnsiString()); |
| break; |
| default: |
| // stop with an error, index maintenance is not allowed on |
| // nonaudited tables |
| *CmpCommon::diags() << DgSqlCode(-4203) << |
| DgTableName(naTable->getTableName().getQualifiedNameAsAnsiString()); |
| bindWA->setErrStatus(); |
| return this; |
| } |
| } |
| } |
| |
| if (bindWA->errStatus()) |
| { |
| return boundExpr; |
| } |
| |
| getInliningInfo().setFlags(II_hasInlinedActions); |
| |
| InliningFinale(bindWA, topNode, origScopeRETDesc); |
| return topNode; |
| } |
| |
| // Now, that we know for sure there are triggers to be fired, tempTableObj |
| // must be initialized. In case there are no regular triggers (not MVImmediate) |
| // defined on the table, the tempTableObj is not initialized yet (see initializing |
| // of tempTableObj above). |
| |
| if (tempTableObj == NULL) |
| { |
| tempTableObj = new(heap) TriggersTempTable(this, bindWA); |
| } |
| |
| setNoFlow(TRUE); |
| |
| if (!tsjRETDescCreated) |
| createOldAndNewCorrelationNames(bindWA); |
| |
| if ( (rowTriggers != NULL) || |
| (riConstraints != NULL) || |
| needIM || |
| isMVLoggingRequired) |
| getInliningInfo().setFlags(II_hasPipelinedActions); |
| |
| // Forbid the use of the Materialize node by the optimizer, for the entire |
| // backbone, if we are now cascaded from a row after trigger. |
| NABoolean forbidMaterializeNodeHere = shouldForbidMaterializeNodeHere(bindWA); |
| Int32 prevStateOfFlags = 0; |
| if (forbidMaterializeNodeHere) |
| { |
| // Set this InliningInfo flag in every node being bound (see RelExpr::bindSelf()) |
| // but save the previous state first. |
| prevStateOfFlags = bindWA->getInliningInfoFlagsToSetRecursivly(); |
| bindWA->setInliningInfoFlagsToSetRecursivly(II_MaterializeNodeForbidden); |
| } |
| |
| // Create and bind the rest of the trigger backbone. |
| if (beforeTriggers == NULL) |
| { |
| if (bindWA->getHostArraysArea() && bindWA->getHostArraysArea()->getTolerateNonFatalError()&& (needIM || riConstraints)) |
| { |
| |
| topNode = inlineAfterOnlyBackboneForUndo(bindWA, |
| *tempTableObj, |
| rowTriggers, |
| stmtTriggers, |
| riConstraints, |
| needIM, |
| isMVLoggingRequired, |
| columns, |
| heap); |
| } |
| else |
| { |
| topNode = inlineAfterOnlyBackbone(bindWA, |
| *tempTableObj, |
| rowTriggers, |
| stmtTriggers, |
| riConstraints, |
| needIM, |
| isMVLoggingRequired, |
| columns, |
| heap); |
| } |
| } |
| else |
| topNode = inlineBeforeAndAfterBackbone(bindWA, |
| beforeSubtree, |
| *tempTableObj, |
| rowTriggers, |
| stmtTriggers, |
| riConstraints, |
| needIM, |
| isMVLoggingRequired, |
| columns, |
| heap); |
| |
| if (forbidMaterializeNodeHere) |
| { |
| // Restore to the the previous state. This effectivly resets the flag |
| // we set before binding, so we don't affect sibtrees that will be bound |
| // after us. |
| bindWA->setInliningInfoFlagsToSetRecursivly(prevStateOfFlags); |
| } |
| |
| bindWA->resetUniqueIudNum(prevIudNum); |
| // indicate that this is a subject table for enable/disable |
| getOptStoi()->getStoi()->setSubjectTable(TRUE); |
| |
| getInliningInfo().setFlags(II_hasInlinedActions); |
| |
| InliningFinale(bindWA, topNode, origScopeRETDesc); |
| return topNode; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // This method checks to see if an update should be transformed into |
| // insert/delete nodes. This is true if the update is on a primary key and |
| // special conditions don't exist. |
| // |
| // Return value: |
| // TRUE - if the update is on a clustering or unique index key and no special |
| // conditions exist. Causes the update to be transformed to a delete |
| // followed by an insert with intervening order by. |
| // FALSE - if the update is on a clustering or unique index key and no special |
| // conditions exist. OR |
| // NOT update is on a clustering or unique index key. |
| ////////////////////////////////////////////////////////////////////////////// |
| NABoolean Update::updatesClusteringKeyOrUniqueIndexKey(BindWA *bindWA) |
| { |
| // This CQD must be ON or AGGRESSIVE in order to enable the transformation |
| // for special conditions. If not, return false |
| if (CmpCommon::getDefault(UPDATE_CLUSTERING_OR_UNIQUE_INDEX_KEY) == DF_OFF) |
| return FALSE; |
| |
| ULng32 numberofKeys = getTableDesc()-> |
| getClusteringIndex()->getIndexKey().entries(); |
| |
| NABoolean hasSysKey = getTableDesc()->getClusteringIndex()-> |
| getNAFileSet()->hasSyskey(); |
| |
| // this restriction (i.e unable to transform update of unique index key |
| // if base table has only syskey as clustering key may be removable |
| // the intention is to revisit this once a similar problem has been |
| // solved for the Halloween feature. |
| if ((numberofKeys == 1) AND hasSysKey) |
| return FALSE; // This should never be reached. |
| |
| // 1. Determine whether columns being updated are clustering key columns |
| // of base table or of a unique index. Set flags used later. |
| const LIST(IndexDesc *) & ixlist = getTableDesc()->getIndexes(); |
| NABoolean isUniqueIndexCol = FALSE; |
| NABoolean isClusteringKeyCol = FALSE; |
| NAString ckColName; // Save column name if is clustering key. |
| Scan * scanNode = getScanNode(); |
| const ValueIdList colUpdated = scanNode->getTableDesc()->getColUpdated(); |
| for (CollIndex indexNo = 0; indexNo < ixlist.entries(); indexNo++) |
| { |
| if (isUniqueIndexCol && isClusteringKeyCol) |
| break ; |
| |
| IndexDesc *idesc = ixlist[indexNo]; |
| if (idesc->isUniqueIndex() || idesc->isClusteringIndex()) |
| { |
| const ValueIdList indexKey = idesc->getIndexKey(); |
| |
| for (CollIndex i = 0; i < colUpdated.entries(); i++) |
| { |
| ItemExpr *updateCol = colUpdated[i].getItemExpr(); |
| CMPASSERT(updateCol->getOperatorType() == ITM_BASECOLUMN); |
| for (CollIndex j = 0; j < indexKey.entries(); j++) |
| { |
| ItemExpr *keyCol = indexKey[j].getItemExpr(); |
| ItemExpr *baseCol = ((IndexColumn*)keyCol)->getDefinition().getItemExpr(); |
| CMPASSERT(baseCol->getOperatorType() == ITM_BASECOLUMN); |
| if (((BaseColumn*)updateCol)->getColNumber() == |
| ((BaseColumn*)baseCol)->getColNumber()) |
| { |
| if (idesc->isUniqueIndex()) |
| isUniqueIndexCol = TRUE; |
| else { |
| isClusteringKeyCol = TRUE; |
| ckColName = ((BaseColumn*)updateCol)->getColName(); |
| } |
| } |
| } |
| } // for (CollIndex ...) |
| } |
| } // for (CollIndex ...) |
| |
| if ((CmpCommon::getDefault(UPDATE_CLUSTERING_OR_UNIQUE_INDEX_KEY) == DF_AGGRESSIVE) && |
| (NOT isClusteringKeyCol)) |
| return FALSE; |
| |
| // 2. If columns being updated are unique index or clustering key columns, |
| // check for 5 unsupported special cases and issue error in each case. |
| if (isUniqueIndexCol || isClusteringKeyCol) |
| { |
| // 2a. Check for first three unsupported special cases: |
| // i. User declared cursor and performing update current of. |
| // ii. Update statement with set on rollback. |
| // iii. Update with a select surrounding it. |
| if ( updateCurrentOf() || |
| newRecBeforeExpr().entries() > 0 || |
| getGroupAttr()->isEmbeddedUpdate() |
| ) |
| { |
| if (isUniqueIndexCol && NOT isClusteringKeyCol) |
| return FALSE; // column being updated is unique key but one of the |
| // three special cases apply. Revert to delete followed by insert |
| // without intervening sort. NOT AN ERROR. |
| |
| // Set appropriate error for first three special cases. |
| if ( updateCurrentOf() ) |
| *CmpCommon::diags() << DgSqlCode(-4118) ; |
| if (newRecBeforeExpr().entries() > 0) |
| *CmpCommon::diags() << DgSqlCode(-4199) ; |
| if (getGroupAttr()->isEmbeddedUpdate()) |
| *CmpCommon::diags() << DgSqlCode(-4198) ; |
| bindWA->setErrStatus(); |
| return FALSE ; // ERROR condition. |
| } |
| |
| // 2b. Check for unsupported special case 4: |
| // iv. There is an MV on this table that is defined on |
| // the clustering key(s). |
| const NATable *naTable = bindWA->getNATable(getTableName()); |
| if (naTable) |
| { |
| // Check for MVs on table. |
| const UsingMvInfoList &mvList = naTable->getMvsUsingMe(); |
| if (!mvList.isEmpty()) |
| { |
| // MV(s) exist. Check to see if any MV is ON STATEMENT and is |
| // significant. (Update on a primary key with an MV on that |
| // key is not supported - return FALSE.) |
| for (CollIndex i = 0; i < mvList.entries(); i++) |
| if (mvList[i]->isInitialized() && |
| mvList[i]->getRefreshType() == COM_ON_STATEMENT) |
| { |
| CorrName mvCorr = CorrName(mvList[i]->getMvName(), bindWA->wHeap()); |
| NATable *naTableMv = bindWA->getNATable(mvCorr); |
| if (bindWA->errStatus()) return FALSE; |
| MVInfoForDML *mvInfo = naTableMv->getMVInfo(bindWA); |
| // getMVInfo() reads from metadata, but saves the info found |
| // and will be called later anyway during binding. |
| UpdateColumns *columns = NULL; |
| columns = new(bindWA->wHeap()) UpdateColumns(getOptStoi()->getStoi()); |
| if (checkUpdateType(mvInfo, naTable->getTableName(), columns) != |
| IRELEVANT) |
| { |
| if (isClusteringKeyCol) // update CK not supported in this case. |
| { |
| *CmpCommon::diags() << DgSqlCode(-4033) << DgColumnName(ckColName); |
| bindWA->setErrStatus(); |
| } |
| return FALSE; |
| } |
| } |
| } |
| } |
| |
| if (isIgnoreTriggers() == FALSE) |
| { |
| ComOperation op; |
| op = COM_UPDATE; |
| BeforeAndAfterTriggers *allTriggers = 0; |
| QualifiedName& subjectTable = getTableDesc()->getCorrNameObj().getQualifiedNameObj(); |
| allTriggers = bindWA->getSchemaDB()->getTriggerDB()->getTriggers(subjectTable, op, bindWA); |
| UpdateColumns *columns = NULL; |
| columns = new(bindWA->wHeap()) UpdateColumns(getOptStoi()->getStoi()); |
| TriggerList *beforeTriggers = NULL; |
| if ((allTriggers != NULL) && (allTriggers->getBeforeTriggers() != NULL)) |
| beforeTriggers = allTriggers->getBeforeTriggers()->getColumnMatchingTriggers(columns); |
| if (beforeTriggers) |
| { |
| if (isClusteringKeyCol) // update CK not supported if table has beforeTriggers. |
| { |
| *CmpCommon::diags() << DgSqlCode(-4033) << DgColumnName(ckColName); |
| bindWA->setErrStatus(); |
| } |
| return FALSE; |
| } |
| } |
| |
| return TRUE; // Column being updated is CK or unique index key and no special cases apply. |
| } // if (isUniqueIndexCol || isClusteringKeyCol) |
| |
| return FALSE ; // Column being updated is not CK or unique index key |
| } |
| |
| RelExpr *Update::transformUpdatePrimaryKey(BindWA *bindWA) |
| { |
| |
| Delete * delNode = new (bindWA->wHeap()) |
| Delete(CorrName(getTableDesc()->getCorrNameObj(), bindWA->wHeap()), |
| NULL, |
| REL_UNARY_DELETE, |
| child(0), |
| NULL); |
| delNode->setNoLogOp(CONSISTENT_NOLOG); |
| delNode->setUpdateCKorUniqueIndexKey(TRUE); |
| delNode->rowsAffected() = GenericUpdate::DO_NOT_COMPUTE_ROWSAFFECTED; |
| |
| ValueIdList selectList, sourceColsList, lhsOfSetClause; |
| |
| getTableDesc()->getUserColumnList(selectList); |
| getScanIndexDesc()->getPrimaryTableDesc()->getUserColumnList(sourceColsList); |
| ValueId vid ; |
| CollIndex pos; |
| |
| // newRecExprArray is a list of assigns. For each assign |
| // child(0) is the LHS of the set clause and child(1) is |
| // the RHS of the SET clause |
| |
| for (CollIndex i=0; i < newRecExprArray().entries(); i++) |
| { |
| lhsOfSetClause.insertAt(i,newRecExprArray().at(i).getItemExpr()->child(0).getValueId()); |
| } |
| |
| for (CollIndex i=0; i < selectList.entries(); i++) |
| { |
| if ((pos = lhsOfSetClause.index(selectList[i])) == NULL_COLL_INDEX) |
| selectList[i] = sourceColsList[i]; |
| else |
| selectList[i] = newRecExprArray().at(pos).getItemExpr()->child(1).getValueId(); |
| } |
| |
| for (CollIndex i=0; i < oldToNewMap().getTopValues().entries(); i++) { |
| BaseColumn *col = (BaseColumn *) oldToNewMap().getBottomValues()[i].getItemExpr(); |
| NABoolean addToOldToNewMap = TRUE; |
| |
| // Copy the oldToNewMap. |
| if (col->getNAColumn()->isComputedColumnAlways()) { |
| // Computed columns can be copied from delete to insert if they don't |
| // change. Don't include the column in this map, though, if one of |
| // the underlying columns gets updated, because the value of the |
| // computed column has to be recomputed. That computation will be |
| // done in the new insert node. |
| ValueIdSet underlyingCols; |
| |
| col->getUnderlyingColumnsForCC(underlyingCols); |
| |
| if (NOT underlyingCols.intersect(lhsOfSetClause).isEmpty()) |
| addToOldToNewMap = FALSE; |
| } |
| |
| // Copy the oldToNewMap. |
| if (addToOldToNewMap) |
| delNode->oldToNewMap().addMapEntry(oldToNewMap().getTopValues()[i], |
| oldToNewMap().getBottomValues()[i]); |
| } |
| |
| RelRoot * rootNode = new (bindWA->wHeap()) |
| RelRoot(delNode, |
| REL_ROOT, |
| selectList.rebuildExprTree(ITM_ITEM_LIST)); |
| |
| RelExpr * boundExpr; |
| Insert * insNode = new (bindWA->wHeap()) |
| Insert(CorrName(getTableDesc()->getCorrNameObj(),bindWA->wHeap()), |
| getTableDesc(), // insert gets the same tabledesc as the update |
| REL_UNARY_INSERT, |
| rootNode, |
| NULL); |
| insNode->setNoLogOp(isNoLogOperation()); |
| insNode->setSubqInUpdateAssign(subqInUpdateAssign()); |
| |
| if (this->rowsAffected() == GenericUpdate::DO_NOT_COMPUTE_ROWSAFFECTED) |
| insNode->rowsAffected() = GenericUpdate::DO_NOT_COMPUTE_ROWSAFFECTED; |
| else |
| insNode->rowsAffected() = GenericUpdate::COMPUTE_ROWSAFFECTED; |
| |
| insNode->setUpdateCKorUniqueIndexKey(TRUE); |
| InliningInfo inlineInfo = getInliningInfo(); |
| insNode->setInliningInfo(&inlineInfo); |
| if (CmpCommon::getDefault(UPDATE_CLUSTERING_OR_UNIQUE_INDEX_KEY) == DF_ON) { |
| insNode->setAvoidHalloween(TRUE); |
| insNode->setHalloweenCannotUseDP2Locks(TRUE); |
| } |
| // used to convey updated columns to insert node's stoi |
| // during inlining the update node is not present anymore, we read the |
| // insert's stoi to figure out which columns are updated. |
| SqlTableOpenInfo * scanStoi = getLeftmostScanNode()->getOptStoi()->getStoi(); |
| short updateColsCount = getOptStoi()->getStoi()->getColumnListCount(); |
| scanStoi->setColumnListCount(updateColsCount); |
| scanStoi->setColumnList(new (bindWA->wHeap()) short[updateColsCount]); |
| for (short i=0; i<updateColsCount; i++) |
| scanStoi->setUpdateColumn(i,getOptStoi()->getStoi()->getUpdateColumn(i)); |
| |
| boundExpr = insNode->bindNode(bindWA); |
| return boundExpr; |
| } |
| |