| /*********************************************************************** |
| // @@@ 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: RelExpr.C |
| * Description: Relational expressions (both physical and logical operators) |
| * Created: 5/17/94 |
| * Language: C++ |
| * |
| * |
| ****************************************************************************** |
| */ |
| |
| #define SQLPARSERGLOBALS_FLAGS // must precede all #include's |
| #define SQLPARSERGLOBALS_NADEFAULTS |
| |
| #include "Debug.h" |
| #include "Sqlcomp.h" |
| #include "AllRelExpr.h" |
| #include "AllItemExpr.h" |
| #include "GroupAttr.h" |
| #include "opt.h" |
| #include "PhyProp.h" |
| #include "mdam.h" |
| #include "ControlDB.h" |
| #include "disjuncts.h" |
| #include "ScanOptimizer.h" |
| #include "CmpContext.h" |
| #include "StmtDDLCreateTrigger.h" |
| #include "ExpError.h" |
| #include "ComTransInfo.h" |
| #include "BindWA.h" |
| #include "Refresh.h" |
| #include "CmpMain.h" |
| #include "ControlDB.h" |
| #include "ElemDDLColDef.h" |
| #include "Analyzer.h" |
| #include "OptHints.h" |
| #include "ComTdbSendTop.h" |
| #include "DatetimeType.h" |
| #include "SequenceGeneratorAttributes.h" |
| #include "SqlParserGlobals.h" |
| #include "AppliedStatMan.h" |
| #include "Generator.h" |
| #include "CmpStatement.h" |
| |
| #define TEXT_DISPLAY_LENGTH 1001 |
| |
| // ---------------------------------------------------------------------- |
| // forward declarations |
| // ---------------------------------------------------------------------- |
| |
| // ----------------------------------------------------------------------- |
| // methods for class ExprGroupId |
| // ----------------------------------------------------------------------- |
| |
| ExprGroupId::ExprGroupId() |
| { |
| groupIdMode_ = STANDALONE; |
| node_ = NULL; |
| groupId_ = INVALID_GROUP_ID; |
| } |
| |
| ExprGroupId::ExprGroupId(const ExprGroupId & other) |
| { |
| groupIdMode_ = other.groupIdMode_; |
| node_ = other.node_; |
| groupId_ = other.groupId_; |
| } |
| |
| ExprGroupId::ExprGroupId(RelExpr *node) |
| { |
| groupIdMode_ = STANDALONE; |
| node_ = node; |
| groupId_ = INVALID_GROUP_ID; |
| } |
| |
| ExprGroupId::ExprGroupId(CascadesGroupId groupId) |
| { |
| groupIdMode_ = MEMOIZED; |
| node_ = NULL; |
| groupId_ = groupId; |
| } |
| |
| ExprGroupId & ExprGroupId::operator = (const ExprGroupId & other) |
| { |
| groupIdMode_ = other.groupIdMode_; |
| node_ = other.node_; |
| groupId_ = other.groupId_; |
| return *this; |
| } |
| |
| ExprGroupId & ExprGroupId::operator = (RelExpr * other) |
| { |
| if (groupIdMode_ == MEMOIZED) |
| { |
| // Trying to assign an actual pointer to an ExprGroupId that |
| // is in CascadesMemo. This is materialization of a binding. |
| groupIdMode_ = BINDING; |
| } |
| else if (groupIdMode_ == BINDING) |
| // sanity check, can't directly overwrite another binding |
| ABORT("Didn't call BINDING::release_expr()"); |
| |
| node_ = other; |
| |
| return *this; |
| } |
| |
| ExprGroupId & ExprGroupId::operator = (CascadesGroupId other) |
| { |
| // The expression is now (again) in CascadesMemo without participating in |
| // a binding. This may happen when an expression is copied into CascadesMemo |
| // (groupIdMode_ was STANDALONE) or when a binding is released (groupIdMode_ |
| // was BINDING). The node_ member is no longer a valid pointer. |
| |
| if (groupIdMode_ == BINDING && groupId_ != other) |
| ABORT("can't change group of an expression during release of binding"); |
| |
| groupIdMode_ = MEMOIZED; |
| groupId_ = other; |
| node_ = NULL; |
| |
| return *this; |
| } |
| |
| NABoolean ExprGroupId::operator == (const ExprGroupId &other) const |
| { |
| // if the two operands have mode ... then do this: |
| // ---------------------------------------- ------------------ |
| // STANDALONE-STANDALONE: ptrs must match |
| // STANDALONE-MEMOIZED: (x) return FALSE |
| // STANDALONE-BINDING: ptrs must match |
| // MEMOIZED-MEMOIZED: (x) groups must match |
| // MEMOIZED-BINDING: (x) groups must match |
| // BINDING-BINDING: ptrs must match |
| |
| if (node_ == NULL OR other.getPtr() == NULL) |
| return (groupId_ == other.getGroupId()); // cases with (x) |
| else |
| return (node_ == other.getPtr()); // ptrs must match |
| } |
| |
| NABoolean ExprGroupId::operator == (const RelExpr *other) const |
| { |
| CMPASSERT(groupIdMode_ != MEMOIZED); |
| return node_ == other; |
| } |
| |
| CascadesGroupId ExprGroupId::getGroupId() const |
| { return ((groupIdMode_ != STANDALONE)? groupId_ : INVALID_GROUP_ID); } |
| |
| void ExprGroupId::releaseBinding() |
| { |
| if (groupIdMode_ != BINDING) |
| ABORT("binding to release was not established"); |
| |
| groupIdMode_ = MEMOIZED; |
| node_ = NULL; |
| } |
| |
| void ExprGroupId::convertBindingToStandalone() |
| { |
| groupIdMode_ = STANDALONE; |
| } |
| |
| void ExprGroupId::setGroupAttr(GroupAttributes *gaPtr) |
| { |
| // If the expression is either in the standalone mode or is |
| // a part of a binding, then store the Group Attributes |
| // in the node. Group attributes in Cascades can not be set through |
| // an individual expression and an attempt to do this results in an abort. |
| |
| CMPASSERT(groupIdMode_ == STANDALONE); |
| node_->setGroupAttr(gaPtr); |
| } // ExprGroupId::setGroupAttr() |
| |
| GroupAttributes * ExprGroupId::getGroupAttr() const |
| { |
| CMPASSERT(node_ != NULL OR groupIdMode_ == MEMOIZED); |
| |
| // If the expression is either in the standalone mode or is |
| // a part of a binding, then use the Group Attributes that |
| // are stored in the node. |
| if (node_ != NULL) |
| return node_->getGroupAttr(); |
| else |
| // otherwise, use the Cascades group's group attributes |
| return (*CURRSTMT_OPTGLOBALS->memo)[groupId_]->getGroupAttr(); |
| } // ExprGroupId::getGroupAttr() |
| |
| // shortcut to get the output estimated log props out of the group |
| // attributes |
| EstLogPropSharedPtr ExprGroupId::outputLogProp(const EstLogPropSharedPtr& inputLogProp) |
| { |
| return getGroupAttr()->outputLogProp(inputLogProp); |
| } |
| |
| // a shortcut to get the bound expression, if it exists ... |
| // or the first logical expression inserted in the Cascades Group or NULL. |
| // Note: The last log expr in the list is the first one inserted. |
| RelExpr * ExprGroupId::getLogExpr() const |
| { |
| if (node_ != NULL) |
| return node_; |
| else if (groupId_ != INVALID_GROUP_ID) |
| return ((*CURRSTMT_OPTGLOBALS->memo)[groupId_]->getLastLogExpr()); |
| return 0; |
| } |
| |
| RelExpr * ExprGroupId::getFirstLogExpr() const |
| { |
| if (node_ != NULL) |
| return node_; |
| else if (groupId_ != INVALID_GROUP_ID) |
| return ((*CURRSTMT_OPTGLOBALS->memo)[groupId_]->getFirstLogExpr()); |
| return 0; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class RelExpr |
| // ----------------------------------------------------------------------- |
| |
| THREAD_P ObjectCounter (*RelExpr::counter_)(0); |
| |
| RelExpr::RelExpr(OperatorTypeEnum otype, |
| RelExpr *leftChild, |
| RelExpr *rightChild, |
| CollHeap *outHeap) |
| : ExprNode(otype) |
| ,selection_(NULL) |
| ,RETDesc_(NULL) |
| ,groupAttr_(NULL) |
| ,groupId_(INVALID_GROUP_ID) |
| ,groupNext_(NULL) |
| ,bucketNext_(NULL) |
| ,operatorCost_(NULL) |
| ,rollUpCost_(NULL) |
| ,physProp_(NULL) |
| ,estRowsUsed_((Cardinality)-1) |
| ,inputCardinality_((Cardinality)-1) |
| ,maxCardEst_((Cardinality)-1) |
| ,contextInsensRules_(outHeap) |
| ,contextSensRules_(outHeap) |
| ,accessSet0_(NULL) // Triggers -- |
| ,accessSet1_(NULL) |
| ,uniqueColumnsTree_(NULL) //++MV |
| ,cardConstraint_(NULL) //++MV |
| ,isinBlockStmt_(FALSE) |
| ,firstNRows_(-1) |
| ,flags_(0) |
| ,rowsetIterator_(FALSE) |
| ,tolerateNonFatalError_(UNSPECIFIED_) |
| ,hint_(NULL) |
| ,markedForElimination_(FALSE) |
| ,isExtraHub_(FALSE) |
| ,potential_(-1) |
| ,seenIUD_(FALSE) |
| ,parentTaskId_(0) |
| ,stride_(0) |
| ,birthId_(0) |
| ,memoExprId_(0) |
| ,sourceMemoExprId_(0) |
| ,sourceGroupId_(0) |
| ,costLimit_(-1) |
| ,cachedTupleFormat_(ExpTupleDesc::UNINITIALIZED_FORMAT) |
| ,cachedResizeCIFRecord_(FALSE) |
| ,dopReduced_(FALSE) |
| ,originalExpr_(NULL) |
| ,operKey_(outHeap) |
| { |
| |
| child_[0] = leftChild; |
| child_[1] = rightChild; |
| (*counter_).incrementCounter(); |
| // QSTUFF |
| setGroupAttr(new (outHeap) GroupAttributes); |
| // QSTUFF |
| } |
| |
| RelExpr::~RelExpr() |
| { |
| // the group attributes maintain a reference count |
| if (groupAttr_ != NULL) |
| groupAttr_->decrementReferenceCount(); |
| |
| // these data structures are always owned by the tree |
| delete selection_; |
| |
| // delete all children, if this is a standalone query |
| // (NOTE: can't use the virtual function getArity() in a destructor!!!) |
| for (Lng32 i = 0; i < MAX_REL_ARITY; i++) |
| { |
| if (child(i).getMode() == ExprGroupId::STANDALONE) |
| { |
| // the input was not obtained from CascadesMemo, so delete it |
| if (child(i).getPtr() != NULL) |
| delete child(i).getPtr(); |
| } |
| } |
| (*counter_).decrementCounter(); |
| |
| delete cardConstraint_; //++MV |
| |
| if (hint_) delete hint_; |
| } // RelExpr::~RelExpr() |
| |
| |
| Int32 RelExpr::getArity() const |
| { |
| switch (getOperatorType()) |
| { |
| case REL_SCAN: |
| return 0; |
| |
| case REL_EXCHANGE: |
| return 1; |
| |
| case REL_JOIN: |
| case REL_TSJ: |
| case REL_ROUTINE_JOIN: |
| case REL_SEMIJOIN: |
| case REL_SEMITSJ: |
| case REL_ANTI_SEMIJOIN: |
| case REL_ANTI_SEMITSJ: |
| case REL_LEFT_JOIN: |
| case REL_FULL_JOIN: |
| case REL_LEFT_TSJ: |
| case REL_NESTED_JOIN: |
| case REL_MERGE_JOIN: |
| case REL_INTERSECT: |
| case REL_EXCEPT: |
| return 2; |
| |
| default: |
| ABORT("RelExpr with unknown arity encountered"); |
| return 0; |
| } |
| } |
| |
| void RelExpr::deleteInstance() |
| { |
| Int32 nc = getArity(); |
| // avoid deleting the children by resetting all child pointers first |
| for (Lng32 i = 0; i < nc; i++) |
| { |
| child(i) = (RelExpr *) NULL; |
| } |
| delete this; |
| } // RelExpr::deleteInstance() |
| |
| TableMappingUDF *RelExpr::castToTableMappingUDF() |
| { |
| return NULL; |
| } |
| |
| ExprNode * RelExpr::getChild(Lng32 index) |
| { |
| return child(index); |
| } // RelExpr::getChild() |
| |
| void RelExpr::setChild(Lng32 index, ExprNode * newChild) |
| { |
| if (newChild) |
| { |
| CMPASSERT(newChild->castToRelExpr()); |
| child(index) = newChild->castToRelExpr(); |
| } |
| else |
| child(index) = (RelExpr *)NULL; |
| } // RelExpr::setChild() |
| |
| // get TableDesc from the expression. It could be directly |
| // attached to the expression, as in Scan, or could be a |
| // part of GroupAnalysis, as in cut-opp. For expressions |
| // which do not have a tableDesc attached to them, like Join |
| // it would be NULL |
| |
| TableDesc* |
| RelExpr::getTableDescForExpr() |
| { |
| TableDesc * tableDesc = NULL; |
| |
| if (getOperatorType() == REL_SCAN) |
| { |
| tableDesc = ((Scan *)this)->getTableDesc(); |
| } |
| else |
| { |
| if(getGroupAttr()->getGroupAnalysis() && |
| getGroupAttr()->getGroupAnalysis()->getNodeAnalysis() ) |
| { |
| TableAnalysis * tableAnalysis = getGroupAttr()->getGroupAnalysis()->getNodeAnalysis()->getTableAnalysis(); |
| if (tableAnalysis) |
| tableDesc = tableAnalysis->getTableDesc(); |
| } |
| } |
| |
| return tableDesc; |
| } |
| |
| // This method clears all logical expressions uptill the leaf node |
| // for multi-join. The methid should be called only before optimization phases |
| // Reason for that is (1)it is very expensive to synthLogProp and should be avoided |
| // (2) we are resetting number of joined tables, which should not be done once it is |
| // set during optimization phases |
| |
| void RelExpr::clearLogExprForSynthDuringAnalysis() |
| { |
| Int32 numChildren = getArity(); |
| |
| if (numChildren >= 1) |
| { |
| GroupAttributes * grp = getGroupAttr(); |
| grp->setLogExprForSynthesis(NULL); |
| grp->resetNumJoinedTables(1); |
| } |
| |
| // clear the log expr for all children |
| for (Lng32 i = 0; i < numChildren; i++) |
| { |
| // only if the child is not a CascadesGroup or NULL |
| if (child(i).getPtr() != NULL) |
| { |
| child(i)->clearLogExprForSynthDuringAnalysis(); |
| } |
| } |
| } |
| |
| void RelExpr::releaseBindingTree(NABoolean memoIsMoribund) |
| { |
| Int32 nc = getArity(); |
| |
| for (Lng32 i = 0; i < nc; i++) |
| { |
| if (memoIsMoribund || child(i).getMode() == ExprGroupId::BINDING) |
| { |
| // recursively release the bindings of the children's children |
| if (child(i).getPtr() != NULL) |
| child(i)->releaseBindingTree(memoIsMoribund); |
| |
| // release the bindings to the children |
| child(i).convertBindingToStandalone(); |
| } |
| } // for each child |
| |
| // indicate that this expression is no longer part of CascadesMemo, |
| // (although its groupNext_ and bucketNext_ pointers are still valid) |
| groupId_ = INVALID_GROUP_ID; |
| |
| if (memoIsMoribund) |
| { |
| groupAttr_ = NULL; |
| groupNext_ = bucketNext_ = NULL; |
| } |
| |
| } |
| |
| void RelExpr::addSelPredTree(ItemExpr *selpred) |
| { |
| ExprValueId sel = selection_; |
| ItemExprTreeAsList(&sel, ITM_AND).insert(selpred); |
| selection_ = sel.getPtr(); |
| } // RelExpr::addSelPredTree() |
| |
| ItemExpr * RelExpr::removeSelPredTree() |
| { |
| ItemExpr * result = selection_; |
| selection_ = NULL; |
| return result; |
| } // RelExpr::removeSelPredTree() |
| |
| //++ MV - |
| void RelExpr::addUniqueColumnsTree(ItemExpr *uniqueColumnsTree) |
| { |
| ExprValueId t = uniqueColumnsTree_; |
| |
| ItemExprTreeAsList(&t, ITM_ITEM_LIST).insert(uniqueColumnsTree); |
| uniqueColumnsTree_ = t.getPtr(); |
| } |
| |
| ItemExpr *RelExpr::removeUniqueColumnsTree() |
| { |
| ItemExpr *result = uniqueColumnsTree_; |
| uniqueColumnsTree_ = NULL; |
| |
| return result; |
| } |
| // MV-- |
| |
| void RelExpr::setGroupAttr(GroupAttributes *gaPtr) |
| { |
| // the new group attributes are now used in one more place |
| if (gaPtr != NULL) |
| gaPtr->incrementReferenceCount(); |
| |
| // the old group attributes are now used in one place less than before |
| // NOTE: old and new group attribute pointers may be the same |
| if (groupAttr_ != NULL) |
| groupAttr_->decrementReferenceCount(); |
| |
| // now assign the new group attribute pointer to the local data member |
| groupAttr_ = gaPtr; |
| } |
| |
| NABoolean RelExpr::reconcileGroupAttr(GroupAttributes *newGroupAttr) |
| { |
| // make sure the new group attributes have all the information needed |
| // and are not inconsistent |
| newGroupAttr->reconcile(*groupAttr_); |
| |
| // unlink from the current group attributes and adopt the new (compatible) |
| // ones |
| setGroupAttr(newGroupAttr); |
| |
| return FALSE; // no re-optimization for now |
| } |
| |
| RelExpr * RelExpr::castToRelExpr() |
| { |
| return this; |
| } |
| |
| const RelExpr * RelExpr::castToRelExpr() const |
| { |
| return this; |
| } |
| |
| NABoolean RelExpr::isLogical() const { return TRUE; } |
| |
| NABoolean RelExpr::isPhysical() const { return FALSE; } |
| |
| NABoolean RelExpr::isCutOp() const { return FALSE; } |
| |
| NABoolean RelExpr::isSubtreeOp() const { return FALSE; } |
| |
| NABoolean RelExpr::isWildcard() const { return FALSE; } |
| |
| ItemExpr * RelExpr::selectList() |
| { |
| // RelRoot redefines this virtual method (see BindRelExpr.cpp); |
| // Tuple and Union use this standard method. |
| RETDesc *rd = getRETDesc(); |
| if (rd) { |
| ValueIdList vids; |
| const ColumnDescList &cols = *rd->getColumnList(); |
| for (CollIndex i = 0; i < cols.entries(); i++) |
| vids.insert(cols[i]->getValueId()); |
| return vids.rebuildExprTree(ITM_ITEM_LIST); |
| } |
| return NULL; |
| } |
| |
| SimpleHashValue RelExpr::hash() |
| { |
| // this method is just defined to have a hash method in ExprNode |
| // without referencing class HashValue (which is too complicated |
| // for the common code directory) |
| return treeHash().getValue(); |
| } |
| |
| HashValue RelExpr::topHash() |
| { |
| HashValue result = (Int32) getOperatorType(); |
| |
| // hash the required input and output values from the GroupAttributes |
| if (groupAttr_ != NULL) |
| result ^= groupAttr_->hash(); |
| |
| // hash the ValueIdSet of the selection predicates |
| result ^= predicates_; |
| |
| // the other data members are not significant for the hash function |
| CMPASSERT(selection_ == NULL); // this method doesn't work in the parser |
| |
| return result; |
| } |
| |
| // this method is not virtual, since combining the hash values of the |
| // top node and its children should be independent of the actual node |
| HashValue RelExpr::treeHash() |
| { |
| HashValue result = topHash(); |
| Int32 maxc = getArity(); |
| |
| for (Lng32 i = 0; i < maxc; i++) |
| { |
| if (child(i).getMode() == ExprGroupId::MEMOIZED) |
| // use the numbers of the input CascadesGroup |
| result ^= child(i).getGroupId(); |
| else |
| // call this method recursively for the children |
| result ^= child(i)->treeHash(); |
| } |
| |
| return result; |
| } |
| |
| NABoolean RelExpr::patternMatch(const RelExpr & other) const |
| { |
| return getOperator().match(other.getOperator()); |
| } |
| |
| // Checks if the selection preds at this join node are of the |
| // form FKtable.col1 = UKTable.col1 and FKtable.col1 = UKTable.col1 |
| // and ..., where FKTable.col1 is the FK column that points to |
| // UKTable.col1. |
| // The third arguments matchingPreds is an output parameter. |
| // It is used to send the a list of FKtable.col1 = UKTable.col1 |
| // type predicates back to the caller, so that it can be used |
| // to adjust the selection preds and equiJoinPreds in the join |
| // node. |
| NABoolean Join::hasRIMatchingPredicates(const ValueIdList& fkCols, |
| const ValueIdList& ucCols, |
| const TableDesc * compRefTabId, |
| ValueIdSet & matchingPreds) const |
| { |
| // if the size of the fkCols does not match with ucCols then something is wrong. |
| // We also assume below that corresponding cols have identical positions |
| // in the two valueidlists and that all entries here are in terms of VEG. |
| CMPASSERT(fkCols.entries() == ucCols.entries()); |
| // There is not possibility of finding a full match |
| // if number ofselection preds is smaller than the fkCols. |
| // number of selection preds can be larger than fkCols.entries, |
| // for example there may be a predicate on the fktable and some |
| // other table which is being joined up above. Since the fktable |
| // is below this join, this join will have that predicate. |
| if ((getSelectionPredicates().entries() < fkCols.entries())) |
| return FALSE; |
| |
| ValueIdList localFKCols(fkCols); |
| ValueIdList localUCCols(ucCols); |
| |
| ValueIdSet compRefTabNonUCCols(compRefTabId->getColumnVEGList()); |
| ValueIdSet localUCColsSet(localUCCols); |
| compRefTabNonUCCols -= localUCColsSet; |
| |
| NABoolean matchFound = FALSE; |
| const ValueIdSet& selPreds = getSelectionPredicates(); |
| matchingPreds.clear(); |
| |
| for (ValueId x = selPreds.init(); |
| selPreds.next(x); |
| selPreds.advance(x)) |
| { |
| ItemExpr *ie = x.getItemExpr(); |
| matchFound = FALSE; |
| if (ie->getOperatorType() == ITM_VEG_PREDICATE) |
| { |
| ValueId vegRef = ((VEGPredicate *)ie)->getVEG()->getVEGReference()->getValueId(); |
| CollIndex fkidx = localFKCols.index(vegRef); |
| if ((fkidx != NULL_COLL_INDEX)&&(localUCCols[fkidx] == vegRef)) |
| { |
| localFKCols.removeAt(fkidx); |
| localUCCols.removeAt(fkidx); |
| matchingPreds.insert(x); |
| } |
| if (compRefTabNonUCCols.contains(vegRef)) |
| { |
| // return false on a predicate |
| // of the form fktable.x = uniquetable.x where x is a nonkey column. |
| matchingPreds.clear(); |
| return FALSE; |
| } |
| } |
| else if ((ie->getOperatorType() == ITM_EQUAL)&& |
| (ie->child(0)->getOperatorType() == ITM_VEG_REFERENCE)&& |
| (ie->child(1)->getOperatorType() == ITM_VEG_REFERENCE)) |
| { |
| ValueId vegRef0 = ((VEGReference *)ie->child(0).getPtr())->getValueId(); |
| ValueId vegRef1 = ((VEGReference *)ie->child(1).getPtr())->getValueId(); |
| ValueId ukVid = NULL_COLL_INDEX; |
| CollIndex fkidx = localFKCols.index(vegRef0); |
| if (fkidx == NULL_COLL_INDEX) |
| { |
| CollIndex fkidx = localFKCols.index(vegRef1); |
| if (fkidx != NULL_COLL_INDEX) |
| ukVid = vegRef0; |
| } |
| else |
| ukVid = vegRef1; |
| if ((fkidx != NULL_COLL_INDEX)&&(localUCCols[fkidx] == ukVid)) |
| { |
| localFKCols.removeAt(fkidx); |
| localUCCols.removeAt(fkidx); |
| matchingPreds.insert(x); |
| } |
| } |
| else |
| { |
| matchingPreds.clear(); |
| return FALSE; // not a VEG Pred (revisit for char-varchar) |
| } |
| } |
| if (localFKCols.isEmpty()) |
| return TRUE ; // all preds have a match with a FK-UC column pair. |
| else |
| { |
| matchingPreds.clear(); |
| return FALSE; |
| } |
| } |
| |
| |
| // Special method added to check for ordered cross product called by |
| // RequiredPhysicalProperty::satisfied() to ensure that if a CQS has |
| // requested an ordered cross product, then one is being produced. |
| NABoolean HashJoin::patternMatch(const RelExpr &other) const |
| { |
| if (other.getOperator() == REL_FORCE_ORDERED_CROSS_PRODUCT) |
| return ((HashJoin *) this)->isOrderedCrossProduct(); |
| |
| |
| |
| else |
| return RelExpr::patternMatch(other); |
| } |
| |
| // Two trees match, if their top nodes and their children are duplicates |
| // (are the same logical or physical expression). This method provides |
| // the generic part for determining a match. It can be called by |
| // redefined virtual methods of derived classes. |
| NABoolean RelExpr::duplicateMatch(const RelExpr & other) const |
| { |
| if (getOperatorType() != other.getOperatorType()) |
| return FALSE; |
| |
| CMPASSERT(selection_ == NULL); // this method doesn't work in the parser |
| |
| if (predicates_ != other.predicates_) |
| return FALSE; |
| |
| if (rowsetIterator_ != other.rowsetIterator_) |
| return FALSE; |
| |
| if (tolerateNonFatalError_ != other.tolerateNonFatalError_) |
| return FALSE; |
| |
| Int32 maxc = getArity(); |
| |
| // determine whether the children match |
| for (Lng32 i = 0; i < maxc; i++) |
| { |
| // different situations, depending on whether the child |
| // and the other node's child is in state MEMOIZED, |
| // BINDING, or STANDALONE. See ExprGroupId::operator == |
| // for an explanation for each of the cases |
| |
| if (child(i).getMode() == ExprGroupId::MEMOIZED OR |
| other.child(i).getMode() == ExprGroupId::MEMOIZED) |
| { |
| // cases marked (x) in ExprGroupId::operator == |
| // (groups must match) |
| if (NOT (child(i) == other.child(i))) |
| return FALSE; |
| } |
| else |
| { |
| // outside of CascadesMemo or in a CascadesBinding, then |
| // call this method recursively for the children |
| if (NOT child(i)->duplicateMatch(*other.child(i).getPtr())) |
| return FALSE; |
| } |
| } |
| |
| return TRUE; |
| } |
| const CorrName RelExpr::invalid = CorrName("~X~invalid"); |
| |
| RelExpr * RelExpr::copyTopNode(RelExpr *derivedNode,CollHeap* outHeap) |
| { |
| RelExpr *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) RelExpr(getOperatorType(), |
| NULL, |
| NULL, |
| outHeap); |
| else |
| result = derivedNode; |
| |
| // don't copy pointers to required input/output values, since we don't |
| // allow duplicate expressions the new node is likely to get new group |
| // attributes |
| |
| // copy selection predicates |
| result->predicates_ = predicates_; |
| |
| // copy pointer to the selection expression tree (Parser only) |
| if (selection_ != NULL) |
| result->selection_ = selection_->copyTree(outHeap)->castToItemExpr(); |
| |
| // -- Triggers |
| // Copy the inlining information and access sets. |
| result->getInliningInfo().merge(&getInliningInfo()); |
| result->setAccessSet0(getAccessSet0()); |
| result->setAccessSet0(getAccessSet0()); |
| |
| //++MV - |
| |
| result->setUniqueColumns(getUniqueColumns()); |
| |
| if (uniqueColumnsTree_ != NULL) |
| result->uniqueColumnsTree_ = |
| uniqueColumnsTree_->copyTree(outHeap)->castToItemExpr(); |
| //--MV - |
| |
| // leave any physical properties or CascadesMemo-related data |
| // off the returned result ??? (see RelExpr::save) |
| |
| result->setBlockStmt(isinBlockStmt()); |
| |
| result->setFirstNRows(getFirstNRows()); |
| |
| result->oltOptInfo() = oltOptInfo(); |
| result->setHint(getHint()); |
| |
| result->setRowsetIterator(isRowsetIterator()); |
| |
| result->setTolerateNonFatalError(getTolerateNonFatalError()); |
| |
| result->setIsExtraHub(isExtraHub()); |
| result->setMarkedForElimination(markedForElimination()); |
| |
| result->seenIUD_ = seenIUD_; |
| |
| // set the expression's potential |
| result->potential_ = potential_; |
| |
| // copy cascades trace info |
| result->parentTaskId_ = parentTaskId_; |
| result->stride_ = stride_; |
| result->birthId_ = birthId_; |
| result->memoExprId_ = memoExprId_; |
| result->sourceMemoExprId_ = sourceMemoExprId_; |
| result->sourceGroupId_ = sourceGroupId_; |
| result->costLimit_ = costLimit_; |
| result->originalExpr_ = this; |
| |
| return result; |
| } |
| |
| // this method is not virtual, since combining the copies of the |
| // top node and its children should be independent of the actual node |
| RelExpr * RelExpr::copyTree(CollHeap* outHeap) |
| { |
| RelExpr * result = copyTopNode(0,outHeap); |
| Int32 arity = getArity(); |
| |
| for (Lng32 i = 0; i < arity; i++) |
| result->child(i) = child(i)->copyTree(outHeap); |
| |
| return result; |
| } |
| |
| // this method is also not virtual, It does same thing as copyTree |
| // except that it copies the RETDesc and groupAttr pointers too |
| // this is method is used to get a copy of the original tree before |
| // inserting it to Cascades. |
| RelExpr * RelExpr::copyRelExprTree(CollHeap* outHeap) |
| { |
| RelExpr * result = copyTopNode(0,outHeap); |
| result->setGroupAttr(new (outHeap) GroupAttributes(*(getGroupAttr()))); |
| result->setRETDesc(getRETDesc()); |
| result->getGroupAttr()->setLogExprForSynthesis(result); |
| |
| Int32 arity = getArity(); |
| |
| for (Lng32 i = 0; i < arity; i++) |
| result->child(i) = child(i)->copyRelExprTree(outHeap); |
| |
| return result; |
| } |
| |
| const RelExpr * RelExpr::getOriginalExpr(NABoolean transitive) const |
| { |
| if (originalExpr_ == NULL) |
| return this; |
| |
| RelExpr *result = originalExpr_; |
| |
| while (result->originalExpr_ && transitive) |
| result = result->originalExpr_; |
| |
| return result; |
| } |
| |
| RelExpr * RelExpr::getOriginalExpr(NABoolean transitive) |
| { |
| if (originalExpr_ == NULL) |
| return this; |
| |
| RelExpr *result = originalExpr_; |
| |
| while (result->originalExpr_ && transitive) |
| result = result->originalExpr_; |
| |
| return result; |
| } |
| |
| void RelExpr::setBlockStmtRecursively(NABoolean x) |
| { |
| setBlockStmt(x); |
| Int32 arity = getArity(); |
| |
| for (Lng32 i = 0; i < arity; i++) |
| child(i)->setBlockStmtRecursively(x); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // create or share an optimization goal for a child group |
| // ----------------------------------------------------------------------- |
| Context * RelExpr::shareContext(Lng32 childIndex, |
| const ReqdPhysicalProperty* const reqdPhys, |
| const InputPhysicalProperty* const inputPhys, |
| CostLimit* costLimit, |
| Context * parentContext, |
| const EstLogPropSharedPtr& inputLogProp, |
| RelExpr *explicitlyRequiredShape) const |
| { |
| // no need to do the job if costLimit id already negative |
| if ( costLimit AND |
| CURRSTMT_OPTDEFAULTS->OPHpruneWhenCLExceeded() AND |
| costLimit->getValue(reqdPhys) < 0 ) |
| return NULL; |
| |
| const ReqdPhysicalProperty* searchForRPP; |
| |
| // if the required physical properties are empty, don't use them |
| if (reqdPhys != NULL AND reqdPhys->isEmpty()) |
| searchForRPP = NULL; |
| else |
| searchForRPP = reqdPhys; |
| |
| // handle force plan directives: if the parent node must match a |
| // certain tree, make sure the child node gets the appropriate |
| // requirement to match a child node of the mustMatch pattern |
| RelExpr *childMustMatch = explicitlyRequiredShape; |
| |
| if (parentContext->getReqdPhysicalProperty() != NULL AND |
| parentContext->getReqdPhysicalProperty()->getMustMatch() != NULL AND |
| explicitlyRequiredShape == NULL) |
| { |
| const RelExpr *parentMustMatch = |
| parentContext->getReqdPhysicalProperty()->getMustMatch(); |
| |
| // Reuse the parent's pattern if this node is a map value ids |
| // node and the required pattern isn't. This is because a map value |
| // ids node, PACK node and UNPACK node is essentially a no-op and |
| // does not need to be specified in CONTROL QUERY SHAPE. |
| // Sorry for putting this DBI code into |
| // places where particular operator types shouldn't be known. |
| // It's the summer of 1997 and we have a deadline for FCS. |
| // Its (almost) summer of 2003 and I am adding the same thingy |
| // for FIRST_N operator. |
| if (((getOperatorType() == REL_MAP_VALUEIDS) && |
| (parentMustMatch->getOperatorType() != REL_MAP_VALUEIDS)) || |
| ((getOperatorType() == REL_PACK) AND |
| (parentMustMatch->getOperatorType() != REL_PACK)) || |
| ((getOperatorType() == REL_UNPACKROWS) AND |
| (parentMustMatch->getOperatorType() != REL_UNPACKROWS)) || |
| ((getOperatorType() == REL_FIRST_N) AND |
| (parentMustMatch->getOperatorType() != REL_FIRST_N)) || |
| (CURRSTMT_OPTDEFAULTS->ignoreExchangesInCQS() AND |
| (getOperatorType() == REL_EXCHANGE) AND |
| (parentMustMatch->getOperatorType() != REL_FORCE_EXCHANGE)) || |
| (CURRSTMT_OPTDEFAULTS->ignoreSortsInCQS() AND |
| (getOperatorType() == REL_SORT) AND |
| (parentMustMatch->getOperatorType() != REL_SORT))) |
| { |
| childMustMatch = (RelExpr *) parentMustMatch; |
| } |
| else |
| { |
| // If the "must match" pattern specifies something other than |
| // a cut op for child "childIndex" then this is our new "must match". |
| if (childIndex < parentMustMatch->getArity() AND |
| NOT parentMustMatch->child(childIndex)->isCutOp()) |
| childMustMatch = parentMustMatch->child(childIndex); |
| } |
| } |
| |
| if (childMustMatch != NULL OR |
| searchForRPP AND searchForRPP->getMustMatch() != NULL) |
| { |
| // we have to change the "must match" attribute of searchForRPP |
| // add the "mustMatch" requirement |
| if (searchForRPP != NULL) |
| { |
| searchForRPP = new (CmpCommon::statementHeap()) |
| ReqdPhysicalProperty(*searchForRPP, |
| childMustMatch); |
| } |
| else |
| searchForRPP = new (CmpCommon::statementHeap()) |
| ReqdPhysicalProperty(childMustMatch); |
| } |
| |
| return (*CURRSTMT_OPTGLOBALS->memo)[child(childIndex).getGroupId()]->shareContext(searchForRPP, |
| inputPhys, |
| costLimit, |
| parentContext, |
| inputLogProp); |
| } // RelExpr::shareContext() |
| |
| ULng32 RelExpr::getDefault(DefaultConstants id) |
| { |
| return ActiveSchemaDB()->getDefaults().getAsULong(id); |
| } |
| |
| void RelExpr::addLocalExpr(LIST(ExprNode *) &xlist, |
| LIST(NAString) &llist) const |
| { |
| if (selection_ != NULL OR |
| NOT predicates_.isEmpty()) |
| { |
| if (predicates_.isEmpty()) |
| xlist.insert(selection_); |
| else |
| xlist.insert(predicates_.rebuildExprTree()); |
| llist.insert("selection_predicates"); |
| } |
| if(NOT uniqueColumns_.isEmpty()) |
| { |
| xlist.insert(uniqueColumns_.rebuildExprTree()); |
| llist.insert("uniqueColumns_"); |
| } |
| } |
| |
| //QSTUFF |
| // we must pushdown the outputs of a genericupdate root to its |
| // descendants to ensure that only those required output values are |
| // tested against indexes when selecting an index for a stream scan |
| // followed by an embedded update. Since we may allow for unions and |
| // for inner updates we just follow the isEmbeddedUpdate() thread once |
| // we reach a generic update root. |
| |
| void RelExpr::pushDownGenericUpdateRootOutputs( const ValueIdSet &outputs) |
| { |
| |
| ValueIdSet rootOutputs = |
| getGroupAttr()->isGenericUpdateRoot() ? |
| getGroupAttr()->getCharacteristicOutputs() : outputs; |
| |
| |
| for (Int32 i=0; i < getArity(); i++) { |
| if (child(i)->castToRelExpr()->getGroupAttr()->isEmbeddedUpdateOrDelete()){ |
| child(i)->castToRelExpr()-> |
| pushDownGenericUpdateRootOutputs(rootOutputs); |
| } |
| } |
| |
| if (NOT rootOutputs.isEmpty()){ |
| getGroupAttr()->setGenericUpdateRootOutputs(rootOutputs); |
| } |
| } |
| |
| //QSTUFF |
| |
| void RelExpr::needSortedNRows(NABoolean val) |
| { |
| // The operators listed below can create OR propogate a GET_N |
| // request. Other operatots will turn a GET_N request into GET_ALL |
| // There are a few exceptions like right side of NJ for semi join etc. |
| // but these are not relevant for FirstN sort |
| // This method should only in the generator since we are using |
| // physical node types. |
| OperatorTypeEnum operatorType = getOperatorType(); |
| if ((operatorType != REL_FIRST_N) && |
| (operatorType != REL_EXCHANGE) && |
| (operatorType != REL_MERGE_UNION) && |
| (operatorType != REL_PROBE_CACHE) && |
| (operatorType != REL_ROOT) && |
| (operatorType != REL_LEFT_NESTED_JOIN) && |
| (operatorType != REL_LEFT_TSJ) && |
| (operatorType != REL_MAP_VALUEIDS)) |
| return ; |
| |
| if ((operatorType == REL_LEFT_NESTED_JOIN) || |
| (operatorType == REL_LEFT_TSJ)) { |
| // left side of left tsj propagates a GET_N request if afterPred is empty. |
| if (getSelectionPred().isEmpty()) |
| child(0)->castToRelExpr()->needSortedNRows(val); |
| |
| return ; |
| } |
| |
| for (Int32 i=0; i < getArity(); i++) { |
| if (child(i)) |
| child(i)->castToRelExpr()->needSortedNRows(val); |
| } |
| } |
| |
| // ----------------------------------------------------------------------- |
| // computeValuesReqdForPredicates() |
| // |
| // There has been some problems with this function (as to how it should |
| // behave). The issue has been whether we should allow an operator to |
| // have the boolean value of a predicate as its output. That is to say, |
| // whether (SCAN T1), for example, could evaluate a predicate such as |
| // (T1.a > 3) and output a value of true or false to its parent. |
| // |
| // In most cases, this wouldn't be an issue since the predicate is used |
| // to filter out all the non-qualified rows. However, such is not the |
| // case when a CASE statement is involved. Part of the CASE statement, |
| // (e.g. the WHEN clause) can be evaluated at the SCAN, while the rest |
| // of the statement could reference some other tables and therefore must |
| // be evaluated at an ancestor node of the tree. |
| // |
| // A complete example is SELECT CASE WHEN T1.A > 3 THEN T2.A ELSE 0 END |
| // FROM T1 JOIN T2 ON T1.C = T2.C. In this case, if we allow a boolean |
| // value to be our output, the (T1.A > 3) could be evaluated at SCAN T1, |
| // and the CASE statement itself at the JOIN. The alternative would be |
| // for SCAN T1 to output T1.A and the CASE statement evaluated wholly at |
| // the JOIN. |
| // |
| // Now, how do all these relate to this function? The purpose of this |
| // function is to turn a predicate into values required to evaluate the |
| // predicate. Thus, the question is: should we allow the boolean value |
| // of (T1.A > 3) be the value required to evaluate the predicate (T1.A > |
| // 3). Or, should the values be T1.A and 3 instead? More generally, |
| // should we go for the leaf values of a non-VEG predicate (there is a |
| // separate story for VEG predicates, see later) or just the bool value |
| // of that predicate? |
| // |
| // This function has been implemented to gather the leaf values. However, |
| // there is no reason why we could |
| // not just require the boolean value. The logic of predicate pushdown |
| // mandates that if the child of the operator is unable to produce that |
| // boolean value, it will figure out what sub-expressions it could produce |
| // in its outputs in order for the boolean value to be evaluated at its |
| // parent. |
| // |
| // On the other hand, since this function has not been changed for quite |
| // a while, we are worried the change might trigger problematic spots in |
| // other places which rely on this function behaving the way it has been. |
| // Through extensive testing, we didn't seem to identify any problems and |
| // therefore, we decided to commit this fix. |
| // |
| // Now for VEGPred's. A VEGPred is considered "evaluable" at an operator |
| // if any *one* of its VEG members is "evaluable". For example, VEGPred( |
| // VEG{T1.a,T2.a}) in the query SELECT T2.B FROM (T1 JOIN T2 ON T1.A = |
| // T2.A) is "evaluable" at SCAN T1 and will be pushed down. Clearly, only |
| // evaluating the predicate there is not enough. We have to keep the |
| // VEGPred at the JOIN as well. The logic in Join::pushdownCoveredExpr() |
| // correctly handle that now. That is, it keeps the predicate even if |
| // it has been pushed down. However, doing so means that in the example |
| // given, SCAN T1 has to return T1.A as an output rather than just the |
| // boolean value of VEGPred(VEG{T1.A,T2.A}). That boolean value is sort |
| // of only local to SCAN T1. This function, therefore, declares that the |
| // value required to evaluate a VEGPred is not the boolean value of the |
| // VEGPred itself but the VEGRef of its VEG. In our example, the required |
| // value is VEGRef(VEG{T1.A,T2.A}). The semantics of this is that SCAN T1 |
| // is asked to return as an output one of the VEG members available to |
| // it. The pre-code generator will thus change this VEGRef into T1.A. |
| // |
| // 8/14/1998 |
| // |
| // ----------------------------------------------------------------------- |
| void RelExpr::computeValuesReqdForPredicates(const ValueIdSet& setOfExpr, |
| ValueIdSet& reqdValues, |
| NABoolean addInstNull) |
| { |
| for (ValueId exprId = setOfExpr.init(); |
| setOfExpr.next(exprId); |
| setOfExpr.advance(exprId)) |
| { |
| if (exprId.getItemExpr()->getOperatorType() == ITM_VEG_PREDICATE) |
| { |
| VEG * vegPtr = ((VEGPredicate *)(exprId.getItemExpr()))->getVEG(); |
| reqdValues += vegPtr->getVEGReference()->getValueId(); |
| |
| // If the VEG for this VEGPredicate contains a member that is |
| // another VEGReference, add it to reqdValues in order to ensure |
| // that it gets retrieved. |
| // |
| for (ValueId x = vegPtr->getAllValues().init(); |
| vegPtr->getAllValues().next(x); |
| vegPtr->getAllValues().advance(x)) |
| { |
| OperatorTypeEnum optype = x.getItemExpr()->getOperatorType(); |
| if ( optype == ITM_VEG_REFERENCE ) |
| // ********************************************************** |
| // Note: this "if" used to have the following cases as well. |
| // We feel that they might not be necessary any more. |
| // || optype == ITM_INSTANTIATE_NULL || |
| // optype == ITM_UNPACKCOL ) |
| // ********************************************************** |
| reqdValues += x; |
| else if ( addInstNull && optype == ITM_INSTANTIATE_NULL ) |
| { // part of fix to soln 10-090618-2434: a full outer join |
| // select ... from t1 inner join t2 on ... |
| // full outer join t3 on ... where t2.RGN = 'EMEA' |
| // whose selection predicate "t2.RGN = <constant>" must have |
| // its null-instantiated "t.RGN" column added to reqdValues. |
| reqdValues += x; |
| } |
| } // end inner for |
| } // endif is a VEGPredicate |
| else |
| { |
| // Not a VEGPred (either a "normal" pred or a "real" value). In |
| // any case, just add the value to the required values set. (For |
| // a "normal" pred, it means the boolean value for the predicate |
| // is required. |
| // |
| reqdValues += exprId; |
| } |
| } // end outer for |
| } // computeValuesReqdForPredicates() |
| |
| void RelExpr::computeValuesReqdForOutput(const ValueIdSet& setOfExpr, |
| const ValueIdSet& newExternalInputs, |
| ValueIdSet& reqdValues) |
| { |
| // if VEGPreds are in the output, get the underlying VEGRefs |
| computeValuesReqdForPredicates(setOfExpr, reqdValues); |
| |
| const GroupAttributes emptyGA; |
| for (ValueId exprId = setOfExpr.init(); |
| setOfExpr.next(exprId); |
| setOfExpr.advance(exprId)) |
| { |
| if ((exprId.getType().getTypeQualifier() == NA_CHARACTER_TYPE) && |
| (exprId.getType().getNominalSize() > CONST_32K)) |
| { |
| exprId.getItemExpr()->getLeafValuesForCoverTest(reqdValues, |
| emptyGA, |
| newExternalInputs); |
| } |
| } |
| } |
| |
| // ----------------------------------------------------------------------- |
| // RelExpr::pushdownCoveredExpr() |
| // ----------------------------------------------------------------------- |
| void RelExpr::pushdownCoveredExpr(const ValueIdSet & outputExpr, |
| const ValueIdSet & newExternalInputs, |
| ValueIdSet & predicatesOnParent, |
| const ValueIdSet * setOfValuesReqdByParent, |
| Lng32 childIndex |
| ) |
| { |
| ValueIdSet exprToEvalOnParent, outputSet, extraHubNonEssOutputs; |
| Int32 firstChild, lastChild; // loop bounds |
| Int32 iter; // loop index variable |
| NABoolean optimizeOutputs; |
| |
| |
| if (getArity() == 0 ) return; // we don't do anything for leaf nodes.. |
| |
| if ((getOperator().match(REL_ANY_TSJ) ) || |
| (getOperator().match(REL_ANY_GEN_UPDATE) ) ) |
| optimizeOutputs = FALSE; |
| else |
| optimizeOutputs = TRUE; |
| |
| |
| if (getOperator().match(REL_ANY_JOIN) && |
| isExtraHub()) |
| extraHubNonEssOutputs = ((Join *)this)->getExtraHubNonEssentialOutputs(); |
| |
| |
| // ----------------------------------------------------------------- |
| // Should the pushdown be attempted on a specific child? |
| // ----------------------------------------------------------------- |
| if ( (childIndex >= 0) AND (childIndex < getArity()) ) |
| { // yes, a child index is given |
| firstChild = (Int32)childIndex; |
| lastChild = firstChild + 1; |
| } |
| else // no, perform pushdown on all |
| { |
| firstChild = 0; |
| lastChild = getArity(); |
| } |
| |
| // --------------------------------------------------------------------- |
| // Examine the set of values required by the parent. Replace each |
| // VEGPredicate with a VEGReferences for its VEG; if its VEG |
| // contains other VEGReferences, add them to exprToEvalOnParent. |
| // --------------------------------------------------------------------- |
| if (setOfValuesReqdByParent) |
| computeValuesReqdForPredicates(*setOfValuesReqdByParent, |
| exprToEvalOnParent); |
| |
| computeValuesReqdForOutput(outputExpr,newExternalInputs,outputSet); |
| |
| |
| // --------------------------------------------------------------------- |
| // Are there any predicates that can be pushed down? |
| // --------------------------------------------------------------------- |
| if ( (getArity() > 0) AND (NOT predicatesOnParent.isEmpty()) ) |
| { |
| // ----------------------------------------------------------------- |
| // 1) Figure out which predicates could be push to which child. |
| // Try to give all predicates to all children. |
| // 2) Modify predOnParent to be those predicates that no could |
| // could take. |
| // 3) Add to the selectionPred() of each child those predicates |
| // it could take (if it is not a cut operator) |
| // 4) Add to exprToEvalOnParent the predicates that could not |
| // be push down to any child (predOnParent) |
| // 5) Recompute the input and outputs for each child given this |
| // set of exprOnParent. |
| // ----------------------------------------------------------------- |
| // Allocate an array to contain the ValueIds of external inputs |
| // that are referenced in the given expressions. |
| // ----------------------------------------------------------------- |
| ValueIdSet referencedInputs[MAX_REL_ARITY]; |
| // ----------------------------------------------------------------- |
| // Allocate a ValueIdSet to contain the ValueIds of the roots of |
| // sub-expressions that are covered by |
| // a) the Group Attributes of a child and |
| // b) the new external inputs. |
| // Note that the containing expression is not covered for each |
| // such sub-expression. |
| // ----------------------------------------------------------------- |
| ValueIdSet coveredSubExprNotUsed; |
| // ----------------------------------------------------------------- |
| // Allocate an array to contain the ValueIds of predicates that |
| // can be pushed down to a specific child. |
| // ----------------------------------------------------------------- |
| ValueIdSet predPushSet[MAX_REL_ARITY]; |
| // ----------------------------------------------------------------- |
| // Check which predicate factors are fully covered by a certain |
| // child. Gather their ValueIds in predPushSet. |
| // ----------------------------------------------------------------- |
| const ValueIdSet emptySet; |
| // ----------------------------------------------------------------- |
| // Join predicates can be pushed below a GU root as the comment a |
| // few lines below does applies only to selection predicates |
| // and not join predicates. The comment below indicates that in |
| // some cases we do not wish to push a user provided predicate on |
| // select below the GU root. These user provided predicates are |
| // stored as selection predicates. |
| // For MTS deletes, an anti-semi-join is used to glue the |
| // inlined tree. For such joins all predicates that are pulled |
| // are stored as join predicates. The change below facilitates |
| // a push down of those predicates. The firstChild condition below |
| // ensures that we are considering join predicates here (see |
| // Join::pushDownCoveredExpr) |
| // ----------------------------------------------------------------- |
| NABoolean pushPredicateBelowGURoot = FALSE; |
| |
| if ((getGroupAttr()->isGenericUpdateRoot() AND |
| getOperator() == REL_ANTI_SEMITSJ AND |
| firstChild == 1 ) OR |
| (NOT (getGroupAttr()->isGenericUpdateRoot()))) |
| { |
| pushPredicateBelowGURoot = TRUE; |
| } |
| |
| for (iter = firstChild; iter < lastChild; iter++) |
| { |
| if (NOT child(iter).getPtr()->isCutOp()){ |
| |
| // QSTUFF |
| // we don't push predicates beyond the root of a generic |
| // update tree. This is done by pretending that those |
| // predicates are not covered by any child. This is |
| // required to allows us to distinguish between the |
| // following two types of expressions: |
| // select * from (delete from x) y where y.x > 3; |
| // select * from (delete from x where x.x > 3) y; |
| |
| if (pushPredicateBelowGURoot ) { |
| // QSTUFF |
| child(iter).getGroupAttr()->coverTest(predicatesOnParent, |
| newExternalInputs, |
| predPushSet[iter], |
| referencedInputs[iter], |
| &coveredSubExprNotUsed); |
| // QSTUFF |
| } |
| // QSTUFF |
| } |
| else |
| // ---------------------------------------------------------- |
| // If this is a cutop these predicates were already pushed |
| // down to the child during predicate pushdown. Compute |
| // which predicates were pushable so that we can remove them |
| // from predOnParent and avoid creating a new group that will |
| // later be merged |
| // ---------------------------------------------------------- |
| |
| // QSTUFF |
| // for more explanation please see comment above |
| |
| if ( pushPredicateBelowGURoot ) { |
| // QSTUFF |
| child(iter).getGroupAttr()->coverTest(predicatesOnParent, |
| emptySet, |
| predPushSet[iter], |
| referencedInputs[iter], |
| &coveredSubExprNotUsed); |
| // QSTUFF |
| } |
| // QSTUFF |
| } // for loop to perform coverTest() |
| // ----------------------------------------------------------------- |
| // From the original set of predicates, delete all those predicates |
| // that will be pushed down. The remaining predicates will be |
| // evaluated on the parent (this node). |
| // ----------------------------------------------------------------- |
| for (iter = firstChild; iter < lastChild; iter++) |
| predicatesOnParent -= predPushSet[iter]; |
| |
| // ----------------------------------------------------------------- |
| // Add the predicates that could not be pushed to any child to the |
| // set of expressions to evaluate on the parent. |
| // ----------------------------------------------------------------- |
| computeValuesReqdForPredicates(predicatesOnParent, |
| exprToEvalOnParent); |
| |
| // ----------------------------------------------------------------- |
| // Perform predicate pushdown |
| // ----------------------------------------------------------------- |
| for (iter = firstChild; iter < lastChild; iter++) |
| { |
| if (NOT child(iter).getPtr()->isCutOp()) |
| { |
| // --------------------------------------------------------- |
| // Reassign predicate factors to the appropriate children |
| // --------------------------------------------------------- |
| child(iter).getPtr()->selectionPred().insert(predPushSet[iter]); |
| // --------------------------------------------------------- |
| // Add the input values that are referenced by the predicates |
| // that were pushed down in the above step, to the Group |
| // Attributes of the child. |
| // We need to call coverTest again to figure out which inputs |
| // are needed for the predicates that will be pushdown. |
| // --------------------------------------------------------- |
| ValueIdSet inputsNeededByPredicates; |
| child(iter).getGroupAttr()->coverTest(predPushSet[iter], |
| referencedInputs[iter], |
| predPushSet[iter], |
| inputsNeededByPredicates, |
| &coveredSubExprNotUsed); |
| child(iter).getPtr()->getGroupAttr()->addCharacteristicInputs |
| (inputsNeededByPredicates); |
| ValueIdSet essChildOutputs; |
| child(iter).getPtr()->getEssentialOutputsFromChildren |
| (essChildOutputs); |
| // ---------------------------------------------------------- |
| // Have the child compute what output it can provide for |
| // the expressions that remain on the parent |
| // ---------------------------------------------------------- |
| |
| // TBD: Fix the hack described in |
| // GroupAttributes::resolveCharacteristicOutputs() |
| if(iter==1 AND getOperator().match(REL_ANY_LEFT_JOIN)) |
| child(iter).getPtr()->getGroupAttr()->computeCharacteristicIO |
| (newExternalInputs, |
| exprToEvalOnParent, |
| outputSet, |
| essChildOutputs, |
| &(getSelectionPred()), |
| TRUE, |
| optimizeOutputs, |
| &extraHubNonEssOutputs |
| ); |
| else |
| child(iter).getPtr()->getGroupAttr()->computeCharacteristicIO |
| (newExternalInputs, |
| exprToEvalOnParent, |
| outputSet, |
| essChildOutputs, |
| NULL, |
| FALSE, |
| optimizeOutputs, |
| &extraHubNonEssOutputs |
| ); |
| |
| }; |
| } // for loop to pushdown predicates |
| } // endif (NOT predicatesOnParent.isEmpty()) |
| else |
| { |
| // --------------------------------------------------------------------- |
| // Compute the characteristic inputs and outputs of each child |
| // --------------------------------------------------------------------- |
| for (iter = firstChild; iter < lastChild; iter++) |
| { |
| // ----------------------------------------------------------------- |
| // Ignore CutOps because they exist simply to facilitate |
| // pattern matching. Their Group Attributes are actually those |
| // of the CascadesGroup. So, don't mess with them! |
| // ----------------------------------------------------------------- |
| if (NOT child(iter).getPtr()->isCutOp()) |
| { |
| ValueIdSet essChildOutputs; |
| child(iter).getPtr()->getEssentialOutputsFromChildren |
| (essChildOutputs); |
| |
| // TBD: Fix the hack described in |
| // GroupAttributes::resolveCharacteristicOutputs() |
| child(iter).getPtr()->getGroupAttr()->computeCharacteristicIO |
| (newExternalInputs, |
| exprToEvalOnParent, |
| outputSet, |
| essChildOutputs, |
| NULL, |
| FALSE, |
| optimizeOutputs, |
| &extraHubNonEssOutputs |
| ); |
| } |
| } // for loop to compute characteristic inputs and outputs |
| } // endelse predicatesOnParent is empty |
| |
| } // RelExpr::pushdownCoveredExpr() |
| |
| // ----------------------------------------------------------------------- |
| // A virtual method for computing output values that an operator can |
| // produce potentially. |
| // ----------------------------------------------------------------------- |
| void RelExpr::getPotentialOutputValues(ValueIdSet & outputValues) const |
| { |
| outputValues.clear(); |
| Int32 nc = getArity(); |
| // For operators that are not leaves, clear the potential outputs |
| // and rebuild them. |
| if (nc > 0) |
| for (Lng32 i = 0; i < nc; i++) |
| outputValues += child(i).getGroupAttr()->getCharacteristicOutputs(); |
| else |
| outputValues += getGroupAttr()->getCharacteristicOutputs(); |
| |
| } // RelExpr::getPotentialOutputValues() |
| |
| void RelExpr::getPotentialOutputValuesAsVEGs(ValueIdSet& outputs) const |
| { |
| getPotentialOutputValues(outputs); |
| } |
| |
| |
| // ----------------------------------------------------------------------- |
| // primeGroupAttributes() |
| // Initialize the Characteristic Inputs And Outputs of this operator. |
| // ----------------------------------------------------------------------- |
| void RelExpr::primeGroupAttributes() |
| { |
| // Ignore CutOps because they exist simply to facilitate |
| // pattern matching. Their Group Attributes are actually those |
| // of the CascadesGroup. So, don't mess with them. |
| if (isCutOp()) |
| return; |
| |
| // The method sets the characteristic outputs of a node to its |
| // potential outputs and sets the required input to the values |
| // it needs. It does this by calling two virtual functions |
| // on RelExpr. |
| |
| ValueIdSet outputValues; |
| getPotentialOutputValues(outputValues); |
| getGroupAttr()->setCharacteristicOutputs(outputValues); |
| |
| recomputeOuterReferences(); |
| } // RelExpr::primeGroupAttributes() |
| |
| // ----------------------------------------------------------------------- |
| // allocateAndPrimeGroupAttributes() |
| // This method is for allocating new Group Attributes for the children |
| // of this operator that were introduced in the dataflow by a rule- |
| // based transformation. Each new child, or set of children, intervene |
| // between this operator and another operator that was originally a |
| // direct child of the latter. The Group Attributes of each newly |
| // introduced child are recursively primed with the Characteristic |
| // Inputs and Outputs of the operators of which it is the parent. |
| // ----------------------------------------------------------------------- |
| void RelExpr::allocateAndPrimeGroupAttributes() |
| { |
| Int32 nc = getArity(); |
| |
| for (Lng32 i = 0; i < nc; i++) |
| { |
| CMPASSERT(child(i).getMode() == ExprGroupId::STANDALONE); |
| // Terminate the recursive descent upon reaching a CutOp. |
| // Ignore CutOps because they exist simply to facilitate |
| // pattern matching. Their Group Attributes are actually |
| // those for the CascadesGroup that they belong to and |
| // must not change. |
| if (NOT child(i)->isCutOp()) |
| { |
| if (child(i).getGroupAttr() == NULL) |
| { |
| // A CutOp must have Group Attributes. |
| child(i)->setGroupAttr(new (CmpCommon::statementHeap()) |
| GroupAttributes()); |
| } |
| // Assign my Characteristic Inputs to my child. |
| // This is done in order to ensure that they are propagated |
| // recursively to all my children who are not CutOps. |
| child(i).getPtr()->getGroupAttr() |
| ->addCharacteristicInputs |
| (getGroupAttr()->getCharacteristicInputs()); |
| // Recompute the potential inputs/outputs for each real child |
| // recursively. |
| // Terminate the recursive descent upon encountering an |
| // operator whose arity == 0 |
| child(i).getPtr()->allocateAndPrimeGroupAttributes(); |
| // Prime the Group Attributes of the child. |
| // The following call primes the child's Characteristic Outputs. |
| // It ensures that the inputs are minimal and outputs are maximal. |
| child(i).getPtr()->primeGroupAttributes(); |
| // Now compute the GroupAnalysis fields |
| child(i).getPtr()->primeGroupAnalysis(); |
| } // endif child is not a CutOp |
| |
| } // for loop |
| |
| } // RelExpr::allocateAndPrimeGroupAttributes() |
| |
| void RelExpr::getEssentialOutputsFromChildren(ValueIdSet & essOutputs) |
| { |
| Int32 nc = getArity(); |
| for (Lng32 i = 0; i < nc; i++) |
| { |
| essOutputs += child(i).getGroupAttr()-> |
| getEssentialCharacteristicOutputs(); |
| } |
| } |
| |
| void RelExpr::fixEssentialCharacteristicOutputs() |
| { |
| ValueIdSet essChildOutputs,nonEssOutputs; |
| getEssentialOutputsFromChildren(essChildOutputs); |
| getGroupAttr()->getNonEssentialCharacteristicOutputs(nonEssOutputs); |
| nonEssOutputs.intersectSet(essChildOutputs); |
| getGroupAttr()->addEssentialCharacteristicOutputs(nonEssOutputs); |
| } |
| |
| // do some analysis on the initial plan |
| // this is called at the end of the analysis phase |
| void RelExpr::analyzeInitialPlan() |
| { |
| Int32 nc = getArity(); |
| |
| for (Lng32 i = 0; i < nc; i++) |
| { |
| child(i)->analyzeInitialPlan(); |
| } |
| |
| } |
| |
| double RelExpr::calculateNoOfLogPlans(Lng32& numOfMergedExprs) |
| { |
| double result = 1; |
| Int32 nc = getArity(); |
| CascadesGroup* group; |
| |
| for (Lng32 i = 0; i < nc; i++) |
| { |
| if (getGroupId() == child(i).getGroupId()) |
| { |
| // This is a recursive reference of an expression to itself |
| // due to a group merge. We cannot call this method on the |
| // child, as we would end up calling this method on ourselves |
| // again! So, we skip the recursive call on our child and |
| // instead return an indication to our caller |
| // (CascadesGroup::calculateNoOfLogPlans) that we encountered |
| // a merged expression. Our caller will then know what to do |
| // to calculate the correct number of logical expressions. |
| numOfMergedExprs++; |
| } |
| else |
| { |
| group = (*CURRSTMT_OPTGLOBALS->memo)[child(i).getGroupId()]; |
| result *= group->calculateNoOfLogPlans(); |
| } |
| } // for each child |
| |
| return result; |
| } |
| |
| // This function is called before any optimization starts |
| // i.e. applied to the normalizer output (reorderJoinTree OK) |
| double RelExpr::calculateSubTreeComplexity |
| (NABoolean& enableJoinToTSJRuleOnPass1) |
| { |
| |
| double result = 0; |
| Int32 freeLeaves = 1; // # of subtree legs that can permutate |
| RelExpr* expr = this; |
| |
| while (expr) |
| { |
| if (expr->getGroupAttr()->isEmbeddedUpdateOrDelete() OR |
| expr->getGroupAttr()->isStream()) |
| { |
| enableJoinToTSJRuleOnPass1 = TRUE; |
| } |
| Int32 nc = expr->getArity(); |
| |
| // The multi-join case |
| if (expr->getOperatorType() == REL_MULTI_JOIN) |
| { |
| for (Int32 i = 0; i < nc; i++) |
| { |
| CascadesGroup* groupi = (*CURRSTMT_OPTGLOBALS->memo)[expr->child(i).getGroupId()]; |
| |
| RelExpr * expri = groupi->getFirstLogExpr(); |
| |
| result += expri-> |
| calculateSubTreeComplexity(enableJoinToTSJRuleOnPass1); |
| |
| } |
| freeLeaves = nc; |
| // end the while loop |
| expr = NULL; |
| } |
| // Not multi-join, and not leaf |
| else if (nc > 0) |
| { |
| if (nc == 1) |
| { |
| // no permutation can take place across groupbys |
| if (expr->getOperator().match(REL_ANY_GROUP)) |
| { |
| if (freeLeaves > 1) |
| { |
| // compute the last permuatation set contribution |
| // to the complexity and start a new one |
| result += freeLeaves * pow(2,freeLeaves-1); |
| freeLeaves = 1; // start again |
| } |
| } |
| } |
| if (nc == 2) |
| { |
| double child1Complexity; |
| CascadesGroup* group1 = (*CURRSTMT_OPTGLOBALS->memo)[expr->child(1).getGroupId()]; |
| if (group1->getGroupAttr()->getNumBaseTables() > 1) |
| { |
| // Only one log expr exist in the group at this point |
| RelExpr * expr1 = group1->getFirstLogExpr(); |
| child1Complexity = |
| expr1->calculateSubTreeComplexity(enableJoinToTSJRuleOnPass1); |
| |
| // adding this comp_bool guard in case this fix causes regressions |
| // and we need to disable this fix. Should be taken out in a subsequent |
| // release. (say 2.2) |
| if (CmpCommon::getDefault(COMP_BOOL_123) == DF_OFF) |
| { |
| // The factor 2 accounts for the fact that the join could be a |
| // join or a TSJ i.e. two possible logical choices. |
| if (expr->getOperator().match(REL_ANY_NON_TSJ_JOIN)) |
| child1Complexity = 2*child1Complexity ; |
| } |
| |
| // add the right child subtree contribution to complexity |
| result += child1Complexity; |
| } |
| // only REL_ANY_NON_TSJ_JOINs can permutate |
| if (expr->getOperator().match(REL_ANY_NON_TSJ_JOIN)) |
| freeLeaves++; // still in same permutation set |
| else |
| { |
| // compute the last permuatation set contribution |
| // to the complexity and start a new one |
| result += freeLeaves * pow(2,freeLeaves-1); |
| freeLeaves = 1; // start again |
| } |
| } |
| // we do not handle VPJoin yet (nc==3) |
| CascadesGroup* group0 = (*CURRSTMT_OPTGLOBALS->memo)[expr->child(0).getGroupId()]; |
| // Only one log expr exist in the group at this point |
| expr = group0->getFirstLogExpr(); |
| } |
| // leaf operators |
| else |
| expr = NULL; |
| } |
| // add last permutation set contribution |
| result += freeLeaves * pow(2,freeLeaves-1); |
| return result; |
| |
| } |
| |
| // calculate a query's MJ complexity, |
| // shoud be called after MJ rewrite |
| double RelExpr::calculateQueryMJComplexity(double &n,double &n2,double &n3,double &n4) |
| { |
| double result = 0; |
| Int32 nc = getArity(); |
| Int32 freeLeaves = nc; // # of subtree legs that can permutate |
| |
| RelExpr * expr = this; |
| |
| if (getOperatorType() == REL_MULTI_JOIN) |
| { |
| for (Int32 i = 0; i < nc; i++) |
| { |
| RelExpr * expri = expr->child(i); |
| |
| NABoolean childIsFullOuterJoinOrTSJ = |
| child(i)->getGroupAnalysis()->getNodeAnalysis()-> |
| getJBBC()->isFullOuterJoinOrTSJJBBC(); |
| |
| |
| if (childIsFullOuterJoinOrTSJ) |
| { |
| NABoolean childIsOuterMost = |
| !(child(i)->getGroupAnalysis()->getNodeAnalysis()-> |
| getJBBC()->getOriginalParentJoin()); |
| |
| if(childIsOuterMost) |
| freeLeaves--; |
| } |
| |
| result += expri-> |
| calculateQueryMJComplexity(n, n2, n3, n4); |
| } |
| //only do this for multijoins since only the children |
| //of the multijoin will be permuted. |
| //Note: This assumes the query tree to be the multijoinized |
| //tree produced after multijoin rewrite in the Analyzer |
| n += freeLeaves; |
| n2 += pow(freeLeaves,2); |
| n3 += pow(freeLeaves,3); |
| n4 += pow(freeLeaves,4); |
| result += freeLeaves * pow(2,freeLeaves-1); |
| } |
| else if(nc > 0) |
| { |
| if (nc == 1) |
| { |
| RelExpr * expr0 = expr->child(0); |
| |
| result += expr0-> |
| calculateQueryMJComplexity(n, n2, n3, n4); |
| } |
| else if (nc == 2) |
| { |
| // only for joins, not for union |
| // these will only be TSJ or Full Outer Joins |
| // other joins become part of JBB |
| if (expr->getOperator().match(REL_ANY_JOIN)) |
| { |
| RelExpr * expr0 = expr->child(0); |
| result += expr0->calculateQueryMJComplexity(n, n2, n3, n4); |
| |
| RelExpr * expr1 = expr->child(1); |
| result += expr1->calculateQueryMJComplexity(n, n2, n3, n4); |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| |
| // ----------------------------------------------------------------------- |
| // the following method is used to created a list of all scan operators |
| // in order by size. |
| // ----------------------------------------------------------------------- |
| void |
| RelExpr::makeListBySize(LIST(CostScalar) & orderedList, // order list of size |
| NABoolean recompute) // recompute memory |
| // limit -not used |
| { |
| Int32 nc = getArity(); |
| RelExpr * expr = this; |
| CostScalar size = 0; |
| |
| if (recompute) |
| { |
| // this needs to be filled in if this ever is redriven by costing |
| CMPASSERT(NOT recompute); |
| } |
| else |
| { |
| if (expr->getOperatorType() == REL_SCAN OR |
| expr->getOperatorType() == REL_GROUPBY) |
| { |
| //++MV, use the global empty input logical properties instead of |
| //initializing a new one |
| size = |
| expr->getGroupAttr()->outputLogProp((*GLOBAL_EMPTY_INPUT_LOGPROP))->getResultCardinality() |
| * expr->getGroupAttr()->getRecordLength() / 1024; |
| } |
| } |
| |
| if (size > 1) // don't include anything 1KB or less |
| { |
| CollIndex idx = 0; |
| |
| for (idx = 0; idx < orderedList.entries(); idx++) |
| { |
| // list should be ordered by increasing estimated rowcount. |
| if (orderedList[idx] >= size) |
| { |
| orderedList.insertAt (idx, size); |
| break; |
| } |
| } |
| |
| // insert at end of list |
| if (idx >= orderedList.entries()) |
| { |
| orderedList.insertAt (orderedList.entries(), size); |
| } |
| |
| } |
| |
| for (Lng32 i = 0; i < nc; i++) |
| { |
| CascadesGroup* group1 = (*CURRSTMT_OPTGLOBALS->memo)[expr->child(i).getGroupId()]; |
| // Only one log expr exist in the group at this point |
| // if onlyMemoryOps is ever set true, we will have to traverse |
| // the tree differently |
| RelExpr * expr1 = group1->getFirstLogExpr(); |
| expr1->makeListBySize(orderedList, recompute); |
| } |
| } |
| |
| // Default implementation every RelExpr returns normal priority |
| PlanPriority RelExpr::computeOperatorPriority |
| (const Context* context, |
| PlanWorkSpace *pws, |
| Lng32 planNumber) |
| { |
| PlanPriority result; // This will create normal plan priority |
| return result; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // Method for debugging |
| // ----------------------------------------------------------------------- |
| void RelExpr::print(FILE * f, |
| const char * prefix, |
| const char * suffix) const |
| { |
| #ifndef NDEBUG |
| ExprNode::print(f,prefix,suffix); |
| |
| fprintf(f,"%sRelational Expression:\n",prefix); |
| |
| if (selection_ != NULL) |
| selection_->print(f,prefix,suffix); |
| else |
| predicates_.print(f,prefix,suffix); |
| |
| // print children or input equivalence classes |
| Int32 nc = getArity(); |
| for (Lng32 i = 0; i < nc; i++) |
| { |
| fprintf(f,"%sExpression input %d:\n",prefix,i); |
| if (child(i).getMode() == ExprGroupId::MEMOIZED) |
| { |
| fprintf(f, |
| "%s input eq. class #%d\n", |
| prefix, |
| child(i).getGroupId()); |
| } |
| else |
| { |
| if (child(i).getPtr() != NULL) |
| child(i)->print(f,CONCAT(prefix," ")); |
| else |
| fprintf(f,"%snonexistent child\n",prefix); |
| } |
| } |
| #endif |
| } |
| |
| Int32 RelExpr::nodeCount() const |
| { |
| Int32 result = 1; // start from me. |
| Int32 nc = getArity(); |
| for (Lng32 i = 0; i < nc; i++) |
| if (child(i).getPtr() != NULL) |
| result += child(i)->nodeCount(); |
| |
| return result; |
| } |
| |
| NABoolean RelExpr::containsNode(OperatorTypeEnum nodeType) |
| { |
| if (getOperatorType() == nodeType) |
| return TRUE; |
| |
| Int32 nc = getArity(); |
| for (Int32 i = 0; i < nc; i++) |
| { |
| |
| if (child(i).getPtr() != NULL && |
| child(i)->containsNode(nodeType)) |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| double RelExpr::computeMemoryQuota(NABoolean inMaster, |
| NABoolean perNode, |
| double BMOsMemoryLimit, // in MB |
| UInt16 totalNumBMOs, // per query |
| double totalBMOsMemoryUsage, // for all BMOs per node in bytes |
| UInt16 numBMOsPerFragment, // per fragment |
| double bmoMemoryUsage, // for the current BMO/Operator per node in bytes |
| Lng32 numStreams, |
| double &bmoQuotaRatio |
| ) |
| { |
| if ( perNode == TRUE ) { |
| Lng32 exeMem = Lng32(BMOsMemoryLimit/(1024*1024)); |
| |
| // the quota is allocated in 2 parts |
| // The constant part divided equally across all bmo operators |
| // The variable part allocated in proportion of the given BMO operator |
| // estimated memory usage to the total estimated memory usage of all BMOs |
| |
| // The ratio can be capped by the CQD |
| double equalQuotaShareRatio = 0; |
| equalQuotaShareRatio = ActiveSchemaDB()->getDefaults().getAsDouble(BMO_MEMORY_EQUAL_QUOTA_SHARE_RATIO); |
| double constMemQuota = 0; |
| double variableMemLimit = exeMem; |
| if (equalQuotaShareRatio > 0 && totalNumBMOs > 1) { |
| constMemQuota = (exeMem * equalQuotaShareRatio )/ totalNumBMOs; |
| variableMemLimit = (1-equalQuotaShareRatio) * exeMem; |
| } |
| double bmoMemoryRatio = bmoMemoryUsage / totalBMOsMemoryUsage; |
| bmoQuotaRatio = bmoMemoryRatio; |
| double bmoMemoryQuotaPerNode = constMemQuota + (variableMemLimit * bmoMemoryRatio); |
| double numInstancesPerNode = numStreams / MINOF(MAXOF(((NAClusterInfoLinux*)gpClusterInfo)->getTotalNumberOfCPUs(), 1), numStreams); |
| double bmoMemoryQuotaPerInstance = bmoMemoryQuotaPerNode / numInstancesPerNode; |
| return bmoMemoryQuotaPerInstance; |
| } else { |
| // the old way to compute quota |
| Lng32 exeMem = getExeMemoryAvailable(inMaster); |
| bmoQuotaRatio = BMOQuotaRatio::NO_RATIO; |
| return exeMem / numBMOsPerFragment; |
| } |
| } |
| |
| Lng32 RelExpr::getExeMemoryAvailable(NABoolean inMaster) const |
| { |
| Lng32 exeMemAvailMB = |
| ActiveSchemaDB()->getDefaults().getAsLong(EXE_MEMORY_AVAILABLE_IN_MB); |
| return exeMemAvailMB; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // methods for class RelExprList |
| // ----------------------------------------------------------------------- |
| void RelExprList::insertOrderByRowcount (RelExpr * expr) |
| { |
| Int32 i = 0; |
| NABoolean done = FALSE; |
| |
| |
| // QSTUFF |
| // insert stream expression as the left most expression |
| // by articially forcing it to have lowest cost |
| // assumes that only one stream and one embedded update clause |
| // is in the statement |
| |
| if (expr->getGroupAttr()->isStream() || |
| expr->getGroupAttr()->isEmbeddedUpdateOrDelete()) |
| { |
| insertAt(0,expr); |
| done = TRUE; |
| } |
| // QSTUFF |
| |
| while (!done && i < (Int32)entries()) |
| { |
| CostScalar thisCard = (*this)[i]-> |
| getGroupAttr()-> |
| getResultCardinalityForEmptyInput(); |
| CostScalar exprCard = expr-> |
| getGroupAttr()-> |
| getResultCardinalityForEmptyInput(); |
| |
| NABoolean increasing = |
| ((ActiveSchemaDB()->getDefaults()).getAsULong(COMP_INT_90)==1); |
| |
| // list should be ordered by increasing estimated rowcount. |
| if (((thisCard >= exprCard ) && increasing) || |
| ((thisCard < exprCard ) && !increasing)) |
| { |
| // QSTUFF |
| // stream and nested updates or deletes expressions should always be |
| // left most, i.e of lowest cost |
| |
| if ( |
| (*this)[i]->getGroupAttr()->isStream() || |
| (*this)[i]->getGroupAttr()->isEmbeddedUpdateOrDelete()) |
| i++; |
| // QSTUFF |
| |
| insertAt (i, expr); |
| done = TRUE; |
| } |
| else |
| i++; |
| } |
| |
| // insert at end of list |
| if (!done) insertAt (entries(), expr); |
| } |
| |
| NABoolean RelExprList::operator== (const RelExprList &other) const |
| { |
| if (entries() != other.entries()) |
| return FALSE; |
| |
| for (Lng32 i = 0; i < (Lng32)entries(); i++) |
| { |
| if ((*this)[i] != other[i]) |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| NABoolean RelExprList::operator!= (const RelExprList &other) const |
| { |
| if ((*this) == other) |
| return FALSE; |
| else |
| return TRUE; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // methods for class CutOp |
| // ----------------------------------------------------------------------- |
| |
| CutOp::~CutOp() {} |
| |
| void CutOp::print(FILE * f, |
| const char * prefix, |
| const char *) const |
| { |
| #ifndef NDEBUG |
| if (getGroupId() == INVALID_GROUP_ID) |
| fprintf(f, "%sLeaf (%d)\n", prefix, index_); |
| else |
| fprintf(f, "%sLeaf (%d, bound to group #%d)\n", |
| prefix, index_, getGroupId()); |
| |
| return; |
| #endif |
| } |
| |
| Int32 CutOp::getArity () const { return 0; } |
| |
| NABoolean CutOp::isCutOp() const { return TRUE; } |
| |
| const NAString CutOp::getText() const |
| { |
| char theText[TEXT_DISPLAY_LENGTH]; |
| |
| if (getGroupId() == INVALID_GROUP_ID) |
| sprintf(theText, "Cut (%d)", index_); |
| else |
| if (index_ < 99) |
| sprintf(theText, "Cut (%d, #%d)", index_, getGroupId()); |
| else |
| // don't display funny indexes (>= 99) |
| sprintf(theText, "Cut (#%d)", getGroupId()); |
| |
| return NAString(theText); |
| } |
| |
| RelExpr * CutOp::copyTopNode(RelExpr * derivedNode, CollHeap* outHeap) |
| { |
| if (getGroupId() == INVALID_GROUP_ID) |
| { |
| // this is a standalone cut operator (e.g. in the tree of a |
| // CONTROL QUERY SHAPE directive), return a copy of it |
| CMPASSERT(derivedNode == NULL); |
| CutOp* result = new (outHeap)CutOp(index_, outHeap); |
| return RelExpr::copyTopNode(result,outHeap); |
| } |
| else |
| { |
| // CutOps are shared among the pattern and the substitute of |
| // a rule. Often the substitute is produced by calling the copyTree() |
| // method on the "before" expression or a part of it. This implementation |
| // of copyTopNode() makes it possible to do that. |
| return this; |
| } |
| } |
| |
| void CutOp::setGroupIdAndAttr(CascadesGroupId groupId) |
| { |
| setGroupId(groupId); |
| |
| // set the group attributes of the leaf node to match the group |
| if (groupId == INVALID_GROUP_ID) |
| setGroupAttr(NULL); |
| else |
| setGroupAttr((*CURRSTMT_OPTGLOBALS->memo)[groupId]->getGroupAttr()); |
| } |
| |
| void CutOp::setExpr(RelExpr *e) |
| { |
| expr_ = e; |
| if (expr_ == NULL) |
| { |
| setGroupIdAndAttr(INVALID_GROUP_ID); |
| } |
| else |
| { |
| setGroupAttr(expr_->getGroupAttr()); // ##shouldn't this line.. |
| // setGroupIdAndAttr(expr_->getGroupId()); // ##..be replaced by this? |
| } |
| } |
| |
| // ----------------------------------------------------------------------- |
| // methods for class SubtreeOp |
| // ----------------------------------------------------------------------- |
| |
| SubtreeOp::~SubtreeOp() {} |
| |
| Int32 SubtreeOp::getArity() const { return 0; } |
| |
| NABoolean SubtreeOp::isSubtreeOp() const { return TRUE; } |
| |
| const NAString SubtreeOp::getText() const { return NAString("Tree Op"); } |
| |
| RelExpr * SubtreeOp::copyTopNode(RelExpr *, CollHeap*) { return this; } |
| |
| // ----------------------------------------------------------------------- |
| // methods for class WildCardOp |
| // ----------------------------------------------------------------------- |
| |
| WildCardOp::~WildCardOp() {} |
| |
| Int32 WildCardOp::getArity() const |
| { |
| switch (getOperatorType()) |
| { |
| case REL_ANY_LEAF_OP: |
| case REL_FORCE_ANY_SCAN: |
| case REL_ANY_ROUTINE: |
| case REL_FORCE_ANY_SCALAR_UDF: |
| case REL_ANY_SCALAR_UDF_ROUTINE: |
| case REL_ANY_LEAF_GEN_UPDATE: |
| case REL_ANY_LEAF_TABLE_MAPPING_UDF: |
| return 0; |
| |
| case REL_ANY_UNARY_GEN_UPDATE: |
| case REL_ANY_UNARY_OP: |
| case REL_ANY_GROUP: |
| case REL_FORCE_EXCHANGE: |
| case REL_ANY_UNARY_TABLE_MAPPING_UDF: |
| case REL_ANY_EXTRACT: |
| return 1; |
| |
| case REL_ANY_BINARY_OP: |
| case REL_ANY_JOIN: |
| case REL_ANY_TSJ: |
| case REL_ANY_SEMIJOIN: |
| case REL_ANY_SEMITSJ: |
| case REL_ANY_ANTI_SEMIJOIN: |
| case REL_ANY_ANTI_SEMITSJ: |
| case REL_ANY_INNER_JOIN: |
| case REL_ANY_NON_TS_INNER_JOIN: |
| case REL_ANY_NON_TSJ_JOIN: |
| case REL_ANY_LEFT_JOIN: |
| case REL_ANY_LEFT_TSJ: |
| case REL_ANY_NESTED_JOIN: |
| case REL_ANY_HASH_JOIN: |
| case REL_ANY_MERGE_JOIN: |
| case REL_FORCE_JOIN: |
| case REL_FORCE_NESTED_JOIN: |
| case REL_FORCE_HASH_JOIN: |
| case REL_FORCE_ORDERED_HASH_JOIN: |
| case REL_FORCE_HYBRID_HASH_JOIN: |
| case REL_FORCE_MERGE_JOIN: |
| case REL_FORCE_ORDERED_CROSS_PRODUCT: |
| case REL_ANY_BINARY_TABLE_MAPPING_UDF: |
| return 2; |
| |
| default: |
| ABORT("WildCardOp with unknown arity encountered"); |
| return 0; |
| } |
| } |
| |
| NABoolean WildCardOp::isWildcard() const { return TRUE; } |
| |
| const NAString WildCardOp::getText() const |
| { |
| switch (getOperatorType()) |
| { |
| case ANY_REL_OR_ITM_OP: |
| return "ANY_REL_OR_ITM_OP"; |
| case REL_ANY_LEAF_OP: |
| return "REL_ANY_LEAF_OP"; |
| case REL_ANY_UNARY_OP: |
| return "REL_ANY_UNARY_OP"; |
| case REL_ANY_ROUTINE: |
| return "REL_ANY_ROUTINE"; |
| case REL_ANY_GEN_UPDATE: |
| return "REL_ANY_GEN_UPDATE"; |
| case REL_ANY_UNARY_GEN_UPDATE: |
| return "REL_ANY_UNARY_GEN_UPDATE"; |
| case REL_ANY_LEAF_GEN_UPDATE: |
| return "REL_ANY_LEAF_GEN_UPDATE"; |
| case REL_ANY_GROUP: |
| return "REL_ANY_GROUP"; |
| case REL_ANY_BINARY_OP: |
| return "REL_ANY_BINARY_OP"; |
| case REL_ANY_JOIN: |
| return "REL_ANY_JOIN"; |
| case REL_ANY_TSJ: |
| return "REL_ANY_TSJ"; |
| case REL_ANY_SEMIJOIN: |
| return "REL_ANY_SEMIJOIN"; |
| case REL_ANY_SEMITSJ: |
| return "REL_ANY_SEMITSJ"; |
| case REL_ANY_INNER_JOIN: |
| return "REL_ANY_INNER_JOIN"; |
| case REL_ANY_LEFT_JOIN: |
| return "REL_ANY_LEFT_JOIN"; |
| case REL_ANY_LEFT_TSJ: |
| return "REL_ANY_LEFT_TSJ"; |
| case REL_ANY_NESTED_JOIN: |
| return "REL_ANY_NESTED_JOIN"; |
| case REL_ANY_HASH_JOIN: |
| return "REL_ANY_HASH_JOIN"; |
| case REL_ANY_MERGE_JOIN: |
| return "REL_ANY_MERGE_JOIN"; |
| case REL_ANY_EXTRACT: |
| return "REL_ANY_EXTRACT"; |
| case REL_FORCE_ANY_SCAN: |
| return "REL_FORCE_ANY_SCAN"; |
| case REL_FORCE_EXCHANGE: |
| return "REL_FORCE_EXCHANGE"; |
| case REL_FORCE_JOIN: |
| return "REL_FORCE_JOIN"; |
| case REL_FORCE_NESTED_JOIN: |
| return "REL_FORCE_NESTED_JOIN"; |
| case REL_FORCE_HASH_JOIN: |
| return "REL_FORCE_HASH_JOIN"; |
| case REL_FORCE_HYBRID_HASH_JOIN: |
| return "REL_FORCE_HYBRID_HASH_JOIN"; |
| case REL_FORCE_ORDERED_HASH_JOIN: |
| return "REL_FORCE_ORDERED_HASH_JOIN"; |
| case REL_FORCE_MERGE_JOIN: |
| return "REL_FORCE_MERGE_JOIN"; |
| default: |
| return "unknown??"; |
| } |
| } |
| |
| RelExpr * WildCardOp::copyTopNode(RelExpr * derivedNode, |
| CollHeap* outHeap) |
| { |
| if (corrNode_ != NULL) |
| return corrNode_->copyTopNode(0, outHeap); |
| else |
| { |
| if (derivedNode != NULL) |
| return derivedNode; |
| else |
| { |
| WildCardOp* result; |
| |
| result = new (outHeap) WildCardOp(getOperatorType(), |
| 0, |
| NULL, |
| NULL, |
| outHeap); |
| return RelExpr::copyTopNode(result,outHeap); |
| } |
| } |
| |
| return NULL; // shouldn't really reach here |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class ScanForceWildCard |
| // ----------------------------------------------------------------------- |
| |
| ScanForceWildCard::ScanForceWildCard(CollHeap * outHeap) : |
| WildCardOp(REL_FORCE_ANY_SCAN), |
| exposedName_(outHeap), |
| indexName_(outHeap) |
| {initializeScanOptions();} |
| |
| ScanForceWildCard::ScanForceWildCard(const NAString& exposedName, |
| CollHeap *outHeap) : |
| WildCardOp(REL_FORCE_ANY_SCAN,0,NULL,NULL,outHeap), |
| exposedName_(exposedName, outHeap), |
| indexName_(outHeap) |
| {initializeScanOptions();} |
| |
| ScanForceWildCard::ScanForceWildCard(const NAString& exposedName, |
| const NAString& indexName, |
| CollHeap *outHeap) : |
| WildCardOp(REL_FORCE_ANY_SCAN,0,NULL,NULL,outHeap), |
| exposedName_(exposedName, outHeap), |
| indexName_(indexName, outHeap) |
| {initializeScanOptions();} |
| |
| ScanForceWildCard::~ScanForceWildCard() |
| { |
| collHeap()->deallocateMemory((void*)enumAlgorithms_); |
| // delete enumAlgorithms_ from the same heap were the |
| // ScanForceWildCard object belong |
| } |
| |
| //---------------------------------------------------------- |
| // initialize class members |
| //---------------------------------------------------------- |
| void ScanForceWildCard::initializeScanOptions() |
| { |
| mdamStatus_ = UNDEFINED; |
| direction_ = UNDEFINED; |
| indexStatus_ = UNDEFINED; |
| numMdamColumns_ = 0; |
| mdamColumnsStatus_ = UNDEFINED; |
| enumAlgorithms_ = NULL; |
| numberOfBlocksToReadPerAccess_ = -1; |
| } |
| |
| //---------------------------------------------------------- |
| // get the enumeration algorithm (density) for column |
| // if beyound specified columns return COLUMN_SYSTEM |
| //---------------------------------------------------------- |
| ScanForceWildCard::scanOptionEnum |
| ScanForceWildCard::getEnumAlgorithmForColumn(CollIndex column) const |
| { |
| if (column >= numMdamColumns_) |
| return ScanForceWildCard::COLUMN_SYSTEM; |
| else |
| return enumAlgorithms_[column]; |
| } |
| |
| //---------------------------------------------------------- |
| // set the following scan option. return FALSE only if |
| // such option does not exist. |
| //---------------------------------------------------------- |
| NABoolean ScanForceWildCard::setScanOptions(ScanForceWildCard::scanOptionEnum option) |
| { |
| if (option == INDEX_SYSTEM) |
| { |
| indexStatus_ = INDEX_SYSTEM; |
| return TRUE; |
| } |
| else if (option == MDAM_SYSTEM) |
| { |
| mdamStatus_ = MDAM_SYSTEM; |
| return TRUE; |
| } |
| else if (option == MDAM_OFF) |
| { |
| mdamStatus_ = MDAM_OFF; |
| return TRUE; |
| } |
| else if (option == MDAM_FORCED) |
| { |
| mdamStatus_ = MDAM_FORCED; |
| return TRUE; |
| } |
| else if (option == DIRECTION_FORWARD) |
| { |
| direction_ = DIRECTION_FORWARD; |
| return TRUE; |
| } |
| else if (option == DIRECTION_REVERSED) |
| { |
| direction_ = DIRECTION_REVERSED; |
| return TRUE; |
| } |
| else if (option == DIRECTION_SYSTEM) |
| { |
| direction_ = DIRECTION_SYSTEM; |
| return TRUE; |
| } |
| else return FALSE; |
| } |
| |
| |
| NABoolean ScanForceWildCard::setIndexName(const NAString& value) |
| { |
| if (value != "") |
| { |
| indexName_ = value; |
| return TRUE; |
| } |
| else |
| return FALSE; // Error should be nonempty string |
| } |
| |
| //---------------------------------------------------------- |
| // set the columns options based on passed values |
| //---------------------------------------------------------- |
| NABoolean ScanForceWildCard:: |
| setColumnOptions(CollIndex numColumns, |
| ScanForceWildCard::scanOptionEnum* columnAlgorithms, |
| ScanForceWildCard::scanOptionEnum mdamColumnsStatus) |
| { |
| mdamStatus_ = MDAM_FORCED; |
| mdamColumnsStatus_ = mdamColumnsStatus; |
| numMdamColumns_ = numColumns; |
| // delete enumAlgorithms_ from the same heap were the |
| // ScanForceWildCard object belong |
| collHeap()->deallocateMemory((void*)enumAlgorithms_); |
| // allocate enumAlgorithms_[numMdamColumns] in the same heap |
| // were the ScanForceWildCard object belong |
| enumAlgorithms_ = (ScanForceWildCard::scanOptionEnum*) |
| collHeap()->allocateMemory(sizeof(ScanForceWildCard::scanOptionEnum)*numMdamColumns_); |
| for (CollIndex i=0; i<numMdamColumns_; i++) |
| enumAlgorithms_[i] = columnAlgorithms[i]; |
| return TRUE; |
| } |
| |
| //---------------------------------------------------------- |
| // set the columns options based on passed values |
| // here options for particular columns are not passed |
| // and hence COLUMN_SYSTEM is assigned |
| //---------------------------------------------------------- |
| NABoolean ScanForceWildCard:: |
| setColumnOptions(CollIndex numColumns, |
| ScanForceWildCard::scanOptionEnum mdamColumnsStatus) |
| { |
| mdamStatus_ = MDAM_FORCED; |
| mdamColumnsStatus_ = mdamColumnsStatus; |
| numMdamColumns_ = numColumns; |
| // delete enumAlgorithms_ from the same heap were the |
| // ScanForceWildCard object belong |
| collHeap()->deallocateMemory((void*)enumAlgorithms_); |
| // allocate enumAlgorithms_[numMdamColumns] in the same heap |
| // were the ScanForceWildCard object belong |
| enumAlgorithms_ = (ScanForceWildCard::scanOptionEnum*) |
| collHeap()->allocateMemory(sizeof(ScanForceWildCard::scanOptionEnum)*numMdamColumns_); |
| //enumAlgorithms_ = new scanOptionEnum[numMdamColumns_]; |
| for (CollIndex i=0; i<numMdamColumns_; i++) |
| enumAlgorithms_[i] = COLUMN_SYSTEM; |
| return TRUE; |
| } |
| |
| //---------------------------------------------------------- |
| // check if the forced scan options conflict with the Mdam |
| // Master switch status |
| //---------------------------------------------------------- |
| NABoolean ScanForceWildCard::doesThisCoflictMasterSwitch() const |
| { |
| char* globalMdamStatus = getenv("MDAM"); |
| if (globalMdamStatus != NULL) |
| { |
| if (strcmp(globalMdamStatus,"OFF")==0 ) |
| { |
| if ((mdamStatus_ == MDAM_FORCED)||(mdamStatus_ == MDAM_SYSTEM)) |
| return TRUE; |
| } |
| } |
| return FALSE; |
| } |
| |
| //---------------------------------------------------------- |
| // merge with another ScanForceWildCard object. |
| // return FALSE if a conflict between the options of |
| // the two objects exists. |
| //---------------------------------------------------------- |
| NABoolean ScanForceWildCard::mergeScanOptions(const ScanForceWildCard &other) |
| { |
| if ((other.exposedName_ != "") |
| &&(other.exposedName_ != exposedName_)) |
| { |
| if (exposedName_ == "") |
| { |
| exposedName_ = other.exposedName_; |
| } |
| else |
| return FALSE; // conflict |
| } |
| |
| if ((other.indexName_ != "") |
| &&(other.indexName_ != indexName_)) |
| { |
| if (indexName_ == "") |
| { |
| indexName_ = other.indexName_; |
| } |
| else |
| return FALSE; // conflict |
| } |
| |
| if (other.indexStatus_ == INDEX_SYSTEM) |
| { |
| indexStatus_ = INDEX_SYSTEM; |
| } |
| |
| if (indexStatus_ == INDEX_SYSTEM) |
| { |
| if (indexName_ != "") |
| return FALSE; // conflict |
| } |
| |
| if ((other.mdamStatus_ == MDAM_OFF) |
| &&(mdamStatus_ != MDAM_OFF)) |
| { |
| if (mdamStatus_ == UNDEFINED) |
| { |
| mdamStatus_ = other.mdamStatus_; |
| } |
| else |
| return FALSE; // conflict |
| } |
| |
| if ((other.mdamStatus_ == MDAM_SYSTEM) |
| &&(mdamStatus_ != MDAM_SYSTEM)) |
| { |
| if (mdamStatus_ == UNDEFINED) |
| { |
| mdamStatus_ = other.mdamStatus_; |
| } |
| else |
| return FALSE; // conflict |
| } |
| |
| if ((other.mdamStatus_ == MDAM_FORCED) |
| &&(mdamStatus_ != MDAM_FORCED)) |
| { |
| if (mdamStatus_ == UNDEFINED) |
| { |
| mdamStatus_ = other.mdamStatus_; |
| } |
| else |
| return FALSE; // conflict |
| } |
| |
| if (other.numMdamColumns_ > 0) |
| { |
| if ((mdamStatus_ == UNDEFINED)||(mdamStatus_ == MDAM_FORCED)) |
| { |
| if (numMdamColumns_ == other.numMdamColumns_) |
| { |
| for (CollIndex i=0; i<numMdamColumns_; i++) |
| { |
| if (enumAlgorithms_[i] != other.enumAlgorithms_[i]) |
| return FALSE; // conflict |
| } |
| if (other.mdamColumnsStatus_ != mdamColumnsStatus_) |
| return FALSE; // conflict |
| } |
| else if (numMdamColumns_ == 0) // i.e. enumAlgorithm is NULL |
| { |
| numMdamColumns_ = other.numMdamColumns_; |
| collHeap()->deallocateMemory((void*)enumAlgorithms_); |
| //delete enumAlgorithms_; |
| enumAlgorithms_ = (ScanForceWildCard::scanOptionEnum*) |
| collHeap()->allocateMemory(sizeof(ScanForceWildCard::scanOptionEnum)*numMdamColumns_); |
| //enumAlgorithms_ = new scanOptionEnum[numMdamColumns_]; |
| for (CollIndex i=0; i<numMdamColumns_; i++) |
| { |
| enumAlgorithms_[i] = other.enumAlgorithms_[i]; |
| } |
| } |
| else |
| return FALSE; // coflict |
| } |
| else |
| return FALSE; // conflict |
| } |
| |
| if (other.mdamColumnsStatus_ != UNDEFINED) |
| { |
| if (mdamColumnsStatus_ == UNDEFINED) |
| { |
| mdamColumnsStatus_ = other.mdamColumnsStatus_; |
| } |
| if (mdamColumnsStatus_ != other.mdamColumnsStatus_) |
| { |
| return FALSE; // conflict |
| } |
| } |
| |
| if ((other.direction_ == DIRECTION_FORWARD) |
| &&(direction_ != DIRECTION_FORWARD)) |
| { |
| if (direction_ == UNDEFINED) |
| { |
| direction_ = other.direction_; |
| } |
| else |
| return FALSE; // conflict |
| } |
| |
| if ((other.direction_ == DIRECTION_REVERSED) |
| &&(direction_ != DIRECTION_REVERSED)) |
| { |
| if (direction_ == UNDEFINED) |
| { |
| direction_ = other.direction_; |
| } |
| else |
| return FALSE; // conflict |
| } |
| |
| if ((other.direction_ == DIRECTION_SYSTEM) |
| &&(direction_ != DIRECTION_SYSTEM)) |
| { |
| if (direction_ == UNDEFINED) |
| { |
| direction_ = other.direction_; |
| } |
| else |
| return FALSE; // conflict |
| } |
| |
| if (other.numberOfBlocksToReadPerAccess_ > 0) |
| { |
| numberOfBlocksToReadPerAccess_ = other.numberOfBlocksToReadPerAccess_; |
| } |
| |
| return TRUE; |
| } |
| |
| //---------------------------------------------------------- |
| // if access path is not given then the default is ANY i.e |
| // system choice unless MDAM is forced then the default is |
| // the base table. |
| //---------------------------------------------------------- |
| void ScanForceWildCard::prepare() |
| { |
| if (mdamStatus_ != ScanForceWildCard::MDAM_FORCED) |
| { |
| if (indexName_ == "") |
| { |
| indexStatus_ = ScanForceWildCard::INDEX_SYSTEM; |
| } |
| } |
| else // mdam is forced |
| { |
| if ((indexName_ == "") && |
| (indexStatus_ != ScanForceWildCard::INDEX_SYSTEM)) |
| { |
| indexName_ = exposedName_; |
| } |
| } |
| if (mdamColumnsStatus_ == ScanForceWildCard::UNDEFINED) |
| { |
| mdamColumnsStatus_ = ScanForceWildCard::MDAM_COLUMNS_REST_BY_SYSTEM; |
| } |
| |
| return; |
| } |
| |
| RelExpr * ScanForceWildCard::copyTopNode(RelExpr *derivedNode, |
| CollHeap* outHeap) |
| { |
| ScanForceWildCard *temp; |
| if (derivedNode != NULL) |
| ABORT("No support for classes derived from ScanForceWildcard"); |
| |
| temp = new (outHeap) ScanForceWildCard(exposedName_,indexName_,outHeap); |
| temp->direction_ = direction_; |
| temp->indexStatus_ = indexStatus_; |
| temp->mdamStatus_ = mdamStatus_; |
| temp->mdamColumnsStatus_ = mdamColumnsStatus_; |
| temp->numMdamColumns_ = numMdamColumns_; |
| temp->numberOfBlocksToReadPerAccess_ = numberOfBlocksToReadPerAccess_; |
| temp->enumAlgorithms_ = new (outHeap) scanOptionEnum[numMdamColumns_]; |
| for (CollIndex i=0; i<numMdamColumns_; i++) |
| { |
| temp->enumAlgorithms_[i]=enumAlgorithms_[i]; |
| } |
| RelExpr *result = temp; |
| WildCardOp::copyTopNode(result,outHeap); |
| |
| return result; |
| } |
| |
| const NAString ScanForceWildCard::getText() const |
| { |
| NAString result("forced scan", CmpCommon::statementHeap()); |
| |
| if (exposedName_ != "") |
| { |
| result += "("; |
| result += exposedName_; |
| |
| if (indexName_ != "") |
| { |
| result += ", index "; |
| result += indexName_; |
| } |
| result += ")"; |
| } |
| |
| return result; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class JoinForceWildCard |
| // ----------------------------------------------------------------------- |
| |
| JoinForceWildCard::JoinForceWildCard(OperatorTypeEnum type, |
| RelExpr *child0, |
| RelExpr *child1, |
| forcedPlanEnum plan, |
| Int32 numOfEsps, |
| CollHeap *outHeap) : |
| WildCardOp(type, 0, child0, child1,outHeap) |
| { |
| plan_ = plan; |
| numOfEsps_ = numOfEsps; |
| } |
| |
| JoinForceWildCard::~JoinForceWildCard() {} |
| |
| RelExpr * JoinForceWildCard::copyTopNode(RelExpr *derivedNode, |
| CollHeap* outHeap) |
| { |
| RelExpr *result; |
| if (derivedNode != NULL) |
| ABORT("No support for classes derived from JoinForceWildcard"); |
| |
| result = new(outHeap) JoinForceWildCard(getOperatorType(), |
| NULL, |
| NULL, |
| plan_, |
| numOfEsps_, |
| outHeap); |
| WildCardOp::copyTopNode(result,outHeap); |
| |
| return result; |
| } |
| |
| const NAString JoinForceWildCard::getText() const |
| { |
| NAString result("forced", CmpCommon::statementHeap()); |
| |
| if (plan_ == FORCED_PLAN0) |
| result += " plan0"; |
| else if (plan_ == FORCED_PLAN1) |
| result += " plan1"; |
| else if (plan_ == FORCED_PLAN2) |
| result += " plan2"; |
| else if (plan_ == FORCED_TYPE1) |
| result += " type1"; |
| else if (plan_ == FORCED_TYPE2) |
| result += " type2"; |
| else if (plan_ == FORCED_INDEXJOIN) |
| result += " indexjoin"; |
| |
| switch (getOperatorType()) |
| { |
| case REL_FORCE_NESTED_JOIN: |
| result += " nested join"; |
| break; |
| case REL_FORCE_MERGE_JOIN: |
| result += " merge join"; |
| break; |
| case REL_FORCE_HASH_JOIN: |
| result += " hash join"; |
| break; |
| case REL_FORCE_HYBRID_HASH_JOIN: |
| result += " hybrid hash join"; |
| break; |
| case REL_FORCE_ORDERED_HASH_JOIN: |
| result += " ordered hash join"; |
| break; |
| default: |
| result += " join"; |
| break; |
| } |
| |
| return result; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class ExchangeForceWildCard |
| // ----------------------------------------------------------------------- |
| |
| ExchangeForceWildCard::ExchangeForceWildCard(RelExpr *child0, |
| forcedExchEnum which, |
| forcedLogPartEnum whatLogPart, |
| Lng32 numBottomEsps, |
| CollHeap *outHeap) : |
| WildCardOp(REL_FORCE_EXCHANGE, 0, child0, NULL, outHeap), |
| which_(which), |
| whatLogPart_(whatLogPart), |
| howMany_(numBottomEsps) |
| { |
| } |
| |
| ExchangeForceWildCard::~ExchangeForceWildCard() {} |
| |
| RelExpr * ExchangeForceWildCard::copyTopNode(RelExpr *derivedNode, |
| CollHeap* outHeap) |
| { |
| RelExpr *result; |
| if (derivedNode != NULL) |
| ABORT("No support for classes derived from ExchangeForceWildcard"); |
| |
| result = new(outHeap) ExchangeForceWildCard(NULL, |
| which_, |
| whatLogPart_, |
| howMany_, |
| outHeap); |
| WildCardOp::copyTopNode(result,outHeap); |
| |
| return result; |
| } |
| |
| const NAString ExchangeForceWildCard::getText() const |
| { |
| NAString result("forced",CmpCommon::statementHeap()); |
| |
| if (which_ == FORCED_PA) |
| result += " PA"; |
| else if (which_ == FORCED_PAPA) |
| result += " PAPA"; |
| else if (which_ == FORCED_ESP_EXCHANGE) |
| result += " ESP"; |
| |
| result += " exchange"; |
| |
| return result; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class UDFForceWildCard |
| // ----------------------------------------------------------------------- |
| |
| UDFForceWildCard::UDFForceWildCard(OperatorTypeEnum op, CollHeap *outHeap) : |
| WildCardOp(op, 0, NULL, NULL, outHeap), |
| functionName_(outHeap), |
| actionName_(outHeap) |
| {} |
| |
| UDFForceWildCard::UDFForceWildCard(const NAString& functionName, |
| const NAString& actionName, |
| CollHeap *outHeap) : |
| WildCardOp(REL_FORCE_ANY_SCALAR_UDF, 0, NULL, NULL, outHeap), |
| functionName_(functionName, outHeap), |
| actionName_(actionName, outHeap) |
| {} |
| |
| UDFForceWildCard::~UDFForceWildCard() |
| { |
| } |
| |
| //---------------------------------------------------------- |
| // merge with another UDFForceWildCard object. |
| // return FALSE if a conflict between the options of |
| // the two objects exists. |
| //---------------------------------------------------------- |
| NABoolean UDFForceWildCard::mergeUDFOptions(const UDFForceWildCard &other) |
| { |
| if ((other.functionName_ != "") |
| &&(other.functionName_ != functionName_)) |
| { |
| if (functionName_ == "") |
| { |
| functionName_ = other.functionName_; |
| } |
| else |
| return FALSE; // conflict |
| } |
| if ((other.actionName_ != "") |
| &&(other.actionName_ != actionName_)) |
| { |
| if (actionName_ == "") |
| { |
| actionName_ = other.actionName_; |
| } |
| else |
| return FALSE; // conflict |
| } |
| |
| return TRUE; |
| } |
| |
| RelExpr * UDFForceWildCard::copyTopNode(RelExpr *derivedNode, |
| CollHeap* outHeap) |
| { |
| UDFForceWildCard *temp; |
| if (derivedNode != NULL) |
| ABORT("No support for classes derived from UDFForceWildCard"); |
| |
| temp = new (outHeap) UDFForceWildCard(functionName_,actionName_,outHeap); |
| RelExpr *result = temp; |
| WildCardOp::copyTopNode(result,outHeap); |
| |
| return result; |
| } |
| |
| const NAString UDFForceWildCard::getText() const |
| { |
| NAString result("forced UDF", CmpCommon::statementHeap()); |
| if (functionName_ != "") |
| { |
| result += "("; |
| result += functionName_; |
| if (actionName_ != "") |
| { |
| result += ", "; |
| result += actionName_; |
| } |
| result += ")"; |
| } |
| return result; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for Control* base class |
| // ----------------------------------------------------------------------- |
| |
| ControlAbstractClass::~ControlAbstractClass() {} |
| |
| NABoolean ControlAbstractClass::duplicateMatch(const RelExpr & other) const |
| { |
| if (NOT RelExpr::duplicateMatch(other)) |
| return FALSE; |
| |
| ControlAbstractClass &o = (ControlAbstractClass &) other; |
| |
| // We do NOT need to compare sqlText's here |
| return (token_ == o.token_ AND value_ == o.value_ AND |
| dynamic_ == o.dynamic_ AND reset_ == o.reset_); |
| } |
| |
| RelExpr *ControlAbstractClass::copyTopNode(RelExpr *derivedNode, CollHeap *h) |
| { |
| CMPASSERT(derivedNode); |
| ((ControlAbstractClass *)derivedNode)->reset_ = reset_; |
| return derivedNode; |
| } |
| |
| NABoolean ControlAbstractClass::alterArkcmpEnvNow() const |
| { |
| return NOT (dynamic() || CmpCommon::context()->GetMode() == STMT_DYNAMIC); |
| } |
| |
| StaticOnly ControlAbstractClass::isAStaticOnlyStatement() const |
| { |
| if (dynamic_) return NOT_STATIC_ONLY; |
| return (getOperatorType() == REL_CONTROL_QUERY_DEFAULT) ? |
| STATIC_ONLY_WITH_WORK_FOR_PREPROCESSOR : STATIC_ONLY_VANILLA; |
| } |
| |
| static void removeLeadingSpace(NAString &sqlText) |
| { |
| if (!sqlText.isNull() && isspace((unsigned char)sqlText[size_t(0)])) sqlText.remove(0, 1); // For VS2003 |
| } |
| |
| static NABoolean beginsWithKeyword(NAString &sqlText, const char *kwd, |
| NABoolean thenRemoveIt = TRUE) |
| { |
| // Assumes Prettify has been called, so only one space (at most) btw tokens. |
| // If this is called more than once, the second time in the text might begin |
| // with a delimiting space. |
| removeLeadingSpace(sqlText); |
| |
| size_t len = strlen(kwd) + 1; // +1 for delimiter (space) |
| if (sqlText.length() > len) { |
| NAString tmp(sqlText,CmpCommon::statementHeap()); |
| tmp.remove(len); |
| char c = tmp[--len]; // delimiter |
| if (!isalnum(c) && c != '_') { |
| tmp.remove(len); // remove the delimiter 'c' from tmp |
| if (tmp == kwd) { |
| if (thenRemoveIt) |
| sqlText.remove(0, len); // leave delimiter, now at beginning |
| return TRUE; |
| } |
| } |
| } |
| return FALSE; |
| } |
| |
| // Convert "PROCEDURE xyz () CONTROL..." to just the "CONTROL..." part. |
| // Convert a dynamic stmt's text of "SET SCHEMA 'x.y';" |
| // into the static "CONTROL QUERY DEFAULT SCHEMA 'x.y';" |
| // for its round-trip to executor and back here as a SQLTEXT_STATIC_COMPILE. |
| void ControlAbstractClass::rewriteControlText( |
| NAString &sqlText, CharInfo::CharSet sqlTextCharSet, |
| ControlAbstractClass *ctrl) |
| { |
| PrettifySqlText(sqlText); // trim, upcase where okay, cvt tabs to spaces |
| |
| if (beginsWithKeyword(sqlText, "PROCEDURE")) { |
| size_t rp = sqlText.index(')'); |
| CMPASSERT(rp != NA_NPOS); |
| sqlText.remove(0, ++rp); |
| removeLeadingSpace(sqlText); |
| } |
| |
| // if SHOWSHAPE or SHOWPLAN, remove them from the beginning. |
| // beginsWithKeyword will remove the keyword, if found. |
| if ((beginsWithKeyword(sqlText, "SHOWSHAPE")) || |
| (beginsWithKeyword(sqlText, "SHOWPLAN"))) { |
| removeLeadingSpace(sqlText); |
| } |
| |
| if (ctrl->dynamic()) { |
| if ((beginsWithKeyword(sqlText, "SET")) && |
| (ctrl->getOperatorType() != REL_SET_SESSION_DEFAULT)) |
| sqlText.prepend(getControlTextPrefix(ctrl)); |
| } |
| else { |
| if (beginsWithKeyword(sqlText, "DECLARE")) |
| sqlText.prepend(getControlTextPrefix(ctrl)); |
| } |
| |
| //## We'll have to fix SqlParser.y, I think, if this stmt appears in |
| //## a compound stmt (IF ... THEN SET SCHEMA 'x' ... ELSE SET SCHEMA 'y' ...) |
| if (ctrl->getOperatorType() != REL_SET_SESSION_DEFAULT) |
| { |
| if ((NOT beginsWithKeyword(sqlText, "CONTROL", FALSE)) && |
| (NOT beginsWithKeyword(sqlText, "CQD", FALSE))) |
| CMPASSERT(0); |
| } |
| } |
| |
| NAString ControlAbstractClass::getControlTextPrefix( |
| const ControlAbstractClass *ctrl) |
| { |
| switch (ctrl->getOperatorType()) { |
| case REL_CONTROL_QUERY_SHAPE: return "CONTROL QUERY SHAPE"; |
| case REL_CONTROL_QUERY_DEFAULT: return "CONTROL QUERY DEFAULT"; |
| case REL_CONTROL_TABLE: return "CONTROL TABLE"; |
| case REL_CONTROL_SESSION: return "CONTROL SESSION"; |
| case REL_SET_SESSION_DEFAULT: return "SET SESSION DEFAULT"; |
| default: return "CONTROL ??"; |
| } |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class ControlQueryShape |
| // ----------------------------------------------------------------------- |
| |
| RelExpr * ControlQueryShape::copyTopNode(RelExpr *derivedNode, |
| CollHeap *h) |
| { |
| ControlQueryShape *result; |
| |
| if (derivedNode == NULL) |
| result = new (h) ControlQueryShape(NULL, getSqlText(), getSqlTextCharSet(), holdShape_, |
| dynamic_, ignoreExchange_, |
| ignoreSort_, h); |
| else |
| result = (ControlQueryShape *) derivedNode; |
| |
| return ControlAbstractClass::copyTopNode(result,h); |
| } |
| |
| const NAString ControlQueryShape::getText() const |
| { |
| NAString result(getControlTextPrefix(this)); |
| |
| if (ignoreExchange_) |
| { |
| if (ignoreSort_) |
| result += " WITHOUT ENFORCERS"; |
| else |
| result += " WITHOUT EXCHANGE"; |
| } |
| else if (ignoreSort_) |
| result += " WITHOUT SORT"; |
| |
| return result; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class ControlQueryDefault |
| // ----------------------------------------------------------------------- |
| |
| ControlQueryDefault::ControlQueryDefault( |
| const NAString &sqlText, CharInfo::CharSet sqlTextCharSet, |
| const NAString &token, |
| const NAString &value, |
| NABoolean dyn, |
| Lng32 holdOrRestoreCQD, |
| CollHeap *h, |
| Int32 reset): |
| ControlAbstractClass(REL_CONTROL_QUERY_DEFAULT, sqlText, sqlTextCharSet, token, value, |
| dyn, h, reset), |
| holdOrRestoreCQD_(holdOrRestoreCQD), |
| attrEnum_(__INVALID_DEFAULT_ATTRIBUTE) |
| {} |
| |
| RelExpr * ControlQueryDefault::copyTopNode(RelExpr *derivedNode, |
| CollHeap *h) |
| { |
| RelExpr *result; |
| |
| if (derivedNode == NULL) { |
| result = new (h) ControlQueryDefault(sqlText_, sqlTextCharSet_, token_, value_, dynamic_, holdOrRestoreCQD_, h); |
| ((ControlQueryDefault *)result)->attrEnum_ = attrEnum_; |
| } |
| else |
| result = derivedNode; |
| |
| return ControlAbstractClass::copyTopNode(result,h); |
| } |
| |
| const NAString ControlQueryDefault::getText() const |
| { |
| return getControlTextPrefix(this); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class ControlTable |
| // ----------------------------------------------------------------------- |
| |
| ControlTable::ControlTable( |
| CorrName *tableName, |
| const NAString &sqlText, CharInfo::CharSet sqlTextCharSet, |
| const NAString &token, |
| const NAString &value, |
| NABoolean dyn, |
| CollHeap *h): |
| ControlAbstractClass(REL_CONTROL_TABLE, sqlText, sqlTextCharSet, token, value, dyn, h), |
| tableName_(tableName) |
| {} |
| |
| RelExpr * ControlTable::copyTopNode(RelExpr *derivedNode, |
| CollHeap *h) |
| { |
| RelExpr *result; |
| |
| if (derivedNode == NULL) |
| result = new (h) ControlTable(tableName_, sqlText_, sqlTextCharSet_, token_, value_, dynamic_, h); |
| else |
| result = derivedNode; |
| |
| return ControlAbstractClass::copyTopNode(result,h); |
| } |
| |
| const NAString ControlTable::getText() const |
| { |
| return getControlTextPrefix(this); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class ControlSession |
| // ----------------------------------------------------------------------- |
| |
| ControlSession::ControlSession( |
| const NAString &sqlText, CharInfo::CharSet sqlTextCharSet, |
| const NAString &token, |
| const NAString &value, |
| NABoolean dyn, |
| CollHeap *h): |
| ControlAbstractClass(REL_CONTROL_SESSION, sqlText, sqlTextCharSet, token, value, dyn, h) |
| {} |
| |
| RelExpr * ControlSession::copyTopNode(RelExpr *derivedNode, |
| CollHeap *h) |
| { |
| RelExpr *result; |
| |
| if (derivedNode == NULL) |
| result = new (h) ControlSession(sqlText_, sqlTextCharSet_, token_, value_, dynamic_, h); |
| else |
| result = derivedNode; |
| |
| return ControlAbstractClass::copyTopNode(result,h); |
| } |
| |
| const NAString ControlSession::getText() const |
| { |
| return getControlTextPrefix(this); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class SetSessionDefault |
| // ----------------------------------------------------------------------- |
| |
| SetSessionDefault::SetSessionDefault( |
| const NAString &sqlText, CharInfo::CharSet sqlTextCharSet, |
| const NAString &token, |
| const NAString &value, |
| CollHeap *h): |
| ControlAbstractClass(REL_SET_SESSION_DEFAULT, sqlText, sqlTextCharSet, token, value, TRUE, h) |
| {} |
| |
| RelExpr * SetSessionDefault::copyTopNode(RelExpr *derivedNode, |
| CollHeap *h) |
| { |
| RelExpr *result; |
| |
| if (derivedNode == NULL) |
| result = new (h) SetSessionDefault(sqlText_, sqlTextCharSet_, token_, value_, h); |
| else |
| result = derivedNode; |
| |
| return ControlAbstractClass::copyTopNode(result,h); |
| } |
| |
| const NAString SetSessionDefault::getText() const |
| { |
| return getControlTextPrefix(this); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class OSIMControl |
| // ----------------------------------------------------------------------- |
| OSIMControl::OSIMControl(OptimizerSimulator::osimMode mode, |
| NAString & localDir, |
| NABoolean force, |
| CollHeap * oHeap) |
| //the real work is done in OSIMControl::bindNode() to control OSIM. |
| //We set operator type to REL_SET_SESSION_DEFAULT, |
| //so as not to define dummy OSIMControl::codeGen() and OSIMControl::work(), |
| //which will do nothing there, |
| : ControlAbstractClass(REL_SET_SESSION_DEFAULT, NAString("DUMMYSQLTEXT", oHeap), |
| CharInfo::ISO88591, NAString("OSIM", oHeap), |
| NAString("DUMMYVALUE", oHeap), TRUE, oHeap) |
| , targetMode_(mode), osimLocalDir_(localDir, oHeap), forceLoad_(force) |
| {} |
| |
| |
| RelExpr * OSIMControl::copyTopNode(RelExpr *derivedNode, CollHeap *h ) |
| { |
| RelExpr *result; |
| |
| if (derivedNode == NULL) |
| result = new (h) OSIMControl(targetMode_, osimLocalDir_, forceLoad_, h); |
| else |
| result = derivedNode; |
| |
| return ControlAbstractClass::copyTopNode(result,h); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class Sort |
| // ----------------------------------------------------------------------- |
| |
| Sort::~Sort() |
| { |
| } |
| |
| Int32 Sort::getArity() const { return 1;} |
| |
| HashValue Sort::topHash() |
| { |
| HashValue result = RelExpr::topHash(); |
| |
| result ^= sortKey_; |
| result ^= arrangedCols_; |
| |
| return result; |
| } |
| |
| NABoolean Sort::duplicateMatch(const RelExpr & other) const |
| { |
| if (NOT RelExpr::duplicateMatch(other)) |
| return FALSE; |
| |
| Sort &o = (Sort &) other; |
| |
| if (NOT (sortKey_ == o.sortKey_) OR |
| NOT(arrangedCols_ == o.arrangedCols_)) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| RelExpr * Sort::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| Sort *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) Sort(NULL, sortKey_, outHeap); |
| else |
| result = (Sort *) derivedNode; |
| |
| // copy arranged columns |
| result->arrangedCols_ = arrangedCols_; |
| |
| return RelExpr::copyTopNode(result, outHeap); |
| } |
| |
| NABoolean Sort::isLogical() const { return FALSE; } |
| |
| NABoolean Sort::isPhysical() const { return TRUE; } |
| |
| const NAString Sort::getText() const |
| { |
| return "sort"; |
| } |
| |
| PlanPriority Sort::computeOperatorPriority |
| (const Context* context, |
| PlanWorkSpace *pws, |
| Lng32 planNumber) |
| { |
| const PhysicalProperty* spp = context->getPlan()->getPhysicalProperty(); |
| Lng32 degreeOfParallelism = spp->getCountOfPartitions(); |
| double val = 1; |
| if (degreeOfParallelism <= 1) |
| { |
| // serial plans are risky. exact an insurance premium from serial plans. |
| val = CURRSTMT_OPTDEFAULTS->riskPremiumSerial(); |
| } |
| CostScalar premium(val); |
| PlanPriority result(0, 0, premium); |
| |
| if (QueryAnalysis::Instance() AND |
| QueryAnalysis::Instance()->optimizeForFirstNRows()) |
| result.incrementLevels(SORT_FIRST_N_PRIORITY,0); |
| |
| // For the option of Max Degree of Parallelism we can either use the |
| // value set in comp_int_9 (if positive) or we use the number of CPUs |
| // if the CQD is set to -1, or feature is disabled if CQD is 0 (default). |
| Lng32 maxDegree = ActiveSchemaDB()->getDefaults().getAsLong(COMP_INT_9); |
| if (CURRSTMT_OPTDEFAULTS->maxParallelismIsFeasible() OR ( maxDegree == -1) ) |
| { |
| // if CQD is set to -1 this mean use the number of CPUs |
| maxDegree = spp->getCurrentCountOfCPUs(); |
| } |
| if (maxDegree > 1) // CQD set to 0 means feature is OFF |
| { |
| if (degreeOfParallelism < maxDegree) |
| result.incrementLevels(0,-10); // need to replace with constant |
| } |
| |
| return result; |
| } |
| |
| void Sort::addLocalExpr(LIST(ExprNode *) &xlist, |
| LIST(NAString) &llist) const |
| { |
| if (sortKey_.entries() > 0) |
| { |
| xlist.insert(sortKey_.rebuildExprTree(ITM_ITEM_LIST)); |
| llist.insert("sort_key"); |
| } |
| |
| if (PartialSortKeyFromChild_.entries() > 0) |
| { |
| xlist.insert(PartialSortKeyFromChild_.rebuildExprTree(ITM_ITEM_LIST)); |
| llist.insert("PartialSort_FromChild"); |
| } |
| |
| // if (NOT arrangedCols_.isEmpty()) |
| // { |
| // xlist.insert(arrangedCols_.rebuildExprTree(ITM_ITEM_LIST)); |
| // llist.insert("arranged_cols"); |
| // } |
| |
| RelExpr::addLocalExpr(xlist,llist); |
| } |
| |
| void Sort::needSortedNRows(NABoolean val) |
| { |
| sortNRows_ = val; |
| // Sort changes a GET_N to GET_ALL, so it does not propagate a |
| // Get_N request. It can simply act on one. |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class SortFromTop |
| // ----------------------------------------------------------------------- |
| RelExpr * SortFromTop::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| SortFromTop *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) SortFromTop(NULL, outHeap); |
| else |
| result = (SortFromTop *) derivedNode; |
| |
| result->getSortRecExpr() = getSortRecExpr(); |
| |
| return Sort::copyTopNode(result, outHeap); |
| } |
| |
| const NAString SortFromTop::getText() const |
| { |
| return "sort_from_top"; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class Exchange |
| // ----------------------------------------------------------------------- |
| |
| Exchange::~Exchange() |
| { |
| } |
| |
| Int32 Exchange::getArity() const { return 1;} |
| |
| RelExpr * Exchange::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| Exchange *result; |
| |
| if (derivedNode == NULL) |
| { |
| result = new (outHeap) Exchange(NULL, outHeap); |
| } |
| else |
| result = (Exchange *) derivedNode; |
| |
| result->upMessageBufferLength_=upMessageBufferLength_; |
| result->downMessageBufferLength_=downMessageBufferLength_; |
| result->hash2RepartitioningWithSameKey_ = hash2RepartitioningWithSameKey_; |
| if (halloweenSortIsMyChild_) |
| result->markHalloweenSortIsMyChild(); |
| |
| return RelExpr::copyTopNode(result, outHeap); |
| } |
| |
| NABoolean Exchange::isLogical() const { return FALSE; } |
| |
| NABoolean Exchange::isPhysical() const { return TRUE; } |
| |
| NABoolean Exchange::isAPA() const |
| { |
| if (NOT isDP2Exchange() OR bottomPartFunc_ == NULL) |
| return FALSE; |
| |
| const LogPhysPartitioningFunction *lpf = bottomPartFunc_-> |
| castToLogPhysPartitioningFunction(); |
| |
| return (lpf == NULL OR NOT lpf->getUsePapa()); |
| } |
| |
| NABoolean Exchange::isAPAPA() const |
| { |
| return (isDP2Exchange() AND bottomPartFunc_ AND NOT isAPA()); |
| } |
| |
| const NAString Exchange::getText() const |
| { |
| NAString result("exchange",CmpCommon::statementHeap()); |
| |
| if (isAPA()) |
| result = "pa_exchange"; |
| else if (isAPAPA()) |
| result = "split_top"; |
| else if (isAnESPAccess()) |
| result = "esp_access"; |
| else if (isEspExchange()) |
| result = "esp_exchange"; |
| |
| const PartitioningFunction *topPartFunc = |
| getTopPartitioningFunction(); |
| const PartitioningFunction *bottomPartFunc = |
| getBottomPartitioningFunction(); |
| |
| Lng32 topNumParts = ANY_NUMBER_OF_PARTITIONS; |
| Lng32 bottomNumParts = ANY_NUMBER_OF_PARTITIONS; |
| |
| if (topPartFunc) |
| topNumParts = topPartFunc->getCountOfPartitions(); |
| if (bottomPartFunc) |
| bottomNumParts = bottomPartFunc->getCountOfPartitions(); |
| |
| if (topNumParts != ANY_NUMBER_OF_PARTITIONS OR |
| bottomNumParts != ANY_NUMBER_OF_PARTITIONS) |
| { |
| char str[TEXT_DISPLAY_LENGTH]; |
| sprintf(str," %d:%d",topNumParts,bottomNumParts); |
| result += str; |
| } |
| |
| if (bottomPartFunc AND isDP2Exchange()) |
| { |
| const LogPhysPartitioningFunction *lpf = |
| bottomPartFunc->castToLogPhysPartitioningFunction(); |
| if (lpf) |
| { |
| if (lpf->getUsePapa()) |
| { |
| char str[TEXT_DISPLAY_LENGTH]; |
| sprintf(str," with %d PA(s)",lpf->getNumOfClients()); |
| result += str; |
| } |
| switch (lpf->getLogPartType()) |
| { |
| case LogPhysPartitioningFunction::LOGICAL_SUBPARTITIONING: |
| result += ", log. subpart."; |
| break; |
| case LogPhysPartitioningFunction::HORIZONTAL_PARTITION_SLICING: |
| result += ", slicing"; |
| break; |
| case LogPhysPartitioningFunction::PA_GROUPED_REPARTITIONING: |
| result += ", repartitioned"; |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| return result; |
| } |
| |
| PlanPriority Exchange::computeOperatorPriority |
| (const Context* context, |
| PlanWorkSpace *pws, |
| Lng32 planNumber) |
| { |
| PlanPriority result; |
| |
| OperatorTypeEnum parOperType = context->getCurrentAncestor()->getPlan()-> |
| getPhysicalExpr()->getOperatorType(); |
| |
| Lng32 cqdValue = ActiveSchemaDB()->getDefaults().getAsLong(COMP_INT_9); |
| if ((cqdValue == -2) AND |
| ((parOperType == REL_ROOT) OR |
| (parOperType == REL_FIRST_N))) |
| result.incrementLevels(10,0); |
| |
| return result; |
| } |
| |
| void Exchange::addLocalExpr(LIST(ExprNode *) &xlist, |
| LIST(NAString) &llist) const |
| { |
| const PartitioningFunction *topPartFunc = |
| getTopPartitioningFunction(); |
| |
| if (topPartFunc AND topPartFunc->getCountOfPartitions() > 1 AND |
| topPartFunc->getPartitioningExpression()) |
| { |
| xlist.insert(topPartFunc->getPartitioningExpression()); |
| llist.insert("partitioning_expression"); |
| } |
| |
| if (NOT sortKeyForMyOutput_.isEmpty()) |
| { |
| xlist.insert(sortKeyForMyOutput_.rebuildExprTree(ITM_ITEM_LIST)); |
| llist.insert("merged_order"); |
| } |
| |
| RelExpr::addLocalExpr(xlist,llist); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class Join |
| // ----------------------------------------------------------------------- |
| Int32 Join::getArity() const { return 2;} |
| |
| // helper function used in |
| // HashJoinRule::topMatch() and |
| // JoinToTSJRule::topMatch() |
| // to make sure that only one of them is turned off. |
| // Otherwise, error 2235 "pass one skipped, but cannot produce a plan |
| // in pass two, originated from file ../optimizer/opt.cpp" |
| NABoolean Join::allowHashJoin() |
| { |
| // HJ is only implementation for full outer join |
| NABoolean fullOuterJoin = |
| (CmpCommon::getDefault(COMP_BOOL_196) != DF_ON) AND isFullOuterJoin(); |
| |
| if (fullOuterJoin) return TRUE; |
| |
| // HJ is only join implementation allowed when avoiding halloween update |
| if (avoidHalloweenR2()) return TRUE; |
| |
| // favor NJ when only one row from outer for sure |
| Cardinality minrows, outerLimit; |
| GroupAttributes *oGrpAttr = child(0).getGroupAttr(); |
| NABoolean hasConstraint = oGrpAttr->hasCardConstraint(minrows, outerLimit); |
| // outerLimit was set to INFINITE_CARDINALITY if no constraint |
| CostScalar outerRows(outerLimit); |
| // if it has no constraint and outer is 1 table |
| if (!hasConstraint && oGrpAttr->getNumBaseTables() == 1) { |
| // use max cardinality estimate |
| outerRows = oGrpAttr->getResultMaxCardinalityForEmptyInput(); |
| } |
| NABoolean favorNJ = |
| CURRSTMT_OPTDEFAULTS->isNestedJoinConsidered() AND |
| (outerRows <= 1) AND |
| GlobalRuleSet->getCurrentPassNumber() > |
| GlobalRuleSet->getFirstPassNumber(); |
| |
| if (favorNJ) return FALSE; // disallow HJ |
| |
| return TRUE; // allow HJ |
| } |
| |
| OperatorTypeEnum Join::getTSJJoinOpType() |
| { |
| switch (getOperatorType()) |
| { |
| case REL_JOIN: |
| case REL_ROUTINE_JOIN: |
| return REL_TSJ; |
| case REL_LEFT_JOIN: |
| return REL_LEFT_TSJ; |
| case REL_SEMIJOIN: |
| return REL_SEMITSJ; |
| case REL_ANTI_SEMIJOIN: |
| return REL_ANTI_SEMITSJ; |
| default: |
| ABORT("Unsupported join type in Join::getTSJJoinOpType()"); |
| return REL_NESTED_JOIN; // Return makes MSVC happy. |
| } // switch |
| } // Join::getNestedJoinOpType() |
| |
| |
| OperatorTypeEnum Join::getNestedJoinOpType() |
| { |
| switch (getOperatorType()) |
| { |
| case REL_JOIN: |
| case REL_ROUTINE_JOIN: |
| case REL_TSJ: |
| return REL_NESTED_JOIN; |
| case REL_LEFT_JOIN: |
| case REL_LEFT_TSJ: |
| return REL_LEFT_NESTED_JOIN; |
| case REL_SEMIJOIN: |
| case REL_SEMITSJ: |
| return REL_NESTED_SEMIJOIN; |
| case REL_ANTI_SEMIJOIN: |
| case REL_ANTI_SEMITSJ: |
| return REL_NESTED_ANTI_SEMIJOIN; |
| default: |
| ABORT("Unsupported join type in Join::getNestedJoinOpType()"); |
| return REL_NESTED_JOIN; // Return makes MSVC happy. |
| } // switch |
| } // Join::getNestedJoinOpType() |
| |
| OperatorTypeEnum Join::getHashJoinOpType(NABoolean isNoOverflow) |
| { |
| if(isNoOverflow) |
| { |
| switch (getOperatorType()) |
| { |
| case REL_JOIN: |
| return REL_ORDERED_HASH_JOIN; |
| case REL_LEFT_JOIN: |
| return REL_LEFT_ORDERED_HASH_JOIN; |
| case REL_SEMIJOIN: |
| return REL_ORDERED_HASH_SEMIJOIN; |
| case REL_ANTI_SEMIJOIN: |
| return REL_ORDERED_HASH_ANTI_SEMIJOIN; |
| default: |
| ABORT("Unsupported join type in Join::getHashJoinOpType()"); |
| return REL_ORDERED_HASH_JOIN; // return makes MSVC happy. |
| } // switch |
| } |
| else |
| { |
| |
| switch (getOperatorType()) |
| { |
| case REL_JOIN: |
| return REL_HYBRID_HASH_JOIN; |
| case REL_LEFT_JOIN: |
| return REL_LEFT_HYBRID_HASH_JOIN; |
| case REL_FULL_JOIN: |
| return REL_FULL_HYBRID_HASH_JOIN; |
| case REL_SEMIJOIN: |
| return REL_HYBRID_HASH_SEMIJOIN; |
| case REL_ANTI_SEMIJOIN: |
| return REL_HYBRID_HASH_ANTI_SEMIJOIN; |
| default: |
| ABORT("Unsupported join type in Join::getHashJoinOpType()"); |
| return REL_HYBRID_HASH_JOIN; // return makes MSVC happy. |
| } // switch |
| }//else |
| |
| } // Join::getHashJoinOpType() |
| |
| NABoolean Join::isCrossProduct() const |
| { |
| // only for our beloved inner non semi joins (for now) |
| if (NOT isInnerNonSemiJoin()) return FALSE; |
| |
| ValueIdSet VEGEqPreds; |
| ValueIdSet newJoinPreds = getSelectionPred(); |
| newJoinPreds += getJoinPred(); |
| |
| // ----------------------------------------------------------------- |
| // Find all the VEGPreds in the newJoinPreds. |
| // Remove all the ones that are not true join predicates |
| // (i.e. they are covered by the inputs, or one of the |
| // children cannot produce it) |
| // ----------------------------------------------------------------- |
| newJoinPreds.lookForVEGPredicates(VEGEqPreds); |
| |
| // remove those VEGPredicates that are covered by the input values |
| VEGEqPreds.removeCoveredExprs(getGroupAttr()-> |
| getCharacteristicInputs()); |
| VEGEqPreds.removeUnCoveredExprs(child(0).getGroupAttr()-> |
| getCharacteristicOutputs()); |
| VEGEqPreds.removeUnCoveredExprs(child(1).getGroupAttr()-> |
| getCharacteristicOutputs()); |
| if (VEGEqPreds.isEmpty()) |
| return TRUE; // is a cross product |
| else |
| return FALSE; |
| } |
| |
| NABoolean Join::isOuterJoin() const |
| { |
| if(isLeftJoin() || isRightJoin() || isFullOuterJoin()) |
| return TRUE; |
| |
| return FALSE; |
| } |
| |
| NABoolean RelExpr::isAnyJoin() const |
| { |
| switch (getOperatorType()) |
| { |
| case REL_JOIN: |
| case REL_ROUTINE_JOIN: |
| case REL_LEFT_JOIN: |
| case REL_ANY_JOIN: |
| case REL_ANY_TSJ: |
| case REL_ANY_INNER_JOIN: |
| case REL_ANY_NON_TS_INNER_JOIN: |
| case REL_ANY_NON_TSJ_JOIN: |
| case REL_ANY_LEFT_JOIN: |
| case REL_ANY_LEFT_TSJ: |
| case REL_ANY_NESTED_JOIN: |
| case REL_ANY_HASH_JOIN: |
| case REL_ANY_MERGE_JOIN: |
| case REL_FORCE_JOIN: |
| case REL_FORCE_NESTED_JOIN: |
| case REL_FORCE_HASH_JOIN: |
| case REL_FORCE_ORDERED_HASH_JOIN: |
| case REL_FORCE_HYBRID_HASH_JOIN: |
| case REL_FORCE_MERGE_JOIN: |
| case REL_INTERSECT: |
| case REL_EXCEPT: |
| return TRUE; |
| |
| default: |
| return FALSE; |
| } |
| } |
| |
| NABoolean Join::isInnerNonSemiJoinWithNoPredicates() const |
| { |
| if (isInnerNonSemiJoin() AND |
| getSelectionPred().isEmpty() AND |
| getJoinPred().isEmpty()) |
| return TRUE; |
| else |
| return FALSE; |
| } |
| |
| |
| NABoolean Join:: isInnerJoin() const |
| { |
| return getOperator().match(REL_ANY_INNER_JOIN); |
| } |
| |
| NABoolean Join::isInnerNonSemiJoin() const |
| { |
| return getOperator().match(REL_ANY_INNER_JOIN) AND |
| NOT getOperator().match(REL_ANY_SEMIJOIN); |
| } |
| |
| NABoolean Join::isInnerNonSemiNonTSJJoin() const |
| { |
| return (getOperator().match(REL_ANY_INNER_JOIN) AND |
| NOT getOperator().match(REL_ANY_SEMIJOIN)) AND |
| NOT getOperator().match(REL_ANY_TSJ); |
| } |
| NABoolean Join::isLeftJoin() const |
| { |
| return getOperator().match(REL_ANY_LEFT_JOIN); |
| } |
| |
| NABoolean Join::isRightJoin() const |
| { |
| return getOperator().match(REL_ANY_RIGHT_JOIN); |
| } |
| |
| NABoolean Join::isFullOuterJoin() const |
| { |
| return (getOperator().match(REL_ANY_FULL_JOIN)); |
| } |
| |
| NABoolean Join::isSemiJoin() const |
| { |
| return getOperator().match(REL_ANY_SEMIJOIN); |
| } |
| |
| NABoolean Join::isAntiSemiJoin() const |
| { |
| return getOperator().match(REL_ANY_ANTI_SEMIJOIN); |
| } |
| |
| NABoolean Join::isTSJ() const |
| { |
| return getOperator().match(REL_ANY_TSJ); |
| } |
| |
| NABoolean Join::isRoutineJoin() const |
| { |
| return (getOperator() == REL_ROUTINE_JOIN); |
| } |
| |
| NABoolean Join::isNonRoutineTSJ() const |
| { |
| return ( getOperator().match(REL_ANY_TSJ) && |
| ( getOperator() != REL_ROUTINE_JOIN)); |
| } |
| |
| NABoolean Join::isNestedJoin() const |
| { |
| return getOperator().match(REL_ANY_NESTED_JOIN); |
| } |
| |
| NABoolean Join::isHashJoin() const |
| { |
| return getOperator().match(REL_ANY_HASH_JOIN); |
| } |
| |
| OperatorTypeEnum Join::getBaseHashType() const |
| { |
| switch (getOperatorType()) |
| { |
| case REL_HASH_JOIN: |
| case REL_LEFT_HASH_JOIN: |
| case REL_HASH_SEMIJOIN: |
| case REL_HASH_ANTI_SEMIJOIN: |
| case REL_ANY_HASH_JOIN: |
| return REL_HASH_JOIN; |
| |
| case REL_HYBRID_HASH_JOIN: |
| case REL_LEFT_HYBRID_HASH_JOIN: |
| case REL_FULL_HYBRID_HASH_JOIN: |
| case REL_HYBRID_HASH_SEMIJOIN: |
| case REL_HYBRID_HASH_ANTI_SEMIJOIN: |
| return REL_HYBRID_HASH_JOIN; |
| |
| case REL_ORDERED_HASH_JOIN: |
| case REL_LEFT_ORDERED_HASH_JOIN: |
| case REL_ORDERED_HASH_SEMIJOIN: |
| case REL_ORDERED_HASH_ANTI_SEMIJOIN: |
| return REL_ORDERED_HASH_JOIN; |
| |
| default: |
| return INVALID_OPERATOR_TYPE; |
| } // switch |
| } // Join::getBaseHashType |
| |
| NABoolean Join::isMergeJoin() const |
| { |
| return getOperator().match(REL_ANY_MERGE_JOIN); |
| } |
| |
| OperatorTypeEnum Join::getMergeJoinOpType() |
| { |
| switch (getOperatorType()) |
| { |
| case REL_JOIN: |
| return REL_MERGE_JOIN; |
| case REL_LEFT_JOIN: |
| return REL_LEFT_MERGE_JOIN; |
| case REL_SEMIJOIN: |
| return REL_MERGE_SEMIJOIN; |
| case REL_ANTI_SEMIJOIN: |
| return REL_MERGE_ANTI_SEMIJOIN; |
| default: |
| ABORT("Unsupported join type in Join::getMergeJoinOpType()"); |
| return REL_MERGE_JOIN; // return makes MSVC happy. |
| } // switch |
| } // Join::getMergeJoinOpType() |
| |
| void Join::pushdownCoveredExpr(const ValueIdSet & outputExpr, |
| const ValueIdSet & newExternalInputs, |
| ValueIdSet & predicatesOnParent, |
| const ValueIdSet * setOfValuesReqdByParent, |
| Lng32 childIndex |
| ) |
| { |
| #ifdef NDEBUG |
| NAString pushdownDebugStr(CmpCommon::statementHeap()); |
| #define PUSHDOWN_DEBUG_SAVE(str) |
| #else |
| Int32 PUSHDOWN_DEBUG = !!getenv("PUSHDOWN_DEBUG"); |
| NAString pushdownDebugStr, pushdownDebugTmp; |
| #define PUSHDOWN_DEBUG_SAVE(str) \ |
| { if (PUSHDOWN_DEBUG) \ |
| { pushdownDebugTmp = ""; \ |
| predicatesOnParent.unparse(pushdownDebugTmp); \ |
| pushdownDebugStr += NAString("\n") + str + ": " + pushdownDebugTmp; \ |
| if (PUSHDOWN_DEBUG == 99) cerr << pushdownDebugStr << endl; \ |
| } \ |
| } |
| PUSHDOWN_DEBUG_SAVE("J1"); |
| #endif |
| |
| // ---------------------------------------------------------------------- |
| // Note: Normally, predicatesOnParent is the set of predicates to be |
| // considered for pushing down. For most of the other nodes, this set |
| // is usually just the set (or maybe a subset) of selection predicates |
| // the node has. However, a Join node distinguishes between selection |
| // predicates (which are specified in the WHERE clause of a SQL query) |
| // and join predicates (which are specified in the ON clause). The two |
| // types of predicates have different criteria (as we will see later) |
| // to satisfy in order to be pushed down. The predicatesOnParent supplied |
| // are treated as selection predicates for the purpose of consideration |
| // for pushing down. Note also that this procedure also considers the |
| // pushing down of join predicates (as in joinPred_ of Join). |
| // ---------------------------------------------------------------------- |
| |
| // This method only supports pushing predicates down to both children, |
| // but not only to one specified. |
| // |
| CMPASSERT(childIndex < 0); |
| |
| NABoolean isATSJFlag = isTSJ(); |
| ValueIdSet exprOnParent ; |
| if (setOfValuesReqdByParent) |
| exprOnParent = *setOfValuesReqdByParent; |
| |
| if (isFullOuterJoin()) |
| { |
| // For Full Outer Join, we cannot push down the selctionPred() |
| // or the joinPred() to either child0 or child1. |
| // Note that for FOJ, predicates are not pulled up. |
| |
| // --------------------------------------------------------------------- |
| // STEP 1: We cannot pushdown join predicates to either child, |
| // so compute values required to evaluate the joinPred() |
| // here at the parent (the Join) and add |
| // it to exprOnParent. |
| // |
| // --------------------------------------------------------------------- |
| computeValuesReqdForPredicates(joinPred(), |
| exprOnParent, TRUE); |
| |
| // --------------------------------------------------------------------- |
| // STEP 2: We cannot pushdown selectionPred() to either child, |
| // so compute values required to evaluate the selectionPred() |
| // here at the parent (the Join) and add |
| // it to exprOnParent. |
| // |
| // --------------------------------------------------------------------- |
| computeValuesReqdForPredicates(selectionPred(), |
| exprOnParent, TRUE); |
| |
| // --------------------------------------------------------------------- |
| // STEP 3: Calling pushdownCoveredExpr on an empty set, so that the child |
| // inputs and outputs are set properly. |
| // --------------------------------------------------------------------- |
| ValueIdSet emptySet; |
| |
| RelExpr::pushdownCoveredExpr(outputExpr, |
| newExternalInputs, |
| emptySet, |
| &exprOnParent, |
| 0); |
| |
| // --------------------------------------------------------------------- |
| // STEP 4: Calling pushdownCoveredExpr on an empty set, so that the child |
| // inputs and outputs are set properly. |
| // --------------------------------------------------------------------- |
| RelExpr::pushdownCoveredExpr(outputExpr, |
| newExternalInputs, |
| emptySet, |
| &exprOnParent, |
| 1); |
| |
| } // if (isFullOuterJoin()) |
| // ----------------------------------------------------------------------- |
| // It might not be obvious, but it turns out that pushing down of |
| // predicates in a Left Join is quite similar to that in an Anti-Semi |
| // Join. One striking similarity is that the join predicates cannot be |
| // pushed down to the left child. In both cases, pushing down join preds |
| // filters out rows from the left child which shouldn't be filtered out. |
| // In the case of Left Join, those rows should be null-instantiated while |
| // in the case of Anti-Semi Join, they are exactly the set of rows which |
| // we *should* return. (An Anti-Semi Join returns rows from the left |
| // which do *not* join with rows from the right). |
| // ----------------------------------------------------------------------- |
| else if ((isLeftJoin() || isAntiSemiJoin()) && (!isFullOuterJoin())) |
| { |
| // --------------------------------------------------------------------- |
| // STEP 1: Try to push down the given predicatesOnParent to first child. |
| // --------------------------------------------------------------------- |
| |
| // --------------------------------------------------------------------- |
| // STEP 1A: Gather all values the left child must still produce even if |
| // predicates are pushed down. |
| // |
| // Selection predicates can only be pushed to the first child of an |
| // outer join. Join predicates can only be pushed to the second. Make |
| // sure the first child produces what we need for the join predicates. |
| // --------------------------------------------------------------------- |
| computeValuesReqdForPredicates(joinPred(), |
| exprOnParent); |
| |
| // --------------------------------------------------------------------- |
| // If this is a TSJ, the left child should also produce those values |
| // that the parent needs to give as inputs to the second child. |
| // --------------------------------------------------------------------- |
| if (isATSJFlag) |
| exprOnParent += child(1).getGroupAttr()->getCharacteristicInputs(); |
| |
| // --------------------------------------------------------------------- |
| // This seems to be the only difference between the case of a Left Join |
| // and the case of a Anti-Semi Join. For an Anti-Semi Join, selection |
| // predicates should be in terms of columns on the left child only (by |
| // definition). Therefore, we don't have to worry about retaining things |
| // like VEGPred(VEG{T1.a,T2.a}). |
| // --------------------------------------------------------------------- |
| ValueIdSet VEGEqPreds1; |
| if (isLeftJoin()) |
| { |
| // --------------------------------------------------------------------- |
| // Find all the VEGPreds in predicatesOnParent. VEGPred(VEG{T1.a,T2.a}) |
| // will be pushed down to Scan T1 even if T2.a is not available there. |
| // Therefore, we still need to keep a copy of this type of predicates |
| // here at this Join node where both T1.a and T2.a will be available. |
| // --------------------------------------------------------------------- |
| predicatesOnParent.lookForVEGPredicates(VEGEqPreds1); |
| |
| // --------------------------------------------------------------------- |
| // Remove those VEGPreds that are covered by the input values, since |
| // VEGPred(VEG{T1.a,3}) needn't be retained at this Join node after it's |
| // pushed down to Scan T1. |
| // --------------------------------------------------------------------- |
| VEGEqPreds1.removeCoveredExprs(newExternalInputs); |
| |
| // --------------------------------------------------------------------- |
| // Remove those VEGPreds which are not covered at second child. For |
| // example VEGPred(VEG{T1.a,T2.a}) in JOIN2 of ((T1 JOIN1 T2) JOIN2 T3) |
| // is not covered at the second child. The predicate should be pushed |
| // down to the first child without being retained at JOIN2. Note that |
| // since predicatesOnParent are selection predicates evaluated after |
| // a Left Join, they are in terms of the null-instantiated outputs from |
| // the Join rather than direct outputs from the second child. |
| // --------------------------------------------------------------------- |
| VEGEqPreds1.removeUnCoveredExprs(nullInstantiatedOutput()); |
| PUSHDOWN_DEBUG_SAVE("J2"); |
| |
| // --------------------------------------------------------------------- |
| // ??? First child not needed ??? since we are trying to push down to |
| // child0, if it's uncovered, it wouldn't be pushed down anyway. |
| //VEGEqPreds1.removeUnCoveredExprs( |
| // child(0).getGroupAttr()->getCharacteristicOutputs()); |
| // --------------------------------------------------------------------- |
| |
| // --------------------------------------------------------------------- |
| // Since these VEGEqPreds1 will be added back to predicatesOnParent |
| // after the attempt to push down to first child, make sure the first |
| // child produces the required values to evaluate them. |
| // --------------------------------------------------------------------- |
| computeValuesReqdForPredicates(VEGEqPreds1, |
| exprOnParent); |
| } // endif (isLeftJoin()) |
| |
| // --------------------------------------------------------------------- |
| // STEP 1B: Perform pushdown to the first child, and add VEGEqPreds |
| // back to predicatesOnParent after the push down. |
| // --------------------------------------------------------------------- |
| RelExpr::pushdownCoveredExpr(outputExpr, |
| newExternalInputs, |
| predicatesOnParent, |
| &exprOnParent, |
| 0); |
| |
| // --------------------------------------------------------------------- |
| // All selection predicates could be pushed to the first child for an |
| // Anti-Semi Join should those predicates should involve columns from |
| // the second child by definition. |
| // --------------------------------------------------------------------- |
| if (isAntiSemiJoin()) |
| { |
| CMPASSERT(predicatesOnParent.isEmpty() |
| // QSTUFF |
| OR getGroupAttr()->isGenericUpdateRoot() |
| // QSTUFF |
| ); |
| } |
| else |
| predicatesOnParent += VEGEqPreds1; |
| PUSHDOWN_DEBUG_SAVE("J3"); |
| |
| // --------------------------------------------------------------------- |
| // STEP 2: Try to push down the join predicates to second child. |
| // --------------------------------------------------------------------- |
| |
| // --------------------------------------------------------------------- |
| // STEP 2A: Gather all values the second child must still produce even |
| // if predicates are pushed down. Start with all the required |
| // values specified by the caller of this method. |
| // --------------------------------------------------------------------- |
| if (setOfValuesReqdByParent) |
| exprOnParent = *setOfValuesReqdByParent; |
| else exprOnParent.clear(); |
| |
| // --------------------------------------------------------------------- |
| // Since the remaining predicatesOnParent could not be pushed down to |
| // the second child and must be evaluated on the Left Join, values reqd |
| // for their evaluation must be included to make sure the second child |
| // produces them. For Anti-Semi Join, predicatesOnParent is empty. |
| // --------------------------------------------------------------------- |
| ValueIdSet inputs = newExternalInputs; |
| ValueIdSet inputsTakenOut; |
| if (isLeftJoin()) |
| { |
| computeValuesReqdForPredicates(predicatesOnParent, |
| exprOnParent); |
| |
| // ------------------------------------------------------------------- |
| // Special case: If this left join is the right child of a Nested |
| // Join, it could happen that the inputs we get from above already |
| // contains a value which our push down logic considers to have |
| // covered the predicatesOnParent which we don't attempt to push |
| // down. This is a very peculiar failure of our cover logic, where |
| // the predicates should have been pushed down but held back cos of |
| // the semantics of an operator (case in point, the left join). To |
| // deal with this, we remove from the available inputs to my child |
| // those values so that the child will produce this as an output. |
| // ------------------------------------------------------------------- |
| // inputTakenOut = inputs; |
| // predicatesOnParent.weedOutUnreferenced(inputsTakenOut); |
| // inputs -= inputsTakenOut; |
| } |
| |
| // --------------------------------------------------------------------- |
| // Also, if this is NOT a TSJ, there are some join predicates which need |
| // to be retained even if they are pushed down to the second child. All |
| // Join predicates are pushable to the second child of a TSJ without |
| // being retained at the TSJ. (See later for an exception) |
| // --------------------------------------------------------------------- |
| ValueIdSet VEGEqPreds2; |
| ValueIdSet availableInputs = inputs; |
| if (isATSJFlag) |
| { |
| availableInputs += child(0).getGroupAttr()->getCharacteristicOutputs(); |
| } |
| else |
| { |
| // ------------------------------------------------------------------- |
| // First, find all the VEGPreds in join predicates. This is similar to |
| // what we did above with predicatesOnParent. VEGPred(VEG{T1.a,T2.a}) |
| // will be pushed down to Scan T2 even if T1.a is not available there. |
| // Therefore, we still need to keep a copy of this type of predicates |
| // here at this Join node where both T1.a and T2.a will be available. |
| // ------------------------------------------------------------------- |
| joinPred().lookForVEGPredicates(VEGEqPreds2); |
| |
| // ------------------------------------------------------------------- |
| // Remove those VEGPreds that are covered by the input values, since |
| // VEGPred(VEG{T2.a,3}) needn't be retained at this Join node after |
| // pushed down to Scan T2. (There is an exception to this. See later.) |
| // ------------------------------------------------------------------- |
| VEGEqPreds2.removeCoveredExprs(availableInputs); //newExternalInputs |
| |
| // ------------------------------------------------------------------- |
| // Remove those VEGPreds which are not covered at first child. For |
| // example VEGPred(VEG{T2.a,T3.a}) in JOIN1 of (T1 JOIN1 (T2 JOIN2 T3)) |
| // is not covered at the first child. The predicate could be pushed |
| // down to the second child without being retained at JOIN2. |
| // ------------------------------------------------------------------- |
| VEGEqPreds2.removeUnCoveredExprs( |
| child(0).getGroupAttr()->getCharacteristicOutputs()); |
| |
| // ------------------------------------------------------------------- |
| // Since these predicates will be added back to the join predicates |
| // after the attempt to push down to second child, make sure the second |
| // child produces the required values to evaluate them. |
| // ------------------------------------------------------------------- |
| computeValuesReqdForPredicates(VEGEqPreds2, |
| exprOnParent); |
| } |
| |
| // --------------------------------------------------------------------- |
| // Now, there are additional join predicates that must be retained |
| // even if they are pushable to the second child. An example would be |
| // VEGPred(VEG{T1.a,T2.a,10}). For an inner join, this predicate can |
| // be pushed to Scan T1 and Scan T2 and evaluated as (T1.a=10) and |
| // (T2.a=10) respectively. However, for a Left Join or Anti-Semi Join, |
| // this predicate (if it's a join predicate) cannot be pushed down to |
| // the first child. The (T1.a=10) part must then be retained at this |
| // Join node. These types of VEGPreds are those covered by T1 and the |
| // external inputs. |
| // --------------------------------------------------------------------- |
| ValueIdSet joinPredsThatStay; |
| joinPredsThatStay = joinPred(); |
| ValueIdSet availableValues = availableInputs; //newExternalInputs |
| availableValues += child(0).getGroupAttr()->getCharacteristicOutputs(); |
| joinPredsThatStay.removeUnCoveredExprs(availableValues); |
| |
| // --------------------------------------------------------------------- |
| // However, we don't want VEGPred like VEGPred(VEG{T2.a,10}) which |
| // actually does not reference an output of T1. |
| // --------------------------------------------------------------------- |
| joinPredsThatStay.removeUnReferencedVEGPreds( |
| child(0).getGroupAttr()->getCharacteristicOutputs()); |
| |
| // --------------------------------------------------------------------- |
| // Also, if some inputs have been taken out deliberately, we want to |
| // make sure other predicates which references the inputs taken out |
| // are going to stay. Otherwise, we will have the issue that not |
| // sufficient values are available at the child to ensure correctness |
| // in evaluating the predicates pushed down to it. The same predicate |
| // must be re-evaluated at this JOIN node. |
| // --------------------------------------------------------------------- |
| if (NOT inputsTakenOut.isEmpty()) |
| { |
| ValueIdSet moreJoinPredsThatStay; |
| joinPred().lookForVEGPredicates(moreJoinPredsThatStay); |
| moreJoinPredsThatStay.removeUnReferencedVEGPreds(inputsTakenOut); |
| joinPredsThatStay += moreJoinPredsThatStay; |
| } |
| |
| // --------------------------------------------------------------------- |
| // Since these predicates will be added back to the join predicates |
| // after the attempt to push down to second child, make sure the second |
| // child produces the required values to evaluate them. |
| // --------------------------------------------------------------------- |
| computeValuesReqdForPredicates(joinPredsThatStay, |
| exprOnParent); |
| |
| //---------------------------------------------------------------------- |
| // Solution 10-030728-8252: check if the second child could produce |
| // expressions of type Instnull(CAST(aggregate)). |
| // See if the CAST could be pushed |
| // up. The Groupby node does not manufacture expressions of the type |
| // cast(aggregate) as outputs in the generator. So do not ask for them |
| //---------------------------------------------------------------------- |
| exprOnParent.replaceInstnullCastAggregateWithAggregateInLeftJoins(this); |
| |
| // --------------------------------------------------------------------- |
| // STEP 2B: Perform pushdown to the second child, and add reqd preds |
| // back to the join predicates after the push down. |
| // --------------------------------------------------------------------- |
| RelExpr::pushdownCoveredExpr(outputExpr, |
| availableInputs, |
| joinPred(), |
| &exprOnParent, |
| 1); |
| |
| |
| // --------------------------------------------------------------------- |
| // Add back those predicates which must stay with the JOIN even after |
| // they are pushed to the second child. |
| // --------------------------------------------------------------------- |
| joinPred() += VEGEqPreds2; |
| joinPred() += joinPredsThatStay; |
| PUSHDOWN_DEBUG_SAVE("J4"); |
| |
| } |
| else |
| // ----------------------------------------------------------------------- |
| // For other types of Join's: Semi and Inner (either TSJ or non-TSJ), |
| // processing is quite similar. Inner Joins has no join prediciates. For |
| // Semi-Join, although we distinguish between join predicates and |
| // selection predicates, both types of predicates are equally pushable. |
| // The only thing is "true join VEGPreds" like VEGPred(VEG{T1.a,T2.a}) |
| // should be retained as join predicates in a Semi-Join but as selection |
| // predicates in an Inner Join after being pushed down. |
| // ----------------------------------------------------------------------- |
| { |
| ValueIdSet predicates1 = predicatesOnParent; |
| ValueIdSet predicates2 = predicatesOnParent; |
| if (isSemiJoin()) |
| { |
| // Join predicates in a Semi-Join are "as pushable as" its selection |
| // predicates. |
| // |
| predicates1 += joinPred(); |
| predicates2 += joinPred(); |
| } |
| else |
| { |
| // Inner Join should have no join predicates. |
| CMPASSERT(joinPred().isEmpty() |
| // QSTUFF |
| OR getGroupAttr()->isGenericUpdateRoot() |
| // QSTUFF |
| ); |
| } |
| |
| // --------------------------------------------------------------------- |
| // STEP 1: Gather all values the children must still produce even if |
| // predicates are pushed down. |
| // |
| // Find all the "true join VEGPreds" in predicates. E.g, VEGPred(VEG{ |
| // T1.a,T2.a}) will be pushed down to Scan T1 and to Scan T2 even if |
| // not both values are availble at either node. Therefore, we still |
| // need to keep a copy of this type of predicates here at this Join node |
| // where both T1.a and T2.a will be available. That means the children |
| // need to provide these values to the Join node. The only exception is |
| // when we are doing a TSJ. The predicates are then all pushed to the |
| // right child, and the right child could then *not* provide the value |
| // to the Join node if it's not a required output from the Join. |
| // --------------------------------------------------------------------- |
| ValueIdSet VEGEqPreds; |
| predicates1.lookForVEGPredicates(VEGEqPreds); |
| |
| // --------------------------------------------------------------------- |
| // Remove those VEGPreds that are covered by the input values, since |
| // VEGPred(VEG{T1.a,3}) needn't be retained at this Join node after |
| // it's pushed down to Scan T1. |
| // --------------------------------------------------------------------- |
| VEGEqPreds.removeCoveredExprs(newExternalInputs); |
| |
| // --------------------------------------------------------------------- |
| // Remove those VEGPreds which are not covered at first child. For |
| // example VEGPred(VEG{T2.a,T3.a}) in JOIN1 of (T1 JOIN1 (T2 JOIN2 T3)) |
| // is not covered at the first child. The predicate could be pushed |
| // down to the second child without being retained at JOIN2. |
| // --------------------------------------------------------------------- |
| VEGEqPreds.removeUnCoveredExprs( |
| child(0).getGroupAttr()->getCharacteristicOutputs()); |
| |
| // --------------------------------------------------------------------- |
| // Remove those VEGPreds which are not covered at second child. For |
| // example VEGPred(VEG{T1.a,T2.a}) in JOIN2 of ((T1 JOIN1 T2) JOIN2 T3) |
| // is not covered at the second child. The predicate could be pushed |
| // down to the first child without being retained at JOIN2. |
| // --------------------------------------------------------------------- |
| VEGEqPreds.removeUnCoveredExprs( |
| child(1).getGroupAttr()->getCharacteristicOutputs()); |
| |
| // --------------------------------------------------------------------- |
| // Since these predicates will be retained at the Join (or pushed down |
| // to the second child in the case of a TSJ), make sure the first |
| // child produces the required values to evaluate them. |
| // --------------------------------------------------------------------- |
| computeValuesReqdForPredicates(VEGEqPreds, |
| exprOnParent); |
| |
| // --------------------------------------------------------------------- |
| // First child of a TSJ should produce inputs required from the second |
| // child as well. |
| // --------------------------------------------------------------------- |
| if (isATSJFlag) |
| exprOnParent += child(1).getGroupAttr()->getCharacteristicInputs(); |
| |
| // --------------------------------------------------------------------- |
| // STEP 2: Try pushing down to the first child. |
| // --------------------------------------------------------------------- |
| RelExpr::pushdownCoveredExpr(outputExpr, |
| newExternalInputs, |
| predicates1, |
| &exprOnParent, |
| 0); |
| PUSHDOWN_DEBUG_SAVE("J5"); |
| |
| // --------------------------------------------------------------------- |
| // Find subset of predicatesOnParent which have *not* been pushed down |
| // to first child. |
| // --------------------------------------------------------------------- |
| predicatesOnParent.intersectSet(predicates1); |
| PUSHDOWN_DEBUG_SAVE("J6"); |
| |
| // --------------------------------------------------------------------- |
| // For a Semi-Join, all selection predicates (which should not involve |
| // columns from the second child) should be pushed down to the first |
| // child by now. Also get rid of the join predicates which have been |
| // pushed down. |
| // --------------------------------------------------------------------- |
| if (isSemiJoin()) |
| { |
| joinPred().intersectSet(predicates1); |
| CMPASSERT(predicatesOnParent.isEmpty() |
| // QSTUFF |
| OR getGroupAttr()->isGenericUpdateRoot() |
| // QSTUFF |
| ); |
| } |
| |
| // --------------------------------------------------------------------- |
| // If this is a TSJ, we do not even need to retain VEGEqPreds at the |
| // Join. Everything remaining should be pushable to the right child. |
| // Therefore, we don't need the right child to output values required |
| // for evaluating VEGEqPreds, unless it's an required output from the |
| // |
| // We do not want to push the predicate down to the right child now for |
| // the RoutineJoin. That will happen later when the RoutineJoin gets |
| // transfered back to a TSJ/nested join by the optimizer impl rules. |
| // |
| // The reason we don't want it pushed here is so that the analyzer does |
| // not have to differentiate what imputs are required for predicates and |
| // which is required for a UDF. By knowing what inputs are required for |
| // the UDF, the analyzer can determine if there is a different join order |
| // that might be cheaper. We will attempt to push the predicate during |
| // the optimizer phase.. |
| // --------------------------------------------------------------------- |
| if (!isRoutineJoin()) |
| { |
| ValueIdSet availableInputs = newExternalInputs; |
| if (isATSJFlag) |
| { |
| if (setOfValuesReqdByParent) |
| exprOnParent = *setOfValuesReqdByParent; |
| else exprOnParent.clear(); |
| availableInputs += child(0).getGroupAttr()->getCharacteristicOutputs(); |
| } |
| |
| // --------------------------------------------------------------------- |
| // STEP 3: Try pushing to second child now. |
| // --------------------------------------------------------------------- |
| RelExpr::pushdownCoveredExpr(outputExpr, |
| availableInputs, |
| predicates2, |
| &exprOnParent, |
| 1); |
| } |
| |
| // --------------------------------------------------------------------- |
| // Find subset of predicatesOnParent which have *not* been pushed down |
| // to second child. |
| // --------------------------------------------------------------------- |
| predicatesOnParent.intersectSet(predicates2); |
| PUSHDOWN_DEBUG_SAVE("J7"); |
| |
| if (isSemiJoin()) |
| { |
| // ------------------------------------------------------------------- |
| // set joinPred to have those predicates that were not pushed down to |
| // the second child. |
| // ------------------------------------------------------------------- |
| joinPred().intersectSet(predicates2); |
| |
| // ------------------------------------------------------------------- |
| // If this is a semi-join that is not a TSJ we need to add all the |
| // true join VEGPreds back to joinPred(). |
| // ------------------------------------------------------------------- |
| if (NOT isATSJFlag) |
| joinPred() += VEGEqPreds; |
| else |
| // ----------------------------------------------------------------- |
| // If it is a TSJ all join predicates should be pushable, no preds |
| // should be remaining in joinPred(). |
| // ----------------------------------------------------------------- |
| CMPASSERT(joinPred().isEmpty() |
| // QSTUFF |
| OR getGroupAttr()->isGenericUpdateRoot() |
| // QSTUFF |
| ); |
| } |
| else |
| { |
| // ------------------------------------------------------------------- |
| // If this is a inner-join that is not a TSJ we need to add all the |
| // true join VEGPreds back to selection predicates. |
| // ------------------------------------------------------------------- |
| if (NOT isATSJFlag OR isRoutineJoin()) |
| { |
| predicatesOnParent += VEGEqPreds; |
| PUSHDOWN_DEBUG_SAVE("J9"); |
| } |
| else |
| // ----------------------------------------------------------------- |
| // If it is a TSJ all selection predicates should be pushable, no |
| // preds should remain. |
| // ----------------------------------------------------------------- |
| CMPASSERT(predicatesOnParent.isEmpty() |
| // QSTUFF |
| OR getGroupAttr()->isGenericUpdateRoot() |
| // QSTUFF |
| ); |
| } |
| } |
| } // Join::pushdownCoveredExpr |
| |
| // -------------------------------------------------------------------------- |
| // Join::pushdownCoveredExprSQO |
| // Rules for pushdown from Join during the SemanticQueryOptimize(SQO) |
| // subphase are different in two ways from the usual. |
| // 1) If left child does not cover any part of a |
| // VEGPred it will still be retained in the Join, so that it can be pulled |
| // further up the query tree as we apply this transformation at other levels |
| // In the usual rules, the VEGPred will be pushed down to the right child |
| // without being retained at the Join. This behaviour is controlled by the |
| // boolean input parameter keepPredsNotCoveredByChild0. Similarly preds not |
| // covered by the right child can also be retained at the Join. This is |
| // controlled by keepPredsNotCoveredByChild1. |
| // 2) If left child is a semiJoin or a TSJ we do not push any predicates |
| // down that side as those selection predicates are supposed to be empty |
| // at this phase of compilation. |
| // --------------------------------------------------------------------------- |
| void Join::pushdownCoveredExprSQO(const ValueIdSet & outputExpr, |
| const ValueIdSet & newExternalInputs, |
| ValueIdSet & predicatesOnParent, |
| ValueIdSet & setOfValuesReqdByParent, |
| NABoolean keepPredsNotCoveredByChild0, |
| NABoolean keepPredsNotCoveredByChild1 |
| ) |
| { |
| |
| ValueIdSet exprOnParent1 = setOfValuesReqdByParent; |
| ValueIdSet exprOnParent = setOfValuesReqdByParent; |
| ValueIdSet exprOnParent2 = setOfValuesReqdByParent; |
| |
| |
| ValueIdSet predicates1 = predicatesOnParent; |
| ValueIdSet predicates2 = predicatesOnParent; |
| |
| if (isLeftJoin()) |
| { |
| // --------------------------------------------------------------------- |
| // STEP 1: Try to push down the given predicatesOnParent to first child. |
| // --------------------------------------------------------------------- |
| |
| // --------------------------------------------------------------------- |
| // STEP 1A: Gather all values the left child must still produce even if |
| // predicates are pushed down. |
| // |
| // Selection predicates can only be pushed to the first child of an |
| // outer join. Join predicates can only be pushed to the second. Make |
| // sure the first child produces what we need for the join predicates. |
| // --------------------------------------------------------------------- |
| computeValuesReqdForPredicates(joinPred(), |
| exprOnParent); |
| |
| |
| ValueIdSet VEGEqPreds1; |
| |
| // --------------------------------------------------------------------- |
| // Find all the VEGPreds in predicatesOnParent. VEGPred(VEG{T1.a,T2.a}) |
| // will be pushed down to Scan T1 even if T2.a is not available there. |
| // Therefore, we still need to keep a copy of this type of predicates |
| // here at this Join node where both T1.a and T2.a will be available. |
| // --------------------------------------------------------------------- |
| predicatesOnParent.lookForVEGPredicates(VEGEqPreds1); |
| |
| // --------------------------------------------------------------------- |
| // Remove those VEGPreds that are covered by the input values, since |
| // VEGPred(VEG{T1.a,3}) needn't be retained at this Join node after it's |
| // pushed down to Scan T1. |
| // --------------------------------------------------------------------- |
| VEGEqPreds1.removeCoveredExprs(newExternalInputs); |
| |
| // --------------------------------------------------------------------- |
| // Remove those VEGPreds which are not covered at second child. For |
| // example VEGPred(VEG{T1.a,T2.a}) in JOIN2 of ((T1 JOIN1 T2) JOIN2 T3) |
| // is not covered at the second child. The predicate should be pushed |
| // down to the first child without being retained at JOIN2. Note that |
| // since predicatesOnParent are selection predicates evaluated after |
| // a Left Join, they are in terms of the null-instantiated outputs from |
| // the Join rather than direct outputs from the second child. |
| // --------------------------------------------------------------------- |
| if (NOT keepPredsNotCoveredByChild1) |
| VEGEqPreds1.removeUnCoveredExprs(nullInstantiatedOutput()); |
| |
| // --------------------------------------------------------------------- |
| // Since these VEGEqPreds1 will be added back to predicatesOnParent |
| // after the attempt to push down to first child, make sure the first |
| // child produces the required values to evaluate them. |
| // --------------------------------------------------------------------- |
| computeValuesReqdForPredicates(VEGEqPreds1, |
| exprOnParent); |
| |
| // --------------------------------------------------------------------- |
| // STEP 1B: Perform pushdown to the first child, and add VEGEqPreds |
| // back to predicatesOnParent after the push down. |
| // --------------------------------------------------------------------- |
| RelExpr::pushdownCoveredExpr(outputExpr, |
| newExternalInputs, |
| predicatesOnParent, |
| &exprOnParent, |
| 0); |
| |
| |
| // --------------------------------------------------------------------- |
| // All selection predicates could be pushed to the first child for an |
| // Anti-Semi Join should those predicates should involve columns from |
| // the second child by definition. |
| // --------------------------------------------------------------------- |
| |
| predicatesOnParent += VEGEqPreds1; |
| |
| // --------------------------------------------------------------------- |
| // STEP 2: Try to push down the join predicates to second child. |
| // --------------------------------------------------------------------- |
| |
| // --------------------------------------------------------------------- |
| // STEP 2A: Gather all values the second child must still produce even |
| // if predicates are pushed down. Start with all the required |
| // values specified by the caller of this method. |
| // --------------------------------------------------------------------- |
| exprOnParent = outputExpr; |
| |
| // --------------------------------------------------------------------- |
| // Since the remaining predicatesOnParent could not be pushed down to |
| // the second child and must be evaluated on the Left Join, values reqd |
| // for their evaluation must be included to make sure the second child |
| // produces them. For Anti-Semi Join, predicatesOnParent is empty. |
| // --------------------------------------------------------------------- |
| ValueIdSet inputs = newExternalInputs; |
| ValueIdSet inputsTakenOut; |
| computeValuesReqdForPredicates(predicatesOnParent, |
| exprOnParent); |
| |
| |
| // --------------------------------------------------------------------- |
| // Also, if this is NOT a TSJ, there are some join predicates which need |
| // to be retained even if they are pushed down to the second child. All |
| // Join predicates are pushable to the second child of a TSJ without |
| // being retained at the TSJ. (See later for an exception) |
| // --------------------------------------------------------------------- |
| ValueIdSet VEGEqPreds2; |
| ValueIdSet availableInputs = inputs; |
| // ------------------------------------------------------------------- |
| // First, find all the VEGPreds in join predicates. This is similar to |
| // what we did above with predicatesOnParent. VEGPred(VEG{T1.a,T2.a}) |
| // will be pushed down to Scan T2 even if T1.a is not available there. |
| // Therefore, we still need to keep a copy of this type of predicates |
| // here at this Join node where both T1.a and T2.a will be available. |
| // ------------------------------------------------------------------- |
| joinPred().lookForVEGPredicates(VEGEqPreds2); |
| |
| // ------------------------------------------------------------------- |
| // Remove those VEGPreds that are covered by the input values, since |
| // VEGPred(VEG{T2.a,3}) needn't be retained at this Join node after |
| // pushed down to Scan T2. (There is an exception to this. See later.) |
| // ------------------------------------------------------------------- |
| VEGEqPreds2.removeCoveredExprs(availableInputs); //newExternalInputs |
| |
| // ------------------------------------------------------------------- |
| // Remove those VEGPreds which are not covered at first child. For |
| // example VEGPred(VEG{T2.a,T3.a}) in JOIN1 of (T1 JOIN1 (T2 JOIN2 T3)) |
| // is not covered at the first child. The predicate could be pushed |
| // down to the second child without being retained at JOIN2. |
| // ------------------------------------------------------------------- |
| if (NOT keepPredsNotCoveredByChild0) |
| VEGEqPreds2.removeUnCoveredExprs( |
| child(0).getGroupAttr()->getCharacteristicOutputs()); |
| |
| // ------------------------------------------------------------------- |
| // Since these predicates will be added back to the join predicates |
| // after the attempt to push down to second child, make sure the second |
| // child produces the required values to evaluate them. |
| // ------------------------------------------------------------------- |
| computeValuesReqdForPredicates(VEGEqPreds2, |
| exprOnParent); |
| |
| |
| // --------------------------------------------------------------------- |
| // Now, there are additional join predicates that must be retained |
| // even if they are pushable to the second child. An example would be |
| // VEGPred(VEG{T1.a,T2.a,10}). For an inner join, this predicate can |
| // be pushed to Scan T1 and Scan T2 and evaluated as (T1.a=10) and |
| // (T2.a=10) respectively. However, for a Left Join or Anti-Semi Join, |
| // this predicate (if it's a join predicate) cannot be pushed down to |
| // the first child. The (T1.a=10) part must then be retained at this |
| // Join node. These types of VEGPreds are those covered by T1 and the |
| // external inputs. |
| // --------------------------------------------------------------------- |
| ValueIdSet joinPredsThatStay; |
| joinPredsThatStay = joinPred(); |
| ValueIdSet availableValues = availableInputs; //newExternalInputs |
| availableValues += child(0).getGroupAttr()->getCharacteristicOutputs(); |
| joinPredsThatStay.removeUnCoveredExprs(availableValues); |
| |
| // --------------------------------------------------------------------- |
| // However, we don't want VEGPred like VEGPred(VEG{T2.a,10}) which |
| // actually does not reference an output of T1. |
| // --------------------------------------------------------------------- |
| if (NOT keepPredsNotCoveredByChild0) |
| joinPredsThatStay.removeUnReferencedVEGPreds( |
| child(0).getGroupAttr()->getCharacteristicOutputs()); |
| |
| // --------------------------------------------------------------------- |
| // Also, if some inputs have been taken out deliberately, we want to |
| // make sure other predicates which references the inputs taken out |
| // are going to stay. Otherwise, we will have the issue that not |
| // sufficient values are available at the child to ensure correctness |
| // in evaluating the predicates pushed down to it. The same predicate |
| // must be re-evaluated at this JOIN node. |
| // --------------------------------------------------------------------- |
| if (NOT inputsTakenOut.isEmpty()) |
| { |
| ValueIdSet moreJoinPredsThatStay; |
| joinPred().lookForVEGPredicates(moreJoinPredsThatStay); |
| moreJoinPredsThatStay.removeUnReferencedVEGPreds(inputsTakenOut); |
| joinPredsThatStay += moreJoinPredsThatStay; |
| } |
| |
| // --------------------------------------------------------------------- |
| // Since these predicates will be added back to the join predicates |
| // after the attempt to push down to second child, make sure the second |
| // child produces the required values to evaluate them. |
| // --------------------------------------------------------------------- |
| computeValuesReqdForPredicates(joinPredsThatStay, |
| exprOnParent); |
| |
| //---------------------------------------------------------------------- |
| // Solution 10-030728-8252: check if the second child could produce |
| // expressions of type Instnull(CAST(aggregate)). |
| // See if the CAST could be pushed |
| // up. The Groupby node does not manufacture expressions of the type |
| // cast(aggregate) as outputs in the generator. So do not ask for them |
| //---------------------------------------------------------------------- |
| exprOnParent.replaceInstnullCastAggregateWithAggregateInLeftJoins(this); |
| |
| // --------------------------------------------------------------------- |
| // STEP 2B: Perform pushdown to the second child, and add reqd preds |
| // back to the join predicates after the push down. |
| // --------------------------------------------------------------------- |
| RelExpr::pushdownCoveredExpr(outputExpr, |
| availableInputs, |
| joinPred(), |
| &exprOnParent, |
| 1); |
| // --------------------------------------------------------------------- |
| // Add back those predicates which must stay with the JOIN even after |
| // they are pushed to the second child. |
| // --------------------------------------------------------------------- |
| joinPred() += VEGEqPreds2; |
| joinPred() += joinPredsThatStay; |
| |
| } |
| else |
| { |
| // STEP 1: Gather all values the children must still produce even if |
| // predicates are pushed down. |
| // |
| // Find all the "true join VEGPreds" in predicates. E.g, VEGPred(VEG{ |
| // T1.a,T2.a}) will be pushed down to Scan T1 and to Scan T2 even if |
| // not both values are availble at either node. Therefore, we still |
| // need to keep a copy of this type of predicates here at this Join node |
| // where both T1.a and T2.a will be available. That means the children |
| // need to provide these values to the Join node. The only exception is |
| // when we are doing a TSJ. The predicates are then all pushed to the |
| // right child, and the right child could then *not* provide the value |
| // to the Join node if it's not a required output from the Join. |
| // --------------------------------------------------------------------- |
| ValueIdSet VEGEqPreds; |
| predicates1.lookForVEGPredicates(VEGEqPreds); |
| |
| // --------------------------------------------------------------------- |
| // Remove those VEGPreds that are covered by the input values, since |
| // VEGPred(VEG{T1.a,3}) needn't be retained at this Join node after |
| // it's pushed down to Scan T1. |
| // --------------------------------------------------------------------- |
| VEGEqPreds.removeCoveredExprs(newExternalInputs); |
| |
| // --------------------------------------------------------------------- |
| // Remove those VEGPreds which are not covered at first child. For |
| // example VEGPred(VEG{T2.a,T3.a}) in JOIN1 of (T1 JOIN1 (T2 JOIN2 T3)) |
| // is not covered at the first child. The predicate could be pushed |
| // down to the second child without being retained at JOIN2. |
| // --------------------------------------------------------------------- |
| if (NOT keepPredsNotCoveredByChild0) |
| VEGEqPreds.removeUnCoveredExprs( |
| child(0).getGroupAttr()->getCharacteristicOutputs()); |
| |
| // --------------------------------------------------------------------- |
| // Remove those VEGPreds which are not covered at second child. For |
| // example VEGPred(VEG{T1.a,T2.a}) in JOIN2 of ((T1 JOIN1 T2) JOIN2 T3) |
| // is not covered at the second child. The predicate could be pushed |
| // down to the first child without being retained at JOIN2. |
| // --------------------------------------------------------------------- |
| if (NOT keepPredsNotCoveredByChild1) |
| VEGEqPreds.removeUnCoveredExprs( |
| child(1).getGroupAttr()->getCharacteristicOutputs()); |
| |
| // --------------------------------------------------------------------- |
| // Since these predicates will be retained at the Join (or pushed down |
| // to the second child in the case of a TSJ), make sure the first |
| // child produces the required values to evaluate them. |
| // --------------------------------------------------------------------- |
| |
| computeValuesReqdForPredicates(VEGEqPreds, |
| exprOnParent); |
| |
| // --------------------------------------------------------------------- |
| // STEP 2: Try pushing down to the first child. |
| // --------------------------------------------------------------------- |
| |
| |
| if (child(0).getPtr()->getOperator().match(REL_ANY_SEMIJOIN) || |
| child(0).getPtr()->getOperator().match(REL_ANY_TSJ)) |
| { |
| computeValuesReqdForPredicates(predicates1, |
| exprOnParent1); |
| ValueIdSet emptySet; |
| RelExpr::pushdownCoveredExpr(outputExpr, |
| newExternalInputs, |
| emptySet, |
| &exprOnParent1, |
| 0); |
| } |
| else |
| { |
| RelExpr::pushdownCoveredExpr(outputExpr, |
| newExternalInputs, |
| predicates1, |
| &exprOnParent, |
| 0); |
| } |
| |
| |
| // --------------------------------------------------------------------- |
| // Find subset of predicatesOnParent which have *not* been pushed down |
| // to first child. |
| // --------------------------------------------------------------------- |
| predicatesOnParent.intersectSet(predicates1); |
| |
| |
| // --------------------------------------------------------------------- |
| // STEP 3: Try pushing to second child now. |
| // --------------------------------------------------------------------- |
| |
| if (child(1).getPtr()->getOperator().match(REL_ANY_SEMIJOIN) || |
| (child(1).getPtr()->getOperator().match(REL_ANY_TSJ) && |
| (child(1).getPtr()->getOperator() != REL_ROUTINE_JOIN))) |
| { |
| computeValuesReqdForPredicates(predicates2, |
| exprOnParent2); |
| ValueIdSet emptySet; |
| RelExpr::pushdownCoveredExpr(outputExpr, |
| newExternalInputs, |
| emptySet, |
| &exprOnParent2, |
| 1); |
| } |
| else |
| { |
| // We do not want to push predicates to the right child of a |
| // routineJoin. |
| if (!isRoutineJoin()) |
| { |
| RelExpr::pushdownCoveredExpr(outputExpr, |
| newExternalInputs, |
| predicates2, |
| &exprOnParent, |
| 1); |
| } |
| } |
| |
| // --------------------------------------------------------------------- |
| // Find subset of predicatesOnParent which have *not* been pushed down |
| // to second child. |
| // --------------------------------------------------------------------- |
| predicatesOnParent.intersectSet(predicates2); |
| |
| |
| // ------------------------------------------------------------------- |
| // If this is a inner-join that is not a TSJ we need to add all the |
| // true join VEGPreds back to selection predicates. |
| // ------------------------------------------------------------------- |
| |
| predicatesOnParent += VEGEqPreds; |
| } |
| |
| } // Join::pushdownCoveredExprSQO |
| |
| |
| void Join::getPotentialOutputValues(ValueIdSet & outputValues) const |
| { |
| outputValues.clear(); |
| switch (getOperatorType()) |
| { |
| case REL_JOIN: |
| case REL_MERGE_JOIN: |
| case REL_NESTED_JOIN: |
| case REL_HASH_JOIN: |
| case REL_HYBRID_HASH_JOIN: |
| case REL_ORDERED_HASH_JOIN: |
| case REL_INDEX_JOIN: |
| case REL_ROUTINE_JOIN: |
| case REL_TSJ: |
| { |
| // Potentially, all the values that are produced by |
| // my left child as well as my right child. |
| outputValues += child(0).getGroupAttr()->getCharacteristicOutputs(); |
| outputValues += child(1).getGroupAttr()->getCharacteristicOutputs(); |
| break; |
| } |
| case REL_LEFT_JOIN: |
| case REL_LEFT_NESTED_JOIN: |
| case REL_LEFT_MERGE_JOIN: |
| case REL_LEFT_ORDERED_HASH_JOIN: |
| case REL_LEFT_HYBRID_HASH_JOIN: |
| case REL_LEFT_TSJ: |
| { |
| // Potentially, all the values that are produced by |
| // my left child and all null instantiated values from |
| // my right child. |
| outputValues += child(0).getGroupAttr()->getCharacteristicOutputs(); |
| outputValues.insertList(nullInstantiatedOutput()); |
| break; |
| } |
| case REL_FULL_JOIN: |
| case REL_UNION_JOIN: |
| case REL_FULL_HYBRID_HASH_JOIN: |
| { |
| // Potentially, all the values that are produced by |
| // my left child and the right child. Since it's a FULL_OUTER_JOIN |
| // all null instantiated values from my right and left child. |
| |
| outputValues.insertList(nullInstantiatedOutput()); |
| outputValues.insertList(nullInstantiatedForRightJoinOutput()); |
| break; |
| } |
| case REL_SEMIJOIN: |
| case REL_ANTI_SEMIJOIN: |
| case REL_SEMITSJ: |
| case REL_ANTI_SEMITSJ: |
| case REL_HASH_SEMIJOIN: |
| case REL_HASH_ANTI_SEMIJOIN: |
| case REL_MERGE_SEMIJOIN: |
| case REL_MERGE_ANTI_SEMIJOIN: |
| case REL_HYBRID_HASH_SEMIJOIN: |
| case REL_HYBRID_HASH_ANTI_SEMIJOIN: |
| case REL_ORDERED_HASH_SEMIJOIN: |
| case REL_ORDERED_HASH_ANTI_SEMIJOIN: |
| case REL_NESTED_SEMIJOIN: |
| case REL_NESTED_ANTI_SEMIJOIN: |
| { |
| // No value from my right child can appear in my output. |
| outputValues += child(0).getGroupAttr()->getCharacteristicOutputs(); |
| break; |
| } |
| case REL_TSJ_FLOW: |
| case REL_NESTED_JOIN_FLOW: |
| { |
| // No value from my left child can appear in my output. |
| outputValues += child(1).getGroupAttr()->getCharacteristicOutputs(); |
| break; |
| } |
| default: |
| { |
| ABORT("Unsupported join type in Join::getPotentialOutputValues()"); |
| break; |
| } |
| } // switch |
| } // Join::getPotentialOutputValues() |
| |
| CostScalar Join::computeMinEstRCForGroup() |
| { |
| CostScalar minCard = csOne; |
| |
| GroupAttributes * ga = getGroupAttr(); |
| |
| RelExpr * logExpr = ga->getLogExprForSynthesis(); |
| if (logExpr != NULL) |
| { |
| logExpr->finishSynthEstLogProp(); |
| minCard = ga->getMinChildEstRowCount(); |
| } |
| |
| return minCard; |
| } |
| |
| // get the highest reduction from local predicates for cols of this join |
| CostScalar |
| Join::highestReductionForCols(ValueIdSet colSet) |
| { |
| // if the child is anything other than scan, then we assume the reduction to be 1 |
| // but before that we still need to see if the column set that we are looking for |
| // belongs to this child or not. |
| // since we don't know to which child tableOne belongs, we shall look at both left |
| // and right histograms for the columns. Start with the left child |
| ColStatDescList completeList = child(0).outputLogProp((*GLOBAL_EMPTY_INPUT_LOGPROP))->colStats(); |
| ColStatDescList rightColStatList = child(1).outputLogProp((*GLOBAL_EMPTY_INPUT_LOGPROP))->colStats(); |
| |
| // form a complete list of histograms from both sides |
| completeList.makeDeepCopy(rightColStatList); |
| // Compute reduction for this column set |
| CostScalar highestUecRedByLocalPreds = highestUecRedByLocalPreds = completeList.getHighestUecReductionByLocalPreds(colSet); |
| return highestUecRedByLocalPreds; |
| } |
| |
| const NAString Join::getText() const |
| { |
| NAString result; |
| |
| switch (getOperatorType()) |
| { |
| case REL_JOIN: |
| result += "join"; |
| break; |
| case REL_LEFT_JOIN: |
| result += "left_join"; |
| break; |
| case REL_RIGHT_JOIN: |
| result += "right_join"; |
| break; |
| case REL_FULL_JOIN: |
| result += "full_join"; |
| break; |
| case REL_UNION_JOIN: |
| result += "union_join"; |
| break; |
| case REL_ROUTINE_JOIN: |
| result += "routine_join"; |
| break; |
| case REL_TSJ: |
| result += "tsj"; |
| break; |
| case REL_TSJ_FLOW: |
| result += "tsj_flow"; |
| break; |
| case REL_LEFT_TSJ: |
| result += "left_tsj"; |
| break; |
| case REL_SEMIJOIN: |
| result += "semi_join"; |
| break; |
| case REL_ANTI_SEMIJOIN: |
| result += "anti_semi_join"; |
| break; |
| case REL_SEMITSJ: |
| result += "semi_tsj"; |
| break; |
| case REL_ANTI_SEMITSJ: |
| result += "anti_semi_tsj"; |
| break; |
| case REL_INDEX_JOIN: |
| result += "index_join"; |
| break; |
| |
| default: |
| result += "UNKNOWN??"; |
| break; |
| } // switch |
| |
| if(CmpCommon::getDefault(COMP_BOOL_183) == DF_ON) |
| { |
| Int32 potential = getPotential(); |
| if(potential < 0) |
| { |
| result += "_-"+ istring(-1*potential); |
| } |
| else |
| result += "_" + istring(potential); |
| } |
| |
| return result; |
| } // Join::getText() |
| |
| HashValue Join::topHash() |
| { |
| HashValue result = RelExpr::topHash(); |
| |
| result ^= joinPred_; |
| |
| return result; |
| } |
| |
| NABoolean Join::duplicateMatch(const RelExpr & other) const |
| { |
| if (!RelExpr::duplicateMatch(other)) |
| return FALSE; |
| |
| Join &o = (Join &) other; |
| |
| if (joinPred_ != o.joinPred_) |
| return FALSE; |
| |
| // Temp member to seperate joins PTRule from others in cascades memo |
| if (joinFromPTRule_ != o.joinFromPTRule_) |
| return FALSE; |
| |
| if (joinForZigZag_ != o.joinForZigZag_) |
| return FALSE; |
| |
| if (avoidHalloweenR2_ != o.avoidHalloweenR2_) |
| return FALSE; |
| |
| if (halloweenForceSort_ != o.halloweenForceSort_) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| |
| RelExpr * Intersect::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| RelExpr *result; |
| |
| if (derivedNode == NULL) |
| { |
| result = new (outHeap) Intersect(NULL, |
| NULL |
| ); |
| } |
| else |
| result = derivedNode; |
| |
| return RelExpr::copyTopNode(result, outHeap); |
| } |
| |
| RelExpr * Except::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| RelExpr *result; |
| |
| if (derivedNode == NULL) |
| { |
| result = new (outHeap) Except(NULL, |
| NULL |
| ); |
| } |
| else |
| result = derivedNode; |
| |
| return RelExpr::copyTopNode(result, outHeap); |
| } |
| |
| RelExpr * Join::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| Join *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) Join(NULL, |
| NULL, |
| getOperatorType(), |
| NULL, |
| FALSE, |
| FALSE, |
| outHeap); |
| else |
| result = (Join *) derivedNode; |
| |
| // copy join predicate parse tree (parser only) |
| if (joinPredTree_ != NULL) |
| result->joinPredTree_ = joinPredTree_->copyTree(outHeap)->castToItemExpr(); |
| |
| result->joinPred_ = joinPred_; |
| |
| // Copy the uniqueness flags |
| result->leftHasUniqueMatches_ = leftHasUniqueMatches_; |
| result->rightHasUniqueMatches_ = rightHasUniqueMatches_; |
| |
| // Copy the equijoin predicates |
| result->equiJoinPredicates_ = equiJoinPredicates_; |
| result->equiJoinExpressions_ = equiJoinExpressions_; |
| |
| result->nullInstantiatedOutput() = nullInstantiatedOutput(); |
| |
| result->nullInstantiatedForRightJoinOutput() = |
| nullInstantiatedForRightJoinOutput(); |
| |
| result->transformComplete_ = transformComplete_; |
| |
| // Copy the required order, if any, that originated from an insert node |
| result->reqdOrder_ = reqdOrder_; |
| |
| // copy flag that marks a mandatory TSJ which could not be unnested |
| result->tsjAfterSQO_ = tsjAfterSQO_; |
| |
| // Copy the flag that indicates if this is a TSJ for a write operation |
| result->tsjForWrite_ = tsjForWrite_; |
| result->tsjForUndo_ = tsjForUndo_; |
| result->tsjForSetNFError_ = tsjForSetNFError_; |
| result->tsjForMerge_ = tsjForMerge_; |
| result->tsjForMergeWithInsert_ = tsjForMergeWithInsert_; |
| result->tsjForMergeUpsert_ = tsjForMergeUpsert_; |
| result->tsjForSideTreeInsert_ = tsjForSideTreeInsert_; |
| result->enableTransformToSTI_ = enableTransformToSTI_; |
| |
| result->forcePhysicalJoinType_ = forcePhysicalJoinType_; |
| result->derivedFromRoutineJoin_ = derivedFromRoutineJoin_; |
| |
| // Temp member to seperate joins PTRule from others in cascades memo |
| result->joinFromPTRule_ = joinFromPTRule_; |
| |
| result->joinForZigZag_ = joinForZigZag_; |
| |
| result->sourceType_ = sourceType_; |
| |
| |
| result->rowsetRowCountArraySize_ = rowsetRowCountArraySize_; |
| |
| result->avoidHalloweenR2_ = avoidHalloweenR2_; |
| |
| result->halloweenForceSort_ = halloweenForceSort_; |
| |
| result->candidateForSubqueryUnnest_ = candidateForSubqueryUnnest_; |
| |
| result->candidateForSubqueryLeftJoinConversion_ = candidateForSubqueryLeftJoinConversion_; |
| |
| result->candidateForSemiJoinTransform_ = candidateForSemiJoinTransform_; |
| |
| result->predicatesToBeRemoved_ = predicatesToBeRemoved_; |
| |
| //++MV |
| result->rightChildMapForLeftJoin_ = rightChildMapForLeftJoin_; |
| //--MV |
| |
| result->isIndexJoin_ = isIndexJoin_; |
| |
| if(!result->isInnerNonSemiJoin()) |
| result->floatingJoin_ = floatingJoin_; |
| |
| result->allowPushDown_ = allowPushDown_; |
| |
| result->extraHubNonEssentialOutputs_ = extraHubNonEssentialOutputs_; |
| |
| result->isForTrafLoadPrep_ = isForTrafLoadPrep_; |
| |
| result->beforeJoinPredOnOuterOnly_ = beforeJoinPredOnOuterOnly_; |
| |
| return RelExpr::copyTopNode(result, outHeap); |
| } |
| |
| void Join::addJoinPredTree(ItemExpr *joinPred) |
| { |
| ExprValueId j = joinPredTree_; |
| |
| ItemExprTreeAsList(&j, ITM_AND).insert(joinPred); |
| joinPredTree_ = j.getPtr(); |
| } |
| |
| ItemExpr * Join::removeJoinPredTree() |
| { |
| ItemExpr * result = joinPredTree_; |
| |
| joinPredTree_ = NULL; |
| |
| return result; |
| } |
| |
| void Join::addLocalExpr(LIST(ExprNode *) &xlist, |
| LIST(NAString) &llist) const |
| { |
| if (joinPredTree_ != NULL OR |
| NOT joinPred_.isEmpty()) |
| { |
| if (joinPred_.isEmpty()) |
| xlist.insert(joinPredTree_); |
| else |
| xlist.insert(joinPred_.rebuildExprTree()); |
| llist.insert("other_join_predicates"); |
| } |
| |
| RelExpr::addLocalExpr(xlist,llist); |
| } |
| |
| void Join::convertToTsj() |
| { |
| switch (getOperatorType()) |
| { |
| case REL_JOIN: |
| setOperatorType(REL_TSJ); |
| break; |
| case REL_LEFT_JOIN: |
| setOperatorType(REL_LEFT_TSJ); |
| break; |
| case REL_SEMIJOIN: |
| setOperatorType(REL_SEMITSJ); |
| break; |
| case REL_ANTI_SEMIJOIN: |
| setOperatorType(REL_ANTI_SEMITSJ); |
| break; |
| default: |
| ABORT("Internal error: Join::convertTsj()"); |
| break; |
| } |
| } // Join::convertToTsj() |
| |
| void Join::convertToNotTsj() |
| { |
| switch (getOperatorType()) |
| { |
| case REL_TSJ: |
| case REL_TSJ_FLOW: |
| case REL_ROUTINE_JOIN: |
| setOperatorType(REL_JOIN); |
| break; |
| case REL_LEFT_TSJ: |
| setOperatorType(REL_LEFT_JOIN); |
| break; |
| case REL_SEMITSJ: |
| setOperatorType(REL_SEMIJOIN); |
| break; |
| case REL_ANTI_SEMITSJ: |
| setOperatorType(REL_ANTI_SEMIJOIN); |
| break; |
| default: |
| ABORT("Internal error: Join::convertTsj()"); |
| break; |
| } |
| } // Join::convertToNotTsj() |
| |
| void Join::convertToNotOuterJoin() |
| { |
| switch (getOperatorType()) |
| { |
| case REL_LEFT_JOIN: |
| setOperatorType(REL_JOIN); |
| break; |
| case REL_LEFT_TSJ: |
| setOperatorType(REL_TSJ); |
| break; |
| default: |
| ABORT("Internal error: Join::convertOuterJoin()"); |
| break; |
| } // end switch |
| } // Join::convertToNotOuterJoin() |
| |
| // ---------------------------------------------------------------------------- |
| // This procedure gets called when synthesising logical properties. |
| // It finds all the equijoin predicates and saves them in equiJoinPredicates_ |
| // But leaves them in the originating selectionPred()/joinPred() |
| // --------------------------------------------------------------------------- |
| void Join::findEquiJoinPredicates() |
| { |
| ValueIdSet allJoinPredicates; |
| ValueId leftExprId, rightExprId; |
| NABoolean predicateIsOrderPreserving; |
| ItemExpr* expr; |
| |
| equiJoinPredicates_.clear(); |
| equiJoinExpressions_.clear(); |
| |
| // If this is a TSJ there is nothing to analyze. All join predicates |
| // have been pushed down to the second child. |
| if(isTSJ()) |
| return; |
| |
| if (isInnerNonSemiJoin()) |
| { |
| allJoinPredicates = selectionPred(); |
| CMPASSERT(joinPred().isEmpty()); |
| } |
| else |
| { |
| // for an outer or semi join, the ON clause is stored in "joinPred" |
| // while the WHERE clause is stored in "selectionPred". |
| allJoinPredicates = joinPred(); |
| } |
| |
| // remove any predicates covered by the inputs |
| allJoinPredicates.removeCoveredExprs(getGroupAttr()-> |
| getCharacteristicInputs()); |
| |
| for (ValueId exprId = allJoinPredicates.init(); |
| allJoinPredicates.next(exprId); |
| allJoinPredicates.advance(exprId)) |
| { |
| expr = exprId.getItemExpr(); |
| |
| if (expr->isAnEquiJoinPredicate(child(0).getGroupAttr(), |
| child(1).getGroupAttr(), |
| getGroupAttr(), |
| leftExprId, rightExprId, |
| predicateIsOrderPreserving)) |
| { |
| equiJoinPredicates_ += exprId; |
| equiJoinExpressions_.addMapEntry(leftExprId, rightExprId); |
| } |
| } |
| |
| } // Join::findEquiJoinPredicates() |
| |
| // --------------------------------------------------------------------------- |
| // separateEquiAndNonEquiJoinPredicates is called from the Join |
| // implementation rules to weed out of equiJoinPredicates_ all |
| // the predicates that can be used by the join. The equiJoin |
| // predicates for the physical operator will be remove from |
| // the selectioPred() or joinPred() where they came from. |
| // --------------------------------------------------------------------------- |
| |
| void Join::separateEquiAndNonEquiJoinPredicates |
| (const NABoolean joinStrategyIsOrderSensitive) |
| { |
| ValueId leftExprId, rightExprId; |
| NABoolean predicateIsOrderPreserving; |
| ItemExpr* expr; |
| |
| // equiJoinPredicates_ has all the equijoin predicates found |
| // when synthesing logical properties. It is a subset of |
| // either selectionPred() or joinPred() |
| |
| ValueIdSet foundEquiJoinPredicates = equiJoinPredicates_; |
| equiJoinPredicates_.clear(); |
| equiJoinExpressions_.clear(); |
| |
| // remove any predicates covered by the inputs |
| foundEquiJoinPredicates.removeCoveredExprs(getGroupAttr()-> |
| getCharacteristicInputs()); |
| |
| for (ValueId exprId = foundEquiJoinPredicates.init(); |
| foundEquiJoinPredicates.next(exprId); |
| foundEquiJoinPredicates.advance(exprId)) |
| { |
| expr = exprId.getItemExpr(); |
| |
| if (expr->isAnEquiJoinPredicate(child(0).getGroupAttr(), |
| child(1).getGroupAttr(), |
| getGroupAttr(), |
| leftExprId, rightExprId, |
| predicateIsOrderPreserving)) |
| { |
| if ( (NOT joinStrategyIsOrderSensitive) OR |
| (joinStrategyIsOrderSensitive AND predicateIsOrderPreserving) ) |
| { |
| equiJoinPredicates_ += exprId; |
| equiJoinExpressions_.addMapEntry(leftExprId, rightExprId); |
| } |
| } |
| else |
| { |
| CMPASSERT(0); // We knew it was an equijoin predicate already |
| } |
| } |
| |
| if (isInnerNonSemiJoin()) |
| { |
| selectionPred() -= equiJoinPredicates_; |
| CMPASSERT(joinPred().isEmpty()); |
| } |
| else |
| { |
| // for an outer or semi join, the ON clause is stored in "joinPred" |
| // while the WHERE clause is stored in "selectionPred". |
| joinPred() -= equiJoinPredicates_; |
| } |
| |
| // Since we have changed the set of equijoin predicates we will consider |
| // we should resyhtnesize the left/rightHasUnqiueMatches_ flags |
| synthConstraints(NULL); |
| |
| } // Join::separateEquiAndNonEquiJoinPredicates() |
| |
| void Join::flipChildren() |
| { |
| NABoolean flipUnique; |
| flipUnique = leftHasUniqueMatches_; |
| leftHasUniqueMatches_ = rightHasUniqueMatches_; |
| rightHasUniqueMatches_ = flipUnique; |
| |
| equiJoinExpressions_.flipSides(); |
| |
| } // Join::flipChildren() |
| |
| // --------------------------------------------------------------------------- |
| // get the parallel join type and return additional info (optional) |
| // |
| // 0: serial join |
| // 1: TYPE1 join (matching partitions on both sides, including SkewBuster) |
| // 2: TYPE2 join (join one partition on one side with the |
| // entire table on the other side) |
| // --------------------------------------------------------------------------- |
| |
| Int32 Join::getParallelJoinType(ParallelJoinTypeDetail *optionalDetail) const |
| { |
| Int32 result = 0; |
| ParallelJoinTypeDetail detailedType = Join::PAR_NONE; |
| const PartitioningFunction *mpf = NULL; |
| const PartitioningFunction *cpf = NULL; |
| |
| if (getPhysicalProperty()) |
| mpf = getPhysicalProperty()->getPartitioningFunction(); |
| |
| if (mpf == NULL OR mpf->getCountOfPartitions() <= 1) |
| { |
| // no parallelism or unknown parallelism, not a parallel join |
| if (optionalDetail) |
| *optionalDetail = detailedType; |
| |
| return 0; |
| } |
| |
| if (child(1)->getPhysicalProperty()) |
| cpf = child(1)->getPhysicalProperty()->getPartitioningFunction(); |
| |
| CMPASSERT( cpf ); |
| if (cpf->castToLogPhysPartitioningFunction()) |
| { |
| // only the child of a join in DP2 can have a logphys part func |
| DCMPASSERT(getPhysicalProperty()->executeInDP2()); |
| |
| cpf = cpf->castToLogPhysPartitioningFunction()-> |
| getPhysPartitioningFunction(); |
| } |
| |
| if (cpf->isAReplicateViaBroadcastPartitioningFunction() OR |
| cpf->isAReplicateNoBroadcastPartitioningFunction()) |
| { |
| // Right child replicates, now check my own partitioning |
| // function to see whether this node just passes on the |
| // replication function. |
| |
| if (mpf->castToLogPhysPartitioningFunction()) |
| { |
| // only a join in DP2 can have a logphys part func |
| DCMPASSERT(getPhysicalProperty()->executeInDP2()); |
| |
| // check the physical part. func of the join in DP2 |
| mpf = mpf->castToLogPhysPartitioningFunction()-> |
| getPhysPartitioningFunction(); |
| } |
| |
| |
| if (NOT mpf->isAReplicateViaBroadcastPartitioningFunction() AND |
| NOT mpf->isAReplicateNoBroadcastPartitioningFunction()) |
| { |
| // See if the right child REALLY replicates data. If this is |
| // a nested join and the chosen plan was a "preferred probing |
| // order" plan, then this is really a type 1 join, because a |
| // ppo plan always demands the two tables be logically |
| // partitioned the same way. |
| if (isNestedJoin() && ((NestedJoin*)this)->probesInOrder()) |
| { |
| result = 1; |
| detailedType = PAR_OCR; |
| } |
| else |
| { |
| // right child replicates data, and the node itself doesn't, |
| // this is a type 2 join |
| result = 2; |
| if (isNestedJoin()) |
| detailedType = PAR_N2J; |
| } |
| } |
| else |
| { |
| // Both the right child and the parent replicate data. |
| // This is not a parallel join, it is a join that simply |
| // passes its replication requirement down to both of its |
| // children. The join will be executed in multiple ESPs, |
| // but it will not employ one of the two parallel algorithms |
| // (TYPE1 or TYPE2). |
| result = 0; |
| } |
| } |
| else |
| { |
| // right child is partitioned, but does not replicate, parallel type 1 join or SkewBuster or OCB |
| PartitioningFunction *opf = NULL; |
| |
| if (child(0)->getPhysicalProperty()) |
| opf = child(0)->getPhysicalProperty()->getPartitioningFunction(); |
| |
| if (opf->isAReplicateViaBroadcastPartitioningFunction()) |
| { |
| // this is an OCB join, which is considered type2 |
| result = 2; |
| detailedType = PAR_OCB; |
| } |
| else |
| { |
| // the regular TYPE1 join (including SkewBuster) |
| result = 1; |
| |
| if (opf->isASkewedDataPartitioningFunction()) |
| detailedType = PAR_SB; |
| |
| } |
| } |
| |
| if (optionalDetail) |
| *optionalDetail = detailedType; |
| |
| return result; |
| } |
| |
| // --------------------------------------------------------------------- |
| // Method to split the order req between the two join children. |
| // return FALSE if not possible |
| // --------------------------------------------------------------------- |
| NABoolean Join::splitOrderReq( |
| const ValueIdList& myOrderReq, /*IN*/ |
| ValueIdList& orderReqOfChild0, /*OUT*/ |
| ValueIdList& orderReqOfChild1 /*OUT*/) const |
| { |
| NABoolean partOfChild0List = TRUE; |
| ValueId exprId; |
| |
| GroupAttributes* child0GA = child(0).getGroupAttr(); |
| GroupAttributes* child1GA = child(1).getGroupAttr(); |
| |
| orderReqOfChild0.clear(); |
| orderReqOfChild1.clear(); |
| |
| for (CollIndex ix = 0; ix < myOrderReq.entries(); ix++) |
| { |
| exprId = myOrderReq.at(ix); |
| |
| // dummy variables for the cover test |
| ValueIdSet newInputs,referencedInputs, |
| coveredSubExpr,uncoveredExpr; |
| NABoolean coveredByChild0 = |
| child0GA->covers(exprId, |
| newInputs, |
| referencedInputs, |
| &coveredSubExpr, |
| &uncoveredExpr); |
| |
| if (NOT coveredByChild0) |
| partOfChild0List = FALSE; |
| |
| if (partOfChild0List) |
| orderReqOfChild0.insertAt(orderReqOfChild0.entries(),exprId); |
| else // i.e. NOT partOfChild0List |
| { |
| //++MV |
| // For left join we need to translate the required sort key to |
| // the right child outputs because there is an InstantiateNull function |
| // on all of the right child outputs. The InstantiateNull function will |
| // cause the cover test to fail and therefore the optimization that merge |
| // the left child sort key with the right child sort key will fail |
| // For more information see NestedJoin::synthPhysicalProperty() |
| if (isLeftJoin()) |
| { |
| const ValueIdMap &map = rightChildMapForLeftJoin(); |
| ValueId tempExprId = exprId; |
| map.mapValueIdDown(tempExprId, exprId); |
| } |
| //--MV |
| |
| coveredSubExpr.clear(); |
| uncoveredExpr.clear(); |
| NABoolean coveredByChild1 = |
| child1GA->covers(exprId, |
| newInputs, |
| referencedInputs, |
| &coveredSubExpr, |
| &uncoveredExpr); |
| |
| if (coveredByChild1) |
| { |
| orderReqOfChild1.insertAt(orderReqOfChild1.entries(),exprId); |
| } |
| else // i.e NOT (partOfChild0List || coveredByChild1) |
| { |
| orderReqOfChild0.clear(); |
| orderReqOfChild1.clear(); |
| return FALSE; |
| } |
| } |
| } // end for all expressions in the required order |
| |
| // Check to see if it is possible to split the order |
| if (child0GA->isUnique(orderReqOfChild0) OR |
| (child0GA->getMaxNumOfRows() <= 1) OR |
| (orderReqOfChild1.entries() == 0)) |
| { |
| return TRUE; |
| } |
| else |
| { |
| orderReqOfChild0.clear(); |
| orderReqOfChild1.clear(); |
| return FALSE; |
| } |
| } // end splitOrderReq() |
| |
| // --------------------------------------------------------------------- |
| // method to split the arrangement req between the two join childs. |
| // return FALSE if not possible |
| // --------------------------------------------------------------------- |
| NABoolean Join::splitArrangementReq( |
| const ValueIdSet& myArrangReq, /*IN*/ |
| ValueIdSet& ArrangReqOfChild0, /*OUT*/ |
| ValueIdSet& ArrangReqOfChild1 /*OUT*/) const |
| { |
| ArrangReqOfChild0.clear(); |
| ArrangReqOfChild1.clear(); |
| |
| ValueId exprId; |
| |
| GroupAttributes* child0GA = child(0).getGroupAttr(); |
| GroupAttributes* child1GA = child(1).getGroupAttr(); |
| |
| for (exprId = myArrangReq.init(); |
| myArrangReq.next(exprId); |
| myArrangReq.advance(exprId)) |
| { |
| // dummy variables for the cover test |
| ValueIdSet newInputs,referencedInputs, |
| coveredSubExpr,uncoveredExpr; |
| |
| // First we see if this element is covered by child 0 |
| if (child0GA->covers(exprId, |
| newInputs, |
| referencedInputs, |
| &coveredSubExpr, |
| &uncoveredExpr)) |
| { |
| ArrangReqOfChild0.insert(exprId); |
| } |
| // Only if an element is not covered by Child0 then we check |
| // Child1. i.e. if it is covered by both we bill it to Child0. |
| else |
| { |
| coveredSubExpr.clear(); |
| uncoveredExpr.clear(); |
| if (child1GA->covers(exprId, |
| newInputs, |
| referencedInputs, |
| &coveredSubExpr, |
| &uncoveredExpr)) |
| { |
| ArrangReqOfChild1.insert(exprId); |
| } |
| else |
| { |
| // If the expression was not covered soley by one of the children, then |
| // we must give up. For example, "T1.a * T2.a" needs both children. |
| ArrangReqOfChild0.clear(); |
| ArrangReqOfChild1.clear(); |
| return FALSE; |
| } |
| } // end if not covered by child0 |
| } // end for all expressions in the required arrangement |
| |
| // Check to see if it is possible to split the arrangement |
| if (child0GA->isUnique(ArrangReqOfChild0) OR |
| (child0GA->getMaxNumOfRows() <= 1) OR |
| (ArrangReqOfChild1.entries() == 0)) |
| { |
| return TRUE; |
| } |
| else |
| { |
| ArrangReqOfChild0.clear(); |
| ArrangReqOfChild1.clear(); |
| return FALSE; |
| } |
| } // end splitArrangementReq() |
| |
| NABoolean Join::ownsVEGRegions() const |
| { |
| return isLeftJoin() OR isAntiSemiJoin() OR isFullOuterJoin(); |
| } |
| |
| PlanPriority NestedJoin::computeOperatorPriority |
| (const Context* context, |
| PlanWorkSpace *pws, |
| Lng32 planNumber) |
| { |
| const PhysicalProperty* spp = context->getPlan()->getPhysicalProperty(); |
| Lng32 degreeOfParallelism = spp->getCountOfPartitions(); |
| NABoolean applySerialPremium = TRUE; |
| |
| double val; |
| Cardinality minrows, maxrows; |
| |
| CostScalar expectedrows = |
| child(0).getGroupAttr()->getResultCardinalityForEmptyInput(); |
| |
| if (child(0).getGroupAttr()->hasCardConstraint(minrows, maxrows) && |
| (maxrows <= ActiveSchemaDB()->getDefaults().getAsLong(COMP_INT_99) |
| OR CostScalar(maxrows) < CostScalar(1.2) * expectedrows)) |
| { |
| // a nested join with at most N outer rows is NOT risky |
| val = 1.0; |
| |
| // In this case premium for serial plan can be waived because cost of |
| // starting ESPs over weighs any benefit we get from parallel plan. |
| // Fix is controlled by COMP_BOOL_75, default value is ON. |
| if (CmpCommon::getDefault(COMP_BOOL_75) == DF_ON) |
| applySerialPremium = FALSE; |
| } |
| else if (context->getInputLogProp() && |
| context->getInputLogProp()->getResultCardinality().value() > 1) |
| { |
| // temporary workaround until we cost HJ under NJ correctly |
| val = 1.0; |
| } |
| else |
| { |
| // a nested join with more than N outer rows is considered risky |
| val = CURRSTMT_OPTDEFAULTS->riskPremiumNJ(); |
| // nested join cache should have a lower risk premium |
| GroupAttributes &rightGA = *child(1).getGroupAttr(); |
| NABoolean probeIsUnique = rightGA.isUnique(rightGA.getCharacteristicInputs()); |
| NABoolean isTypeOfSemiJoin = isSemiJoin() || isAntiSemiJoin(); |
| |
| if ((probeIsUnique || isTypeOfSemiJoin) && |
| (rowsFromRightHaveUniqueMatch() == FALSE) && |
| (getOperatorType() != REL_NESTED_JOIN_FLOW) && |
| (isTSJForWrite() == FALSE ) && |
| (getGroupAttr()-> |
| isEmbeddedUpdateOrDelete() == FALSE ) && |
| (!spp->executeInDP2()) && |
| (CmpCommon::getDefault(NESTED_JOIN_CACHE) != DF_OFF)) |
| { |
| double red=ActiveSchemaDB()->getDefaults().getAsDouble(COMP_INT_89); |
| if (red > 1) |
| { |
| // reduce risk premium because it's a nested join cache operator |
| val = 1 + (val - 1) / red; |
| } |
| } |
| } |
| |
| if (degreeOfParallelism <= 1 && applySerialPremium) |
| { |
| // serial plans are risky. exact an insurance premium from serial plans. |
| val *= CURRSTMT_OPTDEFAULTS->riskPremiumSerial(); |
| } |
| CostScalar premium(val); |
| PlanPriority result(0, 0, premium); |
| |
| // esp parallelism priority logic below does not apply to operators in dp2 |
| if(spp->executeInDP2()) |
| return result; |
| |
| // For the option of Max Degree of Parallelism we can either use the |
| // value set in comp_int_9 (if positive) or we use the number of CPUs |
| // if the CQD is set to -1, or feature is disabled if CQD is 0 (default). |
| Lng32 maxDegree = ActiveSchemaDB()->getDefaults().getAsLong(COMP_INT_9); |
| if (CURRSTMT_OPTDEFAULTS->maxParallelismIsFeasible() OR (maxDegree == -1) ) |
| { |
| // if CQD is set to -1 this mean use the number of CPUs |
| maxDegree = spp->getCurrentCountOfCPUs(); |
| } |
| if (maxDegree > 1) // CQD set to 0 means feature is OFF |
| { |
| if (degreeOfParallelism < maxDegree) |
| result.incrementLevels(0,-10); // need to replace with constant |
| } |
| |
| // fix for SAP case 10-100602-2913, soln 10-100602-0803 |
| // long-running select for DSO activation, query plan for empty table |
| |
| // if nested join has |
| // 1) a tuple list (something with 0 base tables) on left, and |
| // 2) a table on right, and |
| // 3) prefer_key_nested_join is set, and |
| // 4) table's predicate (including pushed join pred) forms begin/end |
| // key on table, and |
| // 5) tuple list is of reasonable size (<= tuplelist_size_threshold), |
| // and |
| // 6) table is small or has no stats |
| // then give nested join plan higher priority |
| // push it by 1 if it has a key range predicate |
| // push it by 2 if it has a unique key predicate |
| |
| // is prefer_key_nested_join active? |
| NABoolean prefer_key_nested_join = |
| (CmpCommon::getDefault(SAP_PREFER_KEY_NESTED_JOIN) == DF_ON); |
| if (prefer_key_nested_join) { |
| GroupAttributes *grpAttr0 = child(0).getGroupAttr(); |
| GroupAttributes *grpAttr1 = child(1).getGroupAttr(); |
| GroupAnalysis *grpA0 = grpAttr0->getGroupAnalysis(); |
| GroupAnalysis *grpA1 = grpAttr1->getGroupAnalysis(); |
| |
| // is left child guaranteed small? |
| NABoolean leftIsSmall = FALSE; |
| Cardinality minLeft, maxLeft; |
| if (grpAttr0->hasCardConstraint(minLeft,maxLeft) AND |
| maxLeft <= ActiveSchemaDB()->getDefaults().getAsLong |
| (SAP_TUPLELIST_SIZE_THRESHOLD)) { |
| leftIsSmall = TRUE; |
| } |
| |
| // is right a single table? |
| FileScan *rScan = NULL; |
| NABoolean rightIsTable = pws->getScanLeaf(1, planNumber, rScan); |
| |
| // is right table small? |
| NABoolean isSmallTable = |
| grpAttr1->getResultCardinalityForEmptyInput() <= |
| ActiveSchemaDB()->getDefaults().getAsLong |
| (SAP_KEY_NJ_TABLE_SIZE_THRESHOLD); |
| |
| // prefer this nested_join iff all above conditions are met |
| if (leftIsSmall && rightIsTable && isSmallTable && rScan) { |
| |
| // is predicate on unique key or prefix key? |
| NABoolean hasUniqKeyPred = FALSE; |
| NABoolean hasPrefixKeyPred = FALSE; |
| const SearchKey *sKey = rScan->getSearchKey(); |
| if (sKey) { |
| hasUniqKeyPred = sKey->isUnique(); |
| // TBD: check if key prefix selects few or many rows |
| hasPrefixKeyPred = sKey->getKeyPredicates().entries() > 0; |
| } |
| // TBD: take care of MDAM case |
| |
| // push priority by 2 if it has a unique key predicate |
| if (hasUniqKeyPred) |
| result.incrementLevels(2,0); |
| |
| // push priority by 1 if it has a prefix key predicate |
| else if (hasPrefixKeyPred) |
| result.incrementLevels(1,0); |
| } |
| } |
| |
| return result; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class NestedJoinFlow |
| // ----------------------------------------------------------------------- |
| RelExpr * NestedJoinFlow::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| NestedJoinFlow *result; |
| |
| if (derivedNode == NULL) |
| { |
| result = new (outHeap) NestedJoinFlow(NULL, |
| NULL, |
| NULL, |
| NULL, |
| outHeap); |
| } |
| else |
| result = (NestedJoinFlow*)derivedNode; |
| |
| result->sendEODtoTgt_ = sendEODtoTgt_; |
| |
| return NestedJoin::copyTopNode(result, outHeap); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class NestedJoin |
| // ----------------------------------------------------------------------- |
| |
| NABoolean NestedJoin::isLogical() const {return FALSE;} |
| |
| NABoolean NestedJoin::isPhysical() const {return TRUE;} |
| |
| const NAString NestedJoin::getText() const |
| { |
| switch (getOperatorType()) |
| { |
| case REL_NESTED_JOIN: |
| return "nested_join"; |
| case REL_LEFT_NESTED_JOIN: |
| return "left_nested_join"; |
| case REL_NESTED_SEMIJOIN: |
| return "nested_semi_join"; |
| case REL_NESTED_ANTI_SEMIJOIN: |
| return "nested_anti_semi_join"; |
| case REL_NESTED_JOIN_FLOW: |
| return "tuple_flow"; |
| default: |
| return "UNKNOWN??"; |
| } // switch |
| } // NestedJoin::getText() |
| |
| RelExpr * NestedJoin::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| RelExpr *result; |
| |
| if (derivedNode == NULL) |
| { |
| result = new (outHeap) NestedJoin(NULL, |
| NULL, |
| getOperatorType(), |
| outHeap); |
| } |
| else |
| result = derivedNode; |
| |
| return Join::copyTopNode(result, outHeap); |
| } |
| |
| NABoolean NestedJoin::allPartitionsProbed() |
| { |
| return TRUE;//all partitions probed |
| } |
| |
| // Conditions to check before applying the nested join probing cache: |
| // 1. The right child has a cardinality constraint of at most one row, |
| // or else the join is a semi-join or anti-semi-join. |
| // 2. The right child's characteristic inputs are not unique for every |
| // request (with exceptions, see below). |
| // 3. The nested join is not a NestedJoinFlow. |
| // 4. The right child does not contain any IUD operations. |
| // 5. The nested join's GroupAttributes do not include embedded IUD. |
| // 6. The execution location is not in DP2. |
| // 7. The nested join cache feature is not suppressed by a default. |
| // 8. The right child does not contain non-deterministic UDRs |
| NABoolean NestedJoin::isProbeCacheApplicable(PlanExecutionEnum loc) const |
| { |
| NABoolean result = FALSE; |
| GroupAttributes &rightGA = *child(1).getGroupAttr(); |
| NABoolean probeIsUnique = rightGA.isUnique(rightGA.getCharacteristicInputs()); |
| |
| if ( !probeIsUnique ) { |
| // dig deep into the right child to see if the searchKey associated with the |
| // only Scan node is unique. If it is unique, we also declare the probe is |
| // unique (i.e., for each probe, there is at most one row returned). The |
| // probe uniqueness property check is for the current implementation in executor |
| // where only one entry per probe in the hash table in probe cache is allocated. |
| |
| RelExpr *childExpr = child(1); |
| |
| // skip over Exchange nodes |
| while (childExpr && (childExpr->getOperator() == REL_EXCHANGE)) |
| childExpr = childExpr->child(0); |
| |
| if (childExpr) |
| { |
| OperatorTypeEnum x = childExpr->getOperator(); |
| if (x == REL_HBASE_ACCESS || x == REL_HBASE_COPROC_AGGR) |
| { |
| HbaseAccess *hbscan = (HbaseAccess*)childExpr; |
| const SearchKey *skey = hbscan->getSearchKey(); |
| if (skey && skey->isUnique()) |
| probeIsUnique = TRUE; |
| } |
| } |
| } |
| |
| NABoolean isTypeOfSemiJoin = isSemiJoin() || isAntiSemiJoin(); |
| |
| if |
| ((probeIsUnique || isTypeOfSemiJoin) && |
| (getOperatorType() != REL_NESTED_JOIN_FLOW) && |
| (isTSJForWrite() == FALSE ) && |
| (getGroupAttr()-> |
| isEmbeddedUpdateOrDelete() == FALSE ) && |
| loc != EXECUTE_IN_DP2 && |
| (CmpCommon::getDefault(NESTED_JOIN_CACHE) != DF_OFF) && |
| (rightGA.getHasNonDeterministicUDRs() == FALSE)) |
| { |
| if (! rowsFromRightHaveUniqueMatch()) |
| { |
| // big if passed and we have a chance of duplicate probes from the left |
| result = TRUE; |
| } |
| else |
| { |
| // If left probes are unique, there isn't a reason for a probe |
| // cache. However, we might be able to pull up some predicate from |
| // the right into the ProbeCache, which might give us non-unique |
| // probes. The code below targets a specific case (ALM 4783): |
| // |
| // NestedJoin |
| // / \ |
| // Aggregate (one equi-join pred is a HAVING pred) |
| // |
| // We can't detect this in the optimizer (where the nested join |
| // may point to a filter or a MEMO group), but that's fine, since |
| // we don't really want to give this unusual case a cost advantage. |
| RelExpr *childExpr = child(1); |
| |
| // skip over Exchange and MapValueIds nodes |
| while (childExpr && |
| (childExpr->getOperator() == REL_EXCHANGE || |
| childExpr->getOperator() == REL_MAP_VALUEIDS)) |
| childExpr = childExpr->child(0); |
| |
| if (childExpr && |
| childExpr->getOperator().match(REL_ANY_GROUP) && |
| CmpCommon::getDefault(NESTED_JOIN_CACHE_PREDS) != DF_OFF) |
| { |
| GroupByAgg *childGB = (GroupByAgg *) childExpr; |
| |
| if (childGB->groupExpr().isEmpty() && |
| ! childGB->selectionPred().isEmpty()) |
| // This is a scalar aggregate with a HAVING predicate, |
| // at least we know that there is a reasonable chance that |
| // we can pull up a HAVING predicate into the probe cache |
| // in method GroupByAgg::tryToPullUpPredicatesInPreCodeGen() |
| result = TRUE; |
| } |
| } |
| } |
| return result; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class MergeJoin |
| // ----------------------------------------------------------------------- |
| |
| NABoolean MergeJoin::isLogical() const {return FALSE;} |
| |
| NABoolean MergeJoin::isPhysical() const {return TRUE;} |
| |
| const NAString MergeJoin::getText() const |
| { |
| switch (getOperatorType()) |
| { |
| case REL_MERGE_JOIN: |
| return "merge_join"; |
| case REL_LEFT_MERGE_JOIN: |
| return "left_merge_join"; |
| case REL_MERGE_SEMIJOIN: |
| return "merge_semi_join"; |
| case REL_MERGE_ANTI_SEMIJOIN: |
| return "merge_anti_semi_join"; |
| default: |
| return "UNKNOWN merge join??"; |
| } // switch |
| } // MergeJoin::getText() |
| |
| RelExpr * MergeJoin::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| RelExpr *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) MergeJoin(NULL, |
| NULL, |
| getOperatorType(), |
| NULL, |
| outHeap); |
| else |
| result = derivedNode; |
| |
| return Join::copyTopNode(result, outHeap); |
| } |
| |
| void MergeJoin::addLocalExpr(LIST(ExprNode *) &xlist, |
| LIST(NAString) &llist) const |
| { |
| xlist.insert(orderedMJPreds_.rebuildExprTree()); |
| llist.insert("merge_join_predicate"); |
| |
| Join::addLocalExpr(xlist,llist); |
| } |
| |
| PlanPriority MergeJoin::computeOperatorPriority |
| (const Context* context, |
| PlanWorkSpace *pws, |
| Lng32 planNumber) |
| { |
| const PhysicalProperty* spp = context->getPlan()->getPhysicalProperty(); |
| Lng32 degreeOfParallelism = spp->getCountOfPartitions(); |
| double val = CURRSTMT_OPTDEFAULTS->riskPremiumMJ(); |
| if (degreeOfParallelism <= 1) |
| { |
| // serial plans are risky. exact an insurance premium from serial plans. |
| val *= CURRSTMT_OPTDEFAULTS->riskPremiumSerial(); |
| } |
| CostScalar premium(val); |
| PlanPriority result(0, 0, premium); |
| |
| // For the option of Max Degree of Parallelism we can either use the |
| // value set in comp_int_9 (if positive) or we use the number of CPUs |
| // if the CQD is set to -1, or feature is disabled if CQD is 0 (default). |
| Lng32 maxDegree = ActiveSchemaDB()->getDefaults().getAsLong(COMP_INT_9); |
| if (CURRSTMT_OPTDEFAULTS->maxParallelismIsFeasible() OR (maxDegree == -1) ) |
| { |
| // if CQD is set to -1 this mean use the number of CPUs |
| maxDegree = spp->getCurrentCountOfCPUs(); |
| } |
| if (maxDegree > 1) // CQD set to 0 means feature is OFF |
| { |
| if (degreeOfParallelism < maxDegree) |
| result.incrementLevels(0,-10); // need to replace with constant |
| } |
| |
| return result; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class HashJoin |
| // ----------------------------------------------------------------------- |
| |
| NABoolean HashJoin::isLogical() const { return FALSE; } |
| |
| NABoolean HashJoin::isPhysical() const { return TRUE; } |
| |
| const NAString HashJoin::getText() const |
| { |
| switch (getOperatorType()) |
| { |
| case REL_HASH_JOIN: |
| return "hash_join"; |
| case REL_LEFT_HASH_JOIN: |
| return "left_hash_join"; |
| case REL_HASH_SEMIJOIN: |
| return "semi_join"; |
| case REL_HASH_ANTI_SEMIJOIN: |
| return "hash_anti_semi_join"; |
| |
| case REL_HYBRID_HASH_JOIN: |
| { |
| if(((HashJoin *)this)->isOrderedCrossProduct()) |
| return "ordered_cross_product"; |
| else |
| return "hybrid_hash_join"; |
| } |
| case REL_LEFT_HYBRID_HASH_JOIN: |
| return "left_hybrid_hash_join"; |
| case REL_FULL_HYBRID_HASH_JOIN: |
| return "full_hybrid_hash_join"; |
| case REL_HYBRID_HASH_SEMIJOIN: |
| return "hybrid_hash_semi_join"; |
| case REL_HYBRID_HASH_ANTI_SEMIJOIN: |
| return "hybrid_hash_anti_semi_join"; |
| |
| case REL_ORDERED_HASH_JOIN: |
| { |
| if (getEquiJoinPredicates().isEmpty()) |
| return "ordered_cross_product"; |
| else |
| return "ordered_hash_join"; |
| } |
| case REL_LEFT_ORDERED_HASH_JOIN: |
| return "left_ordered_hash_join"; |
| case REL_ORDERED_HASH_SEMIJOIN: |
| return "ordered_hash_semi_join"; |
| case REL_ORDERED_HASH_ANTI_SEMIJOIN: |
| return "ordered_hash_anti_semi_join"; |
| default: |
| return "UNKNOWN hash join??"; |
| } // switch |
| } // HashJoin::getText() |
| |
| |
| |
| |
| RelExpr * HashJoin::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| RelExpr *result; |
| |
| if (derivedNode == NULL) { |
| result = new (outHeap) HashJoin(NULL, |
| NULL, |
| getOperatorType(), |
| NULL, |
| outHeap); |
| ((HashJoin *)result)->setIsOrderedCrossProduct(isOrderedCrossProduct()); |
| ((HashJoin *)result)->setReuse(isReuse()); |
| ((HashJoin *)result)->setNoOverflow(isNoOverflow()); |
| } |
| else |
| result = derivedNode; |
| |
| ((HashJoin*)result)->isNotInSubqTransform_ = isNotInSubqTransform_; |
| ((HashJoin*)result)->requireOneBroadcast_ = requireOneBroadcast_; |
| |
| ((HashJoin*)result)->innerAccessOnePartition_ = innerAccessOnePartition_; |
| |
| return Join::copyTopNode(result, outHeap); |
| } |
| |
| PlanPriority HashJoin::computeOperatorPriority |
| (const Context* context, |
| PlanWorkSpace *pws, |
| Lng32 planNumber) |
| { |
| const PhysicalProperty* spp = context->getPlan()->getPhysicalProperty(); |
| Lng32 degreeOfParallelism = spp->getCountOfPartitions(); |
| double val = 1; |
| if (degreeOfParallelism <= 1 && getInnerAccessOnePartition() == FALSE ) |
| { |
| // serial plans are risky. exact an insurance premium from serial plans. |
| // The exception is when only one partition is accessed. |
| val = CURRSTMT_OPTDEFAULTS->riskPremiumSerial(); |
| } |
| CostScalar premium(val); |
| PlanPriority result(0, 0, premium); |
| |
| if (QueryAnalysis::Instance() AND |
| QueryAnalysis::Instance()->optimizeForFirstNRows()) |
| result.incrementLevels(HASH_JOIN_FIRST_N_PRIORITY,0); |
| |
| // For the option of Max Degree of Parallelism we can either use the |
| // value set in comp_int_9 (if positive) or we use the number of CPUs |
| // if the CQD is set to -1, or feature is disabled if CQD is 0 (default). |
| Lng32 maxDegree = ActiveSchemaDB()->getDefaults().getAsLong(COMP_INT_9); |
| if (CURRSTMT_OPTDEFAULTS->maxParallelismIsFeasible() OR (maxDegree == -1) ) |
| { |
| // if CQD is set to -1 this mean use the number of CPUs |
| maxDegree = spp->getCurrentCountOfCPUs(); |
| } |
| if (maxDegree > 1) // CQD set to 0 means feature is OFF |
| { |
| if (degreeOfParallelism < maxDegree) |
| result.incrementLevels(0,-10); // need to replace with constant |
| } |
| |
| return result; |
| } |
| |
| void HashJoin::addLocalExpr(LIST(ExprNode *) &xlist, |
| LIST(NAString) &llist) const |
| { |
| if (NOT getEquiJoinPredicates().isEmpty()) |
| { |
| xlist.insert(getEquiJoinPredicates().rebuildExprTree()); |
| llist.insert("hash_join_predicates"); |
| } |
| |
| if (NOT valuesGivenToChild_.isEmpty()) |
| { |
| xlist.insert(valuesGivenToChild_.rebuildExprTree(ITM_ITEM_LIST)); |
| llist.insert("reuse_comparison_values"); |
| } |
| |
| if (NOT checkInnerNullExpr_.isEmpty()) |
| { |
| xlist.insert(checkInnerNullExpr_.rebuildExprTree()); |
| llist.insert("check_inner_null_expr"); |
| } |
| |
| if (NOT checkOuterNullExpr_.isEmpty()) |
| { |
| xlist.insert(checkOuterNullExpr_.rebuildExprTree()); |
| llist.insert("check_outer_null_expr"); |
| } |
| |
| Join::addLocalExpr(xlist,llist); |
| } |
| void HashJoin::resolveSingleColNotInPredicate() |
| { |
| |
| if (!isAntiSemiJoin() || |
| !isHashJoin()) |
| { |
| return; |
| } |
| |
| ValueIdSet jPred = joinPred(); |
| short notinCount=0; |
| |
| for ( ValueId valId = jPred.init(); |
| jPred.next(valId); |
| jPred.advance(valId)) |
| { |
| ItemExpr * itmExpr = valId.getItemExpr(); |
| |
| if (itmExpr->getOperatorType() == ITM_NOT_IN) |
| { |
| if (((NotIn*)itmExpr)->getEquivEquiPredicate() == NULL_VALUE_ID) |
| { |
| ((NotIn*)itmExpr)->cacheEquivEquiPredicate(); |
| |
| } |
| |
| // use cached value ids |
| equiJoinPredicates() += ((NotIn*)itmExpr)->getEquivEquiPredicate(); |
| |
| joinPred() -=valId; |
| joinPred() += ((NotIn*)itmExpr)->getEquivEquiPredicate(); |
| |
| notinCount++; |
| |
| setIsNotInSubqTransform(TRUE); |
| setRequireOneBroadcast(((NotIn*)itmExpr)->getIsOneInnerBroadcastRequired()); |
| } |
| } |
| |
| |
| DCMPASSERT(notinCount <=1); |
| }//void HashJoin::resolveSingleColNotInPredicate() |
| |
| |
| void Join::resolveSingleColNotInPredicate() |
| { |
| // applies only to anti_semi_joins |
| if (!isAntiSemiJoin()) { |
| return; |
| } |
| |
| short notinCount = 0; |
| |
| ValueIdSet jPred = joinPred(); |
| |
| for ( ValueId valId = jPred.init(); |
| jPred.next(valId); |
| jPred.advance(valId)) |
| { |
| ItemExpr * itmExpr = valId.getItemExpr(); |
| |
| if (itmExpr->getOperatorType() == ITM_NOT_IN) |
| { |
| if (((NotIn*)itmExpr)->getEquivNonEquiPredicate() == NULL_VALUE_ID ) |
| { |
| ((NotIn*)itmExpr)->cacheEquivNonEquiPredicate(); |
| } |
| |
| //use cached valueids |
| joinPred() -= valId; |
| joinPred() += ((NotIn*)itmExpr)->getEquivNonEquiPredicate(); |
| |
| notinCount++; |
| } |
| } |
| |
| |
| DCMPASSERT(notinCount <=1); |
| |
| }//void Join::resolveSingleColNotInPredicate() |
| |
| |
| // Join::rewriteNotInPredicate() |
| // is method is called right after the predicates are pushed down and |
| // the goal is to make sure that only the NotIn predicate is present. |
| // in any other preduicate exist besides the NotIn Predicate than we can not |
| // optimize the hash anti semi join when the outer column is nullable and may |
| // have null values |
| void Join::rewriteNotInPredicate() |
| { |
| // applies only to anti_semi_joins |
| if (!isAntiSemiJoin()) |
| { |
| return; |
| } |
| |
| ValueIdSet jPred = joinPred(); |
| |
| ItemExpr * notinExpr=NULL; |
| |
| NABoolean otherPredicatesExist = FALSE; |
| for ( ValueId valId = jPred.init(); |
| jPred.next(valId); |
| jPred.advance(valId)) |
| { |
| ItemExpr * itmExpr = valId.getItemExpr(); |
| if (itmExpr->getOperatorType() != ITM_NOT_IN) |
| { |
| otherPredicatesExist = TRUE; |
| } |
| else |
| { |
| // assert if we already encoutered a not in |
| DCMPASSERT(notinExpr == NULL); |
| notinExpr = itmExpr; |
| } |
| } |
| |
| if (notinExpr) |
| { |
| //single column |
| DCMPASSERT (notinExpr->child(0)->getOperatorType() != ITM_ITEM_LIST); |
| const NAType &outerType = notinExpr->child(0)->getValueId().getType(); |
| |
| GroupAttributes * leftChildGrpAttr = child(0).getGroupAttr(); |
| GroupAttributes * rightChildGrpAttr = child(1).getGroupAttr(); |
| const ValueIdSet &inputs = getGroupAttr()->getCharacteristicInputs(); |
| ValueIdSet refs; |
| |
| ValueId valId = notinExpr->getValueId(); |
| |
| if ((outerType.supportsSQLnull() && |
| !((NotIn*)notinExpr)->getOuterNullFilteringDetected() && |
| otherPredicatesExist) || |
| //fix for solution Id:10-100331-9194 |
| //select count(*) from h2_data_1k_37 where col_lar2 <> all (select col_id from |
| //h2_data_1k_37 where col_id=100) ; |
| //NotIn(VEGRef(col_lar2),VEGRef(col_id=100)) is in the join prdicate and in t he characteristc |
| //outputs of the child. changing it to euipredicate may lead to wrong results |
| //the below code will change the NotIn(a,b) predicate to NOT(a<>B is true) when the predicate is covered |
| //by one of of children |
| leftChildGrpAttr->covers(valId, inputs, refs) || |
| rightChildGrpAttr->covers(valId, inputs, refs)) |
| { |
| ValueId tmpId = ((NotIn *)notinExpr)->createEquivNonEquiPredicate(); |
| ItemExpr * tmpItemExpr = tmpId.getItemExpr(); |
| valId.replaceItemExpr(tmpItemExpr); |
| } |
| } |
| |
| }//Join::rewriteNotInPredicate() |
| |
| // Join::rewriteNotInPredicate( ValueIdSet & origVidSet, ValueIdSet & newVidSet) |
| // if both the outer and the inner columns are not nullable or are nullable but |
| // have no NULL values then the NotIn Predicate is changed to an equi-predicate. |
| // otherwise the NotIn predicate is not changed and the optimizer will decide what |
| // to do with it |
| // this method is called right after the pull up of the predicates in join::transformNode() |
| // in the case of anti semi join the inner predicates are pulled and added to join pred |
| // and the outer predicates are pulled and added to selectionPredicates |
| // When we look for outer NUll filetering predicates we look in the selection predocates |
| // and when we look inner NULL filtering predicates we look in the join predicates |
| void Join::rewriteNotInPredicate( ValueIdSet & origVidSet, ValueIdSet & newVidSet) |
| { |
| // applies only to anti_semi_joins |
| if (!isAntiSemiJoin()) |
| { |
| return; |
| } |
| |
| ValueIdSet jPred = joinPred(); |
| ValueIdSet selPred = selectionPred(); |
| |
| short notinCount = 0; |
| |
| for ( ValueId valId = joinPred().init(); |
| joinPred().next(valId); |
| joinPred().advance(valId)) |
| { |
| ItemExpr * itmExpr = valId.getItemExpr(); |
| |
| if (itmExpr->getOperatorType() == ITM_NOT_IN) |
| { |
| //single column |
| if (itmExpr->child(0)->getOperatorType() != ITM_ITEM_LIST) |
| { |
| const NAType &innerType = itmExpr->child(1)->getValueId().getType(); |
| const NAType &outerType = itmExpr->child(0)->getValueId().getType(); |
| |
| selPred -= valId; |
| jPred -= valId; |
| |
| NABoolean child0IsNotNullable = selPred.isNotNullable(itmExpr->child(0)) ; |
| NABoolean child1IsNotNullable = jPred.isNotNullable(itmExpr->child(1)) ; |
| |
| if ((!innerType.supportsSQLnull() || child1IsNotNullable) && |
| (!outerType.supportsSQLnull() || child0IsNotNullable) ) |
| { |
| origVidSet += valId; |
| // we can change the not in predicate to an equi-predicate in this case |
| newVidSet += ((NotIn *)itmExpr)->createEquivEquiPredicate(); |
| } |
| else |
| { |
| // outer refrences case are not handled by optimization |
| ValueIdSet rightSideofPred; |
| ValueIdSet tempSet; |
| |
| rightSideofPred.insert(itmExpr->child(1)->getValueId()); |
| |
| rightSideofPred.getReferencedPredicates(child(0)->getGroupAttr()->getCharacteristicOutputs(), tempSet) ; |
| |
| if (!tempSet.isEmpty()) |
| { |
| origVidSet += valId; |
| // we can change the not in predicate to an equi-predicate in this case |
| newVidSet += ((NotIn *)itmExpr)->createEquivNonEquiPredicate(); |
| } |
| else |
| { |
| if (CmpCommon::getDefault(NOT_IN_OUTER_OPTIMIZATION) == DF_OFF) |
| { |
| //NOT_IN_OUTER_OPTIMIZATION == OFF ==> if outer is nullable and may have NULL values |
| // change to Non equi-predicate here |
| if ( outerType.supportsSQLnull() && |
| !child0IsNotNullable) |
| { |
| origVidSet += valId; |
| // we can change the not in predicate to an equi-predicate in this case |
| newVidSet += ((NotIn *)itmExpr)->createEquivNonEquiPredicate(); |
| } |
| } |
| else |
| { |
| // case where outer or inner columns (or both) is nullable and may have NULL values |
| // optimizer will decide depending on the type of join |
| // hash join ==> equi-predicate with cancel expression when inner is nullbale |
| // ==> filter to filter out NULL values coming from outer side |
| // ==> when inner is not empty |
| // ==> NUILL values coming from outer side are not filtered out when |
| // ==> inner is empty |
| // non hash join ==> Non equi-predicate |
| if (child0IsNotNullable) |
| { |
| ((NotIn*)itmExpr)->setOuterNullFilteringDetected(TRUE); |
| } |
| if (child1IsNotNullable) |
| { |
| ((NotIn*)itmExpr)->setInnerNullFilteringDetected(TRUE); |
| } |
| } |
| } |
| } |
| } |
| else |
| { |
| ValueIdSet predSet ; |
| //ValueIdSet jPreds; |
| //ValueIdSet selPred; |
| |
| //jPreds = joinPred(); |
| //selPred = selectionPred(); |
| |
| predSet = NotIn::rewriteMultiColNotInPredicate( valId, |
| joinPred(), |
| selectionPred()); |
| |
| DCMPASSERT(predSet.entries() >0); |
| |
| origVidSet += valId; |
| |
| newVidSet += predSet; |
| |
| } |
| |
| notinCount++; |
| |
| }//if (itmExpr->getOperatorType() == ITM_NOT_IN) |
| |
| } |
| DCMPASSERT(notinCount <=1); |
| |
| }//void Join::rewriteNotInPredicate() |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class Intersect |
| // ----------------------------------------------------------------------- |
| |
| Intersect::Intersect(RelExpr *leftChild, |
| RelExpr *rightChild) |
| : RelExpr(REL_INTERSECT, leftChild, rightChild) |
| { } |
| |
| Intersect::~Intersect() {} |
| |
| Int32 Intersect::getArity() const { return 2; } |
| |
| const NAString Intersect::getText() const |
| { |
| return "intersect"; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // // member functions for class Except |
| // // ----------------------------------------------------------------------- |
| Except::Except(RelExpr *leftChild, |
| RelExpr *rightChild) |
| : RelExpr(REL_EXCEPT, leftChild, rightChild) |
| { } |
| |
| Except::~Except() {} |
| |
| Int32 Except::getArity() const { return 2; } |
| |
| const NAString Except::getText() const |
| { |
| return "except"; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class Union |
| // ----------------------------------------------------------------------- |
| Union::Union(RelExpr *leftChild, |
| RelExpr *rightChild, |
| UnionMap *unionMap, |
| ItemExpr *condExpr, |
| OperatorTypeEnum otype, |
| CollHeap *oHeap, |
| NABoolean sysGenerated, |
| NABoolean mayBeCacheable |
| ) |
| : RelExpr(otype, leftChild, rightChild, oHeap), |
| condExprTree_(condExpr) |
| ,trigExceptExprTree_(NULL) |
| ,previousIF_(NULL) |
| ,flags_(0) |
| ,leftList_(NULL) |
| ,rightList_(NULL) |
| ,currentChild_(-1) |
| ,alternateRightChildOrderExprTree_(NULL) //++MV |
| ,isSystemGenerated_(sysGenerated) |
| ,isSerialUnion_(FALSE) |
| ,variablesSet_(oHeap) |
| |
| { |
| if ( NOT mayBeCacheable ) |
| setNonCacheable(); |
| |
| if (unionMap != NULL) |
| { |
| unionMap_ = unionMap; |
| unionMap_->count_++; |
| } |
| else |
| unionMap_ = new (oHeap) UnionMap; |
| |
| condExpr_.clear(); |
| trigExceptExpr_.clear(); |
| alternateRightChildOrderExpr_.clear(); //++MV |
| variablesSet_.clear(); |
| controlFlags_ = 0; //++ Triggers - |
| } |
| |
| Union::~Union() { if (unionMap_->count_ == 0) delete unionMap_;} |
| |
| Int32 Union::getArity() const { return 2; } |
| |
| void Union::rewriteUnionExpr(const ValueIdSet &unionExpr, |
| ValueIdSet &leftExpr, |
| ValueIdSet &rightExpr) const |
| { |
| // walk the original selection predicates and rewrite them in terms |
| // of the mapped value ids of the union's inputs |
| for (ValueId x = unionExpr.init(); unionExpr.next(x); unionExpr.advance(x)) |
| { |
| ValueId newLeftExpr = |
| x.getItemExpr()->mapAndRewrite(getLeftMap(),TRUE); |
| ValueId newRightExpr = |
| x.getItemExpr()->mapAndRewrite(getRightMap(),TRUE); |
| |
| leftExpr += newLeftExpr; |
| rightExpr += newRightExpr; |
| } |
| } // Union::rewriteExprs() |
| |
| void Union::pushdownCoveredExpr(const ValueIdSet & outputExpr, |
| const ValueIdSet & newExternalInputs, |
| ValueIdSet & predicatesOnParent, |
| const ValueIdSet * setOfValuesReqdByParent, |
| Lng32 // childIndex ignored |
| ) |
| { |
| ValueIdSet resultSet = outputExpr; |
| if (setOfValuesReqdByParent) |
| resultSet += *setOfValuesReqdByParent; |
| resultSet += getGroupAttr()->getCharacteristicInputs(); |
| |
| // alternateRightChildOrderExpr expressions should not be pushed down |
| resultSet.insertList(alternateRightChildOrderExpr()); // ++MV |
| // --------------------------------------------------------------------- |
| // Not all the output columns from the union may be needed. |
| // Map the required input list to the corresponding left |
| // and right required outputs list |
| // --------------------------------------------------------------------- |
| ValueIdSet valuesRequiredFromLeft, valuesRequiredFromRight; |
| rewriteUnionExpr(resultSet, |
| valuesRequiredFromLeft, |
| valuesRequiredFromRight); |
| |
| // --------------------------------------------------------------------- |
| // Rewrite selectionPred() |
| // --------------------------------------------------------------------- |
| ValueIdSet leftPred, rightPred, emptySet; |
| rewriteUnionExpr(predicatesOnParent, leftPred, rightPred); |
| |
| // push the left predicates to the left subtree |
| // empty set for the first argument indicates that there are no |
| // non-essential outputs, (in other words, outputs that are |
| // simply passed through) |
| RelExpr::pushdownCoveredExpr(emptySet, |
| newExternalInputs, |
| leftPred, |
| &valuesRequiredFromLeft, |
| 0 |
| ); |
| |
| // push the right predicates to the right subtree |
| RelExpr::pushdownCoveredExpr(emptySet, |
| newExternalInputs, |
| rightPred, |
| &valuesRequiredFromRight, |
| 1 |
| ); |
| |
| // Verify that all the predicates were pushed |
| leftPred -= child(0)->selectionPred(); |
| CMPASSERT( leftPred.isEmpty() ); |
| |
| rightPred -= child(1)->selectionPred(); |
| CMPASSERT( rightPred.isEmpty() ); |
| |
| // All the predicates have been pushed down to the children. |
| predicatesOnParent.clear(); |
| |
| } // Union::pushdownCoveredExpr |
| |
| void Union::getPotentialOutputValues(ValueIdSet & outputValues) const |
| { |
| outputValues.clear(); |
| // |
| // The output of the union is defined by the ValueIdUnion |
| // expressions that are maintained in the colMapTable_. |
| // |
| Lng32 ne = unionMap_->colMapTable_.entries(); |
| for (Lng32 index = 0; index < ne; index++) |
| { |
| // Accumulate the ValueIds of the result of the union |
| // in the set provided by the caller. |
| outputValues += ((ValueIdUnion *) (unionMap_->colMapTable_[index].getItemExpr()))->getResult(); |
| } |
| } // Union::getPotentialOutputValues() |
| |
| |
| HashValue Union::topHash() |
| { |
| HashValue result = RelExpr::topHash(); |
| |
| // result ^= colMapTable_; |
| |
| return result; |
| } |
| |
| NABoolean Union::duplicateMatch(const RelExpr & other) const |
| { |
| if (NOT RelExpr::duplicateMatch(other)) |
| return FALSE; |
| |
| Union &o = (Union &) other; |
| |
| if (NOT ((unionMap_ == o.unionMap_) AND |
| (condExpr_ == o.condExpr_) AND |
| (trigExceptExpr_ == o.trigExceptExpr_) AND |
| (alternateRightChildOrderExpr_ == o.alternateRightChildOrderExpr_))) //++MV |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| RelExpr * Union::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| Union *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) Union(NULL, |
| NULL, |
| unionMap_, |
| NULL, |
| getOperatorType(), |
| outHeap); |
| else |
| result = (Union *) derivedNode; |
| |
| if (condExprTree_ != NULL) |
| result->condExprTree_ = condExprTree_->copyTree(outHeap)->castToItemExpr(); |
| |
| if (trigExceptExprTree_ != NULL) |
| result->trigExceptExprTree_ = trigExceptExprTree_->copyTree(outHeap)->castToItemExpr(); |
| |
| //++MV - |
| if (alternateRightChildOrderExprTree_ != NULL) |
| result->alternateRightChildOrderExprTree_ = |
| alternateRightChildOrderExprTree_->copyTree(outHeap)->castToItemExpr(); |
| //--MV - |
| |
| result->condExpr_ = condExpr_; |
| result->trigExceptExpr_ = trigExceptExpr_; |
| result->alternateRightChildOrderExpr_ = alternateRightChildOrderExpr_; |
| result->setUnionFlags(getUnionFlags()); |
| //++Triggers - |
| result->controlFlags_ = controlFlags_; |
| |
| result->isSystemGenerated_ = isSystemGenerated_; |
| |
| if (getSerialUnion()) |
| { |
| result->setSerialUnion(); |
| } |
| return RelExpr::copyTopNode(result, outHeap); |
| } |
| |
| void Union::addValueIdUnion(ValueId vidUnion, CollHeap* heap) |
| { |
| ValueIdUnion *xvid = (ValueIdUnion *) vidUnion.getItemExpr(); |
| |
| CMPASSERT(vidUnion.getItemExpr()->getOperatorType() == ITM_VALUEIDUNION); |
| |
| // This method is only called by the binder when it is first |
| // building the unionMap |
| if(unionMap_->count_ > 1) |
| { |
| unionMap_->count_--; |
| unionMap_ = new (heap) UnionMap; |
| } |
| |
| CMPASSERT(unionMap_->count_ == 1); |
| |
| |
| // add the value id to the list of value ids for ValueIdUnion expressions |
| // and also add entries to the two maps that describe the same information |
| unionMap_->colMapTable_.insert(vidUnion); |
| unionMap_->leftColMap_.addMapEntry(vidUnion,xvid->getLeftSource()); |
| unionMap_->rightColMap_.addMapEntry(vidUnion,xvid->getRightSource()); |
| } |
| |
| //++ Triggers - |
| void Union::setNoOutputs() |
| { |
| CMPASSERT(flags_ == UNION_BLOCKED || flags_ == UNION_ORDERED); |
| |
| controlFlags_ |= NO_OUTPUTS; |
| } |
| |
| void Union::addLocalExpr(LIST(ExprNode *) &xlist, |
| LIST(NAString) &llist) const |
| { |
| if (condExprTree_ != NULL) |
| { |
| xlist.insert(condExprTree_); |
| llist.insert("condExprTree"); |
| } |
| |
| if (NOT condExpr_.isEmpty()) |
| { |
| xlist.insert(condExpr_.rebuildExprTree()); |
| llist.insert("condExpr"); |
| } |
| |
| if (trigExceptExprTree_ != NULL) |
| { |
| xlist.insert(trigExceptExprTree_); |
| llist.insert("trigExceptExprTree"); |
| } |
| |
| if (NOT trigExceptExpr_.isEmpty()) |
| { |
| xlist.insert(trigExceptExpr_.rebuildExprTree()); |
| llist.insert("trigExceptExpr"); |
| } |
| |
| if (alternateRightChildOrderExprTree_ != NULL) |
| { |
| xlist.insert(alternateRightChildOrderExprTree_); |
| llist.insert("alternateRightChildOrderExprTree"); |
| } |
| |
| if (NOT alternateRightChildOrderExpr_.isEmpty()) |
| { |
| xlist.insert(alternateRightChildOrderExpr_.rebuildExprTree()); |
| llist.insert("alternateRightChildOrderExpr"); |
| } |
| |
| RelExpr::addLocalExpr(xlist,llist); |
| } |
| |
| const NAString Union::getText() const |
| { |
| NAString text; |
| switch (getUnionFlags()) |
| { |
| case UNION_ORDERED : text += "ordered_union"; break; |
| case UNION_BLOCKED : text += "blocked_union"; break; |
| case UNION_COND_UNARY : text += "unary_union"; break; |
| default : text += "merge_union"; break; |
| } |
| |
| if (getOperatorType() == REL_MERGE_UNION) |
| text += " (phys.)"; |
| |
| return text; |
| } |
| |
| ItemExpr *Union::getCondExprTree() |
| { |
| return condExprTree_; |
| } |
| |
| void Union::addCondExprTree(ItemExpr *condExpr) |
| { |
| ExprValueId t = condExprTree_; |
| |
| ItemExprTreeAsList(&t, ITM_ITEM_LIST).insert(condExpr); |
| condExprTree_ = t.getPtr(); |
| } |
| |
| ItemExpr *Union::removeCondExprTree() |
| { |
| ItemExpr *result = condExprTree_; |
| condExprTree_ = NULL; |
| |
| return result; |
| } |
| |
| ItemExpr *Union::getTrigExceptExprTree() |
| { |
| return trigExceptExprTree_; |
| } |
| |
| void Union::addTrigExceptExprTree(ItemExpr *trigExceptExpr) |
| { |
| ExprValueId t = trigExceptExprTree_; |
| |
| ItemExprTreeAsList(&t, ITM_ITEM_LIST).insert(trigExceptExpr); |
| trigExceptExprTree_ = t.getPtr(); |
| } |
| |
| ItemExpr *Union::removeTrigExceptExprTree() |
| { |
| ItemExpr *result = trigExceptExprTree_; |
| trigExceptExprTree_ = NULL; |
| |
| return result; |
| } |
| |
| // If this Union node is an IF node of a compound statement, this function |
| // returns either the left or right list of value ids associated with the node. |
| // It returns the left one if we are currently visiting the left child. |
| // Otherwise we return the right one. |
| AssignmentStHostVars *Union::getCurrentList(BindWA *bindWA) |
| { |
| if (currentChild_ == 0) { |
| if (!leftList_) { |
| leftList_ = new (bindWA->wHeap()) AssignmentStHostVars(bindWA); |
| } |
| return leftList_; |
| } |
| else { |
| if (!rightList_) { |
| rightList_ = new (bindWA->wHeap()) AssignmentStHostVars(bindWA); |
| } |
| return rightList_; |
| } |
| } |
| |
| |
| // When we are in a CompoundStatement and we have IF statements in it, |
| // we must create a RETDesc for this Union node (which is |
| // actually an IF node). In this function, we create a list of |
| // ValueIdUnion nodes. We figure out which valueids |
| // of the left child must be matched with those of the right child |
| // (for instance, if SET :a appears in both children) and which must |
| // be matched with previously existing valueids (for instance, if |
| // SET :a = ... only appears in one branch, then the ValueIdUnion associated |
| // with that SET statement must reference the value id of :a that existed before |
| // this IF statement). |
| RETDesc * Union::createReturnTable(AssignmentStArea *assignArea, BindWA *bindWA) |
| { |
| AssignmentStHostVars * leftList = leftList_; |
| AssignmentStHostVars * rightList = rightList_; |
| NABoolean foundAMatch = FALSE; |
| AssignmentStHostVars *globalList = assignArea->getAssignmentStHostVars(); |
| NAString const *nameOfLeftVar; |
| |
| RETDesc *resultTable = new (bindWA->wHeap()) RETDesc(bindWA); |
| ColRefName *refName = new (bindWA->wHeap()) ColRefName(); |
| |
| AssignmentStHostVars *listOfPreviousIF = NULL; |
| |
| // We find the list of variables of the previous IF node. We will |
| // need to update it since some of its variables may get new value ids |
| // within the IF statement |
| |
| if (previousIF_) { |
| |
| short currentChild = previousIF_->currentChild(); |
| |
| if (currentChild == 0) { |
| listOfPreviousIF = previousIF_->leftList(); |
| } |
| else { |
| listOfPreviousIF = previousIF_->rightList(); |
| } |
| } |
| |
| // Scan the left list and look for matches in the right List |
| while (leftList && (leftList->var())) { |
| |
| foundAMatch = FALSE; |
| |
| nameOfLeftVar = &(leftList->var()->getName()); |
| |
| rightList = rightList_; |
| |
| while (rightList && rightList->var()) { |
| |
| NAString const *nameOfRightVar = &(rightList->var()->getName()); |
| |
| if (*nameOfLeftVar == *nameOfRightVar) { |
| foundAMatch = TRUE; |
| break; |
| } |
| |
| rightList = rightList->next(); |
| } |
| |
| AssignmentStHostVars *ptrLeftVar = globalList->findVar(*nameOfLeftVar); |
| CMPASSERT(ptrLeftVar); |
| |
| // If we found a match, we create a ValueIdUnion node of the paired match; otherwise |
| // we pair the current value id of the variable in question with the value id it |
| // had before the IF statement. If the variable does not have a value id, we bind it. |
| ValueId value ; |
| if (foundAMatch) { |
| value = rightList->currentValueId(); |
| } |
| else { |
| ValueIdList list = ptrLeftVar->valueIds(); |
| if (list.entries() > 0) { |
| value = ptrLeftVar->currentValueId(); |
| } |
| else { |
| // Get a value id for this variable. |
| ItemExpr *expr = ptrLeftVar->var()->bindNode(bindWA); |
| if (bindWA->errStatus()) { |
| return NULL; |
| } |
| |
| value = expr->getValueId(); |
| } |
| } |
| |
| ValueIdUnion *vidUnion = new (bindWA->wHeap()) |
| ValueIdUnion(leftList->currentValueId(), |
| value, |
| NULL_VALUE_ID); |
| vidUnion->bindNode(bindWA); |
| if (bindWA->errStatus()) { |
| delete vidUnion; |
| return NULL; |
| } |
| |
| ValueId valId = vidUnion->getValueId(); |
| addValueIdUnion(valId,bindWA->wHeap()); |
| |
| resultTable->addColumn(bindWA, *refName, valId); |
| |
| // The variable inside the IF gets the value id of the ValueIdUnion just |
| // generated. |
| ptrLeftVar->setCurrentValueId(valId); |
| |
| // Also update the variable list in the previous IF node |
| if (listOfPreviousIF) { |
| listOfPreviousIF->addToListInIF(leftList->var(), valId); |
| } |
| |
| leftList = leftList->next(); |
| } // while |
| |
| // We now search the right list and do a similar processing for the variables on |
| // the right side that are not on the left |
| rightList = rightList_; |
| |
| while (rightList && (rightList->var())) { |
| |
| foundAMatch = FALSE; |
| |
| NAString const *nameOfRightVar = &(rightList->var()->getName()); |
| |
| AssignmentStHostVars *ptrRightVar = globalList->findVar(*nameOfRightVar); |
| CMPASSERT(ptrRightVar); |
| |
| leftList = leftList_; |
| |
| while (leftList && (leftList->var())) { |
| |
| nameOfLeftVar = &(leftList->var()->getName()); |
| |
| if (*nameOfLeftVar == *nameOfRightVar) { |
| foundAMatch = TRUE; |
| break; |
| } |
| |
| leftList = leftList->next(); |
| } |
| |
| // Create the ValueIdUnion of the two value ids |
| if (!foundAMatch) { |
| ValueId value; |
| ValueIdList list = ptrRightVar->valueIds(); |
| if (list.entries() > 0) { |
| value = ptrRightVar->currentValueId(); |
| } |
| else { |
| // Get a value id for this variable. |
| ItemExpr *expr = ptrRightVar->var()->bindNode(bindWA); |
| value = expr->getValueId(); |
| } |
| |
| ValueIdUnion *vidUnion = new (bindWA->wHeap()) |
| ValueIdUnion(value, rightList->currentValueId(), |
| NULL_VALUE_ID); |
| vidUnion->bindNode(bindWA); |
| if (bindWA->errStatus()) { |
| delete vidUnion; |
| return NULL; |
| } |
| |
| ValueId valId = vidUnion->getValueId(); |
| addValueIdUnion(valId, bindWA->wHeap()); |
| |
| resultTable->addColumn(bindWA, *refName, valId); |
| |
| // The variable inside the IF gets the value id of the ValueIdUnion just |
| // generated. |
| ptrRightVar->setCurrentValueId(valId); |
| |
| // Also update the variable list in the previous IF node |
| if (listOfPreviousIF) { |
| listOfPreviousIF->addToListInIF(rightList->var(), valId); |
| } |
| |
| } // if (!foundAMatch) |
| |
| rightList = rightList->next(); |
| |
| } // while |
| |
| return resultTable; |
| } |
| |
| //++ MV - |
| void Union::addAlternateRightChildOrderExprTree(ItemExpr *alternateRightChildOrderExprTree) |
| { |
| ExprValueId t = alternateRightChildOrderExprTree_; |
| |
| ItemExprTreeAsList(&t, ITM_ITEM_LIST).insert(alternateRightChildOrderExprTree); |
| alternateRightChildOrderExprTree_ = t.getPtr(); |
| } |
| |
| ItemExpr *Union::removeAlternateRightChildOrderExprTree() |
| { |
| ItemExpr *result = alternateRightChildOrderExprTree_; |
| alternateRightChildOrderExprTree_ = NULL; |
| |
| return result; |
| } |
| // MV-- |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class MergeUnion |
| // ----------------------------------------------------------------------- |
| |
| MergeUnion::~MergeUnion() {} |
| |
| NABoolean MergeUnion::isLogical() const { return FALSE; } |
| |
| NABoolean MergeUnion::isPhysical() const { return TRUE; } |
| |
| HashValue MergeUnion::topHash() |
| { |
| HashValue result = Union::topHash(); |
| |
| // result ^= mergeExpr_; |
| |
| return result; |
| } |
| |
| NABoolean MergeUnion::duplicateMatch(const RelExpr & other) const |
| { |
| if (!RelExpr::duplicateMatch(other)) |
| return FALSE; |
| |
| MergeUnion &o = (MergeUnion &) other; |
| |
| // if (mergeExpr_ != o.mergeExpr_) |
| |
| ABORT("duplicateMatch shouldn't be called for physical nodes"); |
| return FALSE; |
| |
| } |
| |
| RelExpr * MergeUnion::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| MergeUnion *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) MergeUnion(NULL, |
| NULL, |
| new (outHeap)UnionMap(*getUnionMap()), |
| getOperatorType(), |
| outHeap); |
| else |
| result = (MergeUnion *) derivedNode; |
| |
| result->mergeExpr_ = mergeExpr_; |
| |
| return Union::copyTopNode(result, outHeap); |
| } |
| |
| void MergeUnion::setSortOrder(const ValueIdList &newSortOrder) |
| { |
| sortOrder_ = newSortOrder; |
| buildMergeExpr(); |
| } |
| |
| void MergeUnion::addLocalExpr(LIST(ExprNode *) &xlist, |
| LIST(NAString) &llist) const |
| { |
| if (sortOrder_.entries() > 0) |
| { |
| xlist.insert(sortOrder_.rebuildExprTree(ITM_ITEM_LIST)); |
| llist.insert("sort_order"); |
| } |
| |
| if (mergeExpr_ != NULL) |
| { |
| xlist.insert(mergeExpr_); |
| llist.insert("merge_expr"); |
| } |
| |
| Union::addLocalExpr(xlist,llist); |
| } |
| |
| void MergeUnion::buildMergeExpr() |
| { |
| // --------------------------------------------------------------------- |
| // build the merge expression (an expression that tells which of the |
| // two input rows, left or right, should be returned next) by creating |
| // an expression "left <= right" from the sort order. |
| // --------------------------------------------------------------------- |
| ItemExpr *leftList = NULL; |
| ItemExpr *rightList = NULL; |
| BiRelat *result = NULL; |
| |
| if (sortOrder_.entries() > 0) |
| { |
| for (Lng32 i = 0; i < (Lng32)sortOrder_.entries(); i++) |
| { |
| ItemExpr *leftItem; |
| ItemExpr *rightItem; |
| |
| leftItem = sortOrder_[i].getItemExpr()-> |
| mapAndRewrite(getLeftMap(),TRUE).getItemExpr(); |
| rightItem = sortOrder_[i].getItemExpr()-> |
| mapAndRewrite(getRightMap(),TRUE).getItemExpr(); |
| |
| // swap left and right if DESC is specified. |
| if(leftItem->getOperatorType() == ITM_INVERSE) |
| { |
| // both streams must be sorted according to the same order. |
| CMPASSERT(rightItem->getOperatorType() == ITM_INVERSE); |
| ItemExpr *temp = leftItem; |
| leftItem = rightItem; |
| rightItem = temp; |
| } |
| |
| // add the newly formed fields of the sort key to the |
| // left and right lists of sort keys |
| if (leftList != NULL) |
| { |
| leftList = new (CmpCommon::statementHeap()) |
| ItemList(leftList,leftItem); |
| rightList = new (CmpCommon::statementHeap()) |
| ItemList(rightList,rightItem); |
| } |
| else |
| { |
| // both left and right list must be NULL |
| leftList = leftItem; |
| rightList = rightItem; |
| } |
| } |
| |
| result = new (CmpCommon::statementHeap()) |
| BiRelat(ITM_LESS_EQ,leftList,rightList); |
| // make the comparison such that NULLs compare greater than instead |
| // of making the expression result NULL |
| result->setSpecialNulls(TRUE); |
| |
| result->synthTypeAndValueId(); |
| } |
| |
| // store the result in the merge expression |
| mergeExpr_ = result; |
| } |
| |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class GroupByAgg |
| // ----------------------------------------------------------------------- |
| |
| GroupByAgg::~GroupByAgg() {} |
| |
| Int32 GroupByAgg::getArity() const { return 1; } |
| |
| void GroupByAgg::pushdownCoveredExpr(const ValueIdSet & outputExpr, |
| const ValueIdSet & newExternalInputs, |
| ValueIdSet & predicatesOnParent, |
| const ValueIdSet * setOfValuesReqdByParent, |
| Lng32 childIndex |
| ) |
| { |
| // --------------------------------------------------------------------- |
| // predicates can only be pushed down if the group by did contain |
| // a group by clause or if this is a scalar groupby for a subquery that |
| // contains null rejecting predicates. If the subquery contains null-rej. |
| // preds then it does not need to do null instantiation for the empty |
| // result set and therefore we do not create a separate VEGRegion for this |
| // subquery. This means that preds can be freely pushed down in this case. |
| // See GroupByAgg::pullUpPreds for a symmetric condition. |
| // --------------------------------------------------------------------- |
| ValueIdSet pushablePredicates; |
| ValueIdSet exprOnParent; |
| ValueIdSet emptySet; |
| |
| if (NOT groupExpr().isEmpty() || containsNullRejectingPredicates()) |
| pushablePredicates = predicatesOnParent; |
| #if 0 |
| else |
| computeValuesReqdForPredicates(predicatesOnParent, |
| exprOnParent); |
| #endif |
| |
| // --------------------------------------------------------------------- |
| // Cause the retrieval of all those values that are needed for |
| // computing the aggregate functions and the group by list. |
| // --------------------------------------------------------------------- |
| getValuesRequiredForEvaluatingAggregate(exprOnParent); |
| exprOnParent += groupExpr(); |
| |
| // --------------------------------------------------------------------- |
| RelExpr::pushdownCoveredExpr(emptySet, |
| newExternalInputs, |
| pushablePredicates, |
| &exprOnParent, |
| childIndex |
| ); |
| |
| // --------------------------------------------------------------------- |
| // Set the value of predicatesOnParent appropriately. |
| // --------------------------------------------------------------------- |
| if (NOT groupExpr().isEmpty() || containsNullRejectingPredicates()) |
| predicatesOnParent.intersectSet(pushablePredicates); |
| |
| } // GroupByAgg::pushdownCoveredExpr |
| |
| void GroupByAgg::getPotentialOutputValues(ValueIdSet & outputValues) const |
| { |
| outputValues.clear(); |
| |
| // Assign the grouping expressions and the aggregate functions |
| // that are computed here as the outputs. |
| // |
| outputValues += groupExpr(); |
| outputValues += aggregateExpr(); |
| |
| // If we're enforcing an ITM_ONE_ROW on (x,y), then we can produce not |
| // merely the ITM_ONE_ROW, but also x and y, so add them to our outputs. |
| // For example, if the aggregate is, say, |
| // ITM_ONE_ROW(VEGRef_10(T.A,ixT.A), VEGRef_15(T.B,ixT.B)) |
| // { example query: select * from S where (select A,B from T) < (100,200) } |
| // then add value ids 10 and 11 to our characteristic outputs. |
| // |
| for (ValueId aggid = aggregateExpr().init(); |
| aggregateExpr().next(aggid); |
| aggregateExpr().advance(aggid)) |
| { |
| ItemExpr *aggie = aggid.getItemExpr(); |
| if (aggie->getOperatorType() == ITM_ONE_ROW) |
| { |
| ValueIdSet moreAvailableOutputs; |
| aggie->child(0)->convertToValueIdSet(moreAvailableOutputs, |
| NULL, ITM_ITEM_LIST, FALSE); |
| outputValues += moreAvailableOutputs; |
| } |
| } |
| } // GroupByAgg::getPotentialOutputValues() |
| |
| const NAString GroupByAgg::getText() const |
| { |
| if (NOT groupExpr().isEmpty()) |
| { |
| if (isNotAPartialGroupBy()) |
| return "groupby"; |
| else if (isAPartialGroupByRoot()) |
| return "partial_groupby_root"; |
| else if (isAPartialGroupByNonLeaf()) |
| return "partial_groupby_non_leaf"; |
| else |
| return "partial_groupby_leaf"; |
| } |
| else |
| { |
| if (isNotAPartialGroupBy()) |
| return "scalar_aggr"; |
| else if (isAPartialGroupByRoot()) |
| return "partial_aggr_root"; |
| else if (isAPartialGroupByNonLeaf()) |
| return "partial_aggr_non_leaf"; |
| else |
| return "partial_aggr_leaf"; |
| } |
| |
| } // GroupByAgg::getText() |
| |
| HashValue GroupByAgg::topHash() |
| { |
| HashValue result = RelExpr::topHash(); |
| |
| result ^= groupExpr_; |
| result ^= aggregateExpr_; |
| result ^= (Int32) formEnum_; // MSVC requires cast. |
| |
| return result; |
| } |
| |
| NABoolean GroupByAgg::duplicateMatch(const RelExpr & other) const |
| { |
| if (!RelExpr::duplicateMatch(other)) |
| return FALSE; |
| |
| GroupByAgg &o = (GroupByAgg &) other; |
| |
| if (groupExpr_ != o.groupExpr_ OR |
| aggregateExpr_ != o.aggregateExpr_ OR |
| formEnum_ != o.formEnum_ ) |
| |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| RelExpr * GroupByAgg::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| GroupByAgg *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) GroupByAgg(NULL, |
| getOperatorType(), |
| NULL, |
| NULL, |
| outHeap); |
| else |
| result = (GroupByAgg *) derivedNode; |
| |
| // copy parse tree nodes (parser only) |
| if (groupExprTree_ != NULL) |
| result->groupExprTree_ = groupExprTree_->copyTree(outHeap); |
| if (aggregateExprTree_ != NULL) |
| result->aggregateExprTree_ = aggregateExprTree_->copyTree(outHeap); |
| |
| result->groupExpr_ = groupExpr_; |
| result->rollupGroupExprList_ = rollupGroupExprList_; |
| result->aggregateExpr_ = aggregateExpr_; |
| result->formEnum_ = formEnum_; |
| result->gbAggPushedBelowTSJ_ = gbAggPushedBelowTSJ_; |
| result->gbAnalysis_ = gbAnalysis_; |
| result->requiresMoveUp_ = requiresMoveUp_ ; |
| result->leftUniqueExpr_ = leftUniqueExpr_; |
| result->containsNullRejectingPredicates_ = containsNullRejectingPredicates_ ; |
| result->parentRootSelectList_ = parentRootSelectList_; |
| result->isMarkedForElimination_ = isMarkedForElimination_; |
| |
| result->selIndexInHaving_ = selIndexInHaving_; |
| result->aggrExprsToBeDeleted_ = aggrExprsToBeDeleted_; |
| |
| result->isRollup_ = isRollup_; |
| result->extraGrpOrderby_= extraGrpOrderby_; |
| result->extraOrderExpr_= extraOrderExpr_; |
| |
| return RelExpr::copyTopNode(result, outHeap); |
| } |
| |
| void GroupByAgg::addGroupExprTree(ItemExpr *groupExpr) |
| { |
| ExprValueId g = groupExprTree_; |
| |
| ItemExprTreeAsList(&g, ITM_ITEM_LIST).insert(groupExpr); |
| groupExprTree_ = g.getPtr(); |
| } |
| |
| ItemExpr * GroupByAgg::removeGroupExprTree() |
| { |
| ItemExpr * result = groupExprTree_; |
| |
| groupExprTree_ = NULL; |
| |
| return result; |
| } |
| |
| void GroupByAgg::addAggregateExprTree(ItemExpr *aggrExpr) |
| { |
| ExprValueId g = groupExprTree_; |
| |
| ItemExprTreeAsList(&g, ITM_ITEM_LIST).insert(aggrExpr); |
| groupExprTree_ = g.getPtr(); |
| } |
| |
| ItemExpr * GroupByAgg::removeAggregateExprTree() |
| { |
| ItemExpr * result = aggregateExprTree_; |
| |
| aggregateExprTree_ = NULL; |
| |
| return result; |
| } |
| |
| void GroupByAgg::getValuesRequiredForEvaluatingAggregate(ValueIdSet& relevantValues) |
| { |
| // Find the values that are needed to evaluate aggregate functions. |
| // NOTE: this should normally just be the direct children of the |
| // aggregate functions. However, some aggregate functions such as |
| // anyTrue sometimes refer to values further down the tree (and |
| // if it's only by using such values as required sort orders). |
| // Handle this special case here (or maybe we should have changed |
| // the anyTrue aggregate function such that it takes separate arguments: |
| // anyTrueGreater(a,b), anyTrueLess(a,b), anyTrueGreaterEq(a,b), ... |
| |
| // for each aggregate expression in the groupby node |
| for (ValueId x = aggregateExpr_.init(); |
| aggregateExpr_.next(x); |
| aggregateExpr_.advance(x)) |
| { |
| Aggregate *agg = (Aggregate *) x.getItemExpr(); |
| Lng32 nc = agg->getArity(); |
| |
| // handle special cases for special aggregate functions |
| switch (agg->getOperatorType()) |
| { |
| case ITM_ANY_TRUE: |
| case ITM_ANY_TRUE_MAX: |
| { |
| ItemExpr *boolInput = agg->child(0); |
| |
| // if the child is a binary comparison operator, then |
| // require both of the children instead of the comparison op. |
| switch (boolInput->getOperatorType()) |
| { |
| case ITM_EQUAL: |
| case ITM_NOT_EQUAL: |
| case ITM_LESS: |
| case ITM_LESS_EQ: |
| case ITM_GREATER: |
| case ITM_GREATER_EQ: |
| relevantValues += boolInput->child(0)->getValueId(); |
| relevantValues += boolInput->child(1)->getValueId(); |
| break; |
| case ITM_VEG_PREDICATE: |
| { |
| VEG * vegPtr = ((VEGPredicate *)boolInput)->getVEG(); |
| relevantValues += vegPtr->getVEGReference()->getValueId(); |
| } |
| break; |
| default: |
| |
| // might not happen right now: an anyTrue with something |
| // other than a binary comparison operator |
| relevantValues += boolInput->getValueId(); |
| break; |
| } |
| } |
| |
| break; |
| |
| case ITM_ONE_ROW: |
| { |
| // collect leaf values into relevant Values |
| ValueIdSet AvailableOutputs_; |
| agg->child(0)->convertToValueIdSet(AvailableOutputs_, |
| NULL, ITM_ITEM_LIST, FALSE); |
| relevantValues += AvailableOutputs_; |
| break; |
| } |
| default: |
| { |
| // all other aggregate functions are handled here |
| // |
| // If we are doing a distinct aggregate we need the |
| // distinct value id. E.g. sum(distinct x*x) with distinct |
| // valueId x, means we eliminate |
| // distinct x's first, then compute sum(x*x) |
| if(agg->isDistinct()) |
| relevantValues += agg->getDistinctValueId(); |
| else |
| relevantValues += agg->child(0)->getValueId(); |
| |
| // for each child of this particular aggregate expression |
| for (Lng32 i = 1; i < nc; i++) |
| { |
| // add the value id of that child to "relevantValues" |
| relevantValues += agg->child(i)->getValueId(); |
| } |
| } |
| break; |
| } |
| } |
| } |
| |
| void GroupByAgg::addLocalExpr(LIST(ExprNode *) &xlist, |
| LIST(NAString) &llist) const |
| { |
| if (groupExprTree_ != NULL OR |
| NOT groupExpr_.isEmpty()) |
| { |
| if (groupExpr_.isEmpty()) |
| xlist.insert(groupExprTree_); |
| else if (isRollup() && (NOT rollupGroupExprList_.isEmpty())) |
| xlist.insert(rollupGroupExprList_.rebuildExprTree(ITM_ITEM_LIST)); |
| else |
| xlist.insert(groupExpr_.rebuildExprTree(ITM_ITEM_LIST)); |
| llist.insert("grouping_columns"); |
| } |
| |
| if (aggregateExprTree_ != NULL OR |
| NOT aggregateExpr_.isEmpty()) |
| { |
| if (aggregateExpr_.isEmpty()) |
| xlist.insert(aggregateExprTree_); |
| else |
| xlist.insert(aggregateExpr_.rebuildExprTree(ITM_ITEM_LIST)); |
| llist.insert("aggregates"); |
| } |
| |
| RelExpr::addLocalExpr(xlist,llist); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // Examine the aggregate functions. If any one of them cannot be |
| // evaluated in stages, for example, a partial aggregation followed |
| // by finalization, then do not split this GroupByAgg. |
| // ----------------------------------------------------------------------- |
| NABoolean GroupByAgg::aggregateEvaluationCanBeStaged() const |
| { |
| for (ValueId aggrId = aggregateExpr().init(); |
| aggregateExpr().next(aggrId); |
| aggregateExpr().advance(aggrId)) |
| { |
| CMPASSERT(aggrId.getItemExpr()->isAnAggregate()); |
| if (groupExpr().isEmpty() && |
| (aggrId.getItemExpr()->getOperatorType() == ITM_ONEROW)) |
| return FALSE; |
| |
| if (NOT ((Aggregate *)aggrId.getItemExpr())->evaluationCanBeStaged()) |
| return FALSE; |
| } |
| return TRUE; |
| } // GroupByAgg::aggregateEvaluationCanBeStaged() |
| |
| NABoolean GroupByAgg::executeInDP2() const |
| { |
| CMPASSERT(getPhysicalProperty()); |
| return getPhysicalProperty()->executeInDP2(); |
| } |
| |
| // Try to pull up predicates in preCodeGen, to reduce char. inputs of the |
| // child. Don't actually do this unless "modify" parameter is set to TRUE, |
| // (we want to test this condition in the optimizer for costing). |
| // Return TRUE if we could move some predicates. |
| // Could make this a virtual method on RelExpr if we want to support |
| // this for other operators as well. |
| NABoolean GroupByAgg::tryToPullUpPredicatesInPreCodeGen( |
| const ValueIdSet &valuesAvailableInParent, // pull preds that are covered by these |
| ValueIdSet &pulledPredicates, // return the pulled-up preds |
| ValueIdMap *optionalMap) // optional map to rewrite preds |
| { |
| // other item expressions needed by the child (excluding |
| // selection preds), this is where we make use of the knowledge |
| // that we are dealing with a groupby. |
| ValueIdSet myLocalExpr; |
| ValueIdSet myNewInputs(getGroupAttr()->getCharacteristicInputs()); |
| ValueIdSet mappedValuesAvailableInParent; |
| ValueIdSet tempPulledPreds(selectionPred()); // be optimistic |
| |
| myLocalExpr += child(0).getGroupAttr()->getCharacteristicInputs(); |
| myLocalExpr += groupExpr(); |
| myLocalExpr += aggregateExpr(); |
| // make sure we can still produce our characteristic outputs too |
| myLocalExpr += getGroupAttr()->getCharacteristicOutputs(); |
| |
| // consider only preds that we can evaluate in the parent |
| if (optionalMap) |
| optionalMap->mapValueIdSetDown(valuesAvailableInParent, |
| mappedValuesAvailableInParent); |
| else |
| mappedValuesAvailableInParent = valuesAvailableInParent; |
| tempPulledPreds.removeUnCoveredExprs(mappedValuesAvailableInParent); |
| |
| // add the rest to myLocalExpr |
| myLocalExpr += selectionPred(); |
| myLocalExpr -= tempPulledPreds; |
| |
| // see which of the char. inputs are needed by my local expressions |
| myLocalExpr.weedOutUnreferenced(myNewInputs); |
| |
| // pull up predicates only if that reduces my char. inputs |
| if (NOT (myNewInputs == getGroupAttr()->getCharacteristicInputs())) |
| { |
| ValueIdSet selPredOnlyInputs(getGroupAttr()->getCharacteristicInputs()); |
| |
| // inputs only used by selection predicates |
| selPredOnlyInputs -= myNewInputs; |
| |
| // loop through the selection predicates and pull |
| // those up that reference myNewInputs |
| for (ValueId x=tempPulledPreds.init(); |
| tempPulledPreds.next(x); |
| tempPulledPreds.advance(x)) |
| { |
| if (x.getItemExpr()->referencesOneValueFrom(selPredOnlyInputs)) |
| { |
| // keep this predicate in tempPulledPreds and |
| // remove it from the selection predicates |
| selectionPred() -= x; |
| } |
| else |
| { |
| // this predicate stays on the local node, |
| // remove it from tempPulledPreds |
| tempPulledPreds -= x; |
| } |
| } |
| } |
| else |
| { |
| // no predicates get pulled up |
| tempPulledPreds.clear(); |
| } |
| |
| if (!tempPulledPreds.isEmpty()) |
| { |
| // return pulled predicates |
| if (optionalMap) |
| { |
| ValueIdSet rewrittenPulledPreds; |
| |
| optionalMap->rewriteValueIdSetUp(rewrittenPulledPreds, tempPulledPreds); |
| pulledPredicates += rewrittenPulledPreds; |
| } |
| else |
| pulledPredicates += tempPulledPreds; |
| |
| // just remove pulled up predicates from char. input |
| ValueIdSet newInputs(getGroupAttr()->getCharacteristicInputs()); |
| myLocalExpr += selectionPred(); |
| myLocalExpr -= tempPulledPreds; |
| myLocalExpr.weedOutUnreferenced(newInputs); |
| |
| // adjust char. inputs - this is not exactly |
| // good style, just overwriting the char. inputs, but |
| // hopefully we'll get away with it at this stage in |
| // the processing |
| getGroupAttr()->setCharacteristicInputs(newInputs); |
| } |
| |
| // note that we removed these predicates from our node, it's the |
| // caller's responsibility to take them |
| return (NOT tempPulledPreds.isEmpty()); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class SortGroupBy |
| // ----------------------------------------------------------------------- |
| |
| SortGroupBy::~SortGroupBy() {} |
| |
| NABoolean SortGroupBy::isLogical() const {return FALSE;} |
| |
| NABoolean SortGroupBy::isPhysical() const {return TRUE;} |
| |
| const NAString SortGroupBy::getText() const |
| { |
| if (isRollup()) |
| return "sort_" + GroupByAgg::getText() + "_rollup"; |
| else |
| return "sort_" + GroupByAgg::getText(); |
| } |
| |
| RelExpr * SortGroupBy::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| RelExpr *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) SortGroupBy(NULL, |
| getOperatorType(), |
| NULL, |
| NULL, |
| outHeap); |
| else |
| result = derivedNode; |
| |
| return GroupByAgg::copyTopNode(result, outHeap); |
| } |
| |
| PlanPriority ShortCutGroupBy::computeOperatorPriority |
| (const Context* context, |
| PlanWorkSpace *pws, |
| Lng32 planNumber) |
| { |
| // For min(X) or max(X) where X is the first clustering key column, |
| // allow shortcutgroupby plan to compete with other plans based on cost. |
| // Specifically, match the priority of the wave fix so that a |
| // shortcutgroupby plan can cost compete with the parallel |
| // partialgroupby plan. |
| PlanPriority result; |
| if (QueryAnalysis::Instance() && |
| QueryAnalysis::Instance()->dontSurfTheWave()) { |
| // do this only if the wave fix is a competing plan |
| result.incrementLevels(10, 0); |
| } |
| return result; |
| } |
| |
| PlanPriority SortGroupBy::computeOperatorPriority |
| (const Context* context, |
| PlanWorkSpace *pws, |
| Lng32 planNumber) |
| { |
| const PhysicalProperty* spp = context->getPlan()->getPhysicalProperty(); |
| Lng32 degreeOfParallelism = spp->getCountOfPartitions(); |
| double val = 1; |
| if (degreeOfParallelism <= 1) |
| { |
| // serial plans are risky. exact an insurance premium from serial plans. |
| val = CURRSTMT_OPTDEFAULTS->riskPremiumSerial(); |
| |
| // when dontSurfTheWave is ON, |
| // consider serial sort_partial_aggr_nonleaf risky |
| if (QueryAnalysis::Instance() && |
| QueryAnalysis::Instance()->dontSurfTheWave() && |
| isAPartialGroupByNonLeaf() && val <= 1) |
| { |
| val = 1.1; |
| } |
| } |
| CostScalar premium(val); |
| PlanPriority result(0, 0, premium); |
| |
| // WaveFix Begin |
| // This is part of the fix for the count(*) wave |
| // if there is a scalar aggregate query on a single partitioned table, |
| // something like Select count(*) from fact; |
| // In such a case we would like to get a layer of esps, |
| // doing so causes the plan to fixup in parallel avoiding the serial |
| // fixup if the plan is just the master executor on top of dp2. The |
| // serial fixup causes the query to execute in wave pattern, since |
| // each dp2 is fixed up and then starts execution. Due to serial |
| // fixup a dp2 is fixed up, and then we move to the next dp2 causing |
| // the wave pattern. |
| if (QueryAnalysis::Instance() && |
| QueryAnalysis::Instance()->dontSurfTheWave()) |
| { |
| if (isAPartialGroupByLeaf2() && spp->executeInDP2()) |
| result.incrementLevels(10, 0); |
| else if (isAPartialGroupByLeaf1() && |
| (degreeOfParallelism>1) && |
| (!spp->executeInDP2())) |
| result.incrementLevels(5, 0); |
| } |
| // WaveFix End |
| |
| // The remaining part of the code in this function relates to parallelism |
| // priority and not applicable to scalar aggregates |
| if (groupExpr().isEmpty()) |
| return result; |
| |
| if(spp->executeInDP2()) |
| return result; |
| |
| // For the option of Max Degree of Parallelism we can either use the |
| // value set in comp_int_9 (if positive) or we use the number of CPUs |
| // if the CQD is set to -1, or feature is disabled if CQD is 0 (default). |
| Lng32 maxDegree = ActiveSchemaDB()->getDefaults().getAsLong(COMP_INT_9); |
| if (CURRSTMT_OPTDEFAULTS->maxParallelismIsFeasible() OR (maxDegree == -1) ) |
| { |
| // if CQD is set to -1 this mean use the number of CPUs |
| maxDegree = spp->getCurrentCountOfCPUs(); |
| } |
| if (maxDegree > 1) // CQD set to 0 means feature is OFF |
| { |
| if (degreeOfParallelism < maxDegree) |
| result.incrementLevels(0,-10); // need to replace with constant |
| } |
| |
| return result; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class ShortCutGroupBy |
| // ----------------------------------------------------------------------- |
| |
| const NAString ShortCutGroupBy::getText() const |
| { |
| return "shortcut_" + GroupByAgg::getText(); |
| } |
| |
| RelExpr * ShortCutGroupBy::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| ShortCutGroupBy *result; |
| |
| if (derivedNode == NULL) |
| // This is the top of the derivation chain |
| // Create an empty ShortCutGroupBy node. |
| // |
| result = new (outHeap) ShortCutGroupBy(NULL, |
| getOperatorType(), |
| NULL, |
| NULL, |
| outHeap); |
| else |
| // A node has already been constructed as a derived class. |
| // |
| result = (ShortCutGroupBy *) derivedNode; |
| |
| // Copy the relevant fields. |
| |
| result->opt_for_max_ = opt_for_max_; |
| result->opt_for_min_ = opt_for_min_; |
| result->isnullable_ = isnullable_; |
| result->lhs_anytrue_ = lhs_anytrue_; |
| result->rhs_anytrue_ = rhs_anytrue_; |
| |
| // Copy any data members from the classes lower in the derivation chain. |
| // |
| return GroupByAgg::copyTopNode(result, outHeap); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class PhysShortCutGroupBy |
| // ----------------------------------------------------------------------- |
| |
| RelExpr * PhysShortCutGroupBy::copyTopNode(RelExpr *derivedNode, |
| CollHeap* outHeap) |
| { |
| RelExpr *result; |
| |
| if (derivedNode == NULL) |
| // This is the top of the derivation chain |
| // Create an empty ShortCutGroupBy node. |
| // |
| result = new (outHeap) PhysShortCutGroupBy(NULL, |
| getOperatorType(), |
| NULL, |
| NULL, |
| outHeap); |
| else |
| // A node has already been constructed as a derived class. |
| // |
| result = (PhysShortCutGroupBy *) derivedNode; |
| |
| // PhysShortCutGroupBy has no data members. |
| |
| // Copy any data members from the classes lower in the derivation chain. |
| // |
| return ShortCutGroupBy::copyTopNode(result, outHeap); |
| } |
| |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class HashGroupBy |
| // ----------------------------------------------------------------------- |
| |
| HashGroupBy::~HashGroupBy() {} |
| |
| NABoolean HashGroupBy::isLogical() const {return FALSE;} |
| |
| NABoolean HashGroupBy::isPhysical() const {return TRUE;} |
| |
| const NAString HashGroupBy::getText() const |
| { |
| return "hash_" + GroupByAgg::getText(); |
| } |
| |
| RelExpr * HashGroupBy::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| RelExpr *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) HashGroupBy(NULL, |
| getOperatorType(), |
| NULL, |
| NULL, |
| outHeap); |
| else |
| result = derivedNode; |
| |
| return GroupByAgg::copyTopNode(result, outHeap); |
| } |
| |
| |
| PlanPriority HashGroupBy::computeOperatorPriority |
| (const Context* context, |
| PlanWorkSpace *pws, |
| Lng32 planNumber) |
| { |
| const PhysicalProperty* spp = context->getPlan()->getPhysicalProperty(); |
| Lng32 degreeOfParallelism = spp->getCountOfPartitions(); |
| double val = 1; |
| if (degreeOfParallelism <= 1) |
| { |
| |
| // Don't command premium for serial hash partial groupby plan if : |
| // 1. Operator is partial group by root |
| // 2. process < 5K rows |
| // This is to prevent optimizer choosing parallel plans for small queries. |
| // The idea is either premium has been already applied for groupby leaf level |
| // or leaf is running in parallel, we don't need to run root also in parallel |
| |
| if ( isAPartialGroupByRoot() && |
| CostScalar((ActiveSchemaDB()->getDefaults()). |
| getAsULong(GROUP_BY_PARTIAL_ROOT_THRESHOLD)) >= |
| this->getChild0Cardinality(context) ) |
| val = 1; |
| else |
| // serial plans are risky. extract an insurance premium from serial plans. |
| |
| val = CURRSTMT_OPTDEFAULTS->riskPremiumSerial(); |
| } |
| |
| CostScalar premium(val); |
| PlanPriority result(0, 0, premium); |
| |
| if (QueryAnalysis::Instance() AND |
| QueryAnalysis::Instance()->optimizeForFirstNRows()) |
| result.incrementLevels(HASH_GROUP_BY_FIRST_N_PRIORITY,0); |
| |
| // The remaining part of the code in this funtion relates to parallelism |
| // priority and not applicable to scalar aggregates |
| if (groupExpr().isEmpty()) |
| return result; |
| |
| // esp parallelism priority logic does not apply to operators in dp2 |
| if(spp->executeInDP2()) |
| return result; |
| |
| // For the option of Max Degree of Parallelism we can either use the |
| // value set in comp_int_9 (if positive) or we use the number of CPUs |
| // if the CQD is set to -1, or feature is disabled if CQD is 0 (default). |
| Lng32 maxDegree = ActiveSchemaDB()->getDefaults().getAsLong(COMP_INT_9); |
| if (CURRSTMT_OPTDEFAULTS->maxParallelismIsFeasible() OR (maxDegree == -1) ) |
| { |
| // if CQD is set to -1 this mean use the number of CPUs |
| maxDegree = spp->getCurrentCountOfCPUs(); |
| } |
| if (maxDegree > 1) // CQD set to 0 means feature is OFF |
| { |
| if (degreeOfParallelism < maxDegree) |
| result.incrementLevels(0,-10); // need to replace with constant |
| } |
| |
| //cout<<maxDegree<<"-------"<<spp->getCountOfPartitions()<<endl; |
| |
| return result; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class Scan |
| // ----------------------------------------------------------------------- |
| |
| void Scan::getPotentialOutputValues(ValueIdSet & outputValues) const |
| { |
| outputValues.clear(); |
| // |
| // Assign the set of columns that belong to the table to be scanned |
| // as the output values that can be produced by this scan. |
| // |
| if (potentialOutputs_.isEmpty()) |
| { |
| outputValues.insertList( getTableDesc()->getColumnList() ); |
| outputValues.insertList( getTableDesc()->hbaseTSList() ); |
| outputValues.insertList( getTableDesc()->hbaseVersionList() ); |
| } |
| else |
| outputValues = potentialOutputs_; |
| |
| outputValues += getExtraOutputColumns(); |
| } // Scan::getPotentialOutputValues() |
| |
| void Scan::getPotentialOutputValuesAsVEGs(ValueIdSet& outputs) const |
| { |
| outputs.clear(); |
| ValueIdSet tempSet ; |
| |
| getPotentialOutputValues(tempSet); |
| getTableDesc()->getEquivVEGCols(tempSet, outputs); |
| } |
| |
| |
| |
| Int32 Scan::getArity() const { return 0;} |
| |
| NABoolean Scan::isHiveTable() const |
| { |
| return (getTableDesc() && getTableDesc()->getNATable() ? |
| getTableDesc()->getNATable()->isHiveTable() : |
| FALSE); |
| } |
| |
| NABoolean Scan::isHbaseTable() const |
| { |
| return (getTableDesc() && getTableDesc()->getNATable() ? |
| getTableDesc()->getNATable()->isHbaseTable() : |
| FALSE); |
| } |
| |
| NABoolean Scan::isSeabaseTable() const |
| { |
| return (getTableDesc() && getTableDesc()->getNATable() ? |
| getTableDesc()->getNATable()->isSeabaseTable() : |
| FALSE); |
| } |
| |
| const NAString Scan::getText() const |
| { |
| NAString op(CmpCommon::statementHeap()); |
| |
| if (isSampleScan() == TRUE) |
| op = "sample_scan "; |
| else |
| op = "scan "; |
| return op + userTableName_.getTextWithSpecialType(); |
| } |
| |
| HashValue Scan::topHash() |
| { |
| HashValue result = RelExpr::topHash(); |
| |
| result ^= getTableDesc(); |
| result ^= potentialOutputs_; |
| result ^= numIndexJoins_; |
| |
| return result; |
| } |
| |
| NABoolean Scan::duplicateMatch(const RelExpr & other) const |
| { |
| if (NOT RelExpr::duplicateMatch(other)) |
| return FALSE; |
| |
| Scan &o = (Scan &) other; |
| |
| if (NOT (userTableName_ == o.userTableName_) OR |
| NOT (getTableDesc() == o.getTableDesc()) OR |
| NOT (potentialOutputs_ == o.potentialOutputs_) OR |
| ((forcedIndexInfo_ OR o.forcedIndexInfo_) AND ( |
| //just comparing the entries is probably not enough???? |
| NOT (indexOnlyIndexes_.entries() == o.indexOnlyIndexes_.entries()) OR |
| NOT (possibleIndexJoins_ == o.possibleIndexJoins_) OR |
| NOT (numIndexJoins_ == o.numIndexJoins_))) |
| OR |
| NOT (suppressHints_ == o.suppressHints_) OR |
| NOT (isSingleVPScan_ == o.isSingleVPScan_) OR |
| NOT (getExtraOutputColumns() == o.getExtraOutputColumns()) OR |
| NOT (samplePercent() == o.samplePercent()) OR |
| NOT (clusterSize() == o.clusterSize())) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| RelExpr * Scan::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| Scan *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) |
| Scan(userTableName_, getTableDesc(), REL_SCAN, outHeap); |
| else |
| result = (Scan *) derivedNode; |
| |
| result->baseCardinality_ = baseCardinality_; |
| result->potentialOutputs_ = potentialOutputs_; |
| result->numIndexJoins_ = numIndexJoins_; |
| result->accessOptions_ = accessOptions_; |
| result->pkeyHvarList_ = pkeyHvarList_; |
| result->setOptStoi(stoi_); |
| result->samplePercent(samplePercent()); |
| result->suppressHints_ = suppressHints_; |
| result->clusterSize(clusterSize()); |
| result->scanFlags_ = scanFlags_; |
| result->setExtraOutputColumns(getExtraOutputColumns()); |
| result->isRewrittenMV_ = isRewrittenMV_; |
| result->matchingMVs_ = matchingMVs_; |
| result->hbaseAccessOptions_ = hbaseAccessOptions_; |
| result->commonSubExpr_ = commonSubExpr_; |
| |
| // don't copy values that can be calculated by addIndexInfo() |
| // (some callers will change selection preds, which requires recomputation) |
| |
| return RelExpr::copyTopNode(result, outHeap); |
| } |
| |
| void Scan::copyIndexInfo(RelExpr *derivedNode) |
| { |
| CMPASSERT (derivedNode != NULL AND |
| derivedNode->getOperatorType() == REL_SCAN); |
| |
| Scan * scan = (Scan *)derivedNode; |
| forcedIndexInfo_ = scan->forcedIndexInfo_; |
| if (NOT scan->getIndexOnlyIndexes().isEmpty() OR |
| scan->getPossibleIndexJoins().entries() > 0) |
| { |
| // first copy the possible index join info |
| const LIST(ScanIndexInfo *) & ixJoins = scan->getPossibleIndexJoins(); |
| for (CollIndex i = 0; i < ixJoins.entries(); i++) |
| { |
| ScanIndexInfo * ix = new (CmpCommon::statementHeap()) |
| ScanIndexInfo(*(ixJoins[i])); |
| possibleIndexJoins_.insert(ix); |
| } |
| |
| // now, copy the index descriptors |
| const SET(IndexProperty *) & ixDescs = scan->getIndexOnlyIndexes(); |
| |
| for (CollIndex j = 0; j <ixDescs.entries(); j++) |
| { |
| indexOnlyIndexes_.insert(ixDescs[j]); |
| } |
| } |
| generatedCCPreds_ = scan->generatedCCPreds_; |
| } |
| |
| void Scan::removeIndexInfo() |
| { |
| possibleIndexJoins_.clear(); |
| indexOnlyIndexes_.clear(); |
| indexJoinScans_.clear(); |
| generatedCCPreds_.clear(); |
| forcedIndexInfo_ = FALSE; |
| } |
| |
| /******************************************************* |
| * Generates set of IndexDesc from the set of IndexProperty |
| ********************************************************/ |
| |
| const SET(IndexDesc *) & Scan::deriveIndexOnlyIndexDesc() |
| { |
| indexOnlyScans_.clear(); |
| CollIndex ixCount = indexOnlyIndexes_.entries(); |
| for(CollIndex i=0; i< ixCount; i++) |
| { |
| indexOnlyScans_.insert(indexOnlyIndexes_[i]->getIndexDesc()); |
| } |
| return indexOnlyScans_; |
| |
| } |
| |
| /******************************************************* |
| * Generates set of IndexDesc from the set of ScanIndexInfo |
| ********************************************************/ |
| const SET(IndexDesc *) & Scan::deriveIndexJoinIndexDesc() |
| { |
| indexJoinScans_.clear(); |
| CollIndex ijCount = possibleIndexJoins_.entries(); |
| for(CollIndex i=0; i< ijCount; i++) |
| { |
| ScanIndexInfo *ixi = possibleIndexJoins_[i]; |
| CollIndex uixCount = ixi->usableIndexes_.entries(); |
| for(CollIndex j=0; j < uixCount; j++) |
| { |
| if (ixi->usableIndexes_[j]->getIndexDesc()) |
| indexJoinScans_.insert(ixi->usableIndexes_[j]->getIndexDesc()); |
| } |
| } |
| return indexJoinScans_; |
| } |
| |
| void Scan::addIndexInfo() |
| { |
| // don't do this twice, return if already set |
| if (NOT indexOnlyIndexes_.isEmpty() OR |
| possibleIndexJoins_.entries() > 0) |
| return; |
| |
| forcedIndexInfo_ = FALSE; |
| const TableDesc * tableDesc = getTableDesc(); |
| const LIST(IndexDesc *) & ixlist = tableDesc->getIndexes(); |
| ValueIdSet preds = selectionPred(); |
| // changing back to old predicate tree: |
| if ((CmpCommon::getDefault(RANGESPEC_TRANSFORMATION) == DF_ON ) && |
| (preds.entries())) |
| { |
| ValueIdList selectionPredList(preds); |
| ItemExpr *inputItemExprTree = selectionPredList.rebuildExprTree(ITM_AND,FALSE,FALSE); |
| ItemExpr * resultOld = revertBackToOldTree(CmpCommon::statementHeap(), inputItemExprTree); |
| preds.clear(); |
| resultOld->convertToValueIdSet(preds, NULL, ITM_AND); |
| doNotReplaceAnItemExpressionForLikePredicates(resultOld,preds,resultOld); |
| } |
| |
| |
| if (CmpCommon::getDefault(MTD_GENERATE_CC_PREDS) == DF_ON) |
| { |
| // compute predicates on computed columns from regular predicates, based |
| // on the definition of the computed column. Example: |
| // - regular predicate: a = 99 |
| // - computed column definition: "_SALT_" = HASH2PARTFUNC(a,2) |
| // - computed predicate: "_SALT_" = HASH2PARTFUNC(99,2); |
| ValueIdSet clusteringKeyCols( |
| getTableDesc()->getClusteringIndex()->getClusteringKeyCols()); |
| ValueIdSet selectionPreds(preds); |
| |
| ScanKey::createComputedColumnPredicates( |
| selectionPreds, |
| clusteringKeyCols, |
| getGroupAttr()->getCharacteristicInputs(), |
| generatedCCPreds_); |
| } |
| |
| // a shortcut for tables with no indexes |
| if ((ixlist.entries() == 1)|| |
| (tableDesc->isPartitionNameSpecified())) |
| { |
| // that's easy, there is only one index (the base table) |
| // and that index better have everything we need |
| IndexJoinSelectivityEnum junk; |
| MdamFlags flag=ixlist[0]->pruneMdam(preds,TRUE,junk);; |
| IndexProperty * ixProp = new(CmpCommon::statementHeap()) |
| IndexProperty(ixlist[0], |
| flag); |
| indexOnlyIndexes_.insert(ixProp); |
| return; |
| } |
| |
| // all the value ids that are required by the scan and its parents |
| ValueIdSet requiredValueIds(getGroupAttr()->getCharacteristicOutputs()); |
| |
| |
| // VEGPreds can have two forms, an A IS NOT NULL form and an A=B form |
| // when expanded in the generator. If an index does not provide a |
| // VEG member that the base table provides, a VEGPredicate could be |
| // covered by the index in its IS NOT NULL form (checking a char. input |
| // whether it is not null). To avoid this bug, add all the base cols |
| // that contribute to VEGPredicates as explicitly required values. |
| addBaseColsFromVEGPreds(requiredValueIds); |
| |
| // using old predicate tree: |
| if ((CmpCommon::getDefault(RANGESPEC_TRANSFORMATION) == DF_ON ) && |
| (preds.entries())) |
| { |
| requiredValueIds += preds; |
| } |
| else |
| // selection predicates are also required, add them to requiredValueIds |
| requiredValueIds += selectionPred(); |
| |
| // a list of VEGReferences to the clustering key column(s) |
| ValueIdSet clusteringKeyColumns; |
| |
| // some helper variables |
| ValueIdList clusteringKeyColList; |
| |
| // a set of join predicates between indexes (same for all indexes) |
| ValueIdSet indexJoinPreds; |
| |
| // --------------------------------------------------------------------- |
| // find out the subset of values that are always covered |
| // --------------------------------------------------------------------- |
| // get the clustering key columns and transform them into VEGies |
| CMPASSERT(tableDesc); |
| tableDesc->getEquivVEGCols( |
| tableDesc->getClusteringIndex()->getIndexKey(), |
| clusteringKeyColList); |
| clusteringKeyColumns = clusteringKeyColList; |
| |
| // get the VEGPredicates from the list of VEGReferences; they are |
| // the join predicates between indexes (who all contain the clustering key) |
| for (ValueId x = clusteringKeyColumns.init(); |
| clusteringKeyColumns.next(x); |
| clusteringKeyColumns.advance(x)) |
| { |
| // clustering key columns must be VEGReferences |
| CMPASSERT(x.getItemExpr()->getOperatorType() == ITM_VEG_REFERENCE); |
| |
| if (((VEGReference *) x.getItemExpr())->getVEG()->getSpecialNulls()) |
| ((VEGReference *) x.getItemExpr()) |
| ->getVEG()->getVEGPredicate()->setSpecialNulls(TRUE); |
| |
| // find the corresponding VEGPredicate and add it to the join preds |
| indexJoinPreds += ((VEGReference *) x.getItemExpr())-> |
| getVEG()->getVEGPredicate()->getValueId(); |
| } |
| |
| |
| const NABoolean updatingCol = tableDesc->getColUpdated().entries() > 0; |
| const NABoolean unlimitedIndexJoinsAllowed = |
| ((ActiveControlDB()->getRequiredShape() AND |
| ActiveControlDB()->getRequiredShape()->getShape() AND |
| NOT ActiveControlDB()->getRequiredShape()->getShape()->isCutOp()) |
| OR |
| (getGroupAttr()->isEmbeddedUpdateOrDelete()) |
| OR |
| (getGroupAttr()->isStream()) |
| ); |
| |
| |
| const TableAnalysis * tAnalysis = getTableDesc()->getTableAnalysis(); |
| |
| // with this CQD value set, try to consider minimum indexes possible |
| // if ixProp is no better than any of indexOnlyIndexes_ - don't add |
| // it. If ixProp is better than some of this set - remove them. |
| NABoolean tryToEliminateIndex = |
| CURRSTMT_OPTDEFAULTS->indexEliminationLevel() == OptDefaults::AGGRESSIVE |
| AND NOT unlimitedIndexJoinsAllowed |
| AND tAnalysis; |
| |
| CostScalar indexEliminationThreshold = |
| ActiveSchemaDB()->getDefaults().getAsLong(INDEX_ELIMINATION_THRESHOLD); |
| |
| |
| NABoolean printIndexElimination = |
| CmpCommon::getDefault(NSK_DBG_PRINT_INDEX_ELIMINATION) == DF_ON && |
| CmpCommon::getDefault(NSK_DBG) == DF_ON; |
| |
| ostream &out = CURRCONTEXT_OPTDEBUG->stream(); |
| |
| if ( printIndexElimination ) { |
| out << endl << "call addIndexInfo()" << endl; |
| out << "tryToEliminateIndex=" << (Lng32)tryToEliminateIndex << endl; |
| } |
| |
| // --------------------------------------------------------------------- |
| // For each index, check whether it provides any useful values |
| // --------------------------------------------------------------------- |
| for (CollIndex indexNo = 0; indexNo < ixlist.entries(); indexNo++) |
| { |
| IndexDesc *idesc = ixlist[indexNo]; |
| |
| NABoolean dummy; // halloweenProtection is decided using updateableIndex |
| // in GU::normalizeNode. Here this parameter is not used. |
| // Determine if this index can be used for a scan during an update. |
| if (updatingCol AND NOT updateableIndex(idesc, preds, dummy)) |
| continue; |
| |
| ValueIdSet indexColumns(idesc->getIndexColumns()); |
| ValueIdSet referencedInputs; |
| ValueIdSet coveredSubexpr; |
| ValueIdSet unCoveredExpr; |
| GroupAttributes indexOnlyGA; |
| NABoolean indexOnlyScan; |
| |
| // make group attributes for an index scan |
| indexOnlyGA.addCharacteristicOutputs(idesc->getIndexColumns()); |
| indexOnlyGA.addCharacteristicOutputs(extraOutputColumns_); |
| |
| // does the index cover all required values, and if not, which |
| // ones does it cover and which ones are not covered |
| indexOnlyScan = requiredValueIds.isCovered( |
| getGroupAttr()->getCharacteristicInputs(), |
| indexOnlyGA, |
| referencedInputs, |
| coveredSubexpr, |
| unCoveredExpr); |
| |
| // if this is a sample scan (currently these are only CLUSTER |
| // sampling scans) then do not choose index only scan. Also, |
| // due to an artifact of sampling, the 'isCovered' test above |
| // will not return TRUE even for the ClusteringIndex, so force |
| // it to be true for the ClusteringIndex. Note that |
| // ClusterIndex means that this is the basetable access path. |
| // |
| if (isSampleScan()) |
| { |
| if (idesc->isClusteringIndex()) |
| { |
| // Force it to be TRUE for the basetable access path. |
| // This overrides the value of 'indexOnlyScan' produced |
| // above since for sample scans, the isCovered test will |
| // always fail, even for the basetable. |
| // |
| indexOnlyScan = TRUE; |
| } |
| else |
| { |
| indexOnlyScan = FALSE; |
| } |
| } |
| |
| // if the user specified IN EXCLUSIVE MODE option for this select, |
| // then do not choose index only scan. This is needed so the base |
| // table row could be locked in exclusive mode. |
| if ((indexOnlyScan) && |
| (! idesc->isClusteringIndex()) && |
| (accessOptions().lockMode() == EXCLUSIVE_)) |
| indexOnlyScan = FALSE; |
| |
| //pruneMdam() returns a flag indicating if the index would have |
| //has good enough key access for MDAM access to be viable. For index |
| //join indexes it also returns a IndexJoinSelectivityEnum that |
| //indicates if the index join is going to exceed the cost of just |
| //scanning the base table. |
| if (indexOnlyScan) |
| { |
| // this index supplies all the info we need, consider |
| // it for an index only scan later |
| IndexJoinSelectivityEnum junk; |
| MdamFlags flag=idesc->pruneMdam(preds,TRUE,junk); |
| IndexProperty * ixProp = new(CmpCommon::statementHeap()) |
| IndexProperty(idesc, |
| flag); |
| if (tryToEliminateIndex) |
| { |
| // with this CQD value set, try to consider minimum indexes possible |
| // if ixProp is no better than any of indexOnlyIndexes_ - don't add |
| // it. If ixProp is better than some of this set - remove them. |
| ixProp->updatePossibleIndexes(indexOnlyIndexes_, this); |
| } |
| else |
| indexOnlyIndexes_.insert(ixProp); |
| } |
| else |
| { |
| GroupAttributes indexGA; |
| ValueIdSet ijCoveredPredicates; |
| if(numIndexJoins_ < MAX_NUM_INDEX_JOINS AND |
| NOT unlimitedIndexJoinsAllowed) |
| { |
| //Is any of the predicates covered by key columns of the |
| //alternate index? |
| ValueIdList userKeyColumns(idesc->getIndexKey()); |
| CollIndex numSecondaryIndexKey = |
| idesc->getNAFileSet()->getCountOfColumns( |
| TRUE, // key columns only |
| TRUE, // user-specified key columns only |
| FALSE, // don't exclude system columns |
| FALSE); // don't exclude salt/divisioning columns |
| CollIndex numClusteringKey = |
| userKeyColumns.entries() - numSecondaryIndexKey; |
| if(NOT idesc->isUniqueIndex()) |
| { |
| CollIndex entry = userKeyColumns.entries() -1; |
| for(CollIndex i=0;i<numClusteringKey;i++) |
| { |
| userKeyColumns.removeAt(entry); |
| entry--; |
| } |
| } |
| indexGA.addCharacteristicOutputs(userKeyColumns); |
| |
| ValueIdSet ijReferencedInputs; |
| ValueIdSet ijUnCoveredExpr; |
| |
| ValueId vid; |
| ValueIdSet disjuncts; |
| |
| // Multi-Index OR optimization requires that the index information |
| // is maintained for disjuncts as well. So here we check if the |
| // predicate is of the form A OR B OR C. If it is, then the top |
| // operator is the OR operator. The optimization is considered only |
| // in this case. So if the predicate has an OR on top of the item |
| // expression, then we check if the disjuncts are covered by the |
| // index. |
| |
| if (preds.entries() == 1) |
| { |
| preds.getFirst(vid); |
| if (vid.getItemExpr()->getOperatorType() == ITM_OR) |
| { |
| vid.getItemExpr()->convertToValueIdSet(disjuncts, |
| NULL, |
| ITM_OR, |
| FALSE); |
| } |
| else |
| disjuncts=preds; |
| } |
| else |
| disjuncts=preds; |
| |
| disjuncts.isCovered( |
| getGroupAttr()->getCharacteristicInputs(), |
| indexGA, |
| ijReferencedInputs, |
| ijCoveredPredicates, |
| ijUnCoveredExpr); |
| |
| // we only care about predicates that are entirely covered, |
| // parts of predicates (like constants) that are covered |
| // don't help in this context |
| ijCoveredPredicates.intersectSet(disjuncts); |
| } |
| // This index does not provide all required values. |
| // However, it might be useful to join this index with another |
| // one (most likely the clustering index) to get the rest |
| // of the required values. If this is promising at all, then |
| // add this index to a list of possible index joins. |
| // In that list of possible index joins, group all indexes |
| // that provide the same set of output values (but different |
| // orders) together. |
| if (numIndexJoins_ < MAX_NUM_INDEX_JOINS AND |
| (unlimitedIndexJoinsAllowed OR NOT ijCoveredPredicates.isEmpty())) |
| { |
| // changing back to old predicate tree: |
| ValueIdSet selectionpreds; |
| if((CmpCommon::getDefault(RANGESPEC_TRANSFORMATION) == DF_ON ) && |
| (selectionPred().entries())) |
| { |
| ValueIdList selectionPredList(selectionPred()); |
| ItemExpr *inputItemExprTree = selectionPredList.rebuildExprTree(ITM_AND,FALSE,FALSE); |
| ItemExpr * resultOld = revertBackToOldTree(CmpCommon::statementHeap(), inputItemExprTree); |
| resultOld->convertToValueIdSet(selectionpreds, NULL, ITM_AND); |
| doNotReplaceAnItemExpressionForLikePredicates(resultOld,selectionpreds,resultOld); |
| } |
| |
| // For now, only consider indexes that covers one of the selection |
| // predicates unless control query shape is in effect. |
| // NOTE: we should also consider indexes that potentially |
| // could provide an interesting order or partitioning. To do |
| // that, we would have to check whether their first key column |
| // or any of their partitioning key columns is used. |
| // For exclusive mode, any index can be called a usable index of |
| // of another, only if it produces the same characteristic |
| // outputs as the main index, and also both indexes have the same |
| // uncovered expressions. This is because, in exclusive mode the |
| // base (clustering key) index must always be read even if the |
| // alternate index is index only, because the locks on the |
| // base index are required for exclusive mode. |
| // We can test the index only case with exclusive mode by |
| // requiring the uncovered expressions to be the same |
| // (both would be NULL for index only). |
| |
| // we now have the following information ready: |
| // - coveredSubexpr are the values that the index can deliver |
| // (+ clustering key columns) |
| // - unCoveredExpr are the values that the right child of the |
| // index join should deliver (+ clustering key values) |
| // - we know the clustering key VEGies, whose VEGPredicates |
| // serve as join predicates between the indexes |
| // - we can find out the selection predicates covered |
| // by the index by intersecting them with coveredSubexpr |
| |
| ValueIdSet newOutputsFromIndex(coveredSubexpr); |
| ValueIdSet newIndexPredicates(coveredSubexpr); |
| ValueIdSet newOutputsFromRightScan(unCoveredExpr); |
| |
| newOutputsFromIndex += clusteringKeyColumns; |
| if(CmpCommon::getDefault(RANGESPEC_TRANSFORMATION) == DF_ON ) |
| { |
| newOutputsFromIndex -= selectionpreds; |
| } |
| else |
| newOutputsFromIndex -= selectionPred(); |
| newOutputsFromIndex -= getGroupAttr()-> |
| getCharacteristicInputs(); |
| if(CmpCommon::getDefault(RANGESPEC_TRANSFORMATION) == DF_ON ) |
| { |
| newIndexPredicates.intersectSet(selectionpreds); |
| newOutputsFromRightScan -= selectionpreds; |
| } |
| else |
| { |
| newIndexPredicates.intersectSet(selectionPred()); |
| newOutputsFromRightScan -= selectionPred(); |
| } |
| newOutputsFromRightScan += clusteringKeyColumns; |
| |
| NABoolean idescAbsorbed = FALSE; |
| |
| |
| // does another index have the same covered values? |
| for (CollIndex i = 0; i < possibleIndexJoins_.entries(); i++) |
| { |
| NABoolean isASupersetIndex = |
| possibleIndexJoins_[i]->outputsFromIndex_.contains(newOutputsFromIndex) && |
| possibleIndexJoins_[i]->indexPredicates_.contains(newIndexPredicates); |
| |
| NABoolean isASubsetIndex = |
| newOutputsFromIndex.contains(possibleIndexJoins_[i]->outputsFromIndex_) && |
| newIndexPredicates.contains(possibleIndexJoins_[i]->indexPredicates_); |
| |
| NABoolean isASuperOrSubsetIndex = isASupersetIndex || isASubsetIndex; |
| |
| NABoolean produceSameIndexOutputs = isASupersetIndex && isASubsetIndex; |
| |
| if ((possibleIndexJoins_[i]->inputsToIndex_ == referencedInputs) |
| && ((accessOptions().lockMode() != EXCLUSIVE_) |
| || possibleIndexJoins_[i]->outputsFromRightScan_ == |
| newOutputsFromRightScan)) |
| { |
| ScanIndexInfo *ixi = possibleIndexJoins_[i]; |
| |
| IndexJoinSelectivityEnum isGoodIndexJoin = INDEX_JOIN_VIABLE; |
| MdamFlags mdamFlag = idesc->pruneMdam(ixi->indexPredicates_,FALSE, |
| isGoodIndexJoin, |
| getGroupAttr(),&(ixi->inputsToIndex_)); |
| IndexProperty * ixProp; |
| if(getGroupAttr()->getInputLogPropList().entries() >0) |
| ixProp = new(CmpCommon::statementHeap()) |
| IndexProperty(idesc, mdamFlag, isGoodIndexJoin, |
| (getGroupAttr()->getInputLogPropList())[0]); |
| else |
| ixProp = new(CmpCommon::statementHeap()) |
| IndexProperty(idesc, mdamFlag, isGoodIndexJoin); |
| |
| if ( !tryToEliminateIndex || idesc->indexHintPriorityDelta() > 0) { |
| |
| if ( produceSameIndexOutputs && ixi->indexPredicates_ == newIndexPredicates ) |
| { |
| ixi->usableIndexes_.insert(ixProp); |
| idescAbsorbed = TRUE; |
| break; |
| } |
| |
| } else { |
| |
| CANodeId tableId = tAnalysis->getNodeAnalysis()->getId(); |
| |
| // keep the index that provides the maximal coverage of the |
| // predicate. Do this only when the output from one index is |
| // the super set of the other. For example (a,b) in I1 |
| // (CREATE INDEX T1 on T(a, b)) is a superset of (a) in I2 |
| // (CREATE INDEX T2 on T(a)). |
| if ( isASuperOrSubsetIndex && !produceSameIndexOutputs ) { |
| |
| // Score the index's coverage by computing the remaining length of the |
| // key columns not covering the index predicates. The one with remaining |
| // length of 0 is the best. |
| |
| ValueIdSet indexCols; |
| newIndexPredicates.findAllReferencedIndexCols(indexCols); |
| |
| Lng32 currentPrefixLen = |
| idesc->getIndexKey().findPrefixLength(indexCols); |
| |
| Lng32 currentSuffixLen = idesc->getIndexKey().entries() - currentPrefixLen; |
| |
| Lng32 previousPrefixLen = |
| ixi->usableIndexes_[0]->getIndexDesc() |
| ->getIndexKey().findPrefixLength(indexCols); |
| |
| Lng32 previousSuffixLen = ixi->usableIndexes_[0]->getIndexDesc() |
| ->getIndexKey().entries() - previousPrefixLen; |
| |
| if ( currentSuffixLen < previousSuffixLen ) { |
| |
| if ( printIndexElimination ) |
| out << "Eliminate index join heuristics 1: remove " |
| << ixi->usableIndexes_[0]->getIndexDesc()->getExtIndexName().data() |
| << endl; |
| |
| ixi = new (CmpCommon::statementHeap()) |
| ScanIndexInfo(referencedInputs, newOutputsFromIndex, |
| newIndexPredicates, indexJoinPreds, |
| newOutputsFromRightScan, idesc->getIndexKey(), |
| ixProp); |
| |
| possibleIndexJoins_[i] = ixi; |
| |
| |
| } else { |
| // do nothing. The current index is less useful. |
| if ( printIndexElimination ) |
| out << "Eliminate index join heuristics 1: remove " |
| << idesc->getExtIndexName().data() |
| << endl; |
| } |
| |
| idescAbsorbed = TRUE; |
| |
| } else |
| // if no index is a prefix of the other and the two do not produce |
| // same output, pick one with high selectivity. |
| if ( !isASuperOrSubsetIndex && !produceSameIndexOutputs ) { |
| |
| // two indexes do not produce the same outputs. Select |
| // one with the most selectivity. |
| |
| CostScalar rowsToScan; |
| |
| CostScalar currentDataAccess = |
| computeCpuResourceForIndexJoin(tableId, idesc, |
| newIndexPredicates, rowsToScan); |
| |
| if ( rowsToScan > indexEliminationThreshold ) |
| break; |
| |
| CostScalar previousDataAccess = |
| computeCpuResourceForIndexJoin(tableId, |
| ixi->usableIndexes_[0]->getIndexDesc(), |
| ixi->indexPredicates_, rowsToScan); |
| |
| |
| if ( currentDataAccess < previousDataAccess ) { |
| |
| if ( printIndexElimination ) |
| out << "Eliminate index join heuristics 2: remove " |
| << ixi->usableIndexes_[0]->getIndexDesc()->getExtIndexName().data() |
| << endl; |
| |
| ixi = new (CmpCommon::statementHeap()) |
| ScanIndexInfo(referencedInputs, newOutputsFromIndex, |
| newIndexPredicates, indexJoinPreds, |
| newOutputsFromRightScan, idesc->getIndexKey(), |
| ixProp); |
| |
| possibleIndexJoins_[i] = ixi; |
| } else { |
| |
| // do nothing. The current index is less useful. |
| if ( printIndexElimination ) |
| out << "Eliminate index join heuristics 2: remove " |
| << idesc->getExtIndexName().data() << endl; |
| } |
| |
| idescAbsorbed = TRUE; |
| |
| } else { |
| |
| // must be produceSameIndexOutputs when reach here. |
| CMPASSERT(produceSameIndexOutputs); |
| |
| // Another index produces the same characteristic |
| // outputs. Combine the two indexes in a single |
| // scan. Add this index to the list of indexes, |
| // everything else should be set already |
| if ( possibleIndexJoins_[i]->indexPredicates_ == newIndexPredicates && |
| ixProp->compareIndexPromise(ixi->usableIndexes_[0]) == MORE ) |
| { |
| if ( printIndexElimination ) |
| out << "Eliminate index join heuristics 0: remove " |
| << ixi->usableIndexes_[0]->getIndexDesc()->getExtIndexName().data() |
| << endl; |
| |
| ixi = new (CmpCommon::statementHeap()) |
| ScanIndexInfo(referencedInputs, newOutputsFromIndex, |
| newIndexPredicates, indexJoinPreds, |
| newOutputsFromRightScan, idesc->getIndexKey(), |
| ixProp); |
| |
| possibleIndexJoins_[i] = ixi; |
| |
| idescAbsorbed = TRUE; |
| } |
| } |
| |
| break; |
| } // try to eliminate this index from consideration |
| } // found another index join with the same covered values |
| } // for loop: does another index join have the same covered values? |
| |
| if (!idescAbsorbed) |
| { |
| // create a new index info struct and add this into the |
| // possible index joins list |
| IndexJoinSelectivityEnum isGoodIndexJoin = INDEX_JOIN_VIABLE; |
| |
| MdamFlags mdamFlag = idesc->pruneMdam(newIndexPredicates,FALSE, |
| isGoodIndexJoin, |
| getGroupAttr(),&referencedInputs); |
| |
| IndexProperty * ixProp = (getGroupAttr()->getInputLogPropList().entries() >0) ? |
| new(CmpCommon::statementHeap()) |
| IndexProperty(idesc, mdamFlag, isGoodIndexJoin, |
| (getGroupAttr()->getInputLogPropList())[0]) |
| : |
| new(CmpCommon::statementHeap()) |
| IndexProperty(idesc, mdamFlag, isGoodIndexJoin); |
| |
| ScanIndexInfo *ixi = new (CmpCommon::statementHeap()) |
| ScanIndexInfo(referencedInputs, newOutputsFromIndex, |
| newIndexPredicates, indexJoinPreds, |
| newOutputsFromRightScan, idesc->getIndexKey(), |
| ixProp); |
| |
| possibleIndexJoins_.insert(ixi); |
| |
| } // !idescAbsorbed |
| } // index delivers new values |
| } // not indexOnly access |
| } // for each index |
| |
| |
| if ( printIndexElimination ) { |
| out << "# of index join scans=" << possibleIndexJoins_.entries() << endl; |
| out << "# of index only scans=" << indexOnlyIndexes_.entries() << endl; |
| out << "==================" << endl; |
| } |
| |
| CMPASSERT(indexOnlyIndexes_.entries() > 0); |
| |
| } // Scan::addIndexInfo |
| |
| void Scan::setTableAttributes(CANodeId nodeId) |
| { |
| NodeAnalysis * nodeAnalysis = nodeId.getNodeAnalysis(); |
| |
| if (nodeAnalysis == NULL) |
| return; |
| |
| TableAnalysis * tableAnalysis = nodeAnalysis->getTableAnalysis(); |
| |
| if (tableAnalysis == NULL) |
| return; |
| |
| TableDesc * tableDesc = tableAnalysis->getTableDesc(); |
| const CorrName& name = tableDesc->getNATable()->getTableName(); |
| |
| setTableName((CorrName &)name); |
| setTableDesc(tableDesc); |
| setBaseCardinality(MIN_ONE (tableDesc->getNATable()->getEstRowCount())) ; |
| } |
| |
| NABoolean Scan::updateableIndex(IndexDesc *idx, ValueIdSet& preds, |
| NABoolean & needsHalloweenProtection) |
| { |
| // |
| // Returns TRUE if the index (idx) can be used for a scan during an UPDATE. |
| // Returns TRUE with needsHalloweenProtection also set to TRUE, if the index |
| // needs a blocking sort for Halloween protection |
| // Otherwise, returns FALSE to prevent use of this index. |
| // Using the index in this case requires Halloween protection, but is likely |
| // inefficient since there are no useful preds on the index key columns, so |
| // we don't add this index to list of candidates. |
| // |
| |
| // The conditions of when this index returns TRUE can also be expressed as |
| // returns true for an index if it is |
| // a) a unique/clustering index or |
| // b) has a unique predicate on its key or |
| // c) has an equals or range predicate on all the index columns that |
| // get updated. If one of the key columns being updated has a range predicate |
| // then needsHalloweenProtection is set to TRUE. |
| // Note that if a key column is being updated and has no predicate on it then |
| // we return FALSE. |
| |
| // preds has predicate in non-RangeSpec form, |
| // while pred will be in RangeSpec form if feature is enabled. |
| ValueIdSet pred = getSelectionPredicates(), dummySet; |
| |
| SearchKey searchKey(idx->getIndexKey(), |
| idx->getOrderOfKeyValues(), |
| getGroupAttr()->getCharacteristicInputs(), |
| TRUE, |
| pred, |
| dummySet, // needed by the interface but not used here |
| idx |
| ); |
| |
| // Unique index is OK to use. |
| if (searchKey.isUnique()) |
| return TRUE; |
| |
| const ValueIdList colUpdated = getTableDesc()->getColUpdated(); |
| const ValueIdList indexKey = idx->getIndexKey(); |
| |
| // Determine if the columns being updated are key columns. Each key |
| // column being updated must have an associated equality clause in |
| // the WHERE clause of the UPDATE for it to be used. |
| 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 (getGroupAttr()->isEmbeddedUpdate()){ |
| if (((BaseColumn*)updateCol)->getColNumber() == |
| ((BaseColumn*)baseCol)->getColNumber()) |
| return FALSE; |
| } |
| if ((NOT(idx->isUniqueIndex() || idx->isClusteringIndex()) && |
| (CmpCommon::getDefault(UPDATE_CLUSTERING_OR_UNIQUE_INDEX_KEY) != DF_AGGRESSIVE)) |
| || |
| (CmpCommon::getDefault(UPDATE_CLUSTERING_OR_UNIQUE_INDEX_KEY) == DF_OFF)) |
| { |
| if (((BaseColumn*)updateCol)->getColNumber() == |
| ((BaseColumn*)baseCol)->getColNumber()) |
| { |
| if (preds.containsAsEquiLocalPred(baseCol->getValueId())) |
| continue; |
| else if (preds.containsAsRangeLocalPred(baseCol->getValueId())) |
| needsHalloweenProtection = TRUE; |
| else { |
| needsHalloweenProtection = FALSE; |
| return FALSE; |
| } |
| } // index key col is being updated |
| } // not a clustering or unique index |
| } // loop over index key cols |
| } // loop over cols being updated |
| |
| return TRUE; |
| } // Scan::updateableIndex |
| |
| NABoolean Scan::requiresHalloweenForUpdateUsingIndexScan() |
| { |
| // Returns TRUE if any index that is in the list of indexes that will be |
| // added later in addIndexInfo() to drive the scan for an UPDATE requires |
| // Halloween protection. This is decided by using Scan::updateableIndex(). |
| // If this method returns TRUE we will use a sort node to prevent the |
| // "Halloween Update Problem". |
| |
| // preds are in RangeSpec form |
| ValueIdSet preds = getSelectionPredicates(); |
| const ValueIdList & colUpdated = getTableDesc()->getColUpdated(); |
| const LIST(IndexDesc *) & ixlist = getTableDesc()->getIndexes(); |
| |
| if ((colUpdated.entries() == 0) || (preds.entries() == 0) || |
| (ixlist.entries() == 1) || // this is the clustering index |
| (CmpCommon::getDefault(UPDATE_CLUSTERING_OR_UNIQUE_INDEX_KEY) |
| == DF_AGGRESSIVE)) // this setting means no Halloween protection |
| return FALSE; |
| |
| if (CmpCommon::getDefault(RANGESPEC_TRANSFORMATION) == DF_ON) |
| { |
| ValueIdList selectionPredList(preds); |
| ItemExpr *inputItemExprTree = |
| selectionPredList.rebuildExprTree(ITM_AND,FALSE,FALSE); |
| ItemExpr * resultOld = revertBackToOldTree(STMTHEAP, |
| inputItemExprTree); |
| preds.clear(); |
| resultOld->convertToValueIdSet(preds, NULL, ITM_AND); |
| doNotReplaceAnItemExpressionForLikePredicates(resultOld,preds, |
| resultOld); |
| } |
| |
| NABoolean needsHalloweenProtection ; |
| for (CollIndex indexNo = 0; indexNo < ixlist.entries(); indexNo++) |
| { |
| IndexDesc *idx = ixlist[indexNo]; |
| if (idx->isClusteringIndex() || |
| (idx->isUniqueIndex() && |
| (CmpCommon::getDefault(UPDATE_CLUSTERING_OR_UNIQUE_INDEX_KEY) |
| == DF_ON))) |
| continue ; // skip this idesc |
| |
| needsHalloweenProtection = FALSE; |
| if(updateableIndex(idx, preds, needsHalloweenProtection) && |
| needsHalloweenProtection) |
| return TRUE; // if even one index requires Halloween, then we add the sort |
| } |
| return FALSE; |
| } |
| |
| // how many index descriptors can be used with this scan node? |
| CollIndex Scan::numUsableIndexes() |
| { |
| // start with the index-only indexes |
| CollIndex result = indexOnlyIndexes_.entries(); |
| |
| // for each index join, count all the indexes that result in equivalent |
| // characteristics inputs and outputs |
| for (CollIndex i=0; i < possibleIndexJoins_.entries(); i++) |
| result += possibleIndexJoins_[i]->usableIndexes_.entries(); |
| |
| return result; |
| } |
| |
| // this method works like an operator[], with values for indexNum |
| // between 0 and numUsableIndexes()-1. It returns the associated IndexDesc |
| // and - depending on whether it is an index-only scan or an index join - |
| // the appropriate information class (optional). |
| IndexDesc * Scan::getUsableIndex(CollIndex indexNum, |
| IndexProperty **indexOnlyInfo, |
| ScanIndexInfo **indexJoinInfo) |
| { |
| IndexDesc *result = NULL; |
| IndexProperty *locIndexOnlyInfo = NULL; |
| ScanIndexInfo *locIndexJoinInfo = NULL; |
| |
| if (indexNum < indexOnlyIndexes_.entries()) |
| { |
| // indexNum corresponds to an index-only scan |
| locIndexOnlyInfo = indexOnlyIndexes_[indexNum]; |
| result = locIndexOnlyInfo->getIndexDesc(); |
| } |
| else |
| { |
| // search for index desc "indexNum" in the index joins |
| // (which is a list of sets of index descs, making this |
| // method somewhat complex) |
| indexNum -= indexOnlyIndexes_.entries(); |
| CollIndex ixJoinIx = 0; |
| CollIndex numIndexJoins = possibleIndexJoins_.entries(); |
| |
| if (numIndexJoins > 0) |
| { |
| // loop over the list of index joins, counting index descs until |
| // we find the right index join |
| while (ixJoinIx < numIndexJoins) |
| { |
| ScanIndexInfo *si = possibleIndexJoins_[ixJoinIx]; |
| |
| if (indexNum >= si->usableIndexes_.entries()) |
| { |
| // not there yet, go on to the next index join |
| indexNum -= si->usableIndexes_.entries(); |
| ixJoinIx++; |
| } |
| else |
| { |
| // now we have reached the right index join (if |
| // any), select an index |
| locIndexJoinInfo = si; |
| result = si->usableIndexes_[indexNum]->getIndexDesc(); |
| break; |
| } |
| } |
| } |
| } |
| |
| // return information or NULL for not found |
| if (indexOnlyInfo) |
| *indexOnlyInfo = locIndexOnlyInfo; |
| if (indexJoinInfo) |
| *indexJoinInfo = locIndexJoinInfo; |
| return result; |
| } |
| |
| void Scan::getRequiredVerticalPartitions |
| (SET(IndexDesc *) & requiredVPs, |
| SET(ValueIdSet *) & columnsProvidedByVP) const |
| { |
| // We get requiredVPs and columnsProvidedByVP passed to us that have |
| // no entries. We have to populate them with the vertical partitions |
| // required to service the query and the columns that each of those |
| // VPs will provide. Each entry in columnsProvidedByVP is related to |
| // the corresponding entry in requiredVPs |
| CMPASSERT(requiredVPs.entries() == 0 && |
| columnsProvidedByVP.entries() == 0); |
| |
| const TableDesc * tableDesc = getTableDesc(); |
| const LIST(IndexDesc *) & allVPs = tableDesc->getVerticalPartitions(); |
| |
| #ifdef OLD |
| // Get all the value ids that are required by the scan and its parents |
| ValueIdSet requiredValueIds(getGroupAttr()->getCharacteristicOutputs()); |
| |
| // VEGPreds can have two forms, an A IS NOT NULL form and an A=B form |
| // when expanded in the generator. If an index does not provide a |
| // VEG member that the base table provides, a VEGPredicate could be |
| // covered by the index in its IS NOT NULL form (checking a char. input |
| // whether it is not null). To avoid this bug, add all the base cols |
| // that contribute to VEGPredicates as explicitly required values. |
| addBaseColsFromVEGPreds(requiredValueIds); |
| |
| // selection predicates are also required, add them to requiredValueIds |
| requiredValueIds += getSelectionPred(); |
| |
| // Remove any VEGPreds from required Values |
| ValueIdSet VEGEqPreds; |
| getSelectionPred().lookForVEGPredicates(VEGEqPreds); |
| requiredValueIds -= VEGEqPreds; |
| |
| // The following code gets all the leaf node value ids. It deletes the |
| // characteristic input list of value ids from the leaf value ids. It |
| // does this since predicates are not pushed down to the VPs and the VPs |
| // only provide outputs and do not take any inputs. It reassigns the |
| // remaining leaf value ids as those actually required from the VPs. |
| // This code e.g. will only keep the b from a predicate such as b > 3 |
| // and the c from an expression c + 1 in the select list. |
| ValueIdSet leafValues, emptySet; |
| GroupAttributes emptyGA; |
| requiredValueIds.getLeafValuesForCoverTest(leafValues, emptyGA, emptySet); |
| |
| leafValues -= getGroupAttr()->getCharacteristicInputs(); |
| requiredValueIds = leafValues; |
| #endif |
| |
| // ----------------------------------------------------------------- |
| // Accumulate the ValueIds of all VEGPredicates. |
| // ----------------------------------------------------------------- |
| ValueIdSet VEGEqPreds; |
| getSelectionPred().lookForVEGPredicates(VEGEqPreds); |
| |
| // ----------------------------------------------------------------- |
| // Compute the set of expressions that will be evaluated on the |
| // parent. Add a VEGReference for every VEGPredicate in this set. |
| // ----------------------------------------------------------------- |
| // remaining expressions on parent |
| ValueIdSet requiredValueIdsMembers; |
| RelExpr::computeValuesReqdForPredicates(VEGEqPreds, |
| requiredValueIdsMembers); |
| |
| ValueIdSet requiredValueIds; |
| requiredValueIds.replaceVEGExpressionsAndCopy(requiredValueIdsMembers); |
| |
| // --------------------------------------------------------------------- |
| // Examine the set of values required by the parent (VPJoin node). |
| // Replace each VEGPredicate with a VEGReferences for its VEG; if its |
| // VEG contains other VEGReferences, add them to requiredValueIds. |
| // --------------------------------------------------------------------- |
| RelExpr::computeValuesReqdForPredicates(getGroupAttr()->getCharacteristicOutputs(), |
| requiredValueIds); |
| |
| requiredValueIds += getSelectionPred(); |
| requiredValueIds -= VEGEqPreds; // delete all VEGPredicates |
| |
| // The following code gets all the leaf node value ids. It deletes the |
| // characteristic input list of value ids from the leaf value ids. It |
| // does this since predicates are not pushed down to the VPs and the VPs |
| // only provide outputs and do not take any inputs. It reassigns the |
| // remaining leaf value ids as those actually required from the VPs. |
| // This code e.g. will only keep the b from a predicate such as b > 3 |
| // and the c from an expression c + 1 in the select list. |
| ValueIdSet leafValues, emptySet; |
| GroupAttributes emptyGA; |
| requiredValueIds.getLeafValuesForCoverTest(leafValues, emptyGA, emptySet); |
| |
| leafValues -= getGroupAttr()->getCharacteristicInputs(); |
| requiredValueIds = leafValues; |
| |
| // Remove all basecolumns (logical columns) |
| // |
| for(ValueId expr = requiredValueIds.init(); |
| requiredValueIds.next(expr); |
| requiredValueIds.advance(expr)) { |
| |
| ItemExpr *ie = expr.getItemExpr(); |
| |
| if(ie->getOperatorType() == ITM_BASECOLUMN) |
| requiredValueIds -= expr; |
| } |
| |
| // the values that are covered by every vertical partition (such as |
| // clustering key) |
| ValueIdSet alwaysCovered; |
| // a list of VEGReferences to the clustering key column(s) |
| ValueIdSet clusteringKeyColumns; |
| // some helper variables |
| ValueIdList clusteringKeyColList; |
| GroupAttributes alwaysCoveredGA; |
| ValueIdSet dummyReferencedInputs; |
| ValueIdSet dummyUnCoveredExpr; |
| |
| // --------------------------------------------------------------------- |
| // find out the subset of values that are always covered |
| // --------------------------------------------------------------------- |
| // get the clustering key columns and transform them into VEGies |
| tableDesc->getEquivVEGCols(tableDesc->getClusteringIndex()->getIndexKey(), |
| clusteringKeyColList); |
| clusteringKeyColumns = clusteringKeyColList; |
| |
| // make group attributes that get the original scan node's char. |
| // inputs and the clustering key columns as outputs (every VP |
| // should have the clustering key), to represent the least common |
| // denominator of all VP attributes |
| alwaysCoveredGA.addCharacteristicOutputs(clusteringKeyColumns); |
| |
| requiredValueIds.isCovered( |
| getGroupAttr()->getCharacteristicInputs(), |
| alwaysCoveredGA, |
| dummyReferencedInputs, |
| alwaysCovered, |
| dummyUnCoveredExpr); |
| |
| // alwaysCovered now contains a set of values that should be covered |
| // by every vertical partition |
| |
| |
| ValueIdSet remainingValueIds = requiredValueIds; |
| |
| // --------------------------------------------------------------------- |
| // For each vertical partition, check whether it provides any useful |
| // values |
| // --------------------------------------------------------------------- |
| for (CollIndex indexNo = 0; indexNo < allVPs.entries(); indexNo++) |
| { |
| IndexDesc *idesc = allVPs[indexNo]; |
| |
| ValueIdSet indexColumns(idesc->getIndexColumns()); |
| ValueIdSet *coveredSubexpr = new (CmpCommon::statementHeap()) |
| ValueIdSet(); |
| ValueIdSet noCharacteristicInputs; |
| GroupAttributes vpGroupAttributes; |
| NABoolean onlyOneVPRequired; |
| |
| // make group attributes for a vertical partition scan |
| vpGroupAttributes.addCharacteristicOutputs(idesc->getIndexColumns()); |
| |
| // does the index cover all required values, and if not, which |
| // ones does it cover |
| onlyOneVPRequired = requiredValueIds.isCovered(noCharacteristicInputs, |
| vpGroupAttributes, |
| dummyReferencedInputs, |
| *coveredSubexpr, |
| dummyUnCoveredExpr); |
| |
| if (onlyOneVPRequired) |
| { |
| // This vertical partition supplies all the required values. |
| // That means we have all the required vertical partitions |
| // and we are done. In fact, if we had selected other VPs |
| // before we need to clear them out along with the columns |
| // they provide. |
| // There should not be any selection predicates in this list |
| // since they should have been eliminated from |
| // requiredValueIdsby the leaf value id code earlier. |
| requiredVPs.clear(); |
| columnsProvidedByVP.clear(); |
| requiredVPs.insert(idesc); |
| columnsProvidedByVP.insert(coveredSubexpr); |
| return; |
| } |
| else |
| { |
| if(remainingValueIds.entries() > 0) { |
| coveredSubexpr->clear(); |
| |
| requiredValueIds.isCovered(noCharacteristicInputs, |
| vpGroupAttributes, |
| dummyReferencedInputs, |
| *coveredSubexpr, |
| dummyUnCoveredExpr); |
| |
| |
| // This vertical partition does not provide all required values. |
| // Normally we wouldn't expect it to. But does it provide a |
| // column value other than the clustering key? If it does, it's in! |
| // We should take out the selection predicates since we will |
| // not be evaluating any predicates in the VP scan. |
| if ( (*coveredSubexpr != alwaysCovered) && |
| ((*coveredSubexpr).entries() > 0) ) |
| { |
| requiredVPs.insert(idesc); |
| *coveredSubexpr -= getSelectionPred(); |
| columnsProvidedByVP.insert(coveredSubexpr); |
| } // VP delivers column values |
| |
| remainingValueIds -= *coveredSubexpr; |
| } |
| } // not onlyOneVPRequired |
| } // for each VP |
| |
| return; |
| |
| } // Scan::getRequiredVerticalPartitions |
| |
| void Scan::addBaseColsFromVEGPreds(ValueIdSet &vs) const |
| { |
| // get all the base columns of the table (no VEGies) |
| ValueIdSet baseCols(tabId_->getColumnList()); |
| |
| for (ValueId x = getSelectionPred().init(); |
| getSelectionPred().next(x); |
| getSelectionPred().advance(x)) |
| { |
| ItemExpr *ie = x.getItemExpr(); |
| |
| if (ie->getOperatorType() == ITM_VEG_PREDICATE) |
| { |
| // get the VEG members |
| ValueIdSet vegMembers( |
| ((VEGPredicate *)ie)->getVEG()->getAllValues()); |
| |
| // filter out the base columns of this table that are VEG members |
| // and add them to the output parameter |
| vegMembers.intersectSet(baseCols); |
| vs += vegMembers; |
| } |
| } |
| } |
| |
| void Scan::addLocalExpr(LIST(ExprNode *) &xlist, |
| LIST(NAString) &llist) const |
| { |
| RelExpr::addLocalExpr(xlist,llist); |
| } |
| |
| |
| NABoolean Scan::reconcileGroupAttr(GroupAttributes *newGroupAttr) |
| { |
| addIndexInfo(); |
| const SET(IndexDesc *) & indexOnlyScans = deriveIndexOnlyIndexDesc(); |
| const SET(IndexDesc *) & indexJoinScans = deriveIndexJoinIndexDesc(); |
| // we add the available indexes on this scan node to the |
| // new GroupAttrs availableBtreeIndexes |
| newGroupAttr->addToAvailableBtreeIndexes(indexOnlyScans); |
| newGroupAttr->addToAvailableBtreeIndexes(indexJoinScans); |
| // This one is not actually necessary |
| getGroupAttr()->addToAvailableBtreeIndexes(indexOnlyScans); |
| getGroupAttr()->addToAvailableBtreeIndexes(indexJoinScans); |
| // Now as usual |
| return RelExpr::reconcileGroupAttr(newGroupAttr); |
| } |
| |
| // -------------------------------------------------------------------- |
| // 10-040128-2749 -begin |
| // This will compute based on the context,Current Control table setting |
| // and defaults. |
| // Input : Context |
| // -------------------------------------------------------------------- |
| NABoolean Scan::isMdamEnabled(const Context *context) |
| { |
| NABoolean mdamIsEnabled = TRUE; |
| |
| // ----------------------------------------------------------------------- |
| // Check the status of the enabled/disabled flag in |
| // the defaults: |
| // ----------------------------------------------------------------------- |
| if (CmpCommon::getDefault(MDAM_SCAN_METHOD) == DF_OFF) |
| mdamIsEnabled = FALSE; |
| |
| // ----------------------------------------------------------------------- |
| // Mdam can also be disabled for a particular scan via Control |
| // Query Shape. The information is passed by the context. |
| // ----------------------------------------------------------------------- |
| if (mdamIsEnabled) |
| { |
| const ReqdPhysicalProperty* propertyPtr = |
| context->getReqdPhysicalProperty(); |
| if ( propertyPtr |
| && propertyPtr->getMustMatch() |
| && (propertyPtr->getMustMatch()->getOperatorType() |
| == REL_FORCE_ANY_SCAN)) |
| { |
| ScanForceWildCard* scanForcePtr = |
| (ScanForceWildCard*)propertyPtr->getMustMatch(); |
| if (scanForcePtr->getMdamStatus() == ScanForceWildCard::MDAM_OFF) |
| mdamIsEnabled = FALSE; |
| } |
| } |
| |
| // ----------------------------------------------------------------------- |
| // Mdam can also be disabled for a particular table via a Control |
| // Table command. |
| // ----------------------------------------------------------------------- |
| if (mdamIsEnabled) |
| { |
| const NAString * val = |
| ActiveControlDB()->getControlTableValue(getTableName().getUgivenName(), "MDAM"); |
| if ((val) && (*val == "OFF")) // CT in effect |
| { |
| mdamIsEnabled = FALSE; |
| } |
| } |
| return mdamIsEnabled; |
| } |
| // 10-040128-2749 -end |
| |
| // ----------------------------------------------------------------------- |
| // methods for class ScanIndexInfo |
| // ----------------------------------------------------------------------- |
| ScanIndexInfo::ScanIndexInfo(const ScanIndexInfo & other) : |
| outputsFromIndex_ (other.outputsFromIndex_), |
| indexPredicates_ (other.indexPredicates_), |
| joinPredicates_ (other.joinPredicates_), |
| outputsFromRightScan_ (other.outputsFromRightScan_), |
| transformationDone_ (other.transformationDone_), |
| indexColumns_ (other.indexColumns_), |
| usableIndexes_ (other.usableIndexes_) |
| {} |
| |
| ScanIndexInfo::ScanIndexInfo( |
| const ValueIdSet& inputsToIndex, |
| const ValueIdSet& outputsFromIndex, |
| const ValueIdSet& indexPredicates, |
| const ValueIdSet& joinPredicates, |
| const ValueIdSet& outputsFromRightScan, |
| const ValueIdSet& indexColumns, |
| IndexProperty* ixProp |
| ) : |
| inputsToIndex_(inputsToIndex), |
| outputsFromIndex_(outputsFromIndex), |
| indexPredicates_(indexPredicates), |
| joinPredicates_(joinPredicates), |
| outputsFromRightScan_(outputsFromRightScan), |
| indexColumns_(indexColumns), |
| transformationDone_(FALSE), |
| usableIndexes_(CmpCommon::statementHeap()) |
| { |
| usableIndexes_.insert(ixProp); |
| } |
| |
| |
| // ----------------------------------------------------------------------- |
| // methods for class FileScan |
| // ----------------------------------------------------------------------- |
| |
| FileScan::FileScan(const CorrName& tableName, |
| TableDesc * tableDescPtr, |
| const IndexDesc *indexDescPtr, |
| const NABoolean isReverseScan, |
| const Cardinality& baseCardinality, |
| StmtLevelAccessOptions& accessOpts, |
| GroupAttributes * groupAttributesPtr, |
| const ValueIdSet& selectionPredicates, |
| const Disjuncts& disjuncts, |
| const ValueIdSet& generatedCCPreds, |
| OperatorTypeEnum otype) : |
| Scan (tableName, tableDescPtr, otype), |
| indexDesc_(indexDescPtr), |
| reverseScan_(isReverseScan), |
| executorPredTree_(NULL), |
| mdamKeyPtr_(NULL), |
| disjunctsPtr_(&disjuncts), |
| pathKeys_(NULL), |
| partKeys_(NULL), |
| hiveSearchKey_(NULL), |
| estRowsAccessed_ (0), |
| mdamFlag_(UNDECIDED), |
| skipRowsToPreventHalloween_(FALSE), |
| doUseSearchKey_(TRUE), |
| computedNumOfActivePartitions_(-1) |
| { |
| // Set the filescan properties: |
| |
| |
| // Set the base cardinality to that for the logical scan |
| setBaseCardinality(baseCardinality); |
| |
| // move the statement level access options |
| accessOptions() = accessOpts; |
| |
| // the top node keeps the original group attributes |
| setGroupAttr(groupAttributesPtr); |
| |
| // Initialize selection predicates: |
| // (they are needed to set the executor predicates later in |
| // pre-code gen) |
| selectionPred().insert(selectionPredicates); |
| |
| // Get the predicates on the partitioning key: |
| if (getIndexDesc() && getIndexDesc()->isPartitioned()) |
| { |
| ValueIdSet externalInputs = getGroupAttr()->getCharacteristicInputs(); |
| ValueIdSet dummySet; |
| ValueIdSet selPreds(selectionPredicates); |
| |
| // Create and set the Searchkey for the partitioning key: |
| partKeys_ = new (CmpCommon::statementHeap()) |
| SearchKey(indexDesc_->getPartitioningKey(), |
| indexDesc_->getOrderOfPartitioningKeyValues(), |
| externalInputs, |
| NOT getReverseScan(), |
| selPreds, |
| disjuncts, |
| dummySet, // needed by interface but not used here |
| indexDesc_ |
| ); |
| |
| |
| if ( indexDesc_->getPartitioningFunction() && |
| indexDesc_->getPartitioningFunction()->castToRangePartitioningFunction() ) |
| { |
| const RangePartitioningFunction* rangePartFunc = |
| indexDesc_->getPartitioningFunction()->castToRangePartitioningFunction(); |
| |
| computedNumOfActivePartitions_ = |
| rangePartFunc->computeNumOfActivePartitions(partKeys_, tableDescPtr); |
| } |
| } |
| setComputedPredicates(generatedCCPreds); |
| |
| } // FileScan() |
| |
| void FileScan::getPotentialOutputValues(ValueIdSet & outputValues) const |
| { |
| outputValues.clear(); |
| // |
| // Assign the set of columns that belong to the index to be scanned |
| // as the output values that can be produced by this scan. |
| // |
| outputValues.insertList( getIndexDesc()->getIndexColumns() ); |
| |
| // MV -- |
| // Add the CurrentEpoch column as well. |
| outputValues.insert(getExtraOutputColumns()); |
| } // FileScan::getPotentialOutputValues() |
| |
| NABoolean FileScan::patternMatch(const RelExpr & other) const |
| { |
| // handle the special case of a pattern to force a |
| // specific table or index |
| if (other.getOperatorType() == REL_FORCE_ANY_SCAN) |
| { |
| ScanForceWildCard &w = (ScanForceWildCard &) other; |
| |
| if (w.getExposedName() != "") |
| { |
| QualifiedName wName(w.getExposedName(), 1 /* minimal 1 part name */); |
| |
| if (getTableName().getCorrNameAsString() != "") |
| { |
| // query uses a correlation name, compare that with the wildcard |
| // as a string |
| if (wName.getQualifiedNameAsAnsiString() != |
| ToAnsiIdentifier(getTableName().getCorrNameAsString())) |
| return FALSE; |
| } |
| else |
| { |
| // no correlation name used in the query, compare catalog, schema |
| // and table parts separately, if they exist in the wildcard |
| const NAString& catName = wName.getCatalogName(); |
| const NAString& schName = wName.getSchemaName(); |
| const QualifiedName& x = getTableName(). |
| getExtendedQualNameObj().getQualifiedNameObj(); |
| |
| if ((catName.length() > 0 && x.getCatalogName() != catName) || |
| (schName.length() > 0 && x.getSchemaName() != schName) || |
| x.getObjectName() != wName.getObjectName()) |
| return FALSE; |
| } |
| } |
| |
| // if an index name was specified in the wildcard, check for it |
| if (w.getIndexName() != "") |
| { |
| NAString forcedIndexName(w.getIndexName(), |
| CmpCommon::statementHeap()); |
| |
| // The user can specify the index to be the base table in the |
| // Control Query Shape statement by using the table name (object |
| // name or correlation) as the index name. Ex: scan('t1','t1',..) |
| // since t1 might be a correlation name, its necessary to check |
| // for the corresponding object name and not the table correlation |
| // name when searching for the index match. |
| if (forcedIndexName == w.getExposedName()) |
| forcedIndexName = ToAnsiIdentifier( |
| getTableName().getQualifiedNameObj().getObjectName() |
| ); |
| |
| // get the three-part name of the index |
| const NAString &ixName = indexDesc_->getNAFileSet()->getExtFileSetName(); |
| |
| // Declare a match if either the index name in w is equal to |
| // indexName or if it is equal to the last part of indexName. |
| //if (w.getIndexName() != ixName) |
| if (forcedIndexName != ixName) |
| { |
| QualifiedName ixNameQ(ixName, 1); |
| |
| if ( ToAnsiIdentifier(ixNameQ.getObjectName()) != forcedIndexName ) |
| return FALSE; |
| } |
| } |
| |
| return TRUE; |
| } |
| else |
| return RelExpr::patternMatch(other); |
| } |
| |
| NABoolean FileScan::duplicateMatch(const RelExpr & other) const |
| { |
| if (!Scan::duplicateMatch(other)) |
| return FALSE; |
| |
| FileScan &o = (FileScan &) other; |
| |
| if (//beginKeyPred_ != o.beginKeyPred_ OR |
| //endKeyPred_ != o.endKeyPred_ OR |
| retrievedCols_ != o.retrievedCols_ OR |
| getExecutorPredicates() != o.getExecutorPredicates()) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| RelExpr * FileScan::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| |
| // $$$ Function needs to be updated with new fields |
| // added to the filescan. |
| // If you need to use this function please update it |
| // with those new fields (i.e. SearchKey *, etc's) |
| // then remove the following abort |
| // CMPABORT; |
| FileScan *result; |
| |
| if (derivedNode == NULL) |
| result = new(outHeap) FileScan(getTableName(), |
| getTableDesc(), |
| getIndexDesc(), |
| REL_FILE_SCAN, |
| outHeap); |
| else |
| result = (FileScan *) derivedNode; |
| |
| result->setBaseCardinality(getBaseCardinality()); |
| result->setEstRowsAccessed(getEstRowsAccessed()); |
| result->beginKeyPred_ = beginKeyPred_; |
| result->endKeyPred_ = endKeyPred_; |
| result->setExecutorPredicates(getExecutorPredicates()); |
| result->retrievedCols_ = retrievedCols_; |
| |
| return Scan::copyTopNode(result, outHeap); |
| } |
| |
| NABoolean FileScan::isLogical() const { return FALSE; } |
| |
| NABoolean FileScan::isPhysical() const { return TRUE; } |
| |
| PlanPriority FileScan::computeOperatorPriority |
| (const Context* context, |
| PlanWorkSpace *pws, |
| Lng32 planNumber) |
| { |
| |
| PlanPriority result; |
| |
| // --------------------------------------------------------------------- |
| // If under interactive_access mode, then give preference to plans that |
| // avoid full table scans |
| // Similarly if under firstN optimization mode then give preference to |
| // plans that avoid full table scans |
| // --------------------------------------------------------------------- |
| NABoolean interactiveAccess = |
| (CmpCommon::getDefault(INTERACTIVE_ACCESS) == DF_ON) OR |
| ( QueryAnalysis::Instance() AND |
| QueryAnalysis::Instance()->optimizeForFirstNRows()); |
| int indexPriorityDelta = getIndexDesc()->indexHintPriorityDelta(); |
| |
| if (interactiveAccess) |
| { |
| if(getMdamKeyPtr()) |
| { |
| // We have MDAM. Give this a preference |
| result.incrementLevels(INTERACTIVE_ACCESS_MDAM_PRIORITY,0); |
| } |
| else if(getSearchKeyPtr() AND |
| getSearchKeyPtr()->getKeyPredicates().entries()) |
| { |
| // We have direct index access. Give this a preference |
| result.incrementLevels(INTERACTIVE_ACCESS_PRIORITY,0); |
| } |
| } |
| |
| if (indexPriorityDelta && !areHintsSuppressed()) |
| // yes, the index is one of the indexes listed in the hint |
| result.incrementLevels(indexPriorityDelta, 0); |
| |
| return result; |
| } |
| |
| // currently only used by MV query rewrite |
| PlanPriority PhysicalMapValueIds::computeOperatorPriority |
| (const Context* context, |
| PlanWorkSpace *pws, |
| Lng32 planNumber) |
| { |
| PlanPriority result; |
| |
| // is this MVI wraps one of the favorite MVs |
| // (included in the MVQR_REWRITE_CANDIDATES default) |
| if (includesFavoriteMV()) |
| { |
| result.incrementLevels(MVQR_FAVORITE_PRIORITY,0); |
| } |
| |
| return result; |
| } |
| |
| const NAString FileScan::getText() const |
| { |
| // --------------------------------------------------------------------- |
| // returns: |
| // |
| // file scan t c for a primary index scan on table |
| // t with correlation name c |
| // index scan ix(t c) for an index scan on index ix of |
| // table t |
| // rev. index scan ix(t c) if the scan goes backwards |
| // --------------------------------------------------------------------- |
| |
| NAString op(CmpCommon::statementHeap()); |
| NAString tname(getTableName().getText(),CmpCommon::statementHeap()); |
| |
| if (isSampleScan() == TRUE) |
| op = "sample_"; |
| if (indexDesc_ == NULL OR indexDesc_->isClusteringIndex()) |
| { |
| if (isHiveTable()) |
| op += "hive_scan "; |
| else |
| op += "file_scan "; |
| } |
| else { |
| op += "index_scan "; |
| tname = indexDesc_->getIndexName().getQualifiedNameAsString() + |
| "(" + tname + ")"; |
| } |
| |
| if (reverseScan_) |
| op += NAString("rev "); |
| |
| return op + tname; |
| } |
| |
| const NAString FileScan::getTypeText() const |
| { |
| NAString descr(CmpCommon::statementHeap()); |
| NAString tname(getTableName().getText(),CmpCommon::statementHeap()); |
| |
| if (isSampleScan() == TRUE) |
| descr = "sample "; |
| |
| if (reverseScan_) |
| descr += NAString("reverse "); |
| |
| if (isFullScanPresent() && !getMdamKeyPtr()) |
| { |
| descr += "full scan "; |
| if (getMdamKeyPtr()) |
| descr += "limited by mdam "; |
| } |
| else |
| { |
| descr += "subset scan "; |
| if (getMdamKeyPtr()) |
| descr += "limited by mdam "; |
| } |
| |
| descr += "of "; |
| |
| if (indexDesc_ == NULL OR indexDesc_->isClusteringIndex()) |
| descr += "table "; |
| else { |
| descr += "index "; |
| tname = indexDesc_->getIndexName().getQualifiedNameAsString() + |
| "(" + tname + ")"; |
| } |
| descr += tname; |
| |
| return descr; |
| } |
| |
| void FileScan::addLocalExpr(LIST(ExprNode *) &xlist, |
| LIST(NAString) &llist) const |
| { |
| if (getIndexDesc() != NULL) |
| { |
| const ValueIdList& keyColumns = getIndexDesc()->getIndexKey(); |
| xlist.insert(keyColumns.rebuildExprTree()); |
| llist.insert("key_columns"); |
| |
| } |
| |
| if (executorPredTree_ != NULL OR |
| NOT getExecutorPredicates().isEmpty()) |
| { |
| if (getExecutorPredicates().isEmpty()) |
| xlist.insert(executorPredTree_); |
| else |
| xlist.insert(getExecutorPredicates().rebuildExprTree()); |
| llist.insert("executor_predicates"); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // Display key information |
| // ----------------------------------------------------------------------- |
| if (getMdamKeyPtr() != NULL) |
| { |
| // Mdam access! |
| const CollIndex columns = getIndexDesc()->getIndexKey().entries(); |
| const CollIndex disjEntries = |
| getMdamKeyPtr()->getKeyDisjunctEntries(); |
| |
| // If we are in the optimizer, obtain key preds from |
| // the disjuncts, |
| // else obtain them from the column order list array: |
| if (NOT nodeIsPreCodeGenned()) |
| { |
| // We are in the optimizer... |
| // For every disjunct |
| for (CollIndex i=0; |
| i < disjEntries; |
| i++) |
| { |
| ColumnOrderList kpbc(getIndexDesc()->getIndexKey()); |
| getMdamKeyPtr()->getKeyPredicatesByColumn(kpbc,i); |
| |
| // gather the key predicates: |
| ValueIdSet keyPreds; |
| |
| for (CollIndex j=0; j < columns; j++) |
| { |
| if (kpbc[j]) |
| { |
| keyPreds.insert(*(kpbc[j])); |
| } |
| } |
| |
| // display this disjunct key preds. into the GUI: |
| xlist.insert(keyPreds.rebuildExprTree()); |
| llist.insert("mdam_disjunct"); |
| } // for every disjunct |
| } |
| else |
| { |
| // we are after the generator... |
| |
| const ColumnOrderListPtrArray &columnOrderListPtrArray = |
| getMdamKeyPtr()->getColumnOrderListPtrArray(); |
| |
| // we are in the generator, obtain the key preds |
| // from thr column order list: |
| ValueIdSet *predsPtr = NULL; |
| for (CollIndex n = 0; n < columnOrderListPtrArray.entries(); n++) |
| { |
| // get the list of key predicates associated with the n disjunct: |
| const ColumnOrderList &columnOrderList = |
| *columnOrderListPtrArray[n]; |
| // get predicates for column order i: |
| |
| // gather the key predicates: |
| ValueIdSet keyPreds; |
| |
| const ValueIdSet *predsPtr = NULL; |
| for (CollIndex i = 0; i < columnOrderList.entries(); i++) |
| { |
| predsPtr = columnOrderList[i]; |
| if (predsPtr) |
| { |
| keyPreds.insert(*predsPtr); |
| } |
| } |
| |
| // display this disjunct key preds. into the GUI: |
| xlist.insert(keyPreds.rebuildExprTree()); |
| llist.insert("mdam_disjunct"); |
| } |
| } // mdam after the generator |
| |
| |
| |
| } // mdam access |
| else if (getSearchKeyPtr() != NULL) // Is Single subset access? |
| { |
| // yes! |
| // display preds from search key only if begin/end keys are |
| // not generated yet (e.g. during optimization) |
| if (getBeginKeyPred().isEmpty() AND |
| getEndKeyPred().isEmpty() AND |
| pathKeys_ AND NOT pathKeys_->getKeyPredicates().isEmpty()) |
| { |
| xlist.insert(pathKeys_->getKeyPredicates().rebuildExprTree()); |
| if (pathKeys_ == partKeys_) |
| llist.insert("key_and_part_key_preds"); |
| else |
| llist.insert("key_predicates"); |
| } |
| } |
| |
| // display part key preds only if different from clustering key preds |
| if (partKeys_ AND pathKeys_ != partKeys_ AND |
| NOT partKeys_->getKeyPredicates().isEmpty()) |
| { |
| xlist.insert(partKeys_->getKeyPredicates().rebuildExprTree()); |
| llist.insert("part_key_predicates"); |
| } |
| |
| if (NOT getBeginKeyPred().isEmpty()) |
| { |
| xlist.insert(getBeginKeyPred().rebuildExprTree()); |
| llist.insert("begin_key"); |
| } |
| if (NOT getEndKeyPred().isEmpty()) |
| { |
| xlist.insert(getEndKeyPred().rebuildExprTree()); |
| llist.insert("end_key"); |
| } |
| |
| // xlist.insert(retrievedCols_.rebuildExprTree(ITM_ITEM_LIST)); |
| // llist.insert("retrieved_cols"); |
| |
| RelExpr::addLocalExpr(xlist,llist); |
| } |
| |
| const Disjuncts& FileScan::getDisjuncts() const |
| { |
| CMPASSERT(disjunctsPtr_ != NULL); |
| return *disjunctsPtr_; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // methods for class HbaseAccess |
| // ----------------------------------------------------------------------- |
| |
| HbaseAccess::HbaseAccess(CorrName &corrName, |
| OperatorTypeEnum otype, |
| CollHeap *oHeap) |
| : FileScan(corrName, NULL, NULL, otype, oHeap), |
| listOfSearchKeys_(oHeap), |
| snpType_(SNP_NONE), |
| retHbaseColRefSet_(oHeap), |
| opList_(oHeap) |
| { |
| accessType_ = SELECT_; |
| uniqueHbaseOper_ = FALSE; |
| uniqueRowsetHbaseOper_ = FALSE; |
| } |
| |
| HbaseAccess::HbaseAccess(CorrName &corrName, |
| TableDesc *tableDesc, |
| IndexDesc *idx, |
| const NABoolean isReverseScan, |
| const Cardinality& baseCardinality, |
| StmtLevelAccessOptions& accessOptions, |
| GroupAttributes * groupAttributesPtr, |
| const ValueIdSet& selectionPredicates, |
| const Disjuncts& disjuncts, |
| const ValueIdSet& generatedCCPreds, |
| OperatorTypeEnum otype, |
| CollHeap *oHeap) |
| : FileScan(corrName, tableDesc, idx, |
| isReverseScan, baseCardinality, |
| accessOptions, groupAttributesPtr, |
| selectionPredicates, disjuncts, |
| generatedCCPreds, |
| otype), |
| listOfSearchKeys_(oHeap), |
| snpType_(SNP_NONE), |
| retHbaseColRefSet_(oHeap), |
| opList_(oHeap) |
| { |
| accessType_ = SELECT_; |
| //setTableDesc(tableDesc); |
| uniqueHbaseOper_ = FALSE; |
| uniqueRowsetHbaseOper_ = FALSE; |
| } |
| |
| HbaseAccess::HbaseAccess(CorrName &corrName, |
| NABoolean isRW, NABoolean isCW, |
| CollHeap *oHeap) |
| : FileScan(corrName, NULL, NULL, REL_HBASE_ACCESS, oHeap), |
| isRW_(isRW), |
| isCW_(isCW), |
| listOfSearchKeys_(oHeap), |
| snpType_(SNP_NONE), |
| retHbaseColRefSet_(oHeap), |
| opList_(oHeap) |
| { |
| accessType_ = SELECT_; |
| uniqueHbaseOper_ = FALSE; |
| uniqueRowsetHbaseOper_ = FALSE; |
| } |
| |
| HbaseAccess::HbaseAccess( OperatorTypeEnum otype, |
| CollHeap *oHeap) |
| : FileScan(CorrName(), NULL, NULL, otype, oHeap), |
| listOfSearchKeys_(oHeap), |
| snpType_(SNP_NONE), |
| retHbaseColRefSet_(oHeap), |
| opList_(oHeap) |
| { |
| accessType_ = SELECT_; |
| uniqueHbaseOper_ = FALSE; |
| uniqueRowsetHbaseOper_ = FALSE; |
| } |
| |
| //! HbaseAccess::~HbaseAccess Destructor |
| HbaseAccess::~HbaseAccess() |
| { |
| } |
| |
| //! HbaseAccess::copyTopNode method |
| RelExpr * HbaseAccess::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| HbaseAccess *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) HbaseAccess(REL_HBASE_ACCESS, outHeap); |
| else |
| result = (HbaseAccess *) derivedNode; |
| |
| //result->corrName_ = corrName_; |
| result->accessType_ = accessType_; |
| result->isRW_ = isRW_; |
| result->isCW_ = isCW_; |
| |
| result->setTableDesc(getTableDesc()); |
| result->setIndexDesc(getIndexDesc()); |
| |
| //result->setTableDesc(getTableDesc()); |
| result->listOfSearchKeys_ = listOfSearchKeys_; |
| |
| result->retColRefSet_ = retColRefSet_; |
| |
| result->uniqueHbaseOper_ = uniqueHbaseOper_; |
| result->uniqueRowsetHbaseOper_ = uniqueRowsetHbaseOper_; |
| |
| return Scan::copyTopNode(result, outHeap); |
| // return BuiltinTableValuedFunction::copyTopNode(result, outHeap); |
| } |
| |
| const NAString HbaseAccess::getText() const |
| { |
| NAString op(CmpCommon::statementHeap()); |
| NAString tname(getTableName().getText(),CmpCommon::statementHeap()); |
| |
| NAString sampleOpt(CmpCommon::statementHeap()); |
| if (isSampleScan()) |
| sampleOpt = "sample_"; |
| |
| if (getIndexDesc() == NULL OR getIndexDesc()->isClusteringIndex()) |
| { |
| if (isSeabaseTable()) |
| { |
| if (uniqueRowsetHbaseOper()) |
| (op += "trafodion_vsbb_") += sampleOpt += "scan "; |
| else |
| (op += "trafodion_") += sampleOpt += "scan "; |
| } |
| else |
| (op += "hbase_") += sampleOpt += "scan "; |
| } |
| else |
| { |
| if (isSeabaseTable()) |
| (op += "trafodion_index_") += sampleOpt += "scan "; |
| else |
| (op += "hbase_index_") += sampleOpt += "scan "; |
| |
| tname = getIndexDesc()->getIndexName().getQualifiedNameAsString() + |
| "(" + tname + ")"; |
| } |
| |
| if (getReverseScan()) |
| op += NAString("rev "); |
| |
| return op + tname; |
| } |
| |
| RelExpr *HbaseAccess::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| CorrName &corrName = getTableName(); |
| NATable * naTable = NULL; |
| |
| naTable = bindWA->getSchemaDB()->getNATableDB()-> |
| get(&corrName.getExtendedQualNameObj()); |
| |
| if ( !naTable || bindWA->errStatus()) |
| { |
| *CmpCommon::diags() |
| << DgSqlCode(-1388) |
| << DgString0("Object") |
| << DgString1(corrName.getExposedNameAsAnsiString()); |
| |
| bindWA->setErrStatus(); |
| return this; |
| } |
| |
| // Allocate a TableDesc and attach it to this. |
| // |
| TableDesc * td = bindWA->createTableDesc(naTable, corrName); |
| if (! td || bindWA->errStatus()) |
| return this; |
| |
| setTableDesc(td); |
| setIndexDesc(td->getClusteringIndex()); |
| if (bindWA->errStatus()) |
| return this; |
| |
| RelExpr * re = NULL; |
| // re = BuiltinTableValuedFunction::bindNode(bindWA); |
| re = Scan::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| return re; |
| } |
| |
| void HbaseAccess::getPotentialOutputValues( |
| ValueIdSet & outputValues) const |
| { |
| outputValues.clear(); |
| |
| // since this is a physical operator, it only generates the index columns |
| outputValues.insertList( getIndexDesc()->getIndexColumns() ); |
| outputValues.insertList( getTableDesc()->hbaseTSList() ); |
| outputValues.insertList( getTableDesc()->hbaseVersionList() ); |
| |
| } // HbaseAccess::getPotentialOutputValues() |
| |
| void |
| HbaseAccess::synthEstLogProp(const EstLogPropSharedPtr& inputEstLogProp) |
| { |
| if (getGroupAttr()->isPropSynthesized(inputEstLogProp)) |
| return; |
| |
| // Create a new Output Log Property with cardinality of 10 for now. |
| EstLogPropSharedPtr myEstProps(new (HISTHEAP) EstLogProp(10)); |
| |
| getGroupAttr()->addInputOutputLogProp(inputEstLogProp, myEstProps); |
| |
| } // HbaseAccess::synthEstLogProp |
| |
| void |
| HbaseAccess::synthLogProp(NormWA * normWAPtr) |
| { |
| // Check to see whether this GA has already been associated |
| // with a logExpr for synthesis. If so, no need to resynthesize |
| // for this equivalent log. expression. |
| if (getGroupAttr()->existsLogExprForSynthesis()) return; |
| |
| RelExpr::synthLogProp(normWAPtr); |
| |
| } // HbaseAccess::synthLogProp() |
| |
| // ----------------------------------------------------------------------- |
| // methods for class HBaseAccessCoProcAggr |
| // ----------------------------------------------------------------------- |
| |
| HbaseAccessCoProcAggr::HbaseAccessCoProcAggr(CorrName &corrName, |
| ValueIdSet &aggregateExpr, |
| TableDesc *tableDesc, |
| IndexDesc *idx, |
| const NABoolean isReverseScan, |
| const Cardinality& baseCardinality, |
| StmtLevelAccessOptions& accessOptions, |
| GroupAttributes * groupAttributesPtr, |
| const ValueIdSet& selectionPredicates, |
| const Disjuncts& disjuncts, |
| CollHeap *oHeap) |
| : HbaseAccess(corrName, tableDesc, idx, |
| isReverseScan, baseCardinality, |
| accessOptions, groupAttributesPtr, |
| selectionPredicates, disjuncts, |
| ValueIdSet(), |
| REL_HBASE_COPROC_AGGR), |
| aggregateExpr_(aggregateExpr) |
| { |
| accessType_ = COPROC_AGGR_; |
| |
| } |
| |
| HbaseAccessCoProcAggr::HbaseAccessCoProcAggr(CorrName &corrName, |
| ValueIdSet &aggregateExpr, |
| CollHeap *oHeap) |
| : HbaseAccess(corrName, |
| REL_HBASE_COPROC_AGGR, |
| oHeap), |
| aggregateExpr_(aggregateExpr) |
| { |
| accessType_ = COPROC_AGGR_; |
| } |
| |
| HbaseAccessCoProcAggr::HbaseAccessCoProcAggr( CollHeap *oHeap) |
| : HbaseAccess(REL_HBASE_COPROC_AGGR, oHeap) |
| { |
| accessType_ = SELECT_; |
| |
| uniqueHbaseOper_ = FALSE; |
| uniqueRowsetHbaseOper_ = FALSE; |
| } |
| |
| //! HbaseAccessCoProcAggr::~HbaseAccessCoProcAggr Destructor |
| HbaseAccessCoProcAggr::~HbaseAccessCoProcAggr() |
| { |
| } |
| |
| //! HbaseAccessCoProcAggr::copyTopNode method |
| RelExpr * HbaseAccessCoProcAggr::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| HbaseAccessCoProcAggr *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) HbaseAccessCoProcAggr(outHeap); |
| else |
| result = (HbaseAccessCoProcAggr *) derivedNode; |
| |
| result->aggregateExpr_ = aggregateExpr_; |
| |
| return HbaseAccess::copyTopNode(result, outHeap); |
| } |
| |
| const NAString HbaseAccessCoProcAggr::getText() const |
| { |
| NAString op(CmpCommon::statementHeap()); |
| NAString tname(getTableName().getText(),CmpCommon::statementHeap()); |
| |
| op += "hbase_coproc_aggr "; |
| |
| return op + tname; |
| } |
| |
| RelExpr *HbaseAccessCoProcAggr::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| RelExpr * re = NULL; |
| re = HbaseAccess::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| return re; |
| } |
| |
| void HbaseAccessCoProcAggr::getPotentialOutputValues( |
| ValueIdSet & outputValues) const |
| { |
| outputValues.clear(); |
| |
| outputValues += aggregateExpr(); |
| } // HbaseAccessCoProcAggr::getPotentialOutputValues() |
| |
| PhysicalProperty* |
| HbaseAccessCoProcAggr::synthPhysicalProperty(const Context* myContext, |
| const Lng32 planNumber, |
| PlanWorkSpace *pws) |
| { |
| |
| //---------------------------------------------------------- |
| // Create a node map with a single, active, wild-card entry. |
| //---------------------------------------------------------- |
| NodeMap* myNodeMap = new(CmpCommon::statementHeap()) |
| NodeMap(CmpCommon::statementHeap(), |
| 1, |
| NodeMapEntry::ACTIVE); |
| |
| //------------------------------------------------------------ |
| // Synthesize a partitioning function with a single partition. |
| //------------------------------------------------------------ |
| PartitioningFunction* myPartFunc = |
| new(CmpCommon::statementHeap()) |
| SinglePartitionPartitioningFunction(myNodeMap); |
| |
| PhysicalProperty * sppForMe = |
| new(CmpCommon::statementHeap()) |
| PhysicalProperty(myPartFunc, |
| EXECUTE_IN_MASTER, |
| SOURCE_VIRTUAL_TABLE); |
| |
| // remove anything that's not covered by the group attributes |
| sppForMe->enforceCoverageByGroupAttributes (getGroupAttr()) ; |
| |
| return sppForMe; |
| } // HbaseAccessCoProcAggr::synthPhysicalProperty() |
| |
| // ----------------------------------------------------------------------- |
| // methods for class HbaseDelete |
| // ----------------------------------------------------------------------- |
| |
| HbaseDelete::HbaseDelete(CorrName &corrName, |
| RelExpr *scan, |
| CollHeap *oHeap) |
| : Delete(corrName, NULL, REL_HBASE_DELETE, scan, NULL, NULL, NULL, oHeap), |
| corrName_(corrName), |
| listOfSearchKeys_(oHeap) |
| { |
| hbaseOper() = TRUE; |
| } |
| |
| |
| HbaseDelete::HbaseDelete(CorrName &corrName, |
| TableDesc *tableDesc, |
| CollHeap *oHeap) |
| : Delete(corrName, tableDesc, REL_HBASE_DELETE, NULL, NULL, NULL, NULL,oHeap), |
| corrName_(corrName), |
| listOfSearchKeys_(oHeap) |
| { |
| hbaseOper() = TRUE; |
| |
| } |
| |
| HbaseDelete::HbaseDelete( CollHeap *oHeap) |
| : Delete(CorrName(""), NULL, REL_HBASE_DELETE, NULL, NULL, NULL, NULL, oHeap), |
| listOfSearchKeys_(oHeap) |
| { |
| hbaseOper() = TRUE; |
| } |
| |
| //! HbaseDelete::~HbaseDelete Destructor |
| HbaseDelete::~HbaseDelete() |
| { |
| } |
| |
| //! HbaseDelete::copyTopNode method |
| RelExpr * HbaseDelete::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| HbaseDelete *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) HbaseDelete(corrName_, getTableDesc(), outHeap); |
| else |
| result = (HbaseDelete *) derivedNode; |
| |
| result->corrName_ = corrName_; |
| result->setTableDesc(getTableDesc()); |
| |
| result->listOfSearchKeys_ = listOfSearchKeys_; |
| |
| result->retColRefSet_ = retColRefSet_; |
| |
| return Delete::copyTopNode(result, outHeap); |
| } |
| |
| RelExpr *HbaseDelete::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| RelExpr * re = NULL; |
| re = Delete::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| return re; |
| } |
| |
| //! HbaseDelete::getText method |
| const NAString HbaseDelete::getText() const |
| { |
| NABoolean isSeabase = |
| (getTableDesc() && getTableDesc()->getNATable() ? |
| getTableDesc()->getNATable()->isSeabaseTable() : FALSE); |
| |
| NAString text; |
| |
| if (NOT isSeabase) |
| text = "hbase_"; |
| else |
| text = "trafodion_"; |
| |
| if (uniqueRowsetHbaseOper()) |
| text += "vsbb_"; |
| |
| text += "delete"; |
| |
| return text; |
| } |
| |
| Int32 HbaseDelete::getArity() const |
| { |
| return 0; |
| } |
| |
| void HbaseDelete::getPotentialOutputValues( |
| ValueIdSet & outputValues) const |
| { |
| outputValues.clear(); |
| // since this is a physical operator, it only generates the index columns |
| if (getScanIndexDesc()) |
| outputValues.insertList(getScanIndexDesc()->getIndexColumns()); |
| } // HbaseDelete::getPotentialOutputValues() |
| |
| // ----------------------------------------------------------------------- |
| // methods for class HbaseUpdate |
| // ----------------------------------------------------------------------- |
| |
| HbaseUpdate::HbaseUpdate(CorrName &corrName, |
| RelExpr *scan, |
| CollHeap *oHeap) |
| : UpdateCursor(corrName, NULL, REL_HBASE_UPDATE, scan, oHeap), |
| corrName_(corrName), |
| listOfSearchKeys_(oHeap) |
| { |
| hbaseOper() = TRUE; |
| } |
| |
| |
| HbaseUpdate::HbaseUpdate(CorrName &corrName, |
| TableDesc *tableDesc, |
| CollHeap *oHeap) |
| : UpdateCursor(corrName, tableDesc, REL_HBASE_UPDATE, NULL, oHeap), |
| corrName_(corrName), |
| listOfSearchKeys_(oHeap) |
| { |
| hbaseOper() = TRUE; |
| |
| } |
| |
| HbaseUpdate::HbaseUpdate( CollHeap *oHeap) |
| : UpdateCursor(CorrName(""), NULL, REL_HBASE_UPDATE, NULL, oHeap), |
| listOfSearchKeys_(oHeap) |
| { |
| hbaseOper() = TRUE; |
| } |
| |
| //! HbaseUpdate::~HbaseUpdate Destructor |
| HbaseUpdate::~HbaseUpdate() |
| { |
| } |
| |
| //! HbaseUpdate::copyTopNode method |
| RelExpr * HbaseUpdate::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| HbaseUpdate *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) HbaseUpdate(corrName_, getTableDesc(), outHeap); |
| else |
| result = (HbaseUpdate *) derivedNode; |
| |
| result->corrName_ = corrName_; |
| result->setTableDesc(getTableDesc()); |
| |
| result->listOfSearchKeys_ = listOfSearchKeys_; |
| |
| result->retColRefSet_ = retColRefSet_; |
| |
| return Update::copyTopNode(result, outHeap); |
| } |
| |
| RelExpr *HbaseUpdate::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| RelExpr * re = NULL; |
| re = Update::bindNode(bindWA); |
| if (bindWA->errStatus()) |
| return this; |
| |
| return re; |
| } |
| |
| //! HbaseUpdate::getText method |
| const NAString HbaseUpdate::getText() const |
| { |
| NABoolean isSeabase = |
| (getTableDesc() ? getTableDesc()->getNATable()->isSeabaseTable() : FALSE); |
| |
| NAString text; |
| if (isMerge()) |
| { |
| text = (isSeabase ? "trafodion_merge" : "hbase_merge"); |
| } |
| else |
| { |
| if (NOT isSeabase) |
| text = "hbase_"; |
| else |
| text = "trafodion_"; |
| |
| if (uniqueRowsetHbaseOper()) |
| text += "vsbb_"; |
| |
| text += "update"; |
| } |
| |
| return text; |
| } |
| |
| Int32 HbaseUpdate::getArity() const |
| { |
| return 0; |
| } |
| |
| void HbaseUpdate::getPotentialOutputValues( |
| ValueIdSet & outputValues) const |
| { |
| outputValues.clear(); |
| // Include the index columns from the original Scan, if any |
| if (getScanIndexDesc()) |
| outputValues.insertList(getScanIndexDesc()->getIndexColumns()); |
| |
| // Include the index columns from the updated table, if any |
| if (getIndexDesc()) |
| outputValues.insertList (getIndexDesc()->getIndexColumns()); |
| |
| } // HbaseUpdate::getPotentialOutputValues() |
| |
| |
| // ----------------------------------------------------------------------- |
| // Member functions for DP2 Scan |
| // ----------------------------------------------------------------------- |
| DP2Scan::DP2Scan(const CorrName& tableName, |
| TableDesc * tableDescPtr, |
| const IndexDesc *indexDescPtr, |
| const NABoolean isReverseScan, |
| const Cardinality& baseCardinality, |
| StmtLevelAccessOptions& accessOpts, |
| GroupAttributes * groupAttributesPtr, |
| const ValueIdSet& selectionPredicates, |
| const Disjuncts& disjuncts) |
| : FileScan(tableName, |
| tableDescPtr, |
| indexDescPtr, |
| isReverseScan, |
| baseCardinality, |
| accessOpts, |
| groupAttributesPtr, |
| selectionPredicates, |
| disjuncts, |
| ValueIdSet()) |
| { |
| } |
| |
| // -------------------------------------------------- |
| // methods for class Describe |
| // -------------------------------------------------- |
| void Describe::getPotentialOutputValues(ValueIdSet & outputValues) const |
| { |
| outputValues.clear(); |
| // |
| // Assign the set of columns that belong to the index to be scanned |
| // as the output values that can be produced by this scan. |
| // |
| outputValues.insertList( getTableDesc()->getClusteringIndex()->getIndexColumns() ); |
| } // Describe::getPotentialOutputValues() |
| |
| // ----------------------------------------------------------------------- |
| // methods for class RelRoot |
| // ----------------------------------------------------------------------- |
| |
| RelRoot::RelRoot(RelExpr *input, |
| OperatorTypeEnum otype, |
| ItemExpr *compExpr, |
| ItemExpr *orderBy, |
| ItemExpr *updateCol, |
| RelExpr *reqdShape, |
| CollHeap *oHeap) |
| : RelExpr(otype, input, NULL, oHeap), |
| compExprTree_(compExpr), |
| orderByTree_(orderBy), |
| updateColTree_(updateCol), |
| reqdShape_(reqdShape), |
| viewStoiList_(CmpCommon::statementHeap()), |
| ddlStoiList_(CmpCommon::statementHeap()), |
| stoiUdrList_(CmpCommon::statementHeap()), |
| udfList_(CmpCommon::statementHeap()), |
| securityKeySet_(CmpCommon::statementHeap()), |
| trueRoot_(FALSE), |
| subRoot_(FALSE), |
| displayTree_(FALSE), |
| outputVarCnt_(-1), |
| inputVarTree_(NULL), |
| outputVarTree_(NULL), |
| updatableSelect_(TRUE), |
| updateCurrentOf_(FALSE), |
| currOfCursorName_(NULL), |
| rollbackOnError_(FALSE), |
| readOnlyTransIsOK_(FALSE), |
| needFirstSortedRows_(FALSE), |
| numSimpleVar_(0), |
| numHostVar_(0), |
| childOperType_(NO_OPERATOR_TYPE), |
| hostArraysArea_(NULL), |
| assignmentStTree_(NULL), |
| assignList_(NULL), |
| isRootOfInternalRefresh_(FALSE), |
| isQueryNonCacheable_(FALSE), |
| pMvBindContextForScope_(NULL), |
| parentForRowsetReqdOrder_(NULL), |
| isEmptySelectList_(FALSE), |
| isDontOpenNewScope_(FALSE), |
| triggersList_(NULL), |
| spOutParams_(NULL), |
| downrevCompileMXV_(COM_VERS_CURR_PLAN), |
| numExtractStreams_(0), |
| numBMOs_(0), |
| BMOsMemoryUsage_(0), |
| nBMOsMemoryUsage_(0), |
| uninitializedMvList_(NULL), |
| allOrderByRefsInGby_(FALSE), |
| avoidHalloween_(FALSE), |
| containsOnStatementMV_(FALSE), |
| containsLRU_(FALSE), |
| disableESPParallelism_(FALSE), |
| hasOlapFunctions_(FALSE), |
| hasTDFunctions_(FALSE), |
| isAnalyzeOnly_(FALSE), |
| hasMandatoryXP_(FALSE), |
| partReqType_(ANY_PARTITIONING), |
| partitionByTree_(NULL), |
| predExprTree_(NULL), |
| firstNRowsParam_(NULL), |
| flags_(0) |
| { |
| accessOptions().accessType() = TransMode::ACCESS_TYPE_NOT_SPECIFIED_; |
| accessOptions().lockMode() = LOCK_MODE_NOT_SPECIFIED_; |
| isCIFOn_ = FALSE; |
| } |
| |
| RelRoot::RelRoot(RelExpr *input, |
| TransMode::AccessType at, |
| LockMode lm, |
| OperatorTypeEnum otype, |
| ItemExpr *compExpr, |
| ItemExpr *orderBy, |
| ItemExpr *updateCol, |
| RelExpr *reqdShape, |
| CollHeap *oHeap) |
| : RelExpr(otype, input, NULL, oHeap), |
| compExprTree_(compExpr), |
| orderByTree_(orderBy), |
| updateColTree_(updateCol), |
| reqdShape_(reqdShape), |
| viewStoiList_(CmpCommon::statementHeap()), |
| ddlStoiList_(CmpCommon::statementHeap()), |
| stoiUdrList_(CmpCommon::statementHeap()), |
| udfList_(CmpCommon::statementHeap()), |
| securityKeySet_(CmpCommon::statementHeap()), |
| trueRoot_(FALSE), |
| subRoot_(FALSE), |
| displayTree_(FALSE), |
| outputVarCnt_(-1), |
| inputVarTree_(NULL), |
| outputVarTree_(NULL), |
| updatableSelect_(TRUE), |
| updateCurrentOf_(FALSE), |
| currOfCursorName_(NULL), |
| rollbackOnError_(FALSE), |
| readOnlyTransIsOK_(FALSE), |
| needFirstSortedRows_(FALSE), |
| numSimpleVar_(0), |
| numHostVar_(0), |
| childOperType_(NO_OPERATOR_TYPE), |
| hostArraysArea_(NULL), |
| assignmentStTree_(NULL), |
| assignList_(NULL), |
| isRootOfInternalRefresh_(FALSE), |
| isQueryNonCacheable_(FALSE), |
| pMvBindContextForScope_(NULL), |
| parentForRowsetReqdOrder_(NULL), |
| isEmptySelectList_(FALSE), |
| isDontOpenNewScope_(FALSE), |
| triggersList_(NULL), |
| spOutParams_(NULL), |
| downrevCompileMXV_(COM_VERS_CURR_PLAN), |
| numExtractStreams_(0), |
| numBMOs_(0), |
| BMOsMemoryUsage_(0), |
| nBMOsMemoryUsage_(0), |
| uninitializedMvList_(NULL), |
| allOrderByRefsInGby_(FALSE), |
| avoidHalloween_(FALSE), |
| containsOnStatementMV_(FALSE), |
| containsLRU_(FALSE), |
| disableESPParallelism_(FALSE), |
| hasOlapFunctions_(FALSE), |
| hasTDFunctions_(FALSE), |
| isAnalyzeOnly_(FALSE), |
| hasMandatoryXP_(FALSE), |
| partReqType_(ANY_PARTITIONING), |
| partitionByTree_(NULL), |
| predExprTree_(NULL), |
| firstNRowsParam_(NULL), |
| flags_(0) |
| { |
| accessOptions().accessType() = at; |
| accessOptions().lockMode() = lm; |
| isCIFOn_ = FALSE; |
| } |
| |
| // Why not just use the default copy ctor that C++ provides automatically, ## |
| // rather than having to maintain this??? ## |
| // Is it because of the "numXXXVar_(0)" lines below, ## |
| // or should those be "numXXXVar_(other.numXXXVar_)" ? ## |
| RelRoot::RelRoot(const RelRoot & other) |
| : RelExpr(REL_ROOT, other.child(0)), |
| compExprTree_(other.compExprTree_), |
| orderByTree_(other.orderByTree_), |
| updateColTree_(other.updateColTree_), |
| reqdShape_(other.reqdShape_), |
| viewStoiList_(other.viewStoiList_), |
| ddlStoiList_(other.ddlStoiList_), |
| stoiUdrList_(other.stoiUdrList_), |
| udfList_(other.udfList_), |
| securityKeySet_(other.securityKeySet_), |
| trueRoot_(other.trueRoot_), |
| subRoot_(other.subRoot_), |
| displayTree_(other.displayTree_), |
| outputVarCnt_(other.outputVarCnt_), |
| inputVarTree_(other.inputVarTree_), |
| outputVarTree_(other.outputVarTree_), |
| updatableSelect_(other.updatableSelect_), |
| updateCurrentOf_(other.updateCurrentOf_), |
| currOfCursorName_(other.currOfCursorName_), |
| rollbackOnError_(other.rollbackOnError_), |
| readOnlyTransIsOK_(other.readOnlyTransIsOK_), |
| needFirstSortedRows_(other.needFirstSortedRows_), |
| isRootOfInternalRefresh_(other.isRootOfInternalRefresh_), |
| isQueryNonCacheable_(other.isQueryNonCacheable_), |
| pMvBindContextForScope_(other.pMvBindContextForScope_), |
| isEmptySelectList_(other.isEmptySelectList_), |
| isDontOpenNewScope_(other.isDontOpenNewScope_), |
| // oltOptInfo_(other.oltOptInfo_), |
| numSimpleVar_(0), //## bug? |
| numHostVar_(0), //## bug? |
| childOperType_(other.childOperType_), |
| hostArraysArea_(other.hostArraysArea_), |
| assignmentStTree_(other.assignmentStTree_), |
| assignList_(other.assignList_), |
| triggersList_(other.triggersList_), |
| compExpr_(other.compExpr_), |
| reqdOrder_(other.reqdOrder_), |
| partArrangement_(other.partArrangement_), |
| updateCol_(other.updateCol_), |
| inputVars_(other.inputVars_), |
| accessOptions_(other.accessOptions_), |
| pkeyList_(other.pkeyList_), |
| rowsetReqdOrder_(other.rowsetReqdOrder_), |
| parentForRowsetReqdOrder_(other.parentForRowsetReqdOrder_), |
| spOutParams_ (NULL), // Raj P - 12/2000 - stored procedures (for java) |
| downrevCompileMXV_(COM_VERS_CURR_PLAN), |
| uninitializedMvList_(other.uninitializedMvList_), |
| allOrderByRefsInGby_(other.allOrderByRefsInGby_), |
| numExtractStreams_(other.numExtractStreams_), |
| numBMOs_(other.numBMOs_), |
| BMOsMemoryUsage_(other.BMOsMemoryUsage_), |
| nBMOsMemoryUsage_(other.nBMOsMemoryUsage_), |
| avoidHalloween_(other.avoidHalloween_), |
| disableESPParallelism_(other.disableESPParallelism_), |
| containsOnStatementMV_(other.containsOnStatementMV_), |
| containsLRU_(other.containsLRU_), |
| hasOlapFunctions_(other.hasOlapFunctions_), |
| hasTDFunctions_(other.hasTDFunctions_ ), |
| isAnalyzeOnly_(FALSE), |
| hasMandatoryXP_(other.hasMandatoryXP_), |
| partReqType_(other.partReqType_), |
| partitionByTree_(other.partitionByTree_), |
| isCIFOn_(other.isCIFOn_), |
| predExprTree_(other.predExprTree_), |
| firstNRowsParam_(other.firstNRowsParam_), |
| flags_(other.flags_) |
| { |
| oltOptInfo() = ((RelRoot&)other).oltOptInfo(); |
| setRETDesc(other.getRETDesc()); |
| } |
| |
| RelRoot::~RelRoot() |
| { |
| // Explicitly deleting our members is deliberately not being done -- |
| // we should allocate all RelExpr's on the appropriate heap |
| // (as a NABasicObject) and free the (stmt) heap all in one blow. |
| // delete compExprTree_; |
| // delete orderByTree_; |
| // delete reqdShape_; |
| // delete inputVarTree_; |
| } |
| |
| Int32 RelRoot::getArity() const { return 1; } |
| |
| void RelRoot::getPotentialOutputValues(ValueIdSet & outputValues) const |
| { |
| outputValues.clear(); |
| // |
| // Assign the select list as the outputs |
| // |
| outputValues.insertList(compExpr()); |
| } // RelRoot::getPotentialOutputValues() |
| |
| |
| void RelRoot::pushdownCoveredExpr(const ValueIdSet & outputExpr, |
| const ValueIdSet & newExternalInputs, |
| ValueIdSet & predicatesOnParent, |
| const ValueIdSet * setOfValuesReqdByParent, |
| Lng32 //childIndex ignored |
| ) |
| { |
| //--------------------------------------------------------------------- |
| // case 10-030708-7671: In the case of a cast, the RelRoot operator |
| // needs to ask for original expression it is casting; if it asks for |
| // the cast expression, the child may be incapable of producing it. For |
| // example, a nested join operator can't produce the cast expression, |
| // unless produced by its children |
| //--------------------------------------------------------------------- |
| ValueIdSet originalOutputs = getGroupAttr()->getCharacteristicOutputs(); |
| ValueIdSet updateOutputs(originalOutputs); |
| |
| updateOutputs.replaceCastExprWithOriginal(originalOutputs, this); |
| |
| |
| ValueIdSet myCharInput = getGroupAttr()->getCharacteristicInputs(); |
| |
| // since the orderby list is not a subset of the select list include it. |
| ValueIdSet allMyExpr; |
| ValueIdList orderByList = reqdOrder(); |
| allMyExpr.insertList(orderByList); |
| |
| // add the primary key columns, if they are to be returned. |
| if (updatableSelect() == TRUE) |
| { |
| allMyExpr.insertList(pkeyList()); |
| } |
| // --------------------------------------------------------------------- |
| RelExpr::pushdownCoveredExpr(updateOutputs, |
| myCharInput, |
| predicatesOnParent, |
| &allMyExpr |
| ); |
| |
| // All expressions should have been pushed |
| CMPASSERT(predicatesOnParent.isEmpty()); |
| |
| } // RelRoot::pushdownCoveredExpr |
| |
| const NAString RelRoot::getText() const |
| { |
| NAString result("root"); |
| |
| #ifdef DEBUG_TRIGGERS |
| char totalNodes[20]; |
| sprintf(totalNodes, "(total %d nodes)", nodeCount()); |
| result += totalNodes; |
| |
| if (isDontOpenNewScope()) |
| result += "(no_scope)"; |
| |
| if (isEmptySelectList()) |
| result += "(empty_select_list)"; |
| #endif |
| |
| return result; |
| } |
| |
| HashValue RelRoot::topHash() |
| { |
| HashValue result = RelExpr::topHash(); |
| |
| // it's not (yet) needed to produce a really good hash value for this node |
| result ^= compExpr_.entries(); |
| result ^= inputVars_.entries(); |
| // result ^= compExpr_; |
| // result ^= inputVars_; |
| return result; |
| } |
| |
| NABoolean RelRoot::duplicateMatch(const RelExpr & other) const |
| { |
| if (!RelExpr::duplicateMatch(other)) |
| return FALSE; |
| |
| RelRoot &o = (RelRoot &) other; |
| |
| if (NOT (compExpr_ == o.compExpr_) OR |
| NOT (inputVars_ == o.inputVars_)) |
| return FALSE; |
| |
| if (avoidHalloween_ != o.avoidHalloween_) |
| return FALSE; |
| |
| if (disableESPParallelism_ != o.disableESPParallelism_) |
| return FALSE; |
| |
| if (isAnalyzeOnly_ != o.isAnalyzeOnly_) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| RelExpr * RelRoot::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| RelRoot *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) RelRoot(NULL, |
| getOperatorType(), |
| NULL, |
| NULL, |
| NULL, |
| NULL, |
| outHeap); |
| else |
| result = (RelRoot *) derivedNode; |
| |
| if (compExprTree_ != NULL) |
| result->compExprTree_ = compExprTree_->copyTree(outHeap)->castToItemExpr(); |
| |
| if (inputVarTree_ != NULL) |
| result->inputVarTree_ = inputVarTree_->copyTree(outHeap)->castToItemExpr(); |
| |
| if (outputVarTree_ != NULL) |
| result->outputVarTree_ = outputVarTree_->copyTree(outHeap)->castToItemExpr(); |
| |
| if (predExprTree_ != NULL) |
| result->predExprTree_ = predExprTree_->copyTree(outHeap)->castToItemExpr(); |
| |
| result->compExpr_ = compExpr_; |
| result->inputVars_ = inputVars_; |
| |
| result->accessOptions_ = accessOptions_; |
| |
| result->updatableSelect_ = updatableSelect_; |
| result->updateCurrentOf_ = updateCurrentOf_; |
| if (currOfCursorName()) |
| result->currOfCursorName_ = currOfCursorName()->copyTree(outHeap)->castToItemExpr(); |
| |
| result->rollbackOnError_ = rollbackOnError_; |
| |
| result->isEmptySelectList_ = isEmptySelectList_; |
| result->isDontOpenNewScope_ = isDontOpenNewScope_; |
| |
| result->oltOptInfo() = oltOptInfo(); |
| |
| result->childOperType_ = childOperType_; |
| |
| result->rowsetReqdOrder_ = rowsetReqdOrder_; |
| result->parentForRowsetReqdOrder_ = parentForRowsetReqdOrder_; |
| |
| // Raj P - 12/2000 stored procedures (for java) |
| if ( spOutParams_ ) |
| { |
| result->spOutParams_ = new ItemExprList (outHeap); |
| (*result->spOutParams_) = *spOutParams_; |
| } |
| |
| if( uninitializedMvList_ ) |
| { |
| result->uninitializedMvList_ = new UninitializedMvNameList (outHeap); |
| result->uninitializedMvList_->insert( *uninitializedMvList_ ); |
| } |
| |
| result->setDownrevCompileMXV(getDownrevCompileMXV()); |
| result->numExtractStreams_ = numExtractStreams_; |
| result->allOrderByRefsInGby_ = allOrderByRefsInGby_; |
| |
| result->avoidHalloween_ = avoidHalloween_; |
| |
| result->disableESPParallelism_ = disableESPParallelism_; |
| |
| result->containsOnStatementMV_ = containsOnStatementMV_; |
| |
| result->hasOlapFunctions_ = hasOlapFunctions_; |
| |
| result->containsLRU_ = containsLRU_; |
| |
| result->isAnalyzeOnly_ = isAnalyzeOnly_; |
| |
| result->hasMandatoryXP_ = hasMandatoryXP_ ; |
| |
| if (partitionByTree_ != NULL) |
| result->partitionByTree_ = partitionByTree_->copyTree(outHeap)->castToItemExpr(); |
| |
| result->partReqType_ = partReqType_ ; |
| |
| result->isQueryNonCacheable_ = isQueryNonCacheable_; |
| |
| result->firstNRowsParam_ = firstNRowsParam_; |
| |
| result->flags_ = flags_; |
| |
| return RelExpr::copyTopNode(result, outHeap); |
| } |
| |
| void RelRoot::addCompExprTree(ItemExpr *compExpr) |
| { |
| ExprValueId c = compExprTree_; |
| |
| ItemExprTreeAsList(&c, ITM_ITEM_LIST).insert(compExpr); |
| compExprTree_ = c.getPtr(); |
| } |
| |
| ItemExpr * RelRoot::removeCompExprTree() |
| { |
| ItemExpr * result = compExprTree_; |
| |
| compExprTree_ = NULL; |
| |
| return result; |
| } |
| |
| void RelRoot::addPredExprTree(ItemExpr *predExpr) |
| { |
| ExprValueId c = predExprTree_; |
| |
| ItemExprTreeAsList(&c, ITM_ITEM_LIST).insert(predExpr); |
| predExprTree_ = c.getPtr(); |
| } |
| |
| ItemExpr * RelRoot::removePredExprTree() |
| { |
| ItemExpr * result = predExprTree_; |
| |
| predExprTree_ = NULL; |
| |
| return result; |
| } |
| |
| void RelRoot::addInputVarTree(ItemExpr *inputVar) |
| { |
| ExprValueId c = inputVarTree_; |
| |
| ItemExprTreeAsList(&c, ITM_ITEM_LIST).insert(inputVar); |
| inputVarTree_ = c.getPtr(); |
| } |
| |
| void RelRoot::addAtTopOfInputVarTree(ItemExpr *inputVar) |
| { |
| ExprValueId c = inputVarTree_; |
| |
| ItemExprTreeAsList(&c, ITM_ITEM_LIST).insertAtTop(inputVar); |
| inputVarTree_ = c.getPtr(); |
| } |
| |
| ItemExpr * RelRoot::removeInputVarTree() |
| { |
| ItemExpr * result = inputVarTree_; |
| |
| inputVarTree_ = NULL; |
| |
| return result; |
| } |
| |
| void RelRoot::addOutputVarTree(ItemExpr *outputVar) |
| { |
| if (!outputVarTree_) { |
| outputVarTree_ = new(CmpCommon::statementHeap()) ItemList(outputVar, NULL); |
| } |
| else { |
| ItemExpr *start = outputVarTree_; |
| while (start->child(1)) { |
| start = start->child(1); |
| } |
| start->child(1) = new(CmpCommon::statementHeap()) ItemList(outputVar, NULL); |
| } |
| } |
| |
| // Used by Assignment Statement in a Compound Statement. It adds a host variable |
| // to assignmentStTree_ |
| void RelRoot::addAssignmentStTree(ItemExpr *inputOutputVar) |
| { |
| if (!assignmentStTree_) { |
| assignmentStTree_ = new(CmpCommon::statementHeap()) ItemList(inputOutputVar, NULL); |
| } |
| else { |
| ItemExpr *start = assignmentStTree_; |
| while (start->child(1)) { |
| start = start->child(1); |
| } |
| start->child(1) = new(CmpCommon::statementHeap()) ItemList(inputOutputVar, NULL); |
| } |
| } |
| |
| ItemExpr * RelRoot::removeOutputVarTree() |
| { |
| ItemExpr * result = outputVarTree_; |
| |
| outputVarTree_ = NULL; |
| |
| return result; |
| } |
| |
| void RelRoot::addOrderByTree(ItemExpr *orderBy) |
| { |
| ExprValueId c = orderByTree_; |
| |
| ItemExprTreeAsList(&c, ITM_ITEM_LIST).insert(orderBy); |
| orderByTree_ = c.getPtr(); |
| } |
| |
| ItemExpr * RelRoot::removeOrderByTree() |
| { |
| ItemExpr * result = orderByTree_; |
| |
| orderByTree_ = NULL; |
| |
| return result; |
| } |
| |
| void RelRoot::addPartitionByTree(ItemExpr *partBy) |
| { |
| ExprValueId c = partitionByTree_; |
| |
| ItemExprTreeAsList(&c, ITM_ITEM_LIST).insert(partBy); |
| partitionByTree_ = c.getPtr(); |
| } |
| |
| ItemExpr * RelRoot::removePartitionByTree() |
| { |
| ItemExpr * result = partitionByTree_; |
| |
| partitionByTree_ = NULL; |
| |
| return result; |
| } |
| |
| void RelRoot::addUpdateColTree(ItemExpr *updateCol) |
| { |
| ExprValueId c = updateColTree_; |
| |
| ItemExprTreeAsList(&c, ITM_ITEM_LIST).insert(updateCol); |
| updateColTree_ = c.getPtr(); |
| } |
| |
| ItemExpr * RelRoot::removeUpdateColTree() |
| { |
| ItemExpr * result = updateColTree_; |
| |
| updateColTree_ = NULL; |
| |
| return result; |
| } |
| |
| void RelRoot::addLocalExpr(LIST(ExprNode *) &xlist, |
| LIST(NAString) &llist) const |
| { |
| if (compExprTree_ != NULL OR |
| compExpr_.entries() == 0) |
| xlist.insert(compExprTree_); |
| else |
| xlist.insert(compExpr_.rebuildExprTree(ITM_ITEM_LIST)); |
| llist.insert("select_list"); |
| |
| if (inputVarTree_ != NULL OR |
| inputVars_.entries() > 0) |
| { |
| if (inputVars_.entries() == 0) |
| xlist.insert(inputVarTree_); |
| else |
| xlist.insert(inputVars_.rebuildExprTree(ITM_ITEM_LIST)); |
| llist.insert("input_variables"); |
| } |
| |
| if (orderByTree_ != NULL OR |
| reqdOrder_.entries() > 0) |
| { |
| if (reqdOrder_.entries() == 0) |
| xlist.insert(orderByTree_); |
| else |
| xlist.insert(reqdOrder_.rebuildExprTree(ITM_ITEM_LIST)); |
| llist.insert("order_by"); |
| } |
| |
| if ((partitionByTree_ != NULL) OR |
| (partArrangement_.entries() > 0)) |
| { |
| if (partArrangement_.entries() == 0) |
| xlist.insert(partitionByTree_); |
| else |
| xlist.insert(partArrangement_.rebuildExprTree(ITM_ITEM_LIST)); |
| llist.insert("partition_by"); |
| } |
| |
| |
| if (updateColTree_ != NULL OR |
| updateCol_.entries() > 0) |
| { |
| if (updateCol_.entries() == 0) |
| xlist.insert(updateColTree_); |
| else |
| xlist.insert(updateCol_.rebuildExprTree(ITM_ITEM_LIST)); |
| llist.insert("update_col"); |
| } |
| |
| if (reqdShape_ != NULL) |
| { |
| xlist.insert(reqdShape_); |
| llist.insert("must_match"); |
| } |
| |
| RelExpr::addLocalExpr(xlist,llist); |
| } |
| |
| //---------------------------------------------------------------------------- |
| //++ MV OZ |
| NABoolean RelRoot::hasMvBindContext() const |
| { |
| return (NULL != pMvBindContextForScope_) ? TRUE : FALSE; |
| } |
| |
| MvBindContext * RelRoot::getMvBindContext() const |
| { |
| return pMvBindContextForScope_; |
| } |
| |
| void RelRoot::setMvBindContext(MvBindContext * pMvBindContext) |
| { |
| pMvBindContextForScope_ = pMvBindContext; |
| } |
| |
| NABoolean RelRoot::addOneRowAggregates(BindWA* bindWA, NABoolean forceGroupByAgg) |
| { |
| NABoolean groupByAggNodeAdded = FALSE; |
| RelExpr * childOfRoot = child(0); |
| GroupByAgg *aggNode = NULL; |
| // If the One Row Subquery is already enforced by a scalar aggregate |
| // then we do not need to add an additional one row aggregate. The exceptions to |
| // this rule is if we have count(t1.a) where t1 itself is a one row subquery. |
| // Note that the count is needed in order to have a groupby below the root node. |
| // See soln. 10-071105-8680 |
| // Another exception is when the select list has say max(a) + select a from t1 |
| // In this case there is a onerowsubquery in the select list but it is a child |
| // of BiArith. Due to all these exceptions we are simply going to scan the select |
| // list. As soon as we find something other than an aggregate we take the safe |
| // way out and add a one row aggregate. |
| // Also if the groupby is non scalar then we need to add a one row aggregate. |
| // Also if we have select max(a) + select b from t1 from t2; |
| // Still another exception is if there is a [last 0] on top of this node. We |
| // need an extra GroupByAgg node with one row aggregates in this case so |
| // we can put the FirstN node underneath that. |
| if (!forceGroupByAgg && |
| (childOfRoot->getOperatorType() == REL_GROUPBY)) |
| { |
| aggNode = (GroupByAgg *)childOfRoot; |
| |
| if (!aggNode->groupExpr().isEmpty()) |
| aggNode = NULL; |
| |
| // Check to see if the compExpr contains a subquery. |
| for (CollIndex i=0; i < compExpr().entries(); i++) |
| if (compExpr()[i].getItemExpr()->containsOpType(ITM_ROW_SUBQUERY)) |
| { |
| aggNode = NULL ; |
| break; |
| } |
| |
| } |
| if (aggNode) |
| return groupByAggNodeAdded; |
| |
| const RETDesc *oldTable = getRETDesc(); |
| RETDesc *resultTable = new(bindWA->wHeap()) RETDesc(bindWA); |
| |
| |
| // Transform select list such that that each item has a oneRow parent. |
| for (CollIndex selectListIndex=0; |
| selectListIndex < compExpr().entries(); |
| selectListIndex++) |
| { |
| |
| ItemExpr *colExpr = compExpr()[selectListIndex].getItemExpr(); |
| |
| // Build a new OneRow aggregate on top of the existing expression. |
| ItemExpr *newColExpr = new(bindWA->wHeap()) |
| Aggregate(ITM_ONEROW, colExpr); |
| |
| newColExpr->bindNode(bindWA); |
| |
| ColumnNameMap *xcnmEntry = oldTable->findColumn(compExpr()[selectListIndex]); |
| |
| if (xcnmEntry) // ## I don't recall when this case occurs... |
| resultTable->addColumn(bindWA, |
| xcnmEntry->getColRefNameObj(), |
| newColExpr->getValueId(), |
| USER_COLUMN, |
| xcnmEntry->getColumnDesc()->getHeading()); |
| else |
| { |
| ColRefName colRefName; |
| resultTable->addColumn(bindWA, |
| colRefName, |
| newColExpr->getValueId()); |
| } |
| |
| // Replace the select list expression with the new one. |
| compExpr()[selectListIndex] = newColExpr->getValueId(); |
| } |
| |
| |
| ValueIdSet aggregateExpr(compExpr()) ; |
| GroupByAgg *newGrby = NULL; |
| newGrby = new(bindWA->wHeap()) |
| GroupByAgg(childOfRoot, aggregateExpr); |
| |
| newGrby->bindNode(bindWA) ; |
| child(0) = newGrby ; |
| groupByAggNodeAdded = TRUE; |
| // Set the return descriptor |
| // |
| setRETDesc(resultTable); |
| |
| return groupByAggNodeAdded; |
| } |
| // ----------------------------------------------------------------------- |
| // member functions for class PhysicalRelRoot |
| // ----------------------------------------------------------------------- |
| |
| NABoolean PhysicalRelRoot::isLogical() const { return FALSE; } |
| |
| NABoolean PhysicalRelRoot::isPhysical() const { return TRUE; } |
| |
| |
| // ----------------------------------------------------------------------- |
| // methods for class Tuple |
| // ----------------------------------------------------------------------- |
| |
| THREAD_P Lng32 Tuple::idCounter_(0); |
| |
| Tuple::Tuple(const Tuple & other) : RelExpr(other.getOperatorType()) |
| { |
| selectionPred() = other.getSelectionPred(); |
| |
| tupleExprTree_ = other.tupleExprTree_; |
| tupleExpr_ = other.tupleExpr_; |
| rejectPredicates_ = other.rejectPredicates_; |
| id_ = other.id_; |
| } |
| |
| Tuple::~Tuple() {} |
| |
| Int32 Tuple::getArity() const { return 0; } |
| |
| // ----------------------------------------------------------------------- |
| // A virtual method for computing output values that an operator can |
| // produce potentially. |
| // ----------------------------------------------------------------------- |
| void Tuple::getPotentialOutputValues(ValueIdSet & outputValues) const |
| { |
| outputValues.clear(); |
| outputValues.insertList( tupleExpr() ); |
| } // Tuple::getPotentialOutputValues() |
| |
| HashValue Tuple::topHash() |
| { |
| HashValue result = RelExpr::topHash(); |
| |
| result ^= tupleExpr_.entries(); |
| |
| return result; |
| } |
| |
| NABoolean Tuple::duplicateMatch(const RelExpr & other) const |
| { |
| if (!RelExpr::duplicateMatch(other)) |
| return FALSE; |
| |
| Tuple &o = (Tuple &) other; |
| |
| if (NOT (tupleExpr_ == o.tupleExpr_)) |
| return FALSE; |
| |
| if (NOT (id_ == o.id_)) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| RelExpr * Tuple::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| Tuple *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) Tuple(NULL,outHeap); |
| else |
| result = (Tuple *) derivedNode; |
| |
| if (tupleExprTree_ != NULL) |
| result->tupleExprTree_ = tupleExprTree_->copyTree(outHeap)->castToItemExpr(); |
| |
| result->tupleExpr_ = tupleExpr_; |
| |
| result->id_ = id_; |
| |
| return RelExpr::copyTopNode(result, outHeap); |
| } |
| |
| void Tuple::addTupleExprTree(ItemExpr *tupleExpr) |
| { |
| ExprValueId t = tupleExprTree_; |
| |
| ItemExprTreeAsList(&t, ITM_ITEM_LIST).insert(tupleExpr); |
| tupleExprTree_ = t.getPtr(); |
| } |
| |
| ItemExpr * Tuple::removeTupleExprTree() |
| { |
| ItemExpr * result = tupleExprTree_; |
| |
| tupleExprTree_ = NULL; |
| |
| return result; |
| } |
| |
| void Tuple::addLocalExpr(LIST(ExprNode *) &xlist, |
| LIST(NAString) &llist) const |
| { |
| if (tupleExprTree_ != NULL OR |
| NOT tupleExpr_.isEmpty()) |
| { |
| if(tupleExpr_.isEmpty()) |
| xlist.insert(tupleExprTree_); |
| else |
| xlist.insert(tupleExpr_.rebuildExprTree(ITM_ITEM_LIST)); |
| llist.insert("tuple_expr"); |
| } |
| |
| RelExpr::addLocalExpr(xlist,llist); |
| } |
| |
| const NAString Tuple::getText() const |
| { |
| NAString tmp("values ", CmpCommon::statementHeap()); |
| if (tupleExprTree()) tupleExprTree()->unparse(tmp); |
| else ((ValueIdList &)tupleExpr()).unparse(tmp); |
| return tmp; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // methods for class TupleList |
| // ----------------------------------------------------------------------- |
| TupleList::TupleList(const TupleList & other) : Tuple(other) |
| { |
| castToList_ = other.castToList_; |
| createdForInList_ = other.createdForInList_; |
| } |
| |
| RelExpr * TupleList::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| TupleList *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) TupleList(NULL,outHeap); |
| else |
| result = (TupleList *) derivedNode; |
| |
| result->castToList() = castToList(); |
| |
| return Tuple::copyTopNode(result, outHeap); |
| } |
| |
| void TupleList::addLocalExpr(LIST(ExprNode *) &xlist, |
| LIST(NAString) &llist) const |
| { |
| Tuple::addLocalExpr(xlist,llist); |
| } |
| |
| const NAString TupleList::getText() const |
| { |
| NAString tmp("TupleList",CmpCommon::statementHeap()); |
| |
| return tmp; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class PhysicalTuple |
| // ----------------------------------------------------------------------- |
| |
| NABoolean PhysicalTuple::isLogical() const { return FALSE; } |
| |
| NABoolean PhysicalTuple::isPhysical() const { return TRUE; } |
| |
| // ----------------------------------------------------------------------- |
| // Member functions for class FirstN |
| // ----------------------------------------------------------------------- |
| |
| RelExpr * FirstN::copyTopNode(RelExpr *derivedNode, |
| CollHeap* outHeap) |
| { |
| FirstN *result; |
| |
| if (derivedNode == NULL) { |
| result = new (outHeap) FirstN(NULL, getFirstNRows(), isFirstN(), getFirstNRowsParam(), |
| outHeap); |
| result->setCanExecuteInDp2(canExecuteInDp2()); |
| } |
| else |
| result = (FirstN *) derivedNode; |
| |
| result->reqdOrder().insert(reqdOrder()); |
| |
| return RelExpr::copyTopNode(result, outHeap); |
| } |
| |
| const NAString FirstN::getText() const |
| { |
| NAString result(CmpCommon::statementHeap()); |
| |
| result = "FirstN"; |
| |
| return result; |
| } |
| |
| // helper method to determine if we have a child Fisrt N node that can execute in Dp2. |
| // This method will only search nodes with one child. We do not expect the child First N |
| // to occur below a join or union type node. This method will unwind as soon as the first |
| // FirstN child is found. |
| static NABoolean haveChildFirstNInDp2 (RelExpr * node) |
| { |
| if (node->getArity() != 1) return FALSE; |
| if (node->child(0)) |
| { |
| if (node->child(0)->getOperatorType() == REL_FIRST_N) |
| { // child is FirstN |
| FirstN * innerFirstN = (FirstN *) node->child(0)->castToRelExpr(); |
| if (innerFirstN->canExecuteInDp2()) |
| return TRUE; |
| else |
| return FALSE; |
| } |
| else |
| { // have child but it is not FirstN |
| return (haveChildFirstNInDp2(node->child(0))); |
| } |
| } |
| else |
| return FALSE; // no child even though arity is 1! |
| } |
| |
| RelExpr * FirstN::bindNode(BindWA *bindWA) |
| { |
| if (nodeIsBound()) |
| { |
| bindWA->getCurrentScope()->setRETDesc(getRETDesc()); |
| return this; |
| } |
| |
| if (bindWA->isEmbeddedIUDStatement() && haveChildFirstNInDp2(this)) |
| { |
| setCanExecuteInDp2(TRUE); |
| } |
| |
| return RelExpr::bindNode(bindWA); |
| } |
| |
| NABoolean FirstN::computeRowsAffected() const |
| { |
| if (child(0)) |
| { |
| return child(0)->castToRelExpr()->computeRowsAffected(); |
| } |
| return FALSE; |
| } |
| // member functions for class Filter |
| // ----------------------------------------------------------------------- |
| |
| Filter::~Filter() {} |
| |
| Int32 Filter::getArity() const { return 1; } |
| |
| HashValue Filter::topHash() |
| { |
| HashValue result = RelExpr::topHash(); |
| |
| return result; |
| } |
| |
| NABoolean Filter::duplicateMatch(const RelExpr & other) const |
| { |
| return RelExpr::duplicateMatch(other); |
| } |
| |
| RelExpr * Filter::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| Filter *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) Filter(NULL, outHeap); |
| else |
| result = (Filter *) derivedNode; |
| |
| return RelExpr::copyTopNode(result, outHeap); |
| } |
| |
| void Filter::getPotentialOutputValues(ValueIdSet & outputValues) const |
| { |
| outputValues.clear(); |
| outputValues += child(0)->getGroupAttr()->getCharacteristicOutputs(); |
| } // Filter::getPotentialOutputValues() |
| |
| const NAString Filter::getText() const { return "filter"; } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class Rename |
| // ----------------------------------------------------------------------- |
| |
| Rename::~Rename() {} |
| |
| Int32 Rename::getArity() const { return 1; } |
| |
| HashValue Rename::topHash() |
| { |
| ABORT("Hash functions can't be called in the parser"); |
| return 0x0; |
| } |
| |
| NABoolean Rename::duplicateMatch(const RelExpr & /* other */) const |
| { |
| ABORT("Duplicate match doesn't work in the parser"); |
| return FALSE; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class RenameTable |
| // ----------------------------------------------------------------------- |
| |
| RenameTable::~RenameTable() {} |
| |
| const NAString RenameTable::getText() const |
| { |
| return ("rename_as " + newTableName_.getCorrNameAsString()); |
| } |
| |
| RelExpr * RenameTable::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| RenameTable *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) RenameTable(TRUE, NULL, newTableName_, NULL, outHeap); |
| else |
| result = (RenameTable *) derivedNode; |
| |
| if (newColNamesTree_ != NULL) |
| result->newColNamesTree_ = newColNamesTree_->copyTree(outHeap)->castToItemExpr(); |
| |
| if (viewNATable_ != NULL) |
| result->viewNATable_ = viewNATable_; |
| |
| return RelExpr::copyTopNode(result, outHeap); |
| } |
| |
| ItemExpr * RenameTable::removeColNameTree() |
| { |
| ItemExpr * result = newColNamesTree_; |
| |
| newColNamesTree_ = NULL; |
| |
| return result; |
| } |
| |
| void RenameTable::addLocalExpr(LIST(ExprNode *) &xlist, |
| LIST(NAString) &llist) const |
| { |
| xlist.insert(newColNamesTree_); |
| llist.insert("new_col_names"); |
| |
| RelExpr::addLocalExpr(xlist,llist); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class RenameReference |
| // ----------------------------------------------------------------------- |
| |
| RenameReference::~RenameReference() {} |
| |
| const NAString RenameReference::getText() const |
| { |
| NAString text("rename_ref"); |
| for (CollIndex i=0; i<tableReferences_.entries(); i++) |
| text += " " + tableReferences_[i].getRefName(); |
| return text; |
| } |
| |
| RelExpr * RenameReference::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| RenameReference *result; |
| |
| if (derivedNode == NULL) |
| { |
| TableRefList *tableRef = new(outHeap) TableRefList(tableReferences_); |
| result = new (outHeap) RenameReference(NULL, *tableRef, outHeap); |
| } |
| else |
| result = (RenameReference *) derivedNode; |
| |
| return RelExpr::copyTopNode(result, outHeap); |
| } |
| |
| void RenameReference::addLocalExpr(LIST(ExprNode *) &xlist, |
| LIST(NAString) &llist) const |
| { |
| RelExpr::addLocalExpr(xlist,llist); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class BeforeTrigger |
| // ----------------------------------------------------------------------- |
| |
| BeforeTrigger::~BeforeTrigger() {} |
| |
| Int32 BeforeTrigger::getArity() const |
| { |
| if (child(0) == NULL) |
| return 0; |
| else |
| return 1; |
| } |
| |
| const NAString BeforeTrigger::getText() const |
| { |
| NAString text("before_trigger: "); |
| |
| if (signal_) |
| text += "Signal"; |
| else |
| text += "Set"; |
| |
| return text; |
| } |
| |
| RelExpr * BeforeTrigger::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| BeforeTrigger *result; |
| |
| if (derivedNode == NULL) |
| { |
| TableRefList *tableRef = new(outHeap) TableRefList(tableReferences_); |
| ItemExpr *whenClause = NULL; |
| if (whenClause_ != NULL) |
| whenClause = whenClause_->copyTree(outHeap); |
| |
| if (isSignal_) |
| { |
| RaiseError *signalClause = (RaiseError *)signal_->copyTree(outHeap); |
| result = new (outHeap) BeforeTrigger(*tableRef, whenClause, signalClause, outHeap); |
| } |
| else |
| { |
| CMPASSERT(setList_ != NULL); // Must have either SET or SIGNAL clause. |
| ItemExprList *setList = new(outHeap) ItemExprList(setList_->entries(), outHeap); |
| for (CollIndex i=0; i<setList_->entries(); i++) |
| setList->insert(setList_->at(i)->copyTree(outHeap)); |
| |
| result = new (outHeap) BeforeTrigger(*tableRef, whenClause, setList, outHeap); |
| } |
| } |
| else |
| result = (BeforeTrigger *) derivedNode; |
| |
| return RelExpr::copyTopNode(result, outHeap); |
| } |
| |
| void BeforeTrigger::addLocalExpr(LIST(ExprNode *) &xlist, |
| LIST(NAString) &llist) const |
| { |
| if (whenClause_ != NULL) |
| { |
| llist.insert("WHEN clause"); |
| xlist.insert(whenClause_); |
| } |
| |
| if (signal_) |
| { |
| llist.insert("SIGNAL clause"); |
| xlist.insert(signal_); |
| } |
| else |
| { |
| for (CollIndex i=0; i<setList_->entries(); i++) |
| { |
| llist.insert("SET clause"); |
| xlist.insert(setList_->at(i)); |
| } |
| } |
| |
| RelExpr::addLocalExpr(xlist,llist); |
| } |
| |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class MapValueIds |
| // ----------------------------------------------------------------------- |
| |
| MapValueIds::~MapValueIds() |
| { |
| } |
| |
| Int32 MapValueIds::getArity() const { return 1; } |
| |
| void MapValueIds::pushdownCoveredExpr( |
| const ValueIdSet & outputExpr, |
| const ValueIdSet & newExternalInputs, |
| ValueIdSet & predicatesOnParent, |
| const ValueIdSet * setOfValuesReqdByParent, |
| Lng32 childIndex) |
| { |
| // --------------------------------------------------------------------- |
| // Since the MapValueIds node rewrites predicates, the characteristic |
| // outputs of the node usually make no sense to the child node. For |
| // this reason, translate the characteristic outputs of this node before |
| // passing them down to the children. |
| // --------------------------------------------------------------------- |
| ValueIdSet requiredValues; |
| if (setOfValuesReqdByParent) |
| requiredValues = *setOfValuesReqdByParent; |
| ValueIdSet translatedOutputs; |
| ValueIdSet predsRewrittenForChild; |
| ValueIdSet outputValues(outputExpr); |
| |
| // first subtract the outputs from the required values, then add back |
| // the translated outputs |
| outputValues -= getGroupAttr()->getCharacteristicOutputs(); |
| getMap().rewriteValueIdSetDown(getGroupAttr()->getCharacteristicOutputs(), |
| translatedOutputs); |
| outputValues += translatedOutputs; |
| |
| translatedOutputs.clear(); |
| requiredValues -= getGroupAttr()->getCharacteristicOutputs(); |
| getMap().rewriteValueIdSetDown(getGroupAttr()->getCharacteristicOutputs(), |
| translatedOutputs); |
| requiredValues += translatedOutputs; |
| |
| if (cseRef_) |
| { |
| // If this MapValueIds node represents a common subexpression, |
| // then don't try to push predicates again that already have |
| // been pushed down before. VEGPredicates may not be pushable |
| // at all to the rewritten child, and other predicates might |
| // be duplicated with different ValueIds for the internal |
| // operators such as "=", "+", ">". |
| predicatesOnParent -= cseRef_->getPushedPredicates(); |
| |
| // Also, don't push down VEGPredicates on columns that are |
| // characteristic outputs of the MapValueIds. Those predicates |
| // (or their equivalents) should have already been pushed down. |
| for (ValueId g=predicatesOnParent.init(); |
| predicatesOnParent.next(g); |
| predicatesOnParent.advance(g)) |
| if (g.getItemExpr()->getOperatorType() == ITM_VEG_PREDICATE) |
| { |
| VEG *veg = |
| static_cast<VEGPredicate *>(g.getItemExpr())->getVEG(); |
| ValueId vegRef(veg->getVEGReference()->getValueId()); |
| |
| if (newExternalInputs.contains(vegRef)) |
| { |
| // We are trying to push down a VEGPred and we are at |
| // the same time offering the VEGRef as a new |
| // characteristic input. Example: We want to push |
| // VEGPred_2(a=b) down and we offer its corresponding |
| // VEGRef_1(a,b) as an input. The map in our |
| // MapValueIds node maps VEGRef_1(a,b) on the top to |
| // VEGRef_99(x,y) on the bottom. Note that either one |
| // of the VEGies might contain a constant that the |
| // other one doesn't contain. Note also that the |
| // MapValueIds node doesn't map characteristic inputs, |
| // those are passed unchanged to the child. So, we need |
| // to generate a predicate for the child that: |
| // a) represents the semantics of VEGPred_2(a,b) |
| // b) compares the new characteristic input |
| // VEGRef_1(a,b) with some value in the child |
| // c) doesn't change the members of the existing |
| // VEGies. |
| // The best solution is probably an equals predicate |
| // between the characteristic input and the VEGRef |
| // (or other expression) that is the child's equivalent. |
| // Example: VEGRef_1(a,b) = VEGRef_99(x,y) |
| ValueId bottomVal; |
| ItemExpr *eqPred = NULL; |
| |
| map_.mapValueIdDown(vegRef, bottomVal); |
| |
| if (vegRef != bottomVal && bottomVal != NULL_VALUE_ID) |
| { |
| eqPred = new(CmpCommon::statementHeap()) BiRelat( |
| ITM_EQUAL, |
| vegRef.getItemExpr(), |
| bottomVal.getItemExpr(), |
| veg->getSpecialNulls()); |
| eqPred->synthTypeAndValueId(); |
| // replace g with the new equals predicate |
| // when we do the actual rewrite below |
| map_.addMapEntry(g, eqPred->getValueId()); |
| } |
| } |
| else |
| { |
| // Don't push down VEGPredicates on columns that are |
| // characteristic outputs of the MapValueIds. Those |
| // predicates (or their equivalents) should have |
| // already been pushed down. |
| ValueIdSet vegMembers(veg->getAllValues()); |
| |
| vegMembers += vegRef; |
| vegMembers.intersectSet( |
| getGroupAttr()->getCharacteristicOutputs()); |
| if (!vegMembers.isEmpty()) |
| { |
| // a VEGPred on one of my characteristic outputs, |
| |
| // assume that my child tree already has the |
| // associated VEGPreds and remove this predicate |
| // silently |
| predicatesOnParent -= g; |
| } |
| // else leave the predicate and let the code below deal |
| // with it, this will probably end up in a failed assert |
| // below for predsRewrittenForChild.isEmpty() |
| } |
| } |
| } |
| |
| // rewrite the predicates so they can be applied in the child node |
| getMap().rewriteValueIdSetDown(predicatesOnParent,predsRewrittenForChild); |
| |
| // use the standard method with the new required values |
| RelExpr::pushdownCoveredExpr(outputValues, |
| newExternalInputs, |
| predsRewrittenForChild, |
| &requiredValues, |
| childIndex); |
| |
| // eliminate any VEGPredicates that got duplicated in the parent |
| if (NOT child(0)->isCutOp()) |
| predsRewrittenForChild -= child(0)->selectionPred(); |
| |
| // all predicates must have been pushed down!! |
| CMPASSERT(predsRewrittenForChild.isEmpty()); |
| predicatesOnParent.clear(); |
| |
| // Remove entries from the map that are no longer required since |
| // they no longer appear in the outputs. |
| NABoolean matchWithTopValues = FALSE; |
| if (child(0)->isCutOp() || |
| child(0)->getOperatorType() == REL_FILTER || |
| child(0)->getOperatorType() == REL_MAP_VALUEIDS ) |
| { |
| matchWithTopValues = TRUE; |
| getMap().removeUnusedEntries(getGroupAttr()->getCharacteristicOutputs(), matchWithTopValues); |
| } |
| else |
| { |
| getMap().removeUnusedEntries(child(0)->getGroupAttr()->getCharacteristicOutputs(), matchWithTopValues); |
| } |
| |
| } // MapValueIds::pushdownCoveredExpr |
| |
| void MapValueIds::getPotentialOutputValues(ValueIdSet & outputValues) const |
| { |
| outputValues.clear(); |
| // |
| // The output of the MapValueId is given by the ValueIds |
| // contained in the "upper" portion of a two-tiered mapping |
| // table. |
| // |
| outputValues.insertList((getMap2()).getTopValues()); |
| } // MapValueIds::getPotentialOutputValues() |
| |
| void MapValueIds::addSameMapEntries(const ValueIdSet & newTopBottomValues) |
| { |
| for (ValueId x = newTopBottomValues.init(); |
| newTopBottomValues.next(x); |
| newTopBottomValues.advance(x)) |
| addMapEntry(x,x); |
| } |
| |
| void MapValueIds::addLocalExpr(LIST(ExprNode *) &xlist, |
| LIST(NAString) &llist) const |
| { |
| xlist.insert(map_.getTopValues().rebuildExprTree(ITM_ITEM_LIST)); |
| llist.insert("upper_values"); |
| xlist.insert(map_.getBottomValues().rebuildExprTree(ITM_ITEM_LIST)); |
| llist.insert("lower_values"); |
| |
| RelExpr::addLocalExpr(xlist,llist); |
| } |
| |
| HashValue MapValueIds::topHash() |
| { |
| HashValue result = RelExpr::topHash(); |
| |
| result ^= map_.getTopValues(); |
| result ^= map_.getBottomValues(); |
| |
| return result; |
| } |
| |
| NABoolean MapValueIds::duplicateMatch(const RelExpr & other) const |
| { |
| if (NOT RelExpr::duplicateMatch(other)) |
| return FALSE; |
| |
| MapValueIds &o = (MapValueIds &) other; |
| |
| if (includesFavoriteMV_ != o.includesFavoriteMV_) |
| return FALSE; |
| |
| if (cseRef_ != o.cseRef_) |
| return FALSE; |
| |
| if (map_ != o.map_) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| RelExpr * MapValueIds::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| MapValueIds *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) MapValueIds(NULL,map_,outHeap); |
| else |
| result = static_cast<MapValueIds*>(derivedNode); |
| |
| result->includesFavoriteMV_ = includesFavoriteMV_; |
| result->cseRef_ = cseRef_; |
| |
| return RelExpr::copyTopNode(result, outHeap); |
| } |
| |
| const NAString MapValueIds::getText() const |
| { |
| return "map_value_ids"; |
| // return "expr"; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // Member functions for class PhysicalMapValueIds |
| // ----------------------------------------------------------------------- |
| |
| NABoolean PhysicalMapValueIds::isLogical() const { return FALSE; } |
| |
| NABoolean PhysicalMapValueIds::isPhysical() const { return TRUE; } |
| |
| |
| // ----------------------------------------------------------------------- |
| // Member functions for class Describe |
| // ----------------------------------------------------------------------- |
| |
| RelExpr * Describe::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| Describe *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) |
| Describe(originalQuery_, getDescribedTableName(), format_, labelAnsiNameSpace_); |
| else |
| result = (Describe *) derivedNode; |
| |
| return RelExpr::copyTopNode(result, outHeap); |
| } |
| |
| const NAString Describe::getText() const |
| { |
| return "describe"; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // Member functions for class ProbeCache |
| // ----------------------------------------------------------------------- |
| |
| NABoolean ProbeCache::isLogical() const { return FALSE; } |
| |
| NABoolean ProbeCache::isPhysical() const { return TRUE; } |
| |
| Int32 ProbeCache::getArity() const { return 1; } |
| |
| RelExpr * ProbeCache::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| ProbeCache *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) |
| ProbeCache(NULL, numCachedProbes_, outHeap); |
| else |
| result = (ProbeCache *) derivedNode; |
| |
| result->numCachedProbes_ = numCachedProbes_; |
| result->numInnerTuples_ = numInnerTuples_; |
| |
| return RelExpr::copyTopNode(result, outHeap); |
| } |
| |
| const NAString ProbeCache::getText() const |
| { |
| return "probe_cache"; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // Member functions for class ControlRunningQuery |
| // ----------------------------------------------------------------------- |
| |
| NABoolean ControlRunningQuery::isLogical() const { return TRUE; } |
| |
| NABoolean ControlRunningQuery::isPhysical() const { return TRUE; } |
| |
| Int32 ControlRunningQuery::getArity() const { return 0; } |
| |
| RelExpr * ControlRunningQuery::copyTopNode(RelExpr *derivedNode, |
| CollHeap* outHeap) |
| { |
| ControlRunningQuery *result = NULL; |
| |
| if (derivedNode == NULL) |
| { |
| switch (qs_) |
| { |
| case ControlNidPid: |
| result = new (outHeap) ControlRunningQuery(nid_, pid_, |
| ControlNidPid, action_, forced_, outHeap); |
| break; |
| case ControlPname: |
| result = new (outHeap) |
| ControlRunningQuery(pname_, ControlPname, action_, forced_, |
| outHeap); |
| break; |
| case ControlQid: |
| result = new (outHeap) |
| ControlRunningQuery(queryId_, ControlQid, action_, forced_, |
| outHeap); |
| break; |
| default: |
| CMPASSERT(0); |
| } |
| result->setComment(comment_); |
| } |
| else |
| result = (ControlRunningQuery *) derivedNode; |
| |
| return RelExpr::copyTopNode(result, outHeap); |
| } |
| |
| const NAString ControlRunningQuery::getText() const |
| { |
| return "control_running_query"; |
| } |
| |
| void ControlRunningQuery::setComment(NAString &comment) |
| { |
| NAString cancelComment (comment, CmpCommon::statementHeap()); |
| comment_ = cancelComment; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class CSEInfo (helper for CommonSubExprRef) |
| // ----------------------------------------------------------------------- |
| |
| Int32 CSEInfo::getTotalNumRefs(Int32 restrictToSingleConsumer) const |
| { |
| // shortcut for main query |
| if (cseId_ == CmpStatement::getCSEIdForMainQuery()) |
| return 1; |
| |
| // Calculate how many times we will evaluate this common subexpression |
| // at runtime: |
| // - If the CSE is shared then we evaluate it once |
| // - Otherwise, look at the consumers that are lexical refs |
| // (avoid double-counting refs from multiple copies of a parent) |
| // and add the times they are being executed. |
| Int32 result = 0; |
| NABoolean sharedConsumers = FALSE; |
| LIST(CountedCSEInfo) countsByCSE(CmpCommon::statementHeap()); |
| CollIndex minc = 0; |
| CollIndex maxc = consumers_.entries(); |
| |
| if (restrictToSingleConsumer >= 0) |
| { |
| // count only the executions resulting from a single consumer |
| minc = restrictToSingleConsumer; |
| maxc = minc + 1; |
| } |
| |
| // loop over all consumers or look at just one |
| for (CollIndex c=minc; c<maxc; c++) |
| { |
| if (isShared(c)) |
| { |
| sharedConsumers = TRUE; |
| } |
| else |
| { |
| CommonSubExprRef *consumer = getConsumer(c); |
| CSEInfo *parentInfo = CmpCommon::statement()->getCSEInfoById( |
| consumer->getParentCSEId()); |
| NABoolean duplicateDueToSharedConsumer = FALSE; |
| |
| // Don't double-count consumers that originate from the |
| // same parent CSE, have the same lexical ref number from |
| // the parent, and are shared. |
| if (parentInfo->isShared( |
| consumer->getParentConsumerId())) |
| { |
| for (CollIndex d=0; d<countsByCSE.entries(); d++) |
| if (countsByCSE[d].getInfo() == parentInfo && |
| countsByCSE[d].getLexicalCount() == |
| consumer->getLexicalRefNumFromParent()) |
| { |
| duplicateDueToSharedConsumer = TRUE; |
| break; |
| } |
| |
| // First consumer from this parent CSE with this lexical |
| // ref number, remember that we are going to count |
| // it. Note that we are use the lexical ref "count" in |
| // CountedCSEInfo as the lexical ref number in this |
| // method, so the name "count" means "ref number" in |
| // this context. |
| if (!duplicateDueToSharedConsumer) |
| countsByCSE.insert( |
| CountedCSEInfo( |
| parentInfo, |
| consumer->getLexicalRefNumFromParent())); |
| } // parent consumer is shared |
| |
| if (!duplicateDueToSharedConsumer) |
| { |
| // recursively determine number of times the parent of |
| // this consumer gets executed |
| result += parentInfo->getTotalNumRefs( |
| consumer->getParentConsumerId()); |
| } |
| } // consumer is not shared |
| } // loop over consumer(s) |
| |
| if (sharedConsumers) |
| // all the shared consumers are handled by evaluating the CSE once |
| result++; |
| |
| return result; |
| } |
| |
| CSEInfo::CSEAnalysisOutcome CSEInfo::getAnalysisOutcome(Int32 id) const |
| { |
| if (idOfAnalyzingConsumer_ != id && |
| analysisOutcome_ == CREATE_TEMP) |
| // only the analyzing consumer creates and reads the temp, the |
| // others only read it |
| return TEMP; |
| else |
| return analysisOutcome_; |
| } |
| |
| Int32 CSEInfo::addChildCSE(CSEInfo *childInfo, NABoolean addLexicalRef) |
| { |
| Int32 result = -1; |
| CollIndex foundIndex = NULL_COLL_INDEX; |
| |
| // look for an existing entry |
| for (CollIndex i=0; |
| i<childCSEs_.entries() && foundIndex == NULL_COLL_INDEX; |
| i++) |
| if (childCSEs_[i].getInfo() == childInfo) |
| foundIndex = i; |
| |
| if (foundIndex == NULL_COLL_INDEX) |
| { |
| // create a new entry |
| foundIndex = childCSEs_.entries(); |
| childCSEs_.insert(CountedCSEInfo(childInfo)); |
| } |
| |
| if (addLexicalRef) |
| { |
| // The return value for a lexical ref is the count of lexical |
| // refs for this particular parent/child CSE relationship so far |
| // (0 if this is the first one). Note that we can't say anything |
| // abount counts for expanded refs at this time, those will be |
| // handled later, during the transform phase of the normalizer. |
| result = childCSEs_[foundIndex].getLexicalCount(); |
| childCSEs_[foundIndex].incrementLexicalCount(); |
| } |
| |
| return result; |
| } |
| |
| void CSEInfo::addCSERef(CommonSubExprRef *cse) |
| { |
| CMPASSERT(name_ == cse->getName()); |
| cse->setId(consumers_.entries()); |
| consumers_.insert(cse); |
| if (cse->isALexicalRef()) |
| numLexicalRefs_++; |
| } |
| |
| void CSEInfo::replaceConsumerWithAnAlternative(CommonSubExprRef *c) |
| { |
| Int32 idToReplace = c->getId(); |
| |
| if (consumers_[idToReplace] != c) |
| { |
| CollIndex foundPos = alternativeConsumers_.index(c); |
| |
| CMPASSERT(foundPos != NULL_COLL_INDEX); |
| CMPASSERT(consumers_[idToReplace]->getOriginalRef() == |
| c->getOriginalRef()); |
| // c moves from the list of copies to the real list and another |
| // consumer moves the opposite way |
| alternativeConsumers_.removeAt(foundPos); |
| alternativeConsumers_.insert(consumers_[idToReplace]); |
| consumers_[idToReplace] = c; |
| } |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class CommonSubExprRef |
| // ----------------------------------------------------------------------- |
| |
| NABoolean CommonSubExprRef::isAChildOfTheMainQuery() const |
| { |
| return (parentCSEId_ == CmpStatement::getCSEIdForMainQuery()); |
| } |
| |
| CommonSubExprRef::~CommonSubExprRef() |
| { |
| } |
| |
| Int32 CommonSubExprRef::getArity() const |
| { |
| // always return 1 for now, that may change in the future |
| return 1; |
| } |
| |
| void CommonSubExprRef::addToCmpStatement(NABoolean lexicalRef) |
| { |
| NABoolean alreadySeen = TRUE; |
| |
| // look up whether a CSE with this name already exists |
| CSEInfo *info = CmpCommon::statement()->getCSEInfo(internalName_); |
| |
| // sanity check, make sure that the caller knows whether this is a |
| // lexical ref (generated with CommonSubExprRef constructor) or an |
| // expanded ref (generated with CommonSubExprRef::copyTopNode() |
| // before the bind phase) |
| CMPASSERT(isALexicalRef() == lexicalRef); |
| |
| if (!info) |
| { |
| // make a new object to hold a list of all references |
| // to this CSE (the first one of them will be "this") |
| info = new(CmpCommon::statementHeap()) |
| CSEInfo(internalName_, |
| CmpCommon::statementHeap()); |
| alreadySeen = FALSE; |
| } |
| |
| info->addCSERef(this); |
| |
| if (!alreadySeen) |
| CmpCommon::statement()->addCSEInfo(info); |
| } |
| |
| void CommonSubExprRef::addParentRef(CommonSubExprRef *parentRef) |
| { |
| // Establish the parent/child relationship between two |
| // CommonSubExprRef nodes, or between the main query and a |
| // CommonSubExprRef node (parentRef == NULL). Also, record |
| // the dependency between parent and child CSEs in the lexical |
| // dependency multigraph. Bookkeeping to do: |
| // |
| // - Add the child's CSE to the list of child CSEs of the |
| // parent CSE |
| // - Set the data members in the child ref that point to the |
| // parent CSE and parent ref |
| |
| // parent info is for a parent CSE or for the main query |
| CSEInfo *parentInfo = |
| (parentRef ? CmpCommon::statement()->getCSEInfo(parentRef->getName()) |
| : CmpCommon::statement()->getCSEInfoForMainQuery()); |
| CSEInfo *childInfo = |
| CmpCommon::statement()->getCSEInfo(getName()); |
| |
| CMPASSERT(parentInfo && childInfo); |
| |
| // Update the lexical CSE multigraph, and also return the |
| // count of previously existing edges in the graph. |
| // LexicalRefNumFromParent is set to a positive value only for |
| // lexical refs, the other refs will be handled later, in the SQO |
| // phase. This is since expanded refs may be processed before their |
| // lexical ref and we may not know this value yet. |
| lexicalRefNumFromParent_ = |
| parentInfo->addChildCSE(childInfo, isALexicalRef()); |
| |
| parentCSEId_ = parentInfo->getCSEId(); |
| if (parentRef) |
| parentRefId_ = parentRef->getId(); |
| else |
| parentRefId_ = -1; // main query does not have a CommonSubExprRef |
| } |
| |
| NABoolean CommonSubExprRef::isFirstReference() const |
| { |
| return (CmpCommon::statement()->getCSEInfo(internalName_) == NULL); |
| } |
| |
| void CommonSubExprRef::addLocalExpr(LIST(ExprNode *) &xlist, |
| LIST(NAString) &llist) const |
| { |
| if (NOT columnList_.isEmpty()) |
| { |
| xlist.insert(columnList_.rebuildExprTree(ITM_ITEM_LIST)); |
| llist.insert("column_list"); |
| } |
| |
| if(NOT pushedPredicates_.isEmpty()) |
| { |
| xlist.insert(pushedPredicates_.rebuildExprTree()); |
| llist.insert("pushed_predicates"); |
| } |
| } |
| |
| HashValue CommonSubExprRef::topHash() |
| { |
| HashValue result = RelExpr::topHash(); |
| |
| result ^= internalName_; |
| result ^= id_; |
| result ^= columnList_; |
| result ^= pushedPredicates_; |
| |
| return result; |
| } |
| |
| NABoolean CommonSubExprRef::duplicateMatch(const RelExpr & other) const |
| { |
| if (NOT RelExpr::duplicateMatch(other)) |
| return FALSE; |
| |
| const CommonSubExprRef &o = static_cast<const CommonSubExprRef &>(other); |
| |
| return (internalName_ == o.internalName_ && |
| id_ == o.id_ && |
| columnList_ == o.columnList_ && |
| pushedPredicates_ == o.pushedPredicates_); |
| } |
| |
| RelExpr * CommonSubExprRef::copyTopNode(RelExpr *derivedNode, |
| CollHeap* outHeap) |
| { |
| CommonSubExprRef *result = NULL; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) CommonSubExprRef(NULL, |
| internalName_.data(), |
| outHeap); |
| else |
| result = static_cast<CommonSubExprRef *>(derivedNode); |
| |
| // copy fields that are common for bound and unbound nodes |
| result->hbAccessOptionsFromCTE_ = hbAccessOptionsFromCTE_; |
| |
| if (nodeIsBound()) |
| { |
| // if the node is bound, we assume that the copy is serving the same function |
| // as the original, as an alternative, create an "alternative ref" |
| result->setId(id_); |
| |
| result->parentCSEId_ = parentCSEId_; |
| result->parentRefId_ = parentRefId_; |
| result->lexicalRefNumFromParent_ = lexicalRefNumFromParent_; |
| result->columnList_ = columnList_; |
| result->nonVEGColumns_ = nonVEGColumns_; |
| result->commonInputs_ = commonInputs_; |
| result->pushedPredicates_ = pushedPredicates_; |
| result->nonSharedPredicates_ = nonSharedPredicates_; |
| result->cseEstLogProps_ = cseEstLogProps_; |
| // don't copy the tempScan_ |
| |
| // Mark this as an alternative ref, not that this does not |
| // change the status of lexical vs. expanded ref |
| result->isAnExpansionOf_ = isAnExpansionOf_; |
| result->isAnAlternativeOf_ = |
| ( isAnAlternativeOf_ ? isAnAlternativeOf_ : this); |
| |
| CmpCommon::statement()->getCSEInfo(internalName_)-> |
| registerAnAlternativeConsumer(result); |
| } |
| else |
| { |
| // If the node is not bound, we assume that we created an |
| // "expanded" reference to a common subexpression in a tree that |
| // itself is a reference to a CTE (Common Table Expression). |
| // See the comment in RelMisc.h for method isAnExpandedRef() |
| // to explain the term "expanded" ref. |
| result->isAnExpansionOf_ = |
| (isAnExpansionOf_ ? isAnExpansionOf_ : this); |
| result->addToCmpStatement(FALSE); |
| } |
| |
| return result; |
| } |
| |
| const NAString CommonSubExprRef::getText() const |
| { |
| NAString result("cse "); |
| char buf[20]; |
| |
| result += ToAnsiIdentifier(internalName_); |
| |
| snprintf(buf, sizeof(buf), " %d", id_); |
| result += buf; |
| |
| return result; |
| } |
| |
| Union * CommonSubExprRef::makeUnion(RelExpr *lc, |
| RelExpr *rc, |
| NABoolean blocked) |
| { |
| // Make a regular or blocked union with no characteristic outputs |
| Union *result; |
| ValueIdSet newInputs(lc->getGroupAttr()->getCharacteristicInputs()); |
| |
| result = new(CmpCommon::statementHeap()) Union(lc, rc); |
| |
| newInputs += rc->getGroupAttr()->getCharacteristicInputs(); |
| |
| result->setGroupAttr(new (CmpCommon::statementHeap()) GroupAttributes()); |
| result->getGroupAttr()->addCharacteristicInputs(newInputs); |
| |
| if(blocked) |
| result->setBlockedUnion(); |
| |
| return result; |
| } |
| |
| void CommonSubExprRef::display() |
| { |
| if (isAChildOfTheMainQuery()) |
| printf("Parent: main query, lexical ref %d\n", |
| lexicalRefNumFromParent_); |
| else |
| printf("Parent: %s(consumer %d, lexical ref %d)\n", |
| CmpCommon::statement()->getCSEInfoById( |
| parentCSEId_)->getName().data(), |
| parentRefId_, |
| lexicalRefNumFromParent_); |
| printf("Original columns:\n"); |
| columnList_.display(); |
| printf("\nCommon inputs:\n"); |
| commonInputs_.display(); |
| printf("\nPushed predicates:\n"); |
| pushedPredicates_.display(); |
| printf("\nNon shared preds to be applied to scan:\n"); |
| nonSharedPredicates_.display(); |
| printf("\nPotential values for VEG rewrite:\n"); |
| nonVEGColumns_.display(); |
| } |
| |
| void CommonSubExprRef::displayAll(const char *optionalId) |
| { |
| const LIST(CSEInfo *) *cses = CmpCommon::statement()->getCSEInfoList(); |
| |
| if (cses) |
| for (CollIndex i=0; i<cses->entries(); i++) |
| if (!optionalId || |
| strlen(optionalId) == 0 || |
| cses->at(i)->getName() == optionalId) |
| { |
| CSEInfo *info = cses->at(i); |
| CollIndex nc = info->getNumConsumers(); |
| NABoolean isMainQuery = |
| (info->getCSEId() == CmpStatement::getCSEIdForMainQuery()); |
| |
| if (isMainQuery) |
| { |
| printf("\n\n==========================\n"); |
| printf("MainQuery:\n"); |
| } |
| else |
| { |
| printf("\n\n==========================\n"); |
| printf("CSE: %s (%d consumers, %d lexical ref(s), %d total execution(s))\n", |
| info->getName().data(), |
| nc, |
| info->getNumLexicalRefs(), |
| info->getTotalNumRefs()); |
| } |
| |
| const LIST(CountedCSEInfo) &children(info->getChildCSEs()); |
| |
| for (CollIndex j=0; j<children.entries(); j++) |
| printf(" references CSE: %s %d times\n", |
| children[j].getInfo()->getName().data(), |
| children[j].getLexicalCount()); |
| |
| if (info->getIdOfAnalyzingConsumer() >= 0) |
| { |
| const char *outcome = "?"; |
| ValueIdList cols; |
| CommonSubExprRef *consumer = |
| info->getConsumer(info->getIdOfAnalyzingConsumer()); |
| const ValueIdList &cCols(consumer->getColumnList()); |
| |
| switch (info->getAnalysisOutcome(0)) |
| { |
| case CSEInfo::UNKNOWN_ANALYSIS: |
| outcome = "UNKNOWN"; |
| break; |
| case CSEInfo::EXPAND: |
| outcome = "EXPAND"; |
| break; |
| case CSEInfo::CREATE_TEMP: |
| outcome = "CREATE_TEMP"; |
| break; |
| case CSEInfo::TEMP: |
| outcome = "TEMP"; |
| break; |
| case CSEInfo::ERROR: |
| outcome = "ERROR"; |
| break; |
| default: |
| outcome = "???"; |
| break; |
| } |
| |
| printf(" analyzed by consumer %d, outcome: %s\n", |
| info->getIdOfAnalyzingConsumer(), |
| outcome); |
| |
| makeValueIdListFromBitVector( |
| cols, |
| cCols, |
| info->getNeededColumns()); |
| printf("\n columns of temp table:\n"); |
| cols.display(); |
| printf("\n commonPredicates:\n"); |
| info->getCommonPredicates().display(); |
| if (info->getVEGRefsWithDifferingConstants().entries() > 0) |
| { |
| printf("\n vegRefsWithDifferingConstants:\n"); |
| info->getVEGRefsWithDifferingConstants().display(); |
| } |
| if (info->getVEGRefsWithDifferingInputs().entries() > 0) |
| { |
| printf("\n vegRefsWithDifferingInputs:\n"); |
| info->getVEGRefsWithDifferingInputs().display(); |
| } |
| if (info->getCSETreeKeyColumns().entries() > 0) |
| { |
| ValueIdList keyCols; |
| |
| makeValueIdListFromBitVector( |
| keyCols, |
| cCols, |
| info->getCSETreeKeyColumns()); |
| printf("\n CSE key columns:\n"); |
| keyCols.display(); |
| } |
| printf("\n DDL of temp table:\n%s\n", |
| info->getTempTableDDL().data()); |
| } // analyzed |
| else if (info->getAnalysisOutcome(0) == |
| CSEInfo::ELIMINATED_IN_BINDER) |
| printf(" eliminated in the binder\n"); |
| else if (!isMainQuery) |
| printf(" not yet analyzed\n"); |
| |
| for (int c=0; c<nc; c++) |
| { |
| printf("\n\n----- Consumer %d:\n", c); |
| info->getConsumer(c)->display(); |
| } |
| } // a CSE we want to display |
| } |
| |
| void CommonSubExprRef::makeValueIdListFromBitVector(ValueIdList &tgt, |
| const ValueIdList &src, |
| const NABitVector &vec) |
| { |
| for (CollIndex b=0; vec.nextUsed(b); b++) |
| tgt.insert(src[b]); |
| } |
| |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class GenericUpdate |
| // ----------------------------------------------------------------------- |
| |
| |
| GenericUpdate::~GenericUpdate() {} |
| |
| Int32 GenericUpdate::getArity() const |
| { |
| if (getOperator().match(REL_ANY_LEAF_GEN_UPDATE)) |
| return 0; |
| else if (getOperator().match(REL_ANY_UNARY_GEN_UPDATE)) |
| return 1; |
| else |
| ABORT("Don't know opcode in GenericUpdate::getArity()"); |
| |
| return 0; // return makes MSVC happy. |
| } |
| |
| void GenericUpdate::getPotentialOutputValues(ValueIdSet & outputValues) const |
| { |
| outputValues = potentialOutputs_; |
| if (producedMergeIUDIndicator_ != NULL_VALUE_ID) |
| outputValues += producedMergeIUDIndicator_; |
| } |
| |
| const NAString GenericUpdate::getUpdTableNameText() const |
| { |
| return updatedTableName_.getTextWithSpecialType(); |
| } |
| |
| void GenericUpdate::computeUsedCols() |
| { |
| ValueIdSet requiredValueIds(newRecExpr_); |
| ValueIdSet coveredExprs; |
| |
| // --------------------------------------------------------------------- |
| // Call the "coverTest" method, offering it all the index columns |
| // as additional inputs. "coverTest" will mark those index columns that |
| // it actually needs to satisfy the required value ids, and that is |
| // what we actually want. The actual cover test should always succeed, |
| // otherwise the update node would have been inconsistent. |
| // --------------------------------------------------------------------- |
| |
| // use the clustering index, unless set otherwise |
| if (indexDesc_ == NULL) |
| indexDesc_ = getTableDesc()->getClusteringIndex(); |
| |
| if (isMerge()) |
| { |
| requiredValueIds.insertList(mergeInsertRecExpr_); |
| } |
| |
| requiredValueIds.insertList(beginKeyPred_); |
| requiredValueIds.insertList(endKeyPred_); |
| requiredValueIds.insertList(getCheckConstraints()); |
| |
| // QSTUFF |
| requiredValueIds.insertList(newRecBeforeExpr_); |
| // QSTUFF |
| |
| getGroupAttr()->coverTest(requiredValueIds, |
| indexDesc_->getIndexColumns(), // all index columns |
| coveredExprs, // dummy parameter |
| usedColumns_); // needed index cols |
| |
| // usedColumns_ is now set correctly |
| } // GenericUpdate::computeUsedCols |
| |
| |
| const NAString GenericUpdate::getText() const |
| { |
| return ("GenericUpdate " + getUpdTableNameText()); |
| } |
| |
| HashValue GenericUpdate::topHash() |
| { |
| HashValue result = RelExpr::topHash(); |
| |
| result ^= newRecExpr_; |
| |
| if (isMerge()) |
| result ^= mergeInsertRecExpr_; |
| |
| // result ^= keyExpr_; |
| |
| return result; |
| } |
| |
| NABoolean GenericUpdate::duplicateMatch(const RelExpr & other) const |
| { |
| if (NOT RelExpr::duplicateMatch(other)) |
| return FALSE; |
| |
| GenericUpdate &o = (GenericUpdate &) other; |
| |
| if (newRecExpr_ != o.newRecExpr_ OR |
| (isMerge() && |
| ((mergeInsertRecExpr_ != o.mergeInsertRecExpr_) OR |
| (mergeUpdatePred_ != o.mergeUpdatePred_)) ) OR |
| NOT (beginKeyPred_ == o.beginKeyPred_) OR |
| NOT (endKeyPred_ == o.endKeyPred_)) |
| return FALSE; |
| |
| // later, replace this with the getTableDesc() ??? |
| if (NOT (updatedTableName_ == o.updatedTableName_)) |
| return FALSE; |
| |
| if (mtsStatement_ != o.mtsStatement_) |
| return FALSE; |
| |
| if (noRollback_ != o.noRollback_) |
| return FALSE; |
| |
| if (avoidHalloweenR2_ != o.avoidHalloweenR2_) |
| return FALSE; |
| |
| if (avoidHalloween_ != o.avoidHalloween_) |
| return FALSE; |
| |
| if (halloweenCannotUseDP2Locks_ != o.halloweenCannotUseDP2Locks_) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| RelExpr * GenericUpdate::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| GenericUpdate *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) |
| GenericUpdate(updatedTableName_, |
| getTableDesc(), |
| getOperatorType(), |
| NULL, |
| NULL, NULL, |
| outHeap); |
| else |
| result = (GenericUpdate *) derivedNode; |
| |
| result->setIndexDesc((IndexDesc *)getIndexDesc()); |
| |
| if (newRecExprTree_) |
| result->newRecExprTree_ = newRecExprTree_->copyTree(outHeap)->castToItemExpr(); |
| |
| // ## Should usedColumns_ be copied here? Is it missing deliberately or only by mistake? |
| result->updateToSelectMap_ = updateToSelectMap_; |
| result->newRecExpr_ = newRecExpr_; |
| result->newRecExprArray_ = newRecExprArray_; |
| // QSTUFF |
| result->newRecBeforeExpr_ = newRecBeforeExpr_; |
| result->newRecBeforeExprArray_ = newRecBeforeExprArray_; |
| // QSTUFF |
| |
| result->mergeInsertRecExpr_ = mergeInsertRecExpr_; |
| result->mergeInsertRecExprArray_ = mergeInsertRecExprArray_; |
| result->mergeUpdatePred_ = mergeUpdatePred_; |
| |
| result->beginKeyPred_ = beginKeyPred_; |
| result->endKeyPred_ = endKeyPred_; |
| result->executorPred_ = executorPred_; |
| result->potentialOutputs_ = potentialOutputs_; |
| result->indexNewRecExprArrays_ = indexNewRecExprArrays_; |
| result->indexBeginKeyPredArray_ = indexBeginKeyPredArray_; |
| result->indexEndKeyPredArray_ = indexEndKeyPredArray_; |
| result->indexNumberArray_ = indexNumberArray_; |
| result->scanIndexDesc_ = scanIndexDesc_; |
| result->accessOptions_ = accessOptions_; |
| result->checkConstraints_ = checkConstraints_; |
| result->rowsAffected_ = rowsAffected_; |
| result->setOptStoi(stoi_); |
| result->setNoFlow(noFlow_); |
| result->setMtsStatement(mtsStatement_); |
| result->setNoRollbackOperation(noRollback_); |
| result->setAvoidHalloweenR2(avoidHalloweenR2_); |
| result->avoidHalloween_ = avoidHalloween_; |
| result->halloweenCannotUseDP2Locks_ = halloweenCannotUseDP2Locks_; |
| result->setIsMergeUpdate(isMergeUpdate_); |
| result->setIsMergeDelete(isMergeDelete_); |
| result->subqInUpdateAssign_ = subqInUpdateAssign_; |
| result->setUpdateCKorUniqueIndexKey(updateCKorUniqueIndexKey_); |
| result->hbaseOper() = hbaseOper(); |
| result->uniqueHbaseOper() = uniqueHbaseOper(); |
| result->cursorHbaseOper() = cursorHbaseOper(); |
| result->uniqueRowsetHbaseOper() = uniqueRowsetHbaseOper(); |
| result->canDoCheckAndUpdel() = canDoCheckAndUpdel(); |
| result->setNoCheck(noCheck()); |
| result->noDTMxn() = noDTMxn(); |
| result->useMVCC() = useMVCC(); |
| result->useSSCC() = useSSCC(); |
| |
| if (currOfCursorName()) |
| result->currOfCursorName_ = currOfCursorName()->copyTree(outHeap)->castToItemExpr(); |
| |
| if (preconditionTree_) |
| result->preconditionTree_ = preconditionTree_->copyTree(outHeap)->castToItemExpr(); |
| result->setPrecondition(precondition_); |
| result->exprsInDerivedClasses_ = exprsInDerivedClasses_; |
| result->producedMergeIUDIndicator_ = producedMergeIUDIndicator_; |
| result->referencedMergeIUDIndicator_ = referencedMergeIUDIndicator_; |
| |
| return RelExpr::copyTopNode(result, outHeap); |
| } |
| |
| PlanPriority GenericUpdate::computeOperatorPriority |
| (const Context* context, |
| PlanWorkSpace *pws, |
| Lng32 planNumber) |
| { |
| |
| PlanPriority result; |
| |
| NABoolean interactiveAccess = |
| (CmpCommon::getDefault(INTERACTIVE_ACCESS) == DF_ON) OR |
| ( QueryAnalysis::Instance() AND |
| QueryAnalysis::Instance()->optimizeForFirstNRows()); |
| |
| return result; |
| } |
| |
| void GenericUpdate::addNewRecExprTree(ItemExpr *expr) |
| { |
| ExprValueId newRec = newRecExprTree_; |
| ItemExprTreeAsList(&newRec, ITM_AND).insert(expr); |
| newRecExprTree_ = newRec.getPtr(); |
| } |
| |
| |
| ItemExpr * GenericUpdate::removeNewRecExprTree() |
| { |
| ItemExpr * result = newRecExprTree_; |
| |
| newRecExprTree_ = NULL; |
| |
| return result; |
| } |
| |
| void GenericUpdate::addLocalExpr(LIST(ExprNode *) &xlist, |
| LIST(NAString) &llist) const |
| { |
| |
| if (newRecExprTree_ != NULL OR |
| NOT newRecExpr_.isEmpty()) |
| { |
| if (newRecExpr_.isEmpty()) |
| xlist.insert(newRecExprTree_); |
| else |
| xlist.insert(newRecExpr_.rebuildExprTree(ITM_ITEM_LIST)); |
| llist.insert("new_rec_expr"); |
| } |
| |
| if ((isMerge()) && |
| (NOT mergeInsertRecExpr_.isEmpty())) |
| { |
| xlist.insert(mergeInsertRecExpr_.rebuildExprTree(ITM_ITEM_LIST)); |
| llist.insert("merge_insert_rec_expr"); |
| } |
| |
| if ((isMerge()) && |
| (NOT mergeUpdatePred_.isEmpty())) |
| { |
| xlist.insert(mergeUpdatePred_.rebuildExprTree(ITM_ITEM_LIST)); |
| llist.insert("merge_update_where_pred"); |
| } |
| |
| Int32 indexNo = 0; |
| for(; indexNo < (Int32)indexNewRecExprArrays_.entries(); indexNo++) { |
| ValueIdArray array = indexNewRecExprArrays_[indexNo]; |
| ValueIdList list; |
| for(Int32 i = 0; i < (Int32)array.entries(); i++) |
| list.insert(array[i]); |
| xlist.insert(list.rebuildExprTree(ITM_ITEM_LIST)); |
| llist.insert("new idx rec expr"); |
| } |
| |
| if (executorPredTree_ != NULL OR |
| NOT executorPred_.isEmpty()) |
| { |
| if (executorPred_.isEmpty()) |
| xlist.insert(executorPredTree_); |
| else |
| xlist.insert(executorPred_.rebuildExprTree()); |
| llist.insert("predicate"); |
| } |
| |
| // display preds from search key only if begin/end keys are |
| // not generated yet (e.g. during optimization) |
| if (beginKeyPred_.isEmpty() AND endKeyPred_.isEmpty() AND |
| pathKeys_ AND NOT pathKeys_->getKeyPredicates().isEmpty()) |
| { |
| xlist.insert(pathKeys_->getKeyPredicates().rebuildExprTree()); |
| if (pathKeys_ == partKeys_) |
| llist.insert("key_and_part_key_preds"); |
| else |
| llist.insert("key_predicates"); |
| } |
| |
| // display part key preds only if different from clustering key preds |
| if (partKeys_ AND pathKeys_ != partKeys_ AND |
| NOT partKeys_->getKeyPredicates().isEmpty()) |
| { |
| xlist.insert(partKeys_->getKeyPredicates().rebuildExprTree()); |
| llist.insert("part_key_predicates"); |
| } |
| |
| if (NOT beginKeyPred_.isEmpty()) |
| { |
| xlist.insert(beginKeyPred_.rebuildExprTree(ITM_AND)); |
| llist.insert("begin_key"); |
| } |
| |
| for(indexNo = 0; indexNo < (Int32)indexBeginKeyPredArray_.entries(); indexNo++){ |
| if(NOT indexBeginKeyPredArray_[indexNo].isEmpty()) { |
| xlist.insert(indexBeginKeyPredArray_[indexNo] |
| .rebuildExprTree(ITM_ITEM_LIST)); |
| llist.insert("index_begin_key"); |
| } |
| } |
| |
| if (NOT endKeyPred_.isEmpty()) |
| { |
| xlist.insert(endKeyPred_.rebuildExprTree(ITM_AND)); |
| llist.insert("end_key"); |
| } |
| |
| for(indexNo = 0; indexNo < (Int32)indexEndKeyPredArray_.entries(); indexNo++) { |
| if(NOT indexEndKeyPredArray_[indexNo].isEmpty()) { |
| xlist.insert(indexEndKeyPredArray_[indexNo] |
| .rebuildExprTree(ITM_ITEM_LIST)); |
| llist.insert("index_end_key"); |
| } |
| } |
| |
| if (NOT getCheckConstraints().isEmpty()) |
| { |
| xlist.insert(getCheckConstraints().rebuildExprTree(ITM_AND)); |
| llist.insert("check_constraint"); |
| } |
| |
| if (preconditionTree_ != NULL OR |
| precondition_.entries() > 0) |
| { |
| if (preconditionTree_ != NULL) |
| xlist.insert(preconditionTree_); |
| else |
| xlist.insert(precondition_.rebuildExprTree(ITM_AND)); |
| llist.insert("precondition"); |
| } |
| |
| RelExpr::addLocalExpr(xlist,llist); |
| } |
| |
| NABoolean GenericUpdate::updateCurrentOf() |
| { |
| return currOfCursorName() != NULL |
| #ifndef NDEBUG |
| || getenv("FORCE_UPD_CURR_OF") |
| #endif |
| ; |
| } |
| |
| //++MV - returns the GenericUpdateOutputFunction's that are in the |
| // potential outputs |
| |
| NABoolean GenericUpdate::getOutputFunctionsForMV(ValueId &valueId, |
| OperatorTypeEnum opType) const |
| { |
| const ValueIdSet& outputs = getGroupAttr()->getCharacteristicOutputs(); |
| for (ValueId vid= outputs.init(); |
| outputs.next(vid); |
| outputs.advance(vid) ) |
| { |
| ItemExpr *expr = vid.getItemExpr(); |
| |
| if (expr->getOperatorType() == ITM_CAST) |
| expr = expr->child(0); |
| |
| if (expr->getOperator().match(opType) && |
| expr->isAGenericUpdateOutputFunction() ) |
| { |
| valueId = vid; |
| return TRUE; |
| } |
| } |
| |
| return FALSE; |
| } |
| |
| NABoolean GenericUpdate::computeRowsAffected() const |
| { |
| if (rowsAffected_ == GenericUpdate::COMPUTE_ROWSAFFECTED) |
| return TRUE; |
| else |
| return FALSE; |
| }; |
| |
| void GenericUpdate::configTSJforHalloween( Join* tsj, OperatorTypeEnum opType, |
| CostScalar inputCardinality) |
| { |
| if (avoidHalloween()) |
| { |
| // If we use DP2's FELOCKSELF (i.e., DP2Locks) method to |
| // protect against Halloween, then lock escalation will |
| // be disabled in the Generator. So DP2 wants us to use |
| // no more than 25000 locks per volume. |
| // Also, notice that |
| // by design, we are relying on good cardinality estimates. |
| // If the estimates are too low, then there may be |
| // runtime errors. |
| |
| const PartitioningFunction *partFunc = getTableDesc()-> |
| getClusteringIndex()->getPartitioningFunction(); |
| const Lng32 numParts = partFunc ? partFunc->getCountOfPartitions() : |
| 1; |
| const Lng32 maxLocksAllParts = 25000 * numParts; |
| |
| if ((opType == REL_LEAF_INSERT) && |
| (inputCardinality < maxLocksAllParts) |
| && |
| ! getHalloweenCannotUseDP2Locks() && |
| (CmpCommon::getDefault(BLOCK_TO_PREVENT_HALLOWEEN) != DF_ON) |
| ) |
| tsj->setHalloweenForceSort(Join::NOT_FORCED); |
| else |
| tsj->setHalloweenForceSort(Join::FORCED); |
| } |
| } |
| |
| void GenericUpdate::pushdownCoveredExpr(const ValueIdSet &outputExpr, |
| const ValueIdSet &newExternalInputs, |
| ValueIdSet &predicatesOnParent, |
| const ValueIdSet *setOfValuesReqdByParent, |
| Lng32 childIndex |
| ) |
| { |
| |
| // --------------------------------------------------------------------- |
| // determine the set of local expressions that need to be evaluated |
| // - assign expressions (reference source & target cols) |
| // - source cols alone (in case order is required) |
| // - characteristic outputs for this node |
| // --------------------------------------------------------------------- |
| // QSTUFF ?? again need to understand details |
| ValueIdSet localExprs(newRecExpr()); |
| |
| if (setOfValuesReqdByParent) |
| localExprs += *setOfValuesReqdByParent; |
| |
| // QSTUFF |
| localExprs.insertList(newRecBeforeExpr()); |
| // QSTUFF |
| |
| |
| if (isMerge()) |
| { |
| localExprs.insertList(mergeInsertRecExpr()); |
| } |
| |
| localExprs.insertList(beginKeyPred()); |
| |
| localExprs.insertList(updateToSelectMap().getBottomValues()); |
| if (setOfValuesReqdByParent) |
| localExprs += *setOfValuesReqdByParent ; |
| localExprs += exprsInDerivedClasses_; |
| |
| // --------------------------------------------------------------------- |
| // Check which expressions can be evaluated by my child. |
| // Modify the Group Attributes of those children who inherit some of |
| // these expressions. |
| // Since an GenericUpdate has no predicates, supply an empty set. |
| // --------------------------------------------------------------------- |
| RelExpr::pushdownCoveredExpr( outputExpr, |
| newExternalInputs, |
| predicatesOnParent, |
| &localExprs); |
| |
| /*to fix jira 18-20180111-2901 |
| *For query " insert into to t1 select seqnum(seq1, next) from t1;", there is no SORT as left child of TSJ, and it |
| *is a self-referencing updates Halloween problem. In NestedJoin::genWriteOpLeftChildSortReq(), child(0) |
| *producing no outputs for this query, which means that there is no column to sort on. So we solve this by |
| *having the source for Halloween insert produce at least one output column always. |
| * */ |
| if (avoidHalloween() && child(0) && |
| child(0)->getOperatorType() == REL_SCAN && |
| child(0)->getGroupAttr()) |
| { |
| if (child(0)->getGroupAttr()->getCharacteristicOutputs().isEmpty()) |
| { |
| ValueId exprId; |
| ValueId atLeastOne; |
| |
| ValueIdSet output_source = child(0)->getTableDescForExpr()->getColumnList(); |
| for (exprId = output_source.init(); |
| output_source.next(exprId); |
| output_source.advance(exprId)) |
| { |
| atLeastOne = exprId; |
| if (!(exprId.getItemExpr()->doesExprEvaluateToConstant(FALSE, TRUE))) |
| { |
| child(0)->getGroupAttr()->addCharacteristicOutputs(exprId); |
| break; |
| } |
| } |
| if (child(0)->getGroupAttr()->getCharacteristicOutputs().isEmpty()) |
| { |
| child(0)->getGroupAttr()->addCharacteristicOutputs(atLeastOne); |
| } |
| } |
| } |
| } |
| |
| /* |
| NABoolean Insert::reconcileGroupAttr(GroupAttributes *newGroupAttr) |
| { |
| SET(IndexDesc *) x; |
| const IndexDesc* y = getTableDesc()->getClusteringIndex(); |
| x.insert((IndexDesc*)y); |
| |
| newGroupAttr->addToAvailableBtreeIndexes(x); |
| |
| // Now as usual |
| return RelExpr::reconcileGroupAttr(newGroupAttr); |
| } |
| */ |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class Insert |
| // ----------------------------------------------------------------------- |
| Insert::Insert(const CorrName &name, |
| TableDesc *tabId, |
| OperatorTypeEnum otype, |
| RelExpr *child , |
| ItemExpr *insertCols , |
| ItemExpr *orderBy , |
| CollHeap *oHeap , |
| InsertType insertType, |
| NABoolean createUstatSample) |
| : GenericUpdate(name,tabId,otype,child,NULL,NULL,oHeap), |
| insertColTree_(insertCols), |
| orderByTree_(orderBy), |
| targetUserColPosList_(NULL), |
| bufferedInsertsAllowed_(FALSE), |
| insertType_(insertType), |
| noBeginSTInsert_(FALSE), |
| noCommitSTInsert_(FALSE), |
| enableTransformToSTI_(FALSE), |
| enableAqrWnrEmpty_(FALSE), |
| systemGeneratesIdentityValue_(FALSE), |
| insertSelectQuery_(FALSE), |
| boundView_(NULL), |
| overwriteHiveTable_(FALSE), |
| isSequenceFile_(FALSE), |
| isUpsert_(FALSE), |
| isTrafLoadPrep_(FALSE), |
| createUstatSample_(createUstatSample), |
| xformedEffUpsert_(FALSE), |
| baseColRefs_(NULL) |
| { |
| insert_a_tuple_ = FALSE; |
| if ( child ) { |
| if ( child->getOperatorType() == REL_TUPLE ) { |
| insert_a_tuple_ = TRUE; |
| if (!name.isLocationNameSpecified()) { |
| setCacheableNode(CmpMain::PARSE); |
| } |
| } |
| else if ( child->getOperatorType() == REL_TUPLE_LIST && |
| !name.isLocationNameSpecified() ) { |
| setCacheableNode(CmpMain::PARSE); |
| } |
| } |
| else |
| // this is a patch to pass regression for maximum parallelism project, |
| // if we insert a default values not a real tuple the child is NULL |
| // but we'd like to identify is as a tuple insert. March,2006 |
| if (CmpCommon::getDefault(COMP_BOOL_66) == DF_OFF) |
| { |
| insert_a_tuple_ = TRUE; |
| } |
| } |
| |
| Insert::~Insert() {} |
| |
| void Insert::addInsertColTree(ItemExpr *expr) |
| { |
| ExprValueId newCol = insertColTree_; |
| ItemExprTreeAsList(&newCol, ITM_AND).insert(expr); |
| insertColTree_ = newCol.getPtr(); |
| } |
| |
| ItemExpr * Insert::removeInsertColTree() |
| { |
| ItemExpr * result = insertColTree_; |
| |
| insertColTree_ = NULL; |
| |
| return result; |
| } |
| |
| ItemExpr * Insert::getInsertColTree() |
| { |
| return insertColTree_; |
| } |
| |
| const NAString Insert::getText() const |
| { |
| NAString text("insert",CmpCommon::statementHeap()); |
| |
| return (text + " " + getUpdTableNameText()); |
| } |
| |
| RelExpr * Insert::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| Insert *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) Insert(getTableName(), |
| getTableDesc(), |
| getOperatorType(), |
| NULL, |
| NULL, |
| NULL, |
| outHeap, |
| getInsertType()); |
| else |
| result = (Insert *) derivedNode; |
| |
| result->rrKeyExpr() = rrKeyExpr(); |
| result->partNumInput() = partNumInput(); |
| result->rowPosInput() = rowPosInput(); |
| result->totalNumPartsInput() = totalNumPartsInput(); |
| result->reqdOrder() = reqdOrder(); |
| |
| result->noBeginSTInsert_ = noBeginSTInsert_; |
| result->noCommitSTInsert_ = noCommitSTInsert_; |
| result->enableTransformToSTI() = enableTransformToSTI(); |
| result->enableAqrWnrEmpty() = enableAqrWnrEmpty(); |
| |
| if (insertColTree_ != NULL) |
| result->insertColTree_ = insertColTree_->copyTree(outHeap)->castToItemExpr(); |
| |
| result->insertATuple() = insertATuple(); |
| result->setInsertSelectQuery(isInsertSelectQuery()); |
| result->setOverwriteHiveTable(getOverwriteHiveTable()); |
| result->setSequenceFile(isSequenceFile()); |
| result->isUpsert_ = isUpsert_; |
| result->isTrafLoadPrep_ = isTrafLoadPrep_; |
| result->createUstatSample_ = createUstatSample_; |
| result->xformedEffUpsert_ = xformedEffUpsert_; |
| return GenericUpdate::copyTopNode(result, outHeap); |
| } |
| |
| void Insert::setNoBeginCommitSTInsert(NABoolean noBeginSTI, NABoolean noCommitSTI) |
| { |
| noBeginSTInsert_ = noBeginSTI; |
| noCommitSTInsert_ = noCommitSTI; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class Update |
| // ----------------------------------------------------------------------- |
| Update::Update(const CorrName &name, |
| TableDesc *tabId, |
| OperatorTypeEnum otype, |
| RelExpr *child, |
| ItemExpr *newRecExpr, |
| ItemExpr *currOfCursorName, |
| CollHeap *oHeap) |
| : GenericUpdate(name,tabId,otype,child,newRecExpr,currOfCursorName,oHeap), |
| estRowsAccessed_(0) |
| { |
| setCacheableNode(CmpMain::BIND); |
| } |
| |
| Update::~Update() {} |
| |
| const NAString Update::getText() const |
| { |
| NAString text("update",CmpCommon::statementHeap()); |
| |
| return (text + " " + getUpdTableNameText()); |
| } |
| |
| RelExpr * Update::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| Update *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) Update(getTableName(), |
| getTableDesc(), |
| getOperatorType(), |
| NULL, |
| NULL, NULL, |
| outHeap); |
| else |
| result = (Update *) derivedNode; |
| |
| result->setEstRowsAccessed(getEstRowsAccessed()); |
| |
| return GenericUpdate::copyTopNode(result, outHeap); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class MergeUpdate |
| // ----------------------------------------------------------------------- |
| MergeUpdate::MergeUpdate(const CorrName &name, |
| TableDesc *tabId, |
| OperatorTypeEnum otype, |
| RelExpr *child, |
| ItemExpr *setExpr, |
| ItemExpr *insertCols, |
| ItemExpr *insertValues, |
| CollHeap *oHeap, |
| ItemExpr *where) |
| : Update(name,tabId,otype,child,setExpr,NULL,oHeap), |
| insertCols_(insertCols), insertValues_(insertValues), |
| where_(where), xformedUpsert_(FALSE), needsBindScope_(TRUE) |
| { |
| setCacheableNode(CmpMain::BIND); |
| |
| setIsMergeUpdate(TRUE); |
| |
| // if there is a WHERE NOT MATCHED INSERT action, then the scan |
| // has to take place in the merge node at run time, so we have |
| // to suppress the TSJ transformation on this node |
| if (insertValues) |
| setNoFlow(TRUE); |
| } |
| |
| MergeUpdate::~MergeUpdate() {} |
| |
| const NAString MergeUpdate::getText() const |
| { |
| NAString text("merge_update",CmpCommon::statementHeap()); |
| |
| return (text + " " + getUpdTableNameText()); |
| } |
| |
| RelExpr * MergeUpdate::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| MergeUpdate *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) MergeUpdate(getTableName(), |
| getTableDesc(), |
| getOperatorType(), |
| child(0), |
| NULL, |
| insertCols(), insertValues(), |
| outHeap, where_); |
| else |
| result = (MergeUpdate *) derivedNode; |
| if (xformedUpsert()) |
| result->setXformedUpsert(); |
| |
| return Update::copyTopNode(result, outHeap); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class Delete |
| // ----------------------------------------------------------------------- |
| |
| Delete::Delete(const CorrName &name, TableDesc *tabId, OperatorTypeEnum otype, |
| RelExpr *child, ItemExpr *newRecExpr, |
| ItemExpr *currOfCursorName, |
| ConstStringList * csl, |
| CollHeap *oHeap) |
| : GenericUpdate(name,tabId,otype,child,newRecExpr,currOfCursorName,oHeap), |
| csl_(csl),estRowsAccessed_(0) |
| { |
| setCacheableNode(CmpMain::BIND); |
| } |
| |
| Delete::~Delete() {} |
| |
| const NAString Delete::getText() const |
| { |
| NAString text("delete",CmpCommon::statementHeap()); |
| |
| return (text + " " + getUpdTableNameText()); |
| } |
| |
| RelExpr * Delete::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| Delete *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) Delete(getTableName(), |
| getTableDesc(), |
| getOperatorType(), |
| NULL, |
| NULL, NULL, |
| csl_, |
| outHeap); |
| else |
| result = (Delete *) derivedNode; |
| |
| result->csl() = csl(); |
| result->setEstRowsAccessed(getEstRowsAccessed()); |
| |
| return GenericUpdate::copyTopNode(result, outHeap); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class MergeDelete |
| // ----------------------------------------------------------------------- |
| MergeDelete::MergeDelete(const CorrName &name, |
| TableDesc *tabId, |
| OperatorTypeEnum otype, |
| RelExpr *child, |
| ItemExpr *insertCols, |
| ItemExpr *insertValues, |
| CollHeap *oHeap) |
| : Delete(name,tabId,otype,child,NULL,NULL,NULL,oHeap), |
| insertCols_(insertCols), insertValues_(insertValues) |
| { |
| setCacheableNode(CmpMain::BIND); |
| |
| setIsMergeDelete(TRUE); |
| |
| // if there is a WHERE NOT MATCHED INSERT action, then the scan |
| // has to take place in the merge node at run time, so we have |
| // to suppress the TSJ transformation on this node |
| if (insertValues) |
| setNoFlow(TRUE); |
| } |
| |
| MergeDelete::~MergeDelete() {} |
| |
| const NAString MergeDelete::getText() const |
| { |
| NAString text("merge_delete",CmpCommon::statementHeap()); |
| |
| return (text + " " + getUpdTableNameText()); |
| } |
| |
| RelExpr * MergeDelete::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| MergeDelete *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) MergeDelete(getTableName(), |
| getTableDesc(), |
| getOperatorType(), |
| child(0), |
| insertCols(), insertValues(), |
| outHeap); |
| else |
| result = (MergeDelete *) derivedNode; |
| |
| return Delete::copyTopNode(result, outHeap); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class InsertCursor |
| // ----------------------------------------------------------------------- |
| |
| InsertCursor::~InsertCursor() {} |
| |
| NABoolean InsertCursor::isLogical() const { return FALSE; } |
| |
| NABoolean InsertCursor::isPhysical() const { return TRUE; } |
| |
| const NAString InsertCursor::getText() const |
| { |
| NAString text("insert", CmpCommon::statementHeap()); |
| |
| if ((insertType_ == VSBB_INSERT_SYSTEM) || |
| (insertType_ == VSBB_INSERT_USER)) |
| text = text + "_vsbb"; |
| else if ((insertType_ == VSBB_LOAD) || |
| (insertType_ == VSBB_LOAD_APPEND) || |
| (insertType_ == VSBB_LOAD_NO_DUP_KEY_CHECK) || |
| (insertType_ == VSBB_LOAD_APPEND_NO_DUP_KEY_CHECK)) |
| text = text + "_sidetree"; |
| else if (insertType_ == VSBB_LOAD_AUDITED) |
| text = text + "_sidetree_audited"; |
| |
| text = text + " (physical)"; |
| |
| return (text + " " + getUpdTableNameText()); |
| } |
| |
| RelExpr * InsertCursor::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| RelExpr *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) InsertCursor(getTableName(), |
| getTableDesc(), |
| getOperatorType(), |
| NULL, |
| outHeap); |
| else |
| result = derivedNode; |
| |
| return Insert::copyTopNode(result, outHeap); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class HiveInsert |
| // ----------------------------------------------------------------------- |
| const NAString HiveInsert::getText() const |
| { |
| NAString text("hive_insert", CmpCommon::statementHeap()); |
| |
| text += " (physical)"; |
| |
| return (text + " " + getUpdTableNameText()); |
| } |
| |
| RelExpr * HiveInsert::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| RelExpr *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) HiveInsert(getTableName(), |
| getTableDesc(), |
| getOperatorType(), |
| NULL, |
| outHeap); |
| else |
| result = derivedNode; |
| |
| return Insert::copyTopNode(result, outHeap); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class HbaseInsert |
| // ----------------------------------------------------------------------- |
| const NAString HbaseInsert::getText() const |
| { |
| NABoolean isSeabase = |
| (getTableDesc() && getTableDesc()->getNATable() ? |
| getTableDesc()->getNATable()->isSeabaseTable() : FALSE); |
| |
| NAString text; |
| |
| if (NOT isSeabase) |
| text = "hbase_"; |
| else |
| text = "trafodion_"; |
| |
| if (isUpsert()) |
| { |
| if (getInsertType() == Insert::UPSERT_LOAD) |
| { |
| if (getIsTrafLoadPrep()) |
| text += "load_preparation"; |
| else |
| text += "load"; |
| } |
| else if (vsbbInsert()) |
| text += "vsbb_upsert"; |
| else |
| text += "upsert"; |
| } |
| else |
| { |
| if (vsbbInsert()) |
| text += "vsbb_upsert"; |
| else |
| text += "insert"; |
| } |
| |
| return (text + " " + getUpdTableNameText()); |
| } |
| |
| RelExpr * HbaseInsert::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| HbaseInsert *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) HbaseInsert(getTableName(), |
| getTableDesc(), |
| getOperatorType(), |
| NULL, |
| outHeap); |
| else |
| result = (HbaseInsert *) derivedNode; |
| |
| result->returnRow_ = returnRow_; |
| |
| return Insert::copyTopNode(result, outHeap); |
| } |
| |
| RelExpr * HBaseBulkLoadPrep::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| RelExpr *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) HbaseInsert(getTableName(), |
| getTableDesc(), |
| getOperatorType(), |
| NULL, |
| outHeap); |
| else |
| result = derivedNode; |
| |
| return Insert::copyTopNode(result, outHeap); |
| } |
| // ----------------------------------------------------------------------- |
| // member functions for class UpdateCursor |
| // ----------------------------------------------------------------------- |
| |
| UpdateCursor::~UpdateCursor() {} |
| |
| NABoolean UpdateCursor::isLogical() const { return FALSE; } |
| |
| NABoolean UpdateCursor::isPhysical() const { return TRUE; } |
| |
| const NAString UpdateCursor::getText() const |
| { |
| NAString text("cursor_update",CmpCommon::statementHeap()); |
| |
| return (text + " " + getUpdTableNameText()); |
| } |
| |
| |
| RelExpr * UpdateCursor::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| RelExpr *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) UpdateCursor(getTableName(), |
| getTableDesc(), |
| getOperatorType(), |
| NULL, |
| outHeap); |
| else |
| result = derivedNode; |
| |
| return Update::copyTopNode(result, outHeap); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // member functions for class DeleteCursor |
| // ----------------------------------------------------------------------- |
| |
| DeleteCursor::~DeleteCursor() {} |
| |
| NABoolean DeleteCursor::isLogical() const { return FALSE; } |
| |
| NABoolean DeleteCursor::isPhysical() const { return TRUE; } |
| |
| const NAString DeleteCursor::getText() const |
| { |
| NAString text("cursor_delete",CmpCommon::statementHeap()); |
| |
| return (text + " " + getUpdTableNameText()); |
| } |
| |
| RelExpr * DeleteCursor::copyTopNode(RelExpr *derivedNode, CollHeap* outHeap) |
| { |
| RelExpr *result; |
| |
| if (derivedNode == NULL) |
| result = new (outHeap) DeleteCursor(getTableName(), |
| getTableDesc(), |
| getOperatorType(), |
| NULL, |
| outHeap); |
| else |
| result = derivedNode; |
| |
| return Delete::copyTopNode(result, outHeap); |
| } |
| |
| |
| ///////////////////////////////////////////////////////////////////// |
| |
| void |
| RelExpr::unparse(NAString &result, |
| PhaseEnum /* phase */, |
| UnparseFormatEnum /* form */, |
| TableDesc * tabId) const |
| { |
| result += getText(); |
| |
| #ifndef NDEBUG |
| if (getenv("UNPARSE_FULL")) |
| { |
| if (selection_) |
| { |
| result += "["; |
| selection_->unparse(result /*, phase, form */); |
| result += "]"; |
| } |
| if (predicates_.entries()) |
| { |
| result += "{"; |
| predicates_.unparse(result /*, phase, form */); |
| result += "}"; |
| } |
| } |
| #endif |
| |
| Int32 maxi = getArity(); |
| if (maxi) |
| { |
| result += "("; |
| for (Lng32 i = 0; i < maxi; i++) |
| { |
| if (i > 0) |
| result += ", "; |
| if ( child(i).getPtr() == NULL ) |
| continue; |
| child(i)->unparse(result); |
| } |
| result += ")"; |
| } |
| } |
| |
| // ----------------------------------------------------------------------- |
| // methods for class Transpose |
| // ----------------------------------------------------------------------- |
| |
| // Transpose::~Transpose() ----------------------------------------------- |
| // The destructor |
| // |
| Transpose::~Transpose() |
| { |
| } |
| |
| // Transpose::topHash() -------------------------------------------------- |
| // Compute a hash value for a chain of derived RelExpr nodes. |
| // Used by the Cascade engine as a quick way to determine if |
| // two nodes are identical. |
| // Can produce false positives (nodes appear to be identical), |
| // but should not produce false negatives (nodes are definitely different) |
| // |
| // Inputs: none (other than 'this') |
| // |
| // Outputs: A HashValue of this node and all nodes in the |
| // derivation chain below (towards the base class) this node. |
| // |
| HashValue Transpose::topHash() |
| { |
| // Compute a hash value of the derivation chain below this node. |
| // |
| HashValue result = RelExpr::topHash(); |
| |
| // transUnionVector is the only relevant |
| // data members at this point. The other data members do not |
| // live past the binder. |
| // |
| for(CollIndex i = 0; i < transUnionVectorSize(); i++) { |
| result ^= transUnionVector()[i]; |
| } |
| |
| return result; |
| } |
| |
| // Transpose::duplicateMatch() |
| // A more thorough method to compare two RelExpr nodes. |
| // Used by the Cascades engine when the topHash() of two |
| // nodes returns the same hash values. |
| // |
| // Inputs: other - a reference to another node of the same type. |
| // |
| // Outputs: NABoolean - TRUE if this node is 'identical' to the |
| // 'other' node. FALSE otherwise. |
| // |
| // In order to match, this node must match all the way down the |
| // derivation chain to the RelExpr class. |
| // |
| // For the Transpose node, the only relevant data member which |
| // needs to be compared is transUnionVals_. The other data members |
| // do not exist passed the binder. |
| // |
| NABoolean |
| Transpose::duplicateMatch(const RelExpr & other) const |
| { |
| // Compare this node with 'other' down the derivation chain. |
| // |
| if (!RelExpr::duplicateMatch(other)) |
| return FALSE; |
| |
| // Cast the RelExpr to a Transpose node. (This must be a Transpose node) |
| // |
| Transpose &o = (Transpose &) other; |
| |
| // If the transUnionVectors are the same size and have the same entries, |
| // then the nodes are identical |
| // |
| if(transUnionVectorSize() != o.transUnionVectorSize()) |
| return FALSE; |
| |
| for(CollIndex i = 0; i < transUnionVectorSize(); i++) { |
| if (!(transUnionVector()[i] == o.transUnionVector()[i])) |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| // Transpose::copyTopNode ---------------------------------------------- |
| // Copy a chain of derived nodes (Calls RelExpr::copyTopNode). |
| // Needs to copy all relevant fields. |
| // Used by the Cascades engine. |
| // |
| // Inputs: derivedNode - If Non-NULL this should point to a node |
| // which is derived from this node. If NULL, then this |
| // node is the top of the derivation chain and a node must |
| // be constructed. |
| // |
| // Outputs: RelExpr * - A Copy of this node. |
| // |
| // If the 'derivedNode is non-NULL, then this method is being called |
| // from a copyTopNode method on a class derived from this one. If it |
| // is NULL, then this is the top of the derivation chain and a transpose |
| // node must be constructed. |
| // |
| // In either case, the relevant data members must be copied to 'derivedNode' |
| // and 'derivedNode' is passed to the copyTopNode method of the class |
| // below this one in the derivation chain (RelExpr::copyTopNode() in this |
| // case). |
| // |
| RelExpr * |
| Transpose::copyTopNode(RelExpr *derivedNode, CollHeap *outHeap) |
| { |
| Transpose *result; |
| |
| if (derivedNode == NULL) |
| // This is the top of the derivation chain |
| // Create an empty Transpose node. |
| // |
| result = new (outHeap) Transpose(NULL,NULL,NULL,outHeap); |
| else |
| // A node has already been constructed as a derived class. |
| // |
| result = (Transpose *) derivedNode; |
| |
| // Copy the relavant fields. |
| |
| result->transUnionVectorSize_ = transUnionVectorSize(); |
| |
| result->transUnionVector() = |
| new (outHeap) ValueIdList[transUnionVectorSize()]; |
| |
| for(CollIndex i = 0; i < transUnionVectorSize(); i++) { |
| result->transUnionVector()[i] = transUnionVector()[i]; |
| } |
| |
| // copy pointer to expressions |
| // These are not available after bindNode() |
| // |
| if (transValsTree_ != NULL) |
| result->transValsTree_ = transValsTree_->copyTree(outHeap)->castToItemExpr(); |
| |
| if (keyCol_ != NULL) |
| result->keyCol_ = keyCol_->copyTree(outHeap)->castToItemExpr(); |
| |
| |
| // Copy any data members from the classes lower in the derivation chain. |
| // |
| return RelExpr::copyTopNode(result, outHeap); |
| } |
| |
| |
| // Transpose::addLocalExpr() ----------------------------------------------- |
| // Insert into a list of expressions all the expressions of this node and |
| // all nodes below this node in the derivation chain. Insert into a list of |
| // names, all the names of the expressions of this node and all nodes below |
| // this node in the derivation chain. This method is used by the GUI tool |
| // and by the Explain Function to have a common method to get all the |
| // expressions associated with a node. |
| // |
| // Inputs/Outputs: xlist - a list of expressions. |
| // llist - a list of names of expressions. |
| // |
| // The xlist contains a list of all the expressions associated with this |
| // node. The llist contains the names of these expressions. (This lists |
| // must be kept in the same order). |
| // Transpose::addLocalExpr potentially adds the transUnionVals_ expression |
| // ("transpose_union_values"), the transValsTree_ expression |
| // ("transpose_values"), and the keyCol_ expression ("key_column"). |
| // |
| // It then calls RelExpr::addLocalExpr() which will add any RelExpr |
| // expressions to the list. |
| // |
| void Transpose::addLocalExpr(LIST(ExprNode *) &xlist, |
| LIST(NAString) &llist) const |
| { |
| |
| for(CollIndex i = 0; i < transUnionVectorSize(); i++) { |
| if (NOT transUnionVector()[i].isEmpty()) { |
| xlist.insert(transUnionVector()[i].rebuildExprTree()); |
| llist.insert("transpose_union_vector"); |
| } |
| } |
| |
| // This is only available as an ItemExpr tree. It is never |
| // stored as a ValueIdSet. This is not available after bindNode(). |
| // |
| if(transValsTree_) { |
| xlist.insert(transValsTree_); |
| llist.insert("transpose_values"); |
| } |
| |
| // This is only available as an ItemExpr tree. It is never |
| // stored as a ValueIdSet. This is not available after bindNode(). |
| // |
| if(keyCol_) { |
| xlist.insert(keyCol_); |
| llist.insert("key_column"); |
| } |
| |
| RelExpr::addLocalExpr(xlist,llist); |
| } |
| |
| // Transpose::getPotentialOutputValues() --------------------------------- |
| // Construct a Set of the potential outputs of this node. |
| // |
| // Inputs: none (other than 'this') |
| // |
| // Outputs: outputValues - a ValueIdSet representing the potential outputs |
| // of this node. |
| // |
| // The potential outputs for the transpose node are the new columns |
| // generated by the transpose node, plus the outputs produced by the |
| // child node. The new columns generated by transpose are the key |
| // column and the value colunms (one for each transpose group). |
| // |
| void |
| Transpose::getPotentialOutputValues(ValueIdSet & outputValues) const |
| { |
| // Make sure the ValueIdSet is empty. |
| // |
| outputValues.clear(); |
| |
| // Add the values generated by the transpose node. |
| // |
| for(CollIndex i = 0; i < transUnionVectorSize(); i++) { |
| outputValues.insertList( transUnionVector()[i] ); |
| } |
| |
| // Add the values produced by the child. |
| // |
| outputValues += child(0).getGroupAttr()->getCharacteristicOutputs(); |
| |
| } // Transpose::getPotentialOutputValues() |
| |
| |
| // Transpose::pushdownCoveredExpr() ------------------------------------ |
| // |
| // In order to compute the Group Attributes for a relational operator |
| // an analysis of all the scalar expressions associated with it is |
| // performed. The purpose of this analysis is to identify the sources |
| // of the values that each expression requires. As a result of this |
| // analysis values are categorized as external dataflow inputs or |
| // those that can be produced completely by a certain child of the |
| // relational operator. |
| // |
| // This method is invoked on each relational operator. It causes |
| // a) the pushdown of predicates and |
| // b) the recomputation of the Group Attributes of each child. |
| // The recomputation is required either because the child is |
| // assigned new predicates or is expected to compute some of the |
| // expressions that are required by its parent. |
| // |
| // Parameters: |
| // |
| // const ValueIdSet &setOfValuesReqdByParent |
| // IN: a read-only reference to a set of expressions that are |
| // associated with this operator. Typically, they do not |
| // include the predicates. |
| // |
| // ValueIdSet & newExternalInputs |
| // IN : a reference to a set of new external inputs (ValueIds) |
| // that are provided by this operator for evaluating the |
| // the above expressions. |
| // |
| // ValueIdSet & predicatesOnParent |
| // IN : the set of predicates existing on the operator |
| // OUT: a subset of the original predicates. Some of the |
| // predicate factors may have been pushed down to |
| // the operator's children. |
| // |
| // long childIndex |
| // IN : This is an optional parameter. |
| // If supplied, it is a zero-based index for a specific child |
| // on which the predicate pushdown should be attempted. |
| // If not supplied, or a null pointer is supplied, then |
| // the pushdown is attempted on all the children. |
| // |
| // --------------------------------------------------------------------- |
| void Transpose::pushdownCoveredExpr(const ValueIdSet &outputExpr, |
| const ValueIdSet &newExternalInputs, |
| ValueIdSet &predicatesOnParent, |
| const ValueIdSet *setOfValuesReqdByParent, |
| Lng32 childIndex |
| ) |
| { |
| |
| ValueIdSet exprOnParent; |
| if (setOfValuesReqdByParent) |
| exprOnParent = *setOfValuesReqdByParent; |
| |
| // Add all the values required for the transpose expressions |
| // to the values required by the parent. |
| // Don't add the valueIds of the ValueIdUnion nodes, but the |
| // valueIds of the contents of the ValueIdUnion nodes. |
| // |
| for(CollIndex v = 0; v < transUnionVectorSize(); v++) { |
| ValueIdList &valIdList = transUnionVector()[v]; |
| |
| for(CollIndex i = 0; i < valIdList.entries(); i++) { |
| ValueIdUnion *valIdu = ((ValueIdUnion *)valIdList[i]. |
| getValueDesc()->getItemExpr()); |
| |
| exprOnParent.insertList(valIdu->getSources()); |
| } |
| } |
| |
| ValueIdSet pushablePredicates(predicatesOnParent); |
| |
| RelExpr::pushdownCoveredExpr(outputExpr, |
| newExternalInputs, |
| pushablePredicates, |
| &exprOnParent, |
| childIndex); |
| |
| predicatesOnParent.intersectSet(pushablePredicates); |
| } // Transpose::pushdownCoveredExpr |
| |
| // Transpose::removeTransValsTree() ------------------------------------- |
| // Return the transValsTree_ ItemExpr tree and set to NULL, |
| // |
| // Inputs: none (Other than 'this') |
| // |
| // Outputs: ItemExpr * - the value of transValsTree_ |
| // |
| // Side Effects: Sets the value of transValsTree_ to NULL. |
| // |
| // Called by Transpose::bindNode(). The value of transValsTree_ is not |
| // needed after the binder. |
| // |
| const ItemExpr * |
| Transpose::removeTransValsTree() |
| { |
| ItemExpr *result = transValsTree_; |
| transValsTree_ = (ItemExpr *)NULL; |
| return result; |
| } |
| |
| // Transpose::removeKeyCol() ------------------------------------- |
| // Return the keyCol_ ItemExpr tree and set it to NULL, |
| // |
| // Inputs: none (Other than 'this') |
| // |
| // Outputs: ItemExpr * - the value of keyCol_ |
| // |
| // Side Effects: Sets the value of keyCol_ to NULL. |
| // |
| // Call by Transpose::bindNode(). The value of keyCol_ is not |
| // needed after the binder. |
| // |
| const ItemExpr * |
| Transpose::removeKeyCol() |
| { |
| ItemExpr *result = keyCol_; |
| keyCol_ = (ItemExpr *)NULL; |
| return result; |
| } |
| |
| // This method is used in case there are expressions in the Transpose column |
| // list. It traverses through the expression to get the column under them |
| // If it is a unary expression, it gets the column directly below the expression. |
| // If the expression has two children, it goes through both the children |
| // to see which one of them has a higher UEC. It returns the ValueId of that |
| // column. This column is later used to determine the UEC of the final |
| // transpose column. |
| |
| ValueId Transpose::getSourceColFromExprForUec(ValueId sourceValId, |
| const ColStatDescList & childColStatsList) |
| { |
| |
| if (sourceValId.getItemExpr()->getOperatorType() == ITM_VEG_REFERENCE) |
| return sourceValId; |
| |
| ValueIdSet vegCols; |
| sourceValId.getItemExpr()-> |
| findAll(ITM_VEG_REFERENCE, vegCols, TRUE, TRUE); |
| |
| // case 1 : expression with a constant, return sourceValId |
| // case 2 :(only one expr) concentrates on simple expressions only |
| // case 3 :(Multiple exprs) Expression of type EXPR1 OP EXPR2 |
| // where EXPR1 , EXPR2 is VEGREF or EXPR we will assume the max UEC |
| // admist the list of base columns found will be used. |
| // This is an approximation but better that the worst case. |
| if (vegCols.entries() == 0) |
| { |
| // case 1 |
| return sourceValId; |
| } |
| if(vegCols.entries() == 1) |
| { |
| // case 2 |
| // There is only one get that. |
| vegCols.getFirst(sourceValId); |
| } |
| else |
| { |
| //case 3 |
| //Initialize for safety. |
| vegCols.getFirst(sourceValId); |
| //CostScalars are initialized by their constructor to zero. |
| CostScalar currentMaxUEC,currentUEC; |
| CollIndex index = NULL_COLL_INDEX; |
| |
| for(ValueId currentValId = vegCols.init() |
| ;vegCols.next(currentValId) |
| ;vegCols.advance(currentValId)) |
| { |
| index = NULL_COLL_INDEX; |
| childColStatsList.getColStatDescIndex(index, currentValId); |
| if (index == NULL_COLL_INDEX) continue; |
| |
| currentUEC = childColStatsList[index]->getColStats() |
| ->getTotalUec(); |
| //get the UEC and find the max and corresponding valueID |
| //and assign it ti sourceValId. |
| if(currentUEC > currentMaxUEC) |
| { |
| currentMaxUEC = currentUEC; |
| sourceValId = currentValId; |
| } |
| }// end of for |
| }//end of elsif |
| return sourceValId; |
| } |
| |
| // The destructor |
| // |
| PhysTranspose::~PhysTranspose() |
| { |
| } |
| |
| // PhysTranspose::copyTopNode ---------------------------------------------- |
| // Copy a chain of derived nodes (Calls Transpose::copyTopNode). |
| // Needs to copy all relevant fields. |
| // Used by the Cascades engine. |
| // |
| // Inputs: derivedNode - If Non-NULL this should point to a node |
| // which is derived from this node. If NULL, then this |
| // node is the top of the derivation chain and a node must |
| // be constructed. |
| // |
| // Outputs: RelExpr * - A Copy of this node. |
| // |
| // If the 'derivedNode is non-NULL, then this method is being called |
| // from a copyTopNode method on a class derived from this one. If it |
| // is NULL, then this is the top of the derivation chain and a transpose |
| // node must be constructed. |
| // |
| // In either case, the relevant data members must be copied to 'derivedNode' |
| // and 'derivedNode' is passed to the copyTopNode method of the class |
| // below this one in the derivation chain (Transpose::copyTopNode() in this |
| // case). |
| // |
| RelExpr * |
| PhysTranspose::copyTopNode(RelExpr *derivedNode, CollHeap *outHeap) |
| { |
| PhysTranspose *result; |
| |
| if (derivedNode == NULL) |
| // This is the top of the derivation chain |
| // Generate an empty PhysTranspose node. |
| // |
| result = new (outHeap) PhysTranspose(NULL,outHeap); |
| else |
| // A node has already been constructed as a derived class. |
| // |
| result = (PhysTranspose *) derivedNode; |
| |
| // PhysTranspose has no data members. |
| |
| // Copy any data members from the classes lower in the derivation chain. |
| // |
| return Transpose::copyTopNode(result, outHeap); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // methods for class Pack |
| // ----------------------------------------------------------------------- |
| |
| // Constructor |
| Pack::Pack(ULng32 pf, |
| RelExpr* child, |
| ItemExpr* packingExprTree, |
| CollHeap* oHeap) |
| : RelExpr(REL_PACK,child,NULL,oHeap), |
| packingFactorLong_(pf), |
| packingFactorTree_(NULL), |
| packingExprTree_(packingExprTree) |
| { |
| setNonCacheable(); |
| packingFactor().clear(); |
| packingExpr().clear(); |
| requiredOrder_.clear(); |
| } |
| |
| // Destructor. |
| Pack::~Pack() |
| { |
| } |
| |
| // ----------------------------------------------------------------------- |
| // Pack:: some Accessors/Mutators. |
| // ----------------------------------------------------------------------- |
| |
| ItemExpr* Pack::removePackingFactorTree() |
| { |
| ItemExpr* pf = packingFactorTree_; |
| packingFactorTree_ = NULL; |
| return pf; |
| } |
| |
| ItemExpr* Pack::removePackingExprTree() |
| { |
| ItemExpr* pe = packingExprTree_; |
| packingExprTree_ = NULL; |
| return pe; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // Pack::getText() |
| // ----------------------------------------------------------------------- |
| const NAString Pack::getText() const |
| { |
| return "PACK"; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // Pack::topHash() |
| // ----------------------------------------------------------------------- |
| HashValue Pack::topHash() |
| { |
| // The base class's topHash deals with inputs/outputs and operator type. |
| HashValue result = RelExpr::topHash(); |
| |
| // Packing factor and packing expression are the differentiating factors. |
| result ^= packingFactor(); |
| result ^= packingExpr(); |
| result ^= requiredOrder(); |
| |
| return result; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // Pack::duplicateMatch() |
| // ----------------------------------------------------------------------- |
| NABoolean Pack::duplicateMatch(const RelExpr& other) const |
| { |
| // Assume optimizer already matches inputs/outputs in Group Attributes. |
| |
| // Base class checks for operator type, predicates and children. |
| if(NOT RelExpr::duplicateMatch(other)) return FALSE; |
| |
| // Base class implementation already makes sure other is a Pack node. |
| Pack& otherPack = (Pack &) other; |
| |
| // If the required order keys are not the same |
| // then the nodes are not identical |
| // |
| if (!(requiredOrder() == otherPack.requiredOrder())) |
| return FALSE; |
| |
| // Packing factor is the only remaining thing to check. |
| return (packingFactor_ == otherPack.packingFactor() AND |
| packingExpr_ == otherPack.packingExpr()); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // Pack::copyTopNode() |
| // ----------------------------------------------------------------------- |
| RelExpr* Pack::copyTopNode(RelExpr* derivedNode, CollHeap* outHeap) |
| { |
| Pack* result; |
| |
| // This the real node we want to copy. Construct a new Pack node. |
| if(derivedNode == NULL) |
| { |
| result = new (outHeap) Pack (packingFactorLong(),NULL,NULL,outHeap); |
| result->packingFactor() = packingFactor(); |
| result->packingExpr() = packingExpr(); |
| //result->setRequiredOrder(requiredOrder()); |
| result->requiredOrder() = requiredOrder(); |
| result->setFirstNRows(getFirstNRows()); |
| } |
| else |
| // --------------------------------------------------------------------- |
| // The real node we want to copy is of a derived class. The duplicate |
| // has already been made and store in derived node. All I need to do is |
| // to copy the members stored with this base class. |
| // --------------------------------------------------------------------- |
| { |
| result = (Pack *) derivedNode; |
| result->packingFactorLong() = packingFactorLong(); |
| result->packingFactor() = packingFactor(); |
| result->packingExpr() = packingExpr(); |
| |
| result->requiredOrder() = requiredOrder(); |
| result->setFirstNRows(getFirstNRows()); |
| } |
| |
| // Call base class to make copies of its own data members. |
| return RelExpr::copyTopNode(result,outHeap); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // Pack::getPotentialOutputValues() |
| // ----------------------------------------------------------------------- |
| void Pack::getPotentialOutputValues(ValueIdSet& outputValues) const |
| { |
| // Just the outputs of the packing expression. |
| outputValues.clear(); |
| outputValues.insertList(packingExpr_); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // Pack::getNonPackedExpr() returns the non-packed sub-expressions of the |
| // packing expression. |
| // ----------------------------------------------------------------------- |
| void Pack::getNonPackedExpr(ValueIdSet& vidset) |
| { |
| for(CollIndex i = 0; i < packingExpr().entries(); i++) |
| { |
| ItemExpr* packItem = packingExpr().at(i).getItemExpr(); |
| vidset.insert(packItem->child(0)->getValueId()); |
| } |
| } |
| |
| // ----------------------------------------------------------------------- |
| // Pack::pushdownCoveredExpr() needs to add the sub-expressions of the |
| // packing expression to nonPredExprOnOperator and then make use of the |
| // default implementation. It is expected in the first phase, nothing |
| // can be pushed down though. |
| // ----------------------------------------------------------------------- |
| void Pack::pushdownCoveredExpr(const ValueIdSet& outputExpr, |
| const ValueIdSet& newExternalInputs, |
| ValueIdSet& predOnOperator, |
| const ValueIdSet* nonPredExprOnOperator, |
| Lng32 childId) |
| { |
| ValueIdSet exprNeededByOperator; |
| getNonPackedExpr(exprNeededByOperator); |
| if (nonPredExprOnOperator) |
| exprNeededByOperator += *nonPredExprOnOperator; |
| exprNeededByOperator.insertList(requiredOrder()); |
| |
| RelExpr::pushdownCoveredExpr(outputExpr, |
| newExternalInputs, |
| predOnOperator, |
| &exprNeededByOperator, |
| childId); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // Pack::addLocalExpr() adds the packing expressions to be displayed by |
| // the GUI debugger. |
| // ----------------------------------------------------------------------- |
| void Pack::addLocalExpr(LIST(ExprNode*)& xlist, |
| LIST(NAString)& llist) const |
| { |
| if(packingExprTree_ != NULL) |
| { |
| xlist.insert(packingExprTree_); |
| llist.insert("pack_expr_tree"); |
| } |
| |
| if (requiredOrder().entries() > 0) { |
| xlist.insert(requiredOrder().rebuildExprTree(ITM_ITEM_LIST)); |
| llist.insert("required_order"); |
| } |
| |
| if(NOT packingExpr_.isEmpty()) |
| { |
| xlist.insert(packingExpr_.rebuildExprTree()); |
| llist.insert("pack_expr"); |
| } |
| |
| RelExpr::addLocalExpr(xlist,llist); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // methods for class PhyPack |
| // ----------------------------------------------------------------------- |
| |
| // Destructor. |
| PhyPack::~PhyPack() |
| { |
| } |
| |
| // ----------------------------------------------------------------------- |
| // PhyPack::copyTopNode() |
| // ----------------------------------------------------------------------- |
| RelExpr* PhyPack::copyTopNode(RelExpr* derivedNode, CollHeap* outHeap) |
| { |
| PhyPack* result; |
| |
| // This the real node we want to copy. Construct a new PhyPack node. |
| if(derivedNode == NULL) |
| { |
| result = new (outHeap) PhyPack (0,NULL,outHeap); |
| } |
| else |
| // --------------------------------------------------------------------- |
| // The real node we want to copy is of a derived class. The duplicate |
| // has already been made and store in derived node. All I need to do is |
| // to copy the members stored with this base class. |
| // --------------------------------------------------------------------- |
| { |
| result = (PhyPack *) derivedNode; |
| } |
| |
| // Tell base class to copy its members. PhyPack has no added members. |
| return Pack::copyTopNode(result,outHeap); |
| } |
| |
| |
| // ----------------------------------------------------------------------- |
| // methods for class Rowset |
| // ----------------------------------------------------------------------- |
| // Constructor |
| Rowset::Rowset(ItemExpr *inputHostvars, ItemExpr *indexExpr, |
| ItemExpr *sizeExpr, RelExpr * childExpr, CollHeap* oHeap) |
| : RelExpr(REL_ROWSET,childExpr,NULL,oHeap), |
| inputHostvars_(inputHostvars), |
| indexExpr_(indexExpr), |
| sizeExpr_(sizeExpr) |
| { |
| setNonCacheable(); |
| } // Rowset::Rowset() |
| |
| // Destructor. |
| Rowset::~Rowset() |
| { |
| } // Rowset::~Rowset() |
| |
| RelExpr * Rowset::copyTopNode(RelExpr *derivedNode, |
| CollHeap* oHeap) |
| { |
| Rowset *result; |
| |
| if (derivedNode == NULL) |
| result = new (oHeap) Rowset(inputHostvars_, indexExpr_, sizeExpr_, NULL, oHeap); |
| else { |
| result = (Rowset *) derivedNode; |
| } |
| |
| return RelExpr::copyTopNode(result,oHeap); |
| } // Rowset::copyTopNode() |
| |
| Int32 Rowset::getArity() const |
| { |
| return 0; // This is a leaf node |
| } // Rowset::getArity() |
| |
| |
| const NAString Rowset::getText() const |
| { |
| NAString result("RowSet",CmpCommon::statementHeap()); |
| |
| if (sizeExpr_) { |
| if (sizeExpr_->getOperatorType() == ITM_CONSTANT) { |
| char str[TEXT_DISPLAY_LENGTH]; |
| sprintf(str, " " PF64, |
| ((ConstValue *)sizeExpr_)->getExactNumericValue()); |
| result += str; |
| } else if (sizeExpr_->getOperatorType() == ITM_HOSTVAR) |
| result += " " + ((HostVar *)sizeExpr_)->getName(); |
| else |
| result += " ??"; |
| } |
| |
| result += " ("; |
| for (ItemExpr *hostVarTree = inputHostvars_; |
| hostVarTree != NULL; |
| hostVarTree = hostVarTree->child(1)) { |
| if (inputHostvars_ != hostVarTree) |
| result += ", "; |
| |
| HostVar *hostVar = (HostVar *)hostVarTree->getChild(0); |
| result += hostVar->getName(); |
| } |
| result += ")"; |
| |
| if (indexExpr_) |
| result += ("KEY BY " + |
| ((ColReference *)indexExpr_)->getColRefNameObj().getColName()); |
| |
| return result; |
| } |
| |
| // returns the name of the exposed index of the Rowset |
| const NAString Rowset::getIndexName() const |
| { |
| // A hack to check if the Rowset has an index expression |
| NAString result("",CmpCommon::statementHeap()); |
| |
| if (indexExpr_) |
| result += ((ColReference *)indexExpr_)->getColRefNameObj().getColName(); |
| return(result); |
| } |
| |
| // ----------------------------------------------------------------------- |
| // methods for class Rowset |
| // ----------------------------------------------------------------------- |
| // Constructor |
| RowsetRowwise::RowsetRowwise(RelExpr * childExpr, |
| CollHeap* oHeap) |
| : Rowset(NULL, NULL, NULL, childExpr, oHeap) |
| { |
| } // RowsetRowwise::RowsetRowwise() |
| |
| RelExpr * RowsetRowwise::copyTopNode(RelExpr *derivedNode, |
| CollHeap* oHeap) |
| { |
| Rowset *result; |
| |
| if (derivedNode == NULL) |
| result = new (oHeap) RowsetRowwise(NULL, oHeap); |
| else { |
| result = (RowsetRowwise *) derivedNode; |
| } |
| |
| return Rowset::copyTopNode(result,oHeap); |
| } // RowsetRowwise::copyTopNode() |
| |
| const NAString RowsetRowwise::getText() const |
| { |
| NAString result("RowSet Rowwise",CmpCommon::statementHeap()); |
| |
| return result; |
| } |
| |
| Int32 RowsetRowwise::getArity() const |
| { |
| return 1; |
| } // Rowset::getArity() |
| |
| RowsetFor::RowsetFor(RelExpr *child, |
| ItemExpr *inputSizeExpr, |
| ItemExpr *outputSizeExpr, |
| ItemExpr *indexExpr, |
| ItemExpr *maxSizeExpr, |
| ItemExpr *maxInputRowlen, |
| ItemExpr *rwrsBuffer, |
| ItemExpr *partnNum, |
| CollHeap *oHeap) |
| : RelExpr(REL_ROWSETFOR,child,NULL,oHeap), |
| inputSizeExpr_(inputSizeExpr), |
| outputSizeExpr_(outputSizeExpr), |
| indexExpr_(indexExpr), |
| maxSizeExpr_(maxSizeExpr), |
| maxInputRowlen_(maxInputRowlen), |
| rwrsBuffer_(rwrsBuffer), |
| partnNum_(partnNum), |
| rowwiseRowset_(FALSE), |
| packedFormat_(FALSE), |
| compressed_(FALSE), |
| dcompressInMaster_(FALSE), |
| compressInMaster_(FALSE), |
| partnNumInBuffer_(FALSE) |
| { |
| setNonCacheable(); |
| } |
| |
| // Destructor. |
| RowsetFor::~RowsetFor() |
| { |
| } |
| |
| RelExpr * RowsetFor::copyTopNode(RelExpr *derivedNode, |
| CollHeap* oHeap) |
| { |
| RowsetFor *result; |
| |
| if (derivedNode == NULL) |
| result = new (oHeap) RowsetFor(NULL, inputSizeExpr_, outputSizeExpr_, |
| indexExpr_, |
| maxSizeExpr_, maxInputRowlen_, |
| rwrsBuffer_, partnNum_, oHeap); |
| else |
| result = (RowsetFor *) derivedNode; |
| |
| result->rowwiseRowset_ = rowwiseRowset_; |
| result->setBufferAttributes(packedFormat_, |
| compressed_, dcompressInMaster_, |
| compressInMaster_, partnNumInBuffer_); |
| |
| return RelExpr::copyTopNode(result,oHeap); |
| } |
| |
| Int32 RowsetFor::getArity() const |
| { |
| return 1; |
| } // RowsetFor::getArity() |
| |
| const NAString RowsetFor::getText() const |
| { |
| NAString result("RowSetFor ", CmpCommon::statementHeap()); |
| |
| if (inputSizeExpr_) { |
| if (inputSizeExpr_->getOperatorType() == ITM_CONSTANT) { |
| char str[TEXT_DISPLAY_LENGTH]; |
| sprintf(str, PF64, |
| ((ConstValue *)inputSizeExpr_)->getExactNumericValue()); |
| result += "INPUT SIZE "; |
| result += str; |
| } else if (inputSizeExpr_->getOperatorType() == ITM_HOSTVAR) |
| result += "INPUT SIZE " + ((HostVar *)inputSizeExpr_)->getName(); |
| else |
| result += "INPUT SIZE ??"; |
| |
| if (outputSizeExpr_ || indexExpr_) |
| result += ","; |
| } |
| |
| if (outputSizeExpr_) { |
| if (outputSizeExpr_->getOperatorType() == ITM_CONSTANT) { |
| char str[TEXT_DISPLAY_LENGTH]; |
| sprintf(str, PF64, |
| ((ConstValue *)outputSizeExpr_)->getExactNumericValue()); |
| result += "OUTPUT SIZE "; |
| result += str; |
| } else if (outputSizeExpr_->getOperatorType() == ITM_HOSTVAR) |
| result += "OUTPUT SIZE " + ((HostVar *)outputSizeExpr_)->getName(); |
| else |
| result += "OUTPUT SIZE ??"; |
| |
| if (indexExpr_) |
| result += ","; |
| |
| } |
| |
| if (indexExpr_) |
| result += ("KEY BY " + |
| ((ColReference *)indexExpr_)->getColRefNameObj().getColName()); |
| |
| return result; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // methods for class RowsetInto |
| // ----------------------------------------------------------------------- |
| // Constructor |
| RowsetInto::RowsetInto(RelExpr *child, ItemExpr *outputHostvars, |
| ItemExpr *sizeExpr, CollHeap* oHeap) |
| : RelExpr(REL_ROWSET_INTO,child,NULL,oHeap), |
| outputHostvars_(outputHostvars), |
| sizeExpr_(sizeExpr), |
| requiredOrderTree_(NULL) |
| { |
| setNonCacheable(); |
| requiredOrder_.clear(); |
| } // RowsetInto::RowsetInto() |
| |
| // Destructor. |
| RowsetInto::~RowsetInto() |
| { |
| } // RowsetInto::~RowsetInto() |
| |
| RelExpr * RowsetInto::copyTopNode(RelExpr *derivedNode, |
| CollHeap* oHeap) |
| { |
| RowsetInto *result; |
| |
| if (derivedNode == NULL) |
| result = new (oHeap) RowsetInto(NULL, outputHostvars_, sizeExpr_, oHeap); |
| else |
| result = (RowsetInto *) derivedNode; |
| |
| return RelExpr::copyTopNode(result,oHeap); |
| } // RowsetInto::copyTopNode() |
| |
| Int32 RowsetInto::getArity() const |
| { |
| return 1; // This select-list root node |
| } // RowsetInto::getArity() |
| |
| |
| const NAString RowsetInto::getText() const |
| { |
| NAString result("RowsetINTO",CmpCommon::statementHeap()); |
| |
| if (sizeExpr_) { |
| if (sizeExpr_->getOperatorType() == ITM_CONSTANT) { |
| char str[TEXT_DISPLAY_LENGTH]; |
| sprintf(str, " " PF64 , |
| ((ConstValue *)sizeExpr_)->getExactNumericValue()); |
| result += str; |
| } else if (sizeExpr_->getOperatorType() == ITM_HOSTVAR) |
| result += " " + ((HostVar *)sizeExpr_)->getName(); |
| else |
| result += " ??"; |
| } |
| |
| result += " ("; |
| for (ItemExpr *hostVarTree = outputHostvars_; |
| hostVarTree != NULL; |
| hostVarTree = hostVarTree->child(1)) { |
| if (outputHostvars_ != hostVarTree) |
| result += ", "; |
| |
| HostVar *hostVar = (HostVar *)hostVarTree->getChild(0); |
| result += hostVar->getName(); |
| } |
| result += ")"; |
| |
| return result; |
| } |
| |
| |
| NABoolean RelExpr::treeContainsEspExchange() |
| { |
| Lng32 nc = getArity(); |
| if (nc > 0) |
| { |
| if ((getOperatorType() == REL_EXCHANGE) && |
| (child(0)->castToRelExpr()->getPhysicalProperty()->getPlanExecutionLocation() |
| != EXECUTE_IN_DP2)) |
| { |
| return TRUE; |
| } |
| |
| for (Lng32 i = 0; i < nc; i++) |
| { |
| if (child(i)->treeContainsEspExchange()) |
| return TRUE; |
| } |
| } |
| return FALSE; |
| } |
| |
| NABoolean Exchange::areProbesHashed(const ValueIdSet pkey) |
| { |
| return getGroupAttr()->getCharacteristicInputs().contains(pkey); |
| } |
| |
| |
| void Exchange::computeBufferLength(const Context *myContext, |
| const CostScalar &numConsumers, |
| const CostScalar &numProducers, |
| CostScalar &upMessageBufferLength, |
| CostScalar &downMessageBufferLength) |
| { |
| |
| CostScalar numDownBuffers = (Int32) ActiveSchemaDB()->getDefaults().getAsULong |
| (GEN_SNDT_NUM_BUFFERS); |
| CostScalar numUpBuffers = (Int32) ActiveSchemaDB()->getDefaults().getAsULong |
| (GEN_SNDB_NUM_BUFFERS); |
| CostScalar maxOutDegree = MAXOF(numConsumers, numProducers); |
| |
| CostScalar upSizeOverride = ActiveSchemaDB()->getDefaults().getAsLong |
| (GEN_SNDT_BUFFER_SIZE_UP); |
| |
| // The adjustment is a fudge factor to improve scalability by |
| // reducing the buffer size |
| // "penalty" when the number of connections is high due |
| //to a high degree of parallelism. |
| // The net result is to increase the memory "floor" and "ceiling" |
| // (that are base d on the |
| // number of connections) by up to fourfold. |
| //Too high a ceiling can cause memory pressure, |
| // a high level of paging activity, etc., |
| //while too low a ceiling can cause a large |
| // number of IPC messages and dispatches, and a |
| // resultant increase in path lengt h. |
| // The adjustment attempts to strike a balance between |
| // the two opposing clusters of performance factors. |
| |
| CostScalar adjMaxNumConnections = |
| maxOutDegree < 32 || upSizeOverride == 1 || upSizeOverride > 2 ? maxOutDegree : |
| maxOutDegree < 64 ? 32 : |
| maxOutDegree < 128 ? 40 : |
| maxOutDegree < 256 ? 50 : |
| maxOutDegree < 512 ? 64 : 70; |
| |
| CostScalar overhead = CostScalar(50); |
| |
| // compute numProbes, probeSize, cardinality, outputSize |
| CostScalar downRecordLength = getGroupAttr()-> |
| getCharacteristicInputs().getRowLength(); |
| |
| CostScalar upRecordLength = getGroupAttr()-> |
| getCharacteristicOutputs().getRowLength(); |
| |
| const CostScalar & numOfProbes = |
| ( myContext->getInputLogProp()->getResultCardinality() ).minCsOne(); |
| |
| // use no more than 50 KB and try to send all rows down in a single message |
| CostScalar reasonableBufferSpace1 = |
| CostScalar(50000) / (maxOutDegree * numDownBuffers); |
| |
| reasonableBufferSpace1 = |
| MINOF(reasonableBufferSpace1, |
| (downRecordLength + overhead) * numOfProbes); |
| |
| const EstLogPropSharedPtr inputLP = myContext->getInputLogProp(); |
| CostScalar numRowsUp = child(0).outputLogProp(inputLP)-> |
| getResultCardinality(); |
| |
| const PartitioningFunction* const parentPartFunc = |
| myContext->getPlan()->getPhysicalProperty()->getPartitioningFunction(); |
| |
| if (parentPartFunc->isAReplicateViaBroadcastPartitioningFunction()) |
| numRowsUp = numRowsUp * numConsumers; |
| |
| // check for an overriding define for the buffer size |
| CostScalar downSizeOverride = ActiveSchemaDB()->getDefaults().getAsLong |
| (GEN_SNDT_BUFFER_SIZE_DOWN); |
| |
| if (downSizeOverride.isGreaterThanZero()) |
| reasonableBufferSpace1 = downSizeOverride; |
| |
| // we MUST be able to fit at least one row into a buffer |
| |
| CostScalar controlAppendedLength= ComTdbSendTop::minSendBufferSize( |
| (Lng32)downRecordLength.getValue()); |
| downMessageBufferLength = |
| MAXOF(controlAppendedLength, |
| reasonableBufferSpace1); |
| |
| // Total size of output buffer that needs to be sent to the parent. |
| CostScalar totalBufferSize = upRecordLength * numRowsUp; |
| |
| // Divide this by number of connections to get total buffer per connection. |
| CostScalar bufferSizePerConnection = totalBufferSize / adjMaxNumConnections; |
| |
| // Aim for a situation where atleast 80 messages are sent per connection. |
| CostScalar reasonableBufferSpace2 = |
| bufferSizePerConnection / ActiveSchemaDB() |
| ->getDefaults().getAsLong(GEN_EXCHANGE_MSG_COUNT); |
| |
| // Now Esp has numUpBuffers of size reasonableBufferSpace2 per |
| // each stream (connection), so total memory to be allocated |
| // in this Esp would be: |
| // reasonableBufferSpace2 * numUpBuffers * maxOutDegree. |
| // We need to apply ceiling and floor to this memory i.e.: |
| // 4MB > reasonableBufferSpace2 * numUpBuffers * maxOutDegree > 50KB. |
| // OR divide both ceiling and floor by numUpBuffers * maxOutDegree. |
| Int32 maxMemKB = ActiveSchemaDB() |
| ->getDefaults().getAsLong(GEN_EXCHANGE_MAX_MEM_IN_KB); |
| if (maxMemKB <= 0) maxMemKB = 4000; // 4MB if not set or negative |
| CostScalar maxMem1 = maxMemKB * 1000; |
| CostScalar maxMem2 = maxMemKB * 4000; |
| CostScalar ceiling = MINOF(maxMem1 / |
| (numUpBuffers * adjMaxNumConnections), |
| maxMem2 / |
| (numUpBuffers * maxOutDegree)); |
| |
| CostScalar floor = MINOF(CostScalar(50000) / |
| (numUpBuffers * adjMaxNumConnections), |
| CostScalar(200000) / |
| (numUpBuffers * maxOutDegree)); |
| |
| // Apply the floor. |
| reasonableBufferSpace2 = |
| MAXOF(floor, reasonableBufferSpace2); |
| |
| // Apply the ceiling. |
| reasonableBufferSpace2 = |
| MINOF(ceiling, reasonableBufferSpace2); |
| |
| // Make sure the floor is at least 5K to avoid performance problem. |
| reasonableBufferSpace2 = |
| MAXOF(CostScalar(5000), reasonableBufferSpace2); |
| |
| // Make sure that it is at most 31k-1356 |
| reasonableBufferSpace2 = |
| MINOF( reasonableBufferSpace2, 31 * 1024 - 1356); |
| |
| if (upSizeOverride.isGreaterThanZero()) |
| reasonableBufferSpace2 = upSizeOverride; |
| |
| // we MUST be able to fit at least one row into a buffer |
| controlAppendedLength = ComTdbSendTop::minReceiveBufferSize( |
| (Lng32) (upRecordLength.getValue()) ); |
| upMessageBufferLength = |
| MAXOF( controlAppendedLength, reasonableBufferSpace2); |
| |
| // convert Buffers to kilo bytes |
| upMessageBufferLength_= upMessageBufferLength = upMessageBufferLength/CostScalar(1024); |
| downMessageBufferLength_ = downMessageBufferLength = downMessageBufferLength/ CostScalar(1024); |
| } // Exchange::computeBufferLength() |
| |
| |
| ////////////////////////////////////////////////////////////////////// |
| // Class pcgEspFragment related methods |
| ////////////////////////////////////////////////////////////////////// |
| |
| void pcgEspFragment::addChild(Exchange* esp) |
| { |
| CollIndex newIndex = childEsps_.entries(); |
| childEsps_.insertAt(newIndex, esp); |
| } |
| |
| // Verify that the newly added exchange node at the end of childEsps_[] |
| // is compatible with others. |
| // Note that preCodeGen traversal the query tree via a depth-first |
| // search. Each time the leave exchange node in this fragment is encountered, |
| // this method is called, The order of visit to the child exchanges |
| // is from left to right. |
| // |
| NABoolean pcgEspFragment::tryToReduceDoP() |
| { |
| float threshold; |
| ActiveSchemaDB()-> |
| getDefaults().getFloat(DOP_REDUCTION_ROWCOUNT_THRESHOLD, threshold); |
| |
| if ( threshold == 0.0 || getTotaleRows() >= threshold ) { |
| return FALSE; |
| } |
| |
| |
| // Defensive programming. Nothing to verify when there is no child esp. |
| if ( childEsps_.entries() == 0 ) return FALSE; |
| |
| |
| // Get the ptr to last exchange |
| CollIndex lastIdx = childEsps_.entries()-1; |
| Exchange* xch = childEsps_[lastIdx]; |
| |
| // |
| // No dop reduction for Parallel Extract |
| // |
| if ( xch->getExtractProducerFlag() || xch->getExtractConsumerFlag() ) |
| return FALSE; |
| |
| PartitioningFunction* partFunc = |
| xch->getPhysicalProperty()->getPartitioningFunction(); |
| |
| // |
| // If xch's count of partitions is less than newDoP, bail out |
| // |
| Lng32 newDoP = CURRSTMT_OPTDEFAULTS->getDefaultDegreeOfParallelism(); |
| if ( partFunc->getCountOfPartitions() < newDoP ) |
| return FALSE; |
| |
| |
| // Do not reduce dop if this exchange is a PA, except if it is |
| // the right child of a TYPE2 join, using replicate-no-broadcast. |
| // An extra exchange is needed otherwise to bridge the |
| // new DoP and all original partitions, unless the newDoP is a factor |
| // of #part of the hash2 partfunc for the PA node. |
| // |
| if ( xch->child(0)->getPhysicalProperty() |
| ->getPlanExecutionLocation() == EXECUTE_IN_DP2) |
| { |
| if ( partFunc->isAHash2PartitioningFunction() ) { |
| if ( partFunc->getCountOfPartitions() % newDoP != 0 ) |
| return FALSE; |
| } else |
| if (!partFunc->isAReplicateNoBroadcastPartitioningFunction()) |
| return FALSE; |
| } |
| |
| |
| Lng32 suggestedNewDoP = newDoP; |
| |
| // |
| // Make a copy of the part func as the scaling method can side effect. |
| // |
| PartitioningFunction* partFuncCopy = |
| xch->getPhysicalProperty()->getPartitioningFunction()->copy(); |
| |
| PartitioningFunction* newPF = |
| partFuncCopy->scaleNumberOfPartitions(suggestedNewDoP); |
| |
| // |
| // If the part func can not scale to newDoP, bail out. |
| // |
| if ( suggestedNewDoP != newDoP ) |
| return FALSE; |
| |
| // |
| // Find a common dop for all child esps in the fragment first. |
| // A common dop is one associated with 1st esp that has non- |
| // broadcasting part func. All other child esps with non-broadcasting |
| // partFunc should be use the "common dop". This is true prior to |
| // the dop reduction attempt. If it is already found (commonDoP_ > 0), |
| // just use it. |
| // |
| if ( commonDoP_ == 0 && |
| partFuncCopy->isAReplicationPartitioningFunction() == FALSE ) |
| commonDoP_ = partFuncCopy->getCountOfPartitions(); |
| |
| |
| // If the dop at child exchange A can be reduced but not at |
| // child exchange B, we may end up with in an inconsistent state. |
| // The following code detects it. |
| if ( commonDoP_ > 0 ) { |
| |
| if ( |
| partFuncCopy->isAReplicationPartitioningFunction() == FALSE && |
| partFuncCopy->getCountOfPartitions() != commonDoP_ |
| ) |
| return FALSE; |
| } |
| |
| // |
| // The new dop is acceptable. |
| // |
| newDoP_ = newDoP; |
| setValid(TRUE); |
| |
| return TRUE; |
| } |
| |
| void pcgEspFragment::invalidate() |
| { |
| setValid(FALSE); |
| |
| //for ( CollIndex i=0; i<childEsps_.entries(); i++ ) { |
| // childEsps_[i]->getEspFragPCG()->invalidate(); |
| //} |
| } |
| |
| void pcgEspFragment::adjustDoP(Lng32 newDop) |
| { |
| for ( CollIndex i=0; i<childEsps_.entries(); i++ ) { |
| |
| Exchange* xch = childEsps_[i]; |
| |
| // Recursively adjust the dop for my child fragments. |
| // Each exchange will have its own pcgEspFragment to work with. |
| xch->doDopReduction(); |
| } |
| } |
| |
| void RelExpr::doDopReduction() |
| { |
| |
| Int32 nc = getArity(); |
| |
| for (Lng32 i = 0; i < nc; i++) |
| { |
| child(i)->doDopReduction(); |
| } |
| } |
| |
| void Exchange::doDopReduction() |
| { |
| // |
| // Once this method is called for the top most Exchange, we can |
| // recursively call the same method for all the esp fragments via |
| // the pointers (to esps) saved in the pcgEsgFragment objects. |
| // |
| Lng32 newDop = getEspFragPCG()->getNewDop(); |
| |
| if ( getEspFragPCG()->isValid() ) { |
| |
| // Adjust the partfunc for all nodes within the fragment, starting |
| // from my child and down to every bottom-defining exchanges. |
| child(0)->doDopReductionWithinFragment(newDop); |
| } |
| |
| // Next recursively call the same method for all fragments below me. |
| getEspFragPCG()->adjustDoP(newDop); |
| } |
| |
| void RelExpr::doDopReductionWithinFragment(Lng32 newDoP) |
| { |
| adjustTopPartFunc(newDoP); |
| |
| if ( getOperatorType() == REL_EXCHANGE ) { |
| |
| // |
| // Need to adjust the Logical part of the part func if |
| // my child's part func is a LogPhy partfunc. |
| // |
| if ( child(0)->getPhysicalProperty()->getPlanExecutionLocation() |
| == EXECUTE_IN_DP2) |
| { |
| PartitioningFunction *pf = |
| child(0)->getPhysicalProperty()->getPartitioningFunction(); |
| |
| |
| if ( pf->isALogPhysPartitioningFunction() ) |
| child(0)->adjustTopPartFunc(newDoP); |
| } |
| return; |
| } |
| |
| Int32 nc = getArity(); |
| |
| for (Lng32 i = 0; i < nc; i++) |
| { |
| child(i)->doDopReductionWithinFragment(newDoP); |
| } |
| } |
| |
| |
| void RelExpr::adjustTopPartFunc(Lng32 newDop) |
| { |
| ((PhysicalProperty*)getPhysicalProperty())->scaleNumberOfPartitions(newDop); |
| setDopReduced(TRUE); |
| } |
| |
| // Required Resource Estimate Methods - Begin |
| void RelExpr::computeRequiredResources(RequiredResources & reqResources, EstLogPropSharedPtr & inLP) |
| { |
| Int32 nc = getArity(); |
| |
| for (Lng32 i = 0; i < nc; i++) |
| { |
| if (child(i)) |
| child(i)->computeRequiredResources(reqResources, inLP); |
| else |
| child(i).getLogExpr()->computeRequiredResources(reqResources, inLP); |
| } |
| |
| computeMyRequiredResources(reqResources, inLP); |
| } |
| |
| void Join::computeRequiredResources(RequiredResources & reqResources, EstLogPropSharedPtr & inLP) |
| { |
| Int32 nc = getArity(); |
| |
| if (child(0)) |
| child(0)->computeRequiredResources(reqResources, inLP); |
| else |
| child(0).getLogExpr()->computeRequiredResources(reqResources, inLP); |
| |
| EstLogPropSharedPtr inputForRight = inLP; |
| |
| EstLogPropSharedPtr leftOutput = |
| child(0).getGroupAttr()->outputLogProp(inLP); |
| |
| if(isTSJ()) |
| { |
| inputForRight = leftOutput; |
| } |
| |
| if (child(1)) |
| child(1)->computeRequiredResources(reqResources, inputForRight); |
| else |
| child(1).getLogExpr()->computeRequiredResources(reqResources, inputForRight); |
| |
| |
| computeMyRequiredResources(reqResources, inLP); |
| } |
| |
| void RequiredResources::accumulate(CostScalar memRsrcs, |
| CostScalar cpuRsrcs, |
| CostScalar dataAccessCost, |
| CostScalar maxCard) |
| { |
| memoryResources_ += memRsrcs; |
| cpuResources_ += cpuRsrcs; |
| dataAccessCost_ += dataAccessCost; |
| |
| if(maxOperMemReq_ < memRsrcs) |
| maxOperMemReq_ = memRsrcs; |
| |
| if(maxOperCPUReq_ < cpuRsrcs) |
| maxOperCPUReq_ = cpuRsrcs; |
| |
| if(maxOperDataAccessCost_ < dataAccessCost) |
| maxOperDataAccessCost_ = dataAccessCost; |
| |
| if(maxMaxCardinality_ < maxCard) |
| maxMaxCardinality_ = maxCard; |
| } |
| |
| void RelExpr::computeMyRequiredResources(RequiredResources & reqResources, EstLogPropSharedPtr & inLP) |
| { |
| CostScalar cpuResourcesRequired = csZero; |
| CostScalar A = csOne; |
| CostScalar B (getDefaultAsDouble(WORK_UNIT_ESP_DATA_COPY_COST)); |
| |
| Int32 nc = getArity(); |
| |
| for (Lng32 i = 0; i < nc; i++) |
| { |
| GroupAttributes * childGroupAttr = child(i).getGroupAttr(); |
| CostScalar childCardinality = |
| childGroupAttr->outputLogProp(inLP)->getResultCardinality(); |
| CostScalar childRecordSize = childGroupAttr->getCharacteristicOutputs().getRowLength(); |
| |
| cpuResourcesRequired += |
| (A * childCardinality) + |
| (B * childCardinality * childRecordSize ); |
| } |
| |
| CostScalar myMaxCard = getGroupAttr()->getResultMaxCardinalityForInput(inLP); |
| |
| reqResources.accumulate(csZero, |
| cpuResourcesRequired, |
| csZero, |
| myMaxCard); |
| } |
| |
| void RelRoot::computeMyRequiredResources(RequiredResources & reqResources, EstLogPropSharedPtr & inLP) |
| { |
| |
| if (hasOrderBy()) |
| { |
| CostScalar memoryResourcesRequired = csZero; |
| |
| GroupAttributes * childGroupAttr = child(0).getGroupAttr(); |
| CostScalar childCardinality = |
| childGroupAttr->outputLogProp(inLP)->getResultCardinality(); |
| CostScalar childRecordSize = childGroupAttr->getCharacteristicOutputs().getRowLength(); |
| memoryResourcesRequired = (childCardinality * childRecordSize); |
| |
| reqResources.accumulate(memoryResourcesRequired, csZero, csZero); |
| } |
| |
| // add the cpu resources |
| RelExpr::computeMyRequiredResources(reqResources, inLP); |
| } |
| |
| void MultiJoin::computeMyRequiredResources(RequiredResources & reqResources, EstLogPropSharedPtr & inLP) |
| { |
| // get the subset analysis for this MultiJoin |
| JBBSubsetAnalysis * subsetAnalysis = getJBBSubset().getJBBSubsetAnalysis(); |
| |
| subsetAnalysis->computeRequiredResources(this,reqResources, inLP); |
| } |
| |
| |
| void Join::computeMyRequiredResources(RequiredResources & reqResources, EstLogPropSharedPtr & inLP) |
| { |
| CostScalar memoryResourcesRequired = csZero; |
| |
| // only get the max card for this join. The contribution from the children |
| // of this join is done inside Join::computeReequiredResource() where |
| // child(i)->computeRequiredResources() is called (i=0,1). These two calls |
| // will call ::computeMyRequiredResoruce() of the corresponding RelExpr. |
| // |
| GroupAttributes * myGroupAttr = getGroupAttr(); |
| |
| CostScalar myMaxCard = myGroupAttr->getResultMaxCardinalityForInput(inLP); |
| |
| reqResources.accumulate(csZero, csZero, csZero, myMaxCard); |
| |
| if(!isTSJ()) |
| { |
| GroupAttributes * innerChildGroupAttr = child(1).getGroupAttr(); |
| CostScalar innerChildCardinality = |
| innerChildGroupAttr->outputLogProp(inLP)->getResultCardinality(); |
| CostScalar innerChildRecordSize = innerChildGroupAttr->getCharacteristicOutputs().getRowLength(); |
| memoryResourcesRequired = (innerChildCardinality * innerChildRecordSize); |
| |
| reqResources.accumulate(memoryResourcesRequired, csZero, csZero); |
| |
| // add the cpu resources |
| RelExpr::computeMyRequiredResources(reqResources, inLP); |
| } |
| else{ |
| // isTSJ() == TRUE |
| CostScalar cpuResourcesRequired = csZero; |
| CostScalar A = csOne; |
| CostScalar B (getDefaultAsDouble(WORK_UNIT_ESP_DATA_COPY_COST)); |
| |
| Int32 nc = getArity(); |
| EstLogPropSharedPtr inputForChild = inLP; |
| |
| for (Lng32 i = 0; i < nc; i++) |
| { |
| GroupAttributes * childGroupAttr = child(i).getGroupAttr(); |
| CostScalar childCardinality = |
| childGroupAttr->outputLogProp(inputForChild)->getResultCardinality(); |
| CostScalar childRecordSize = childGroupAttr->getCharacteristicOutputs().getRowLength(); |
| |
| cpuResourcesRequired += (B * childCardinality * childRecordSize ); |
| |
| // do this only for the left child |
| if(i < 1) |
| cpuResourcesRequired += (A * childCardinality); |
| |
| inputForChild = child(i).getGroupAttr()->outputLogProp(inputForChild); |
| } |
| |
| reqResources.accumulate(csZero, |
| cpuResourcesRequired, |
| csZero); |
| } |
| } |
| |
| void GroupByAgg::computeMyRequiredResources(RequiredResources & reqResources, EstLogPropSharedPtr & inLP) |
| { |
| CostScalar memoryResourcesRequired = csZero; |
| |
| GroupAttributes * childGroupAttr = child(0).getGroupAttr(); |
| CostScalar childCardinality = |
| childGroupAttr->outputLogProp(inLP)->getResultCardinality(); |
| CostScalar childRecordSize = childGroupAttr->getCharacteristicOutputs().getRowLength(); |
| memoryResourcesRequired = (childCardinality * childRecordSize); |
| |
| reqResources.accumulate(memoryResourcesRequired, csZero, csZero); |
| |
| // add the cpu resources |
| RelExpr::computeMyRequiredResources(reqResources, inLP); |
| } |
| |
| void Scan::computeMyRequiredResources(RequiredResources & reqResources, EstLogPropSharedPtr & inLP) |
| { |
| if(!(QueryAnalysis::Instance() && |
| QueryAnalysis::Instance()->isAnalysisON())) |
| return; |
| |
| |
| //Get a handle to ASM |
| AppliedStatMan * appStatMan = QueryAnalysis::ASM(); |
| const TableAnalysis * tAnalysis = getTableDesc()->getTableAnalysis(); |
| CANodeId tableId = tAnalysis->getNodeAnalysis()->getId(); |
| |
| // Find index joins and index-only scans |
| addIndexInfo(); |
| |
| // Base table scan is one of the index only scans. |
| CostScalar cpuCostIndexOnlyScan = |
| computeCpuResourceForIndexOnlyScans(tableId); |
| |
| CostScalar cpuCostIndexJoinScan = |
| computeCpuResourceForIndexJoinScans(tableId); |
| |
| CostScalar cpuResourcesRequired = cpuCostIndexOnlyScan; |
| if ( getTableDesc()->getNATable()->isHbaseTable()) |
| { |
| if ( cpuCostIndexJoinScan < cpuResourcesRequired ) |
| cpuResourcesRequired = cpuCostIndexJoinScan; |
| } |
| |
| CostScalar dataAccessCost = tAnalysis->getFactTableNJAccessCost(); |
| if(dataAccessCost < 0) |
| { |
| CostScalar rowsToScan = appStatMan-> |
| getStatsForLocalPredsOnCKPOfJBBC(tableId)-> |
| getResultCardinality(); |
| |
| CostScalar numOfProbes(csZero); |
| |
| // skip this for fact table under nested join |
| dataAccessCost = |
| tAnalysis->computeDataAccessCostForTable(numOfProbes, rowsToScan); |
| } |
| |
| |
| CostScalar myMaxCard = getGroupAttr()->getResultMaxCardinalityForInput(inLP); |
| reqResources.accumulate(csZero, cpuResourcesRequired, |
| dataAccessCost, myMaxCard |
| ); |
| |
| } |
| // Required Resource Estimate Methods - End |
| |
| CostScalar |
| Scan::computeCpuResourceRequired(const CostScalar& rowsToScan, const CostScalar& rowSize) |
| { |
| CostScalar A = csOne; |
| CostScalar B (getDefaultAsDouble(WORK_UNIT_ESP_DATA_COPY_COST)); |
| |
| CostScalar cpuResourcesRequired = (B * rowsToScan * rowSize); |
| cpuResourcesRequired += (A * rowsToScan); |
| |
| return cpuResourcesRequired; |
| } |
| |
| CostScalar Scan::computeCpuResourceForIndexOnlyScans(CANodeId tableId) |
| { |
| // If index only scans are available, find the most |
| // promising index and compute the CPU resource for it. |
| |
| const SET(IndexProperty *)& indexOnlyScans = getIndexOnlyIndexes(); |
| |
| IndexProperty* smallestIndex = findSmallestIndex(indexOnlyScans); |
| |
| if ( !smallestIndex ) |
| return COSTSCALAR_MAX; |
| |
| IndexDesc* iDesc = smallestIndex->getIndexDesc(); |
| |
| const ValueIdList &ikeys = iDesc->getIndexKey(); |
| |
| AppliedStatMan * appStatMan = QueryAnalysis::ASM(); |
| |
| EstLogPropSharedPtr estLpropPtr = appStatMan-> |
| getStatsForLocalPredsOnPrefixOfColList(tableId, ikeys); |
| |
| if ( !(estLpropPtr.get()) ) |
| return COSTSCALAR_MAX; |
| |
| return computeCpuResourceRequired(estLpropPtr->getResultCardinality(), |
| iDesc->getRecordLength() |
| ); |
| } |
| |
| |
| CostScalar Scan::computeCpuResourceForIndexJoinScans(CANodeId tableId) |
| { |
| // If index scans are available, find the index with most promising and |
| // compute the CPU resource for it. |
| const LIST(ScanIndexInfo *)& scanIndexJoins = getPossibleIndexJoins(); |
| |
| if ( scanIndexJoins.entries() == 0 ) |
| return COSTSCALAR_MAX; |
| |
| IndexProperty* smallestIndex = findSmallestIndex(scanIndexJoins); |
| IndexDesc* iDesc = smallestIndex->getIndexDesc(); |
| |
| CostScalar rowsToScan; |
| return computeCpuResourceForIndexJoin(tableId, iDesc, iDesc->getIndexKey(), rowsToScan); |
| } |
| |
| CostScalar Scan::computeCpuResourceForIndexJoin(CANodeId tableId, IndexDesc* iDesc, |
| ValueIdSet& indexPredicates, |
| CostScalar& rowsToScan) |
| { |
| ValueIdList ikeysCovered; |
| |
| UInt32 sz = iDesc->getIndexKey().entries(); |
| for (CollIndex i=0; i<sz; i++) { |
| ValueId x = iDesc->getIndexKey()[i]; |
| if ( indexPredicates.containsAsEquiLocalPred(x) ) |
| ikeysCovered.insertAt(i, x); |
| else |
| break; |
| } |
| |
| return computeCpuResourceForIndexJoin(tableId, iDesc, ikeysCovered, rowsToScan); |
| } |
| |
| CostScalar |
| Scan::computeCpuResourceForIndexJoin(CANodeId tableId, IndexDesc* iDesc, |
| const ValueIdList& ikeys, CostScalar& rowsToScan) |
| { |
| AppliedStatMan * appStatMan = QueryAnalysis::ASM(); |
| |
| EstLogPropSharedPtr estLpropPtr = appStatMan-> |
| getStatsForLocalPredsOnPrefixOfColList(tableId, ikeys); |
| |
| if ( !(estLpropPtr.get()) ) { |
| rowsToScan = COSTSCALAR_MAX; |
| return COSTSCALAR_MAX; |
| } |
| |
| rowsToScan = estLpropPtr->getResultCardinality(); |
| CostScalar rowSize = iDesc->getRecordLength(); |
| |
| CostScalar cpuResourceForIndex = computeCpuResourceRequired(rowsToScan, rowSize); |
| |
| rowSize = getTableDesc()->getClusteringIndex()->getRecordLength(); |
| |
| CostScalar cpuResourceForBaseTable = computeCpuResourceRequired(rowsToScan, rowSize); |
| |
| return cpuResourceForIndex + cpuResourceForBaseTable; |
| } |
| |
| IndexProperty* Scan::findSmallestIndex(const SET(IndexProperty *)& indexes) const |
| { |
| CollIndex entries = indexes.entries(); |
| |
| if ( entries == 0 ) return NULL; |
| |
| IndexProperty* smallestIndex = indexes[0]; |
| |
| for (CollIndex i=1; i<entries; i++ ) { |
| IndexProperty* current = indexes[i]; |
| |
| if ( smallestIndex->compareIndexPromise(current) == LESS ) { |
| smallestIndex = current; |
| } |
| } |
| |
| return smallestIndex; |
| } |
| |
| IndexProperty* Scan::findSmallestIndex(const LIST(ScanIndexInfo *)& possibleIndexJoins) const |
| { |
| CollIndex entries = possibleIndexJoins_.entries(); |
| |
| if ( entries == 0 ) return NULL; |
| |
| IndexProperty* smallestIndex = |
| findSmallestIndex(possibleIndexJoins[0]->usableIndexes_); |
| |
| for (CollIndex i=1; i<entries; i++ ) { |
| |
| IndexProperty* current = |
| findSmallestIndex(possibleIndexJoins[i]->usableIndexes_); |
| |
| if ( smallestIndex->compareIndexPromise(current) == LESS ) { |
| smallestIndex = current; |
| } |
| } |
| |
| return smallestIndex; |
| } |
| |
| // This function checks if the passed RelExpr is a UDF rule created by a CQS |
| // (REL_FORCE_ANY_SCALAR_UDF). If not, then RelExpr::patternMatch() is called. |
| // If the CQS rule includes the UDF name this name is checked against the routine |
| // name of this physical isolated scalar UDF. If the CQS rule includes the action |
| // name, then this is checked against the action name of this physical isolated |
| // scalar UDF as well. The function returns TRUE if so, otherwise FALSE. |
| NABoolean PhysicalIsolatedScalarUDF::patternMatch(const RelExpr & other) const |
| { |
| // Check if CQS is a scalar UDF rule. |
| if (other.getOperatorType() == REL_FORCE_ANY_SCALAR_UDF) |
| { |
| UDFForceWildCard &w = (UDFForceWildCard &) other; |
| |
| // Check function name, if specified in UDFForceWildCard. |
| if (w.getFunctionName() != "") |
| { |
| QualifiedName funcName(w.getFunctionName(), 1 /* minimal 1 part name */); |
| |
| // Compare catalog, schema and udf parts separately, |
| // if they exist in the wildcard |
| const NAString& catName = funcName.getCatalogName(); |
| const NAString& schName = funcName.getSchemaName(); |
| const QualifiedName& x = getRoutineName(); |
| |
| if ((catName.length() > 0 && x.getCatalogName() != catName) || |
| (schName.length() > 0 && x.getSchemaName() != schName) || |
| x.getObjectName() != funcName.getObjectName()) |
| return FALSE; |
| } |
| // Check action name, if specified in UDFForceWildCard. |
| if (w.getActionName() != "") |
| { |
| NAString actionName = w.getActionName(); |
| |
| if (getActionNARoutine() && |
| getActionNARoutine()->getActionName()) |
| { |
| // Compare only object parts. Right now actions don't support catalogs and schemas. |
| // This is because action names can have a leading '$' as part of name. |
| const NAString& x = *(getActionNARoutine()->getActionName()); |
| |
| if (x != actionName) |
| return FALSE; |
| } |
| else return FALSE; |
| } |
| return TRUE; |
| } |
| else |
| return RelExpr::patternMatch(other); |
| } |
| |
| |
| const NAString RelExpr::getCascadesTraceInfoStr() |
| { |
| NAString result("RelExpr Cascades Trace Info:\n"); |
| result += " parent taskid: " + istring(getParentTaskId()) + "\n"; |
| result += " sub taskid: " + istring(getSubTaskId()) + "\n"; |
| result += " birth id: " + istring(getBirthId()) + "\n"; |
| result += " memo exprid: " + istring(memoExprId_) + "\n"; |
| result += " source memo exprid: " + istring(sourceMemoExprId_) + "\n"; |
| result += " source groupid: " + istring(sourceGroupId_) + "\n"; |
| char costLimitStr[50]; |
| sprintf(costLimitStr," cost limit %g\n", costLimit_); |
| result += costLimitStr; |
| return result; |
| } |
| |
| // remember the creator and source of this relexpr for cascades display gui |
| void RelExpr::setCascadesTraceInfo(RelExpr *src) |
| { |
| CascadesTask * currentTask = CURRSTMT_OPTDEFAULTS->getCurrentTask(); |
| if (currentTask) |
| { |
| // current task created this relexpr |
| parentTaskId_ = currentTask->getParentTaskId(); |
| stride_ = currentTask->getSubTaskId(); |
| |
| // remember time of my birth |
| birthId_ = CURRSTMT_OPTDEFAULTS->getTaskCount(); |
| |
| // remember my source |
| sourceGroupId_ = currentTask->getGroupId(); |
| if (src) |
| sourceMemoExprId_ = src->memoExprId_; |
| |
| // remember current task's context's CostLimit |
| Context * context = currentTask->getContext(); |
| if(context && context->getCostLimit()) |
| costLimit_ = context->getCostLimit()->getCachedValue(); |
| } |
| // get my MemoExprId and advance it |
| memoExprId_ = CURRSTMT_OPTDEFAULTS->updateGetMemoExprCount(); |
| } |
| |
| NABoolean Join::childNodeContainSkew( |
| CollIndex i, // IN: which child |
| const ValueIdSet& joinPreds, // IN: the join predicate |
| double threshold, // IN: the threshold |
| SkewedValueList** skList // OUT: the skew list |
| ) const |
| { |
| // Can not deal with multicolumn skew in this method. |
| if ( joinPreds.entries() != 1 ) |
| return FALSE; |
| |
| NABoolean statsExist; // a place holder |
| |
| Int32 skews = 0; |
| for(ValueId vid = joinPreds.init(); joinPreds.next(vid); joinPreds.advance(vid)) { |
| *skList = child(i).getGroupAttr()-> getSkewedValues(vid, threshold, |
| statsExist, |
| (*GLOBAL_EMPTY_INPUT_LOGPROP), |
| isLeftJoin()/* include skewed NULLs only for left outer join */ |
| ); |
| |
| if (*skList == NULL || (*skList)->entries() == 0) |
| break; |
| else |
| skews++; |
| } |
| |
| return ( skews == joinPreds.entries() ); |
| } |
| |
| |
| // |
| // Check if some join column is of a SQL type whose run-time |
| // implementation has a limitation for SB to work. |
| // |
| // return |
| // TRUE: no limitation |
| // FALSE: has limitation and SB should not be applied |
| // |
| NABoolean Join::singleColumnjoinPredOKforSB(ValueIdSet& joinPreds) |
| { |
| ValueId vId((CollIndex)0); joinPreds.next(vId); |
| |
| ItemExpr* iePtr = vId.getItemExpr(); |
| |
| if (iePtr->getOperatorType() == ITM_INSTANTIATE_NULL) { |
| iePtr = iePtr -> child(0); |
| } |
| |
| ValueIdSet vidSet; |
| |
| switch (iePtr->getOperatorType()) { |
| case ITM_EQUAL: // this case is used to handle char type when |
| // no VEG is formed for a char predicate, |
| // or joins involving subqueries. |
| case ITM_VEG_PREDICATE: |
| case ITM_VEG_REFERENCE: |
| |
| // We only care columns of type ITM_BASECOLUMN (columns belonging to |
| // base tables or table-valued stored procedures, see comment on class |
| // BaseColumn). |
| iePtr->findAll(ITM_BASECOLUMN, vidSet, TRUE, TRUE); |
| |
| // If no such columns can be found. Do not bother to continue further, |
| // as only base table columns have the potential to be big and skewed. |
| if ( vidSet.entries() == 0 ) |
| return FALSE; |
| |
| break; |
| |
| default: |
| return FALSE; |
| } |
| |
| ValueId colVid((CollIndex)0); vidSet.next(colVid); |
| |
| if ( !colVid.getType().isSkewBusterSupportedType() ) |
| return FALSE; |
| |
| // Additional test |
| if ( colVid.getType().getTypeQualifier() == NA_NUMERIC_TYPE && |
| colVid.getType().getTypeName() == LiteralNumeric ) { |
| |
| // Exact decimal numeric such as NUMERIC(18,15) can be handled, if |
| // all columns involved in join are of the exact same precision and |
| // and scale. The comparison ignores NULL attribute of the type (ALM 4953). |
| for(ValueId x = vidSet.init(); vidSet.next(x); vidSet.advance(x)) |
| { |
| if ( NOT ((NumericType&)(colVid.getType())).equalIgnoreNull(x.getType())) |
| return FALSE; |
| } |
| |
| return TRUE; |
| } else |
| if ( DFS2REC::isAnyCharacter(colVid.getType().getFSDatatype()) ) { |
| |
| if ( ((const CharType&)colVid.getType()).getStrCharLimit() > |
| (Lng32) CmpCommon::getDefaultNumeric(USTAT_MAX_CHAR_BOUNDARY_LEN) ) |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| |
| NABoolean Join::multiColumnjoinPredOKforSB(ValueIdSet& joinPreds) |
| { |
| for(ValueId x = joinPreds.init(); joinPreds.next(x); joinPreds.advance(x)) |
| { |
| ValueIdSet dummy(x); |
| if ( !singleColumnjoinPredOKforSB(dummy) ) |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| // The new way to capture MC skews. All such skews have been computed during |
| // update stats. |
| NABoolean Join::childNodeContainMultiColumnSkew( |
| CollIndex i, // IN: which child to work on |
| const ValueIdSet& joinPreds, // IN: the join predicate |
| double mc_threshold, // IN: multi-column threshold |
| Lng32 countOfPipelines, // IN: |
| SkewedValueList** skList // OUT: the skew list |
| ) |
| { |
| if (joinPreds.entries() <= 1) |
| return FALSE; |
| |
| const ColStatDescList& theColList = |
| child(i).outputLogProp((*GLOBAL_EMPTY_INPUT_LOGPROP))->colStats(); |
| |
| |
| ValueId col; |
| ValueIdSet lhsCols; |
| |
| CollIndex index = NULL_COLL_INDEX; |
| |
| const ValueIdSet& joiningCols = (i==0) ? |
| getEquiJoinExprFromChild0() : getEquiJoinExprFromChild1() ; |
| |
| for (col = joiningCols.init(); |
| joiningCols.next(col); |
| joiningCols.advance(col) ) |
| { |
| theColList.getColStatDescIndex(index, col); |
| if (index != NULL_COLL_INDEX) |
| lhsCols.insert(theColList[index]->getColumn()); |
| } |
| |
| |
| ValueIdList dummyList; |
| |
| const MCSkewedValueList* mcSkewList = |
| ((ColStatDescList&)theColList).getMCSkewedValueListForCols(lhsCols, dummyList); |
| |
| if ( mcSkewList == NULL ) |
| return FALSE; |
| |
| // Apply the frequency threshold to each MC skew and store those passing |
| // the thredhold test to the new skList |
| |
| CostScalar rc = child(i).getGroupAttr()->getResultCardinalityForEmptyInput(); |
| |
| CostScalar thresholdFrequency = rc * mc_threshold; |
| |
| *skList = new (CmpCommon::statementHeap()) |
| SkewedValueList((CmpCommon::statementHeap())); |
| |
| for ( CollIndex i=0; i<mcSkewList->entries(); i++ ) { |
| MCSkewedValue* itm = mcSkewList->at(i); |
| |
| if ( itm->getFrequency() >= thresholdFrequency ) { |
| |
| // Use an EncodedValue object to represent the current MC skew |
| // and transfer the hash value to it. The hash value is |
| // computed in EncodedValue::computeRunTimeHashValue() and is |
| // the run-time version! No modification should be done to it |
| // from this point on. |
| EncodedValue mcSkewed = itm->getHash(); |
| |
| (*skList)->insertInOrder(mcSkewed); |
| } |
| } |
| |
| // Set the run-time hash status flag so that we will not try to build |
| // the run-time hash again in |
| // SkewedDataPartitioningFunction::buildHashListForSkewedValues(). |
| (*skList)->setComputeFinalHash(FALSE); |
| |
| if ( (*skList)->entries() == 0) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| // The old way to guess MC skews and repartition the data stream on one |
| // of the columns with least skews. |
| NABoolean Join::childNodeContainMultiColumnSkew( |
| CollIndex i, // IN: which child to work on |
| const ValueIdSet& joinPreds, // IN: the join predicate |
| double mc_threshold, // IN: multi-column threshold |
| double sc_threshold, // IN: single-column threshold |
| Lng32 countOfPipelines, // IN: |
| SkewedValueList** skList, // OUT: the skew list |
| ValueId& vidOfEquiJoinWithSkew // OUT: the valueId of the column |
| // whose skew list is returned |
| ) const |
| { |
| if (joinPreds.entries() <= 1) |
| return FALSE; |
| |
| typedef SkewedValueList* SkewedValueListPtr; |
| |
| SkewedValueList** skewLists; |
| skewLists = |
| new(CmpCommon::statementHeap()) SkewedValueListPtr[joinPreds.entries()]; |
| |
| CostScalar* skewFactors = |
| new(CmpCommon::statementHeap()) CostScalar[joinPreds.entries()]; |
| |
| // A list of valueIdSets, each valueIdSet element contains a set of |
| // columns from the join predicates. Each set has all columns from the |
| // same table participating in the join predicates. |
| ARRAY(ValueIdSet) mcArray(CmpCommon::statementHeap(), joinPreds.entries()); |
| |
| Int32 skews = 0, leastSkewList = 0; |
| EncodedValue mostFreqVal; |
| |
| CostScalar productOfSkewFactors = csOne; |
| CostScalar productOfUecs = csOne; |
| CostScalar minOfSkewFactor = csMinusOne; |
| CostScalar rc = csMinusOne; |
| CostScalar currentSkew; |
| CollIndex j = 0; |
| NABoolean statsExist; |
| |
| for(ValueId vid = joinPreds.init(); joinPreds.next(vid); joinPreds.advance(vid)) |
| { |
| // Get the skew values for the join predicate in question. |
| skewLists[skews] = child(i).getGroupAttr()-> getSkewedValues(vid, sc_threshold, |
| statsExist, |
| (*GLOBAL_EMPTY_INPUT_LOGPROP), |
| isLeftJoin() /* include skewed NULLs only for left outer join */ |
| ); |
| |
| // When the skew list is null, there are two possibilities. |
| // 1. No stats exists, here we assume the worse (stats has not been updated), and |
| // move to the next join predicate. |
| // 2. The stats is present but we could not detect skews (e.g., the skews are |
| // too small to pass the threshold test). We return FALSE to indicate that the |
| // column is good enough to smooth out the potential skews in other columns. |
| if ( skewLists[skews] == NULL ) { |
| if ( !statsExist ) |
| continue; |
| else |
| return FALSE; // no stats exist |
| } |
| |
| // Pick the shortest skew list seen so far. The final shortest skew list |
| // will be used for run-time skew detection. |
| if ( skews == 0 || |
| (skewLists[skews] && |
| skewLists[skews] -> entries() < skewLists[leastSkewList] -> entries() |
| ) |
| ) |
| { |
| |
| // Obtain the colstat for the child of the join predicate on |
| // the other side of the join. |
| CollIndex brSide = (i==0) ? 1 : 0; |
| ColStatsSharedPtr colStats = child(brSide).getGroupAttr()-> |
| getColStatsForSkewDetection(vid, (*GLOBAL_EMPTY_INPUT_LOGPROP)); |
| |
| if ( colStats == NULL ) |
| return FALSE; // no stats exist for the inner. assume the worst |
| |
| // get the skew list |
| const FrequentValueList & skInner = colStats->getFrequentValues(); |
| |
| CollIndex index = 0; |
| const SkewedValueList& newList = *skewLists[skews]; |
| |
| CostScalar totalFreq = csZero; |
| |
| const NAType* nt = newList.getNAType(); |
| NABoolean useHash = nt->useHashRepresentation(); |
| |
| for (CollIndex index = 0; index < skInner.entries(); index++) |
| { |
| |
| const FrequentValue& fv = skInner[index]; |
| |
| EncodedValue skew = ( useHash ) ? fv.getHash() : fv.getEncodedValue(); |
| |
| if ( nt->getTypeQualifier() == NA_NUMERIC_TYPE && |
| nt->getTypeName() == LiteralNumeric ) { |
| |
| skew = fv.getEncodedValue().computeHashForNumeric((SQLNumeric*)nt); |
| } |
| |
| if ( newList.contains(skew) ) |
| //totalFreq += fv.getFrequency() * fv.getProbability(); |
| totalFreq += fv.getFrequency() ; |
| |
| } |
| |
| CostScalar totalInnerBroadcastInBytes = |
| totalFreq * child(brSide).getGroupAttr()->getRecordLength() * |
| countOfPipelines ; |
| |
| if (totalInnerBroadcastInBytes >= |
| ActiveSchemaDB()->getDefaults() |
| .getAsLong(MC_SKEW_INNER_BROADCAST_THRESHOLD)) |
| // ACX QUERY 5 and 8 have skews on the inner side. Better |
| // to bet on partitioning on all columns to handle the dual skews. |
| // This has been proved by the performance run on 3/21/2012: a |
| // 6% degradation when partition on the remaining non-skew column. |
| return FALSE; |
| |
| |
| leastSkewList = skews; |
| vidOfEquiJoinWithSkew = vid; |
| } |
| |
| |
| // Get the skew factor for the join predicate in question. |
| skewFactors[skews] = currentSkew = child(i).getGroupAttr()-> |
| getSkewnessFactor(vid, mostFreqVal, (*GLOBAL_EMPTY_INPUT_LOGPROP)); |
| |
| // We compute SFa * SFb * SFc ... here |
| productOfSkewFactors *= currentSkew; |
| |
| // Obtain the colstat for the ith child of the join predicate. |
| ColStatsSharedPtr colStats = child(i).getGroupAttr()-> |
| getColStatsForSkewDetection(vid, (*GLOBAL_EMPTY_INPUT_LOGPROP)); |
| |
| if ( colStats == NULL ) |
| return FALSE; // no stats exist. Can not make the decision. return FALSE. |
| |
| // Compute UECa * UECb * UECc ... here |
| productOfUecs *= colStats->getTotalUec(); |
| |
| // get the RC of the table |
| if ( rc == csMinusOne ) |
| rc = colStats->getRowcount(); |
| |
| // Compute the minimal of the skew factors seen so far |
| if ( currentSkew.isGreaterThanZero() ) { |
| if ( minOfSkewFactor == csMinusOne || minOfSkewFactor > currentSkew ) |
| minOfSkewFactor = currentSkew; |
| } |
| |
| skews++; |
| |
| // Collect join columns in this predicate into joinColumns data structure. |
| ValueIdSet joinColumns; |
| vid.getItemExpr() -> findAll(ITM_BASECOLUMN, joinColumns, TRUE, FALSE); |
| |
| // Separate out columns in the join predicates and group them per table. |
| // |
| // For example, if join predicates are t.a=s.b and t.b=s.b and t.c = s.c, |
| // we will have |
| // |
| // mcArray[0] = {t.a, t.b, t.c}, |
| // mcArray[1] = {s.a, s.b, s.c}, |
| // |
| // at the end of the loop over join predicates. |
| // |
| j = 0; |
| for(ValueId x = joinColumns.init(); joinColumns.next(x); joinColumns.advance(x)) |
| { |
| if ( !mcArray.used(j) ) |
| mcArray.insertAt(j, x); |
| else { |
| ValueIdSet& mcSet = mcArray[j]; |
| mcSet.insert(x); |
| } |
| |
| j++; |
| } |
| } // end of the loop of join predicates |
| |
| |
| // Now we can find the multi-column UEC, using one of the two multi-column |
| // ValueIdSets (one for each side of the equi-join predicate). The colstats |
| // list for the side of the child contains the stats (including the mc ones). |
| // one of the mc ones is what we are looking for. |
| // |
| ColStatDescList colStatDescList = |
| child(i).getGroupAttr()-> |
| outputLogProp((*GLOBAL_EMPTY_INPUT_LOGPROP))->getColStats(); |
| |
| CostScalar mcUec = csMinusOne; |
| |
| const MultiColumnUecList* uecList = colStatDescList.getUecList(); |
| |
| for(j=0; j < mcArray.entries() && mcUec == csMinusOne && uecList; j++) |
| { |
| const ValueIdSet& mc = mcArray[j]; |
| |
| // Do a look up with mc. |
| if ( uecList ) |
| mcUec = uecList->lookup(mc); |
| } |
| |
| // |
| // Compute the final value of |
| // min( (SFa * SFb * ... *min(UECa * UECb..,RC))/UEC(abc..), |
| // SFa, SFb, ..., ) |
| // = min(productOfSkewFactors * min(productOfUecs, RC)/mcUEC, |
| // minOfSkewFactor) |
| // |
| // min(productOfUecs, RC)/mcUEC = 1 when mcUEC is not found |
| // |
| CostScalar mcSkewFactor; |
| if ( mcUec == csMinusOne || mcUec == csZero ) |
| mcSkewFactor = MINOF(productOfSkewFactors, minOfSkewFactor); |
| else |
| mcSkewFactor = MINOF( |
| productOfSkewFactors * MINOF(productOfUecs, rc) / mcUec, |
| minOfSkewFactor |
| ); |
| |
| if ( mcSkewFactor > mc_threshold ) |
| { |
| *skList = skewLists[leastSkewList]; |
| return TRUE; |
| } else |
| return FALSE; |
| } |
| |
| // |
| // The content of this method is lifted from |
| // DP2InsertCursorRule::nextSubstitute(). |
| // A Note has been added in that method so that any changes |
| // to it should be "copied" here. |
| // |
| NABoolean Insert::isSideTreeInsertFeasible() |
| { |
| // Sidetree insert is only supported for key sequenced, non-compressed, |
| // non-audited tables with blocksize equal to 4K. |
| // Return error, if this is not the case. |
| |
| Insert::InsertType itype = getInsertType(); |
| |
| // Sidetree insert requested? |
| if (itype != Insert::VSBB_LOAD ) |
| return FALSE; |
| |
| if ((getTableDesc()->getClusteringIndex()->getNAFileSet() |
| ->isCompressed()) || |
| (getTableDesc()->getClusteringIndex()->getNAFileSet() |
| ->getBlockSize() < 4096) || |
| (NOT getTableDesc()->getClusteringIndex()->getNAFileSet() |
| ->isKeySequenced()) || |
| (getTableDesc()->getClusteringIndex()->getNAFileSet() ->isAudited()) |
| ) |
| { |
| return FALSE; |
| } |
| |
| if ( !getInliningInfo().hasPipelinedActions() ) |
| return TRUE; |
| |
| if (getInliningInfo().isEffectiveGU() || |
| getTolerateNonFatalError() == RelExpr::NOT_ATOMIC_) |
| return FALSE; |
| |
| |
| // SideInsert is not allowed when there are pipelined actions (RI, |
| // IM or triggers) except MV range logging. This means the only rows |
| // projected are the very first and last rows as the beginning and |
| // end of the range. |
| |
| NABoolean rangeLoggingRequired = |
| getTableDesc()->getNATable()->getMvAttributeBitmap(). |
| getAutomaticRangeLoggingRequired(); |
| |
| if (getInliningInfo().isProjectMidRangeRows() || !rangeLoggingRequired) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| // big memory growth percent (to be used by SSD overlow enhancement project) |
| short RelExpr::bmoGrowthPercent(CostScalar e, CostScalar m) |
| { |
| // bmo growth is 10% if 100*abs(maxcard-expected)/expected <= 100% |
| // otherwise its 25% |
| CostScalar expectedRows = e.minCsOne(); |
| CostScalar maxRows = m.minCsOne(); |
| CostScalar difference = maxRows - expectedRows; |
| CostScalar uncertainty = |
| (_ABSOLUTE_VALUE_(difference.value()) / expectedRows.value()) * 100; |
| if (uncertainty <= 100) |
| return 10; |
| else |
| return 25; |
| } |
| |
| |
| CostScalar RelExpr::getChild0Cardinality(const Context* context) |
| { |
| EstLogPropSharedPtr inLogProp = context->getInputLogProp(); |
| EstLogPropSharedPtr ch0OutputLogProp = child(0).outputLogProp(inLogProp); |
| |
| const CostScalar ch0RowCount = |
| (ch0OutputLogProp) ? |
| (ch0OutputLogProp->getResultCardinality()).minCsOne() : |
| csOne; |
| |
| return ch0RowCount; |
| } |
| |
| NAString *RelExpr::getKey() |
| { |
| |
| if (operKey_.length() == 0) |
| { |
| char keyBuffer[30]; |
| snprintf(keyBuffer, sizeof(keyBuffer), "%ld", (Int64)this); |
| operKey_ = keyBuffer; |
| } |
| return &operKey_; |
| } |