| /********************************************************************** |
| // @@@ 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: GenRelJoin.C |
| * Description: Join operators |
| * |
| * |
| * Created: 5/17/94 |
| * Language: C++ |
| * |
| * |
| * |
| * |
| ****************************************************************************** |
| */ |
| #include "ComDefs.h" // to get common defines (ROUND8) |
| #include "limits.h" |
| #include "ComOptIncludes.h" |
| #include "RelJoin.h" |
| #include "GroupAttr.h" |
| #include "Analyzer.h" |
| #include "Generator.h" |
| #include "GenExpGenerator.h" |
| //#include "ex_stdh.h" |
| #include "ExpCriDesc.h" |
| #include "ComTdb.h" |
| //#include "ex_tcb.h" |
| #include "ComTdbOnlj.h" |
| #include "ComTdbHashj.h" |
| #include "ComTdbMj.h" |
| #include "ComTdbTupleFlow.h" |
| #include "HashBufferHeader.h" |
| #if 0 |
| // unused feature, done as part of SQ SQL code cleanup effort |
| #include "ComTdbSimpleSample.h" |
| #endif // if 0 |
| #include "DefaultConstants.h" |
| #include "HashRow.h" |
| #include "hash_table.h" // for HashTableHeader |
| #include "ExpSqlTupp.h" // for sizeof(tupp_descriptor) |
| #include "sql_buffer.h" |
| #include "sql_buffer_size.h" |
| #include "CmpStatement.h" |
| #include "ComUnits.h" |
| |
| ///////////////////////////////////////////////////////////////////// |
| // |
| // Contents: |
| // |
| // HashJoin::codeGen() |
| // MergeJoin::codeGen() |
| // NestedJoin::codeGen() |
| // NestedJoinFlow::codeGen() |
| // Join::instantiateValuesForLeftJoin() |
| // |
| ///////////////////////////////////////////////////////////////////// |
| |
| short HashJoin::codeGen(Generator * generator) { |
| |
| // Decide if this join can use the Unique Hash Join option. This |
| // option can be significantly faster than the regular hash join, |
| // but does not support many features. |
| // |
| NABoolean useUniqueHashJoin = canUseUniqueHashJoin(); |
| |
| ExpGenerator * expGen = generator->getExpGenerator(); |
| Space * space = generator->getSpace(); |
| |
| GenAssert( ! isSemiJoin() || ! isAntiSemiJoin(), |
| "Node can not be both semi-join and anti-semi-join" ); |
| GenAssert( ! isReuse() || isNoOverflow(), |
| "Reuse of inner table requires no-overflow!" ); |
| |
| // set flag to enable pcode for indirect varchar |
| NABoolean vcflag = expGen->handleIndirectVC(); |
| if (CmpCommon::getDefault(VARCHAR_PCODE) == DF_ON) { |
| expGen->setHandleIndirectVC( TRUE ); |
| } |
| |
| // values are returned from the right when it is not (anti) semi join |
| NABoolean rightOutputNeeded = ! isSemiJoin() && ! isAntiSemiJoin() ; |
| |
| // the minMaxExpr is used when the min max optimization is in |
| // effect. It is evaluated at the same time as the rightHashExpr |
| // and computes the min and max values for one or more of the join |
| // values coming from the right (inner) side. |
| ex_expr * minMaxExpr = 0; |
| |
| // the rightHashExpr takes a row from the right child (the "build |
| // table") and calculates the hash value. After the expression is |
| // evaluated, the hash value is available in the hashValueTupp_ of |
| // the hash join tcb (compare the description of the workAtp). |
| ex_expr * rightHashExpr = 0; |
| |
| // the rightMoveInExpr expression moves the incoming right row |
| // to a contiguous buffer. This buffer is later inserted into the |
| // hash table. The row in the buffer looks like a right row with a |
| // header added to it. Thus, it is called "extended" right row. The |
| // row header contains the hash value and a pointer for the hash |
| // chain. |
| ex_expr * rightMoveInExpr = 0; |
| |
| // the rightMoveOutExpr moves a "extended" right row to a regular right |
| // row (one without a row header). This is done if the "extended" right |
| // row is part of a result. |
| // This expression is not really required. The "extended" row in the |
| // hash buffer is always contiguous. So is the row in the result buffer. |
| // Thus, the executor can simply use a byte move. This is true, if the |
| // "extended" row in the hash buffer has the following format: |
| // |
| // |------------------------------------------------------| |
| // | Header | right columns | hash expression (see below) | |
| // |------------------------------------------------------| |
| // |
| // A byte move just skips the header (hash value & next pointer) and |
| // hash expression. It only moves the right columns. The following code |
| // guarantees that the hash expression always follows the right row |
| // data. |
| // |
| // Please note that for now the generator still generates the |
| // rightMoveOutExpr. It is simply not used by the executor. |
| ex_expr * rightMoveOutExpr = 0; |
| |
| // the rightSearchExpr compares two "extended" rows from the right child. |
| // Both rows are stored in the contiguous buffer of the appropriate |
| // cluster. The expression is used while chaining rows into the hash |
| // table. The result of the expression is boolean. |
| ex_expr *rightSearchExpr = 0; |
| |
| // the leftHashExpr takes a row from the left child and calculates |
| // the hash value. After the expression is evaluated, the hash value is |
| // available in the hashValueTupp_ of the hash join tcb. |
| ex_expr * leftHashExpr = 0; |
| |
| // the leftMoveExpr moves the incoming left row dircetly to the parents |
| // buffer. This happens during phase 2 of the hash join if the hash table |
| // is immediately probed with the left row. If the row qualifies, there |
| // is no need to move it to an "extended" left row first. |
| ex_expr * leftMoveExpr = 0; |
| |
| // the leftMoveInExpr moves the incomming left row into a contiguous |
| // buffer. The row in the buffer is also extended by a row header to |
| // store the hash value and a hash chain pointer. |
| ex_expr * leftMoveInExpr = 0; |
| |
| // the leftMoveOutExpr is used to move an "extended" left row to the |
| // parents Atp if the row is part of the result. The expression gets |
| // rid of the row header. Again, this expression is not really required. |
| // The same arguments as for the rightMoveOutExpr holds. For the |
| // leftMoveOutExpr it is even easier, because there is no additional |
| // data (hash expression). Again, for now the expression is generated but |
| // ignored. |
| ex_expr * leftMoveOutExpr = 0; |
| |
| // the probeSearchExpr1 compares an incoming left row with an "extended" |
| // right row. This expression is used if the hash table is probed right |
| // away with a left row without moving the left row into a contiguous |
| // buffer. This happens during phase 2 of the hash join. The result of the |
| // expression is boolean. |
| ex_expr * probeSearchExpr1 = 0; |
| |
| // the probeSearchExpr2 compares an "extended" left row with an "extended" |
| // right row. The rsult of the expression is boolean. Ths expression is |
| // used during phase 3 of the hash join |
| ex_expr * probeSearchExpr2 = 0; |
| |
| // the leftJoinExpr is used in some left join cases, were we have to |
| // null instantiate a row. |
| ex_expr * leftJoinExpr = 0; |
| |
| // the nullInstForLeftJoinExpr puts null values into the null |
| // instantiated row. The instatiatied row is a right row without |
| // a row header. |
| ex_expr * nullInstForLeftJoinExpr = 0; |
| |
| // the rightJoinExpr is used in some right join cases, where we have to |
| // null instantiate a row from the left. |
| ex_expr * rightJoinExpr = 0; |
| |
| // the nullInstForRightJoinExpr puts null values into the null instantiated |
| // row. The instatiatied row is a right row without a row header. |
| ex_expr * nullInstForRightJoinExpr = 0; |
| |
| // the beforeJoinPred1 compares a row from the left child with an |
| // "extended" right row. It is used during phase 2 of the hash join. |
| // The result is boolean. |
| ex_expr * beforeJoinPred1 = 0; |
| |
| // the beforeJoinPred2 compares an "extended" left row with an |
| // "extended" right row. It is used during phase 3 of the hash join. |
| // The result is boolean. |
| ex_expr * beforeJoinPred2 = 0; |
| |
| // the afterJoinPred1 compares a row from the left child with an |
| // "extended" right row. It is used during phase 2 of workProbe, |
| // when there is a matching row. Used when there is no overflow, |
| // and the left row is a composite row. |
| // The result is boolean. |
| ex_expr * afterJoinPred1 = 0; |
| |
| // the afterJoinPred2 compares an "extended" left row with an |
| // "extended" right row. It is used during phase 3 of workProbe, |
| // when there is a matching row. This is used after an overflow, |
| // when the left row comes from a hash buffer. |
| // The result is boolean. |
| ex_expr * afterJoinPred2 = 0; |
| |
| // variation of afterJoinPred1, used when the "extended" right row has to |
| // be the NULL instantiated part in a left join. Compares a row from the |
| // left child with an "NullInstantiated" null tuple representing a right row. |
| // Used during phase 2 of workProbe when there is no matching right row. |
| // Used when there is no overflow, and the left row is a composite row. |
| // The result is boolean. |
| ex_expr * afterJoinPred3 = 0; |
| |
| // variation of afterJoinPred2, used when the "extended" right row has to |
| // be the NULL instantiated part in a left join. Compares an "extended" left |
| // row with an "NullInstantiated" null tuple representing a right row. |
| // Used during phase 3 of workProbe when there is no matching right row. |
| // This is used after an overflow, when the left row comes from a hash buffer. |
| // The result is boolean. |
| ex_expr * afterJoinPred4 = 0; |
| |
| // the afterJoinPred5 compares a "NullInstantiated" null tuple representing a |
| // left row with a "NullInstantiated" right row. It is used during workReturnRightRows |
| // to process the rows from the right which did not have a matching left row. |
| // The result is boolean. |
| ex_expr * afterJoinPred5 = 0; |
| |
| const ValueIdSet &rightOutputCols = |
| child(1)->getGroupAttr()->getCharacteristicOutputs(); |
| |
| // right table columns to be inserted into the return buffer |
| // (if not semi join) |
| |
| ValueIdList rightOutputValIds; |
| |
| // right table columns to be inserted into the hash buffer. For |
| // "normal" cases these columns are identical to rightOutputValIds. |
| // However, if the join predicate involves expressions, we also |
| // move these expressions into the hash buffer |
| ValueIdList rightBufferValIds; |
| |
| ValueId valId; |
| |
| // add only those ValueIds to rightOutputValIds and rightBufferValIds |
| // which are not part of the input. |
| for (valId = rightOutputCols.init(); |
| rightOutputCols.next(valId); |
| rightOutputCols.advance(valId)) |
| if (NOT getGroupAttr()->getCharacteristicInputs().contains(valId)) { |
| rightOutputValIds.insert(valId); |
| rightBufferValIds.insert(valId); |
| }; |
| |
| |
| // left table columns to be inserted into the hash buffer and the |
| // return buffer |
| const ValueIdSet &leftOutputCols = |
| child(0)->getGroupAttr()->getCharacteristicOutputs(); |
| |
| ValueIdList leftOutputValIds; |
| |
| // UniqueHashJoin does not MOVE the left values. |
| // It simply passes them to the parent queue using copyAtp() |
| // So do not build the list of left outputs. |
| // |
| if (!useUniqueHashJoin) { |
| |
| // add only those ValueIds to leftOutputValIds |
| // which are not part of the input. |
| for (valId = leftOutputCols.init(); |
| leftOutputCols.next(valId); |
| leftOutputCols.advance(valId)) |
| if (NOT getGroupAttr()->getCharacteristicInputs().contains(valId)) |
| leftOutputValIds.insert(valId); |
| } |
| |
| |
| // allocate 2 map tables, so that we can later remove the right child MT |
| MapTable * myMapTable0 = generator->appendAtEnd(); |
| MapTable * myMapTable1 = generator->appendAtEnd(); |
| |
| ex_cri_desc * givenDesc = generator->getCriDesc(Generator::DOWN); |
| |
| // Incoming records will be divided equally (sans data skew) among the ESPs |
| // so each HJ instance will handle only its share (i.e. divide by #esps) |
| Lng32 saveNumEsps = generator->getNumESPs(); |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // generate the right child |
| //////////////////////////////////////////////////////////////////////////// |
| generator->setCriDesc(givenDesc, Generator::DOWN); |
| child(1)->codeGen(generator); |
| ComTdb * rightChildTdb = (ComTdb *)(generator->getGenObj()); |
| ExplainTuple *rightExplainTuple = generator->getExplainTuple(); |
| |
| // This value was originally set inside generator by my parent exchange node |
| // as a global variable. Now need to reset the saveNumEsps value back into |
| // generator since codegen of child(1) may have changed it. |
| generator->setNumESPs(saveNumEsps); |
| |
| // A MapTable for the Min and Max values used by the min/max |
| // optimization. |
| MapTable *myMapTableMM = NULL; |
| |
| // Normally the left down request is the same as the parent request |
| // (givenDesc). But if this HashJoin is doing the min max |
| // optimization, then the left down request will contain an extra |
| // tuple for the min and max values. In this case, we will create a |
| // new criDesc for the left down request |
| ex_cri_desc * leftDownDesc = givenDesc; |
| |
| // The length of the min max tuple used by the min/max optimization. |
| ULng32 minMaxRowLength = 0; |
| |
| // If we are doing min/max optimization, get the min/max values into |
| // the map table and marked as codegen'ed before we codegen the left |
| // child. These are just the placeholders for the computed min/max |
| // values. Later we will map the actual min/max values to the at |
| // same location in the min/max tuple. |
| if(getMinMaxVals().entries()) |
| { |
| |
| // This map table is a placeholder, used so we can unlink the |
| // min/max mapTable (myMapTableMM) |
| MapTable * myMapTableMM1 = generator->appendAtEnd(); |
| |
| // Add the map table for the min and max values. |
| myMapTableMM = generator->appendAtEnd(); |
| |
| // Allocate a new leftDownDesc which will have one additional |
| // tuple for the min/max values |
| leftDownDesc = new(space) ex_cri_desc(givenDesc->noTuples() + 1, space); |
| |
| // The index of the min/max tuple in the down request. |
| short minMaxValsDownAtpIndex = leftDownDesc->noTuples()-1; |
| |
| // Layout the min/max values in the min/max tuple and add |
| // corresponding entries into the min/max mapTable |
| // (myMapTableMM). |
| expGen->processValIdList(getMinMaxVals(), |
| ExpTupleDesc::SQLARK_EXPLODED_FORMAT, |
| minMaxRowLength, 0, minMaxValsDownAtpIndex); |
| |
| // Mark these as codegen'ed so the scans that use these will not |
| // attempt to generate code for them. |
| generator->getMapInfo(getMinMaxVals()[0])->codeGenerated(); |
| generator->getMapInfo(getMinMaxVals()[1])->codeGenerated(); |
| |
| generator->unlinkNext(myMapTableMM1); |
| } |
| |
| // before we generate the left child, we have to remove the map table of |
| // the right child, because the left child should not see its siblings |
| // valueIds (especially since we sometimes see duplicate valueIds due |
| // to VEGs). Because of these duplicates we have to make sure that |
| // the order of valueIds in the map table is later right child valueIds, |
| // left child valueIds. To make a long story short, we first generate |
| // the right child, unchain the map table of the right child, generate |
| // the left child and then chain the right childs map table in front |
| // of the lefts childs map table. The map table interface is not well |
| // suited for these kinds of chain/unchain operations. This is why we |
| // see so many empty helper map tables in this method here. There are |
| // two other possible solutions to this problem: |
| // 1 - extend the map table interface to make these manipulations easier |
| // 2 - make changes in the normalizer/VEG code to avoid duplicate |
| // valueIds. |
| // For now we have all these extra map tables! |
| |
| // ok, here we go: unchain right child map table |
| generator->unlinkNext(myMapTable0); |
| |
| // Add the min/max maptable so the left child scans that use them will |
| // have access to them. |
| if(myMapTableMM) |
| generator->appendAtEnd(myMapTableMM); |
| |
| // allocate 2 map tables, so that we can later remove the left child MT |
| MapTable * myMapTable2 = generator->appendAtEnd(); |
| MapTable * myMapTable3 = generator->appendAtEnd(); |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // generate code for left child |
| //////////////////////////////////////////////////////////////////////////// |
| generator->setCriDesc(leftDownDesc, Generator::DOWN); |
| child(0)->codeGen(generator); |
| ComTdb * leftChildTdb = (ComTdb *)(generator->getGenObj()); |
| ExplainTuple *leftExplainTuple = generator->getExplainTuple(); |
| ex_cri_desc * leftChildDesc = generator->getCriDesc(Generator::UP); |
| |
| // This value was originally set inside generator by my parent exchange node |
| // as a global variable. Now need to reset the saveNumEsps value back into |
| // generator since codegen of my child(0) may have changed it. |
| generator->setNumESPs(saveNumEsps); |
| |
| short returnedTuples; |
| short returnedLeftRowAtpIndex = -1; |
| short returnedRightRowAtpIndex = -1; |
| short returnedInstRowAtpIndex = -1; |
| short returnedInstRowForRightJoinAtpIndex = -1; |
| |
| // The work atp is passed as the second atp to the expression evaluation |
| // procs. Hence, its atp value is set to 1. |
| short workAtpPos = 1; // second atp |
| |
| short leftRowAtpIndex = -1; |
| short extLeftRowAtpIndex = -1; |
| short rightRowAtpIndex = -1; |
| short extRightRowAtpIndex1 = -1; |
| short extRightRowAtpIndex2 = -1; |
| short hashValueAtpIndex = -1; |
| |
| // The atpIndex of the min/max tuple in the workAtp. |
| short minMaxValsAtpIndex = -1; |
| |
| short numOfATPIndexes = -1; |
| |
| short instRowForLeftJoinAtpIndex = -1; |
| short instRowForRightJoinAtpIndex = -1; |
| short prevInputTuppIndex = -1; |
| |
| // The Unique Hash Join has a different layout for the returned row |
| // and for the work Atp. |
| // |
| if(useUniqueHashJoin) { |
| //////////////////////////////////////////////////////////////////////////// |
| // Unique Hash Join |
| // Layout of row returned by this node. |
| // |
| // |--------------------------------------------------- |
| // | input data | left child's data | hash table row | |
| // | | | | |
| // | | | | |
| // | ( I tupps ) | ( L tupps ) | ( 0/1 tupp ) | |
| // |--------------------------------------------------- |
| // |
| // input data: the atp input to this node by its parent. |
| // left child data: the tupps from the left row. Only, if the left row |
| // is required. |
| // hash table row: the row from the hash table (right child). In case of |
| // a semi join, this row is not returned |
| // |
| //////////////////////////////////////////////////////////////////////////// |
| returnedTuples = leftChildDesc->noTuples(); |
| |
| // Right row goes in last entry. |
| if ( rightOutputNeeded && rightOutputValIds.entries() > 0) { |
| returnedRightRowAtpIndex = returnedTuples++; |
| } |
| |
| ///////////////////////////////////////////////////////////////////////// |
| // Unique Hash Join Layout of WorkATP |
| // in all the computation below, the hashed row value ids are available |
| // in a temporary work atp. They are used from there to build the |
| // hash table. |
| // Before returning from this proc, these value ids are moved to the |
| // map table that is being returned. The work atp contains the following |
| // entries: |
| // index what |
| // ------------------------------------------------------- |
| // 0 constants |
| // 1 temporaries |
| // 2 a row from the left child (not used) |
| // 3 an "extended" row from the right child |
| // 4 another "extended" row from the right child |
| // 5 the calculated hash value |
| // 6 the previous input (in case of HT reuse) |
| ///////////////////////////////////////////////////////////////////////// |
| |
| // not used by unique hash join, but needed for some common code |
| extLeftRowAtpIndex = 2; |
| |
| // not used by unique hash join, but used when generating |
| // rightMoveOutExpr. unique hash join does not use this |
| // but needs the maptable generated as a by product. |
| rightRowAtpIndex = 2; |
| |
| extRightRowAtpIndex1 = 3; |
| extRightRowAtpIndex2 = 4; |
| hashValueAtpIndex = 5; |
| |
| numOfATPIndexes = 6; |
| |
| if(isReuse()) |
| prevInputTuppIndex = numOfATPIndexes++; |
| |
| // If using min/max optimization, add one more tuple for min/max values. |
| if(getMinMaxVals().entries()) |
| minMaxValsAtpIndex = numOfATPIndexes++; |
| |
| } else { |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // Regular Hybrid Hash Join |
| // Layout of row returned by this node. |
| // |
| // |---------------------------------------------------------------------| |
| // | input data | left child's data | hash table row | instantiated for | |
| // | | | | right row (left | |
| // | | | | join) |<| |
| // | ( I tupps ) | ( 0/1 tupp ) | ( 0/1 tupp ) | ( 0/1 tupp ) | | |
| // |---------------------------------------------------------------------| | |
| // | |
| // |-------------------------------------| |
| // | ------------------------ |
| // | | instantiated for | |
| // |--->| left row (right join) | |
| // | (0/1 tupp) | |
| // ------------------------- |
| // |
| // input data: the atp input to this node by its parent. |
| // left child data: a tupp with the left row. Only, if the left row |
| // is required. I.e., in certain cases the left row is |
| // returned. |
| // hash table row: the row from the hash table (right child). In case of |
| // a semi join, this row is not returned |
| // instantiated for |
| // right row: For some left join cases, the hash table rows or |
| // the null values are instantiated. See proc |
| // Join::instantiateValuesForLeftJoin for details at |
| // the end of this file. |
| // instantiated for |
| // left row: For some right join cases, the hash table rows or |
| // the null values are instantiated. See proc |
| // Join::instantiateValuesForRightJoin for details at |
| // the end of this file. |
| // |
| // Returned row to parent contains: |
| // |
| // I + 1 tupp, if the left row is not returned |
| // I + 1 tupp, if this is a semi join. Rows from right are not returned. |
| // |
| // If this is not a semi join, then: |
| // I + 2 tupps, if instantiation is not done. |
| // I + 3 tupps, if instantiation is done only for Left Outer Join. |
| // I + 4 tupps, if instantiation is done only for Full Outer Join. |
| // |
| //////////////////////////////////////////////////////////////////////////// |
| |
| returnedTuples = givenDesc->noTuples(); |
| |
| if (leftOutputValIds.entries()) |
| returnedLeftRowAtpIndex = returnedTuples++; |
| |
| if ( rightOutputNeeded ) { |
| returnedRightRowAtpIndex = returnedTuples++; |
| if (nullInstantiatedOutput().entries() > 0) |
| returnedInstRowAtpIndex = returnedTuples++; |
| } |
| |
| if (nullInstantiatedForRightJoinOutput().entries() > 0) |
| returnedInstRowForRightJoinAtpIndex = returnedTuples++; |
| |
| ///////////////////////////////////////////////////////////////////////// |
| // Regular Hybrid Hash Join Layout of WorkATP |
| // in all the computation below, the hashed row value ids are available |
| // in a temporary work atp. They are used from there to build the |
| // hash table, apply the after predicate, join predicate etc. |
| // Before returning from this proc, these value ids are moved to the |
| // map table that is being returned. The work atp contains the following |
| // entries: |
| // index what |
| // ------------------------------------------------------- |
| // 0 constants |
| // 1 temporaries |
| // 2 a row from the left child |
| // 3 an "extended" row from the left child |
| // 4 a row from the right child |
| // 5 an "extended" row from the right child |
| // 6 another "extended" row from the right child |
| // 7 the calculated hash value |
| // 8 the instatiated right row for left join |
| // 9 the instatiated left row for right join |
| // 10/11/12 the previous input (in case of HT reuse) |
| ///////////////////////////////////////////////////////////////////////// |
| |
| leftRowAtpIndex = 2; |
| extLeftRowAtpIndex = 3; |
| rightRowAtpIndex = 4; |
| extRightRowAtpIndex1 = 5; |
| extRightRowAtpIndex2 = 6; |
| hashValueAtpIndex = 7; |
| |
| numOfATPIndexes = 8; |
| |
| if (nullInstantiatedOutput().entries() > 0) |
| instRowForLeftJoinAtpIndex = numOfATPIndexes++; |
| |
| if (nullInstantiatedForRightJoinOutput().entries() > 0) |
| instRowForRightJoinAtpIndex = numOfATPIndexes++; |
| |
| if(isReuse()) |
| prevInputTuppIndex = numOfATPIndexes++; |
| |
| // If using min/max optimization, add one more tuple for min/max values. |
| if(getMinMaxVals().entries()) |
| minMaxValsAtpIndex = numOfATPIndexes++; |
| } |
| |
| // If this HashJoin is doing min/max optimization, then generate the |
| // aggregate expressions to compute the Min and Max values. |
| if(getMinMaxVals().entries()) { |
| |
| // Link in the map table for the right child values. |
| MapTable *lastMap = generator->getLastMapTable(); |
| generator->appendAtEnd(myMapTable1); |
| |
| // A List to contain the Min and Max aggregates |
| ValueIdList mmPairs; |
| |
| for (CollIndex mmCol = 0; mmCol < getMinMaxCols().entries(); mmCol++) { |
| |
| // Cast the value coming from the right child to the common type |
| // used to compute the Min/Max values. |
| // |
| ItemExpr *rightCol = getMinMaxCols()[mmCol].getItemExpr(); |
| |
| rightCol = new(generator->wHeap()) |
| Cast(rightCol, getMinMaxVals()[(mmCol*2)].getType().newCopy(generator->wHeap())); |
| |
| // The min and max aggregates |
| ItemExpr *minVal = new(generator->wHeap()) Aggregate(ITM_MIN, rightCol, FALSE); |
| ItemExpr *maxVal = new(generator->wHeap()) Aggregate(ITM_MAX, rightCol, FALSE); |
| |
| minVal->bindNode(generator->getBindWA()); |
| maxVal->bindNode(generator->getBindWA()); |
| |
| ValueId minId = minVal->getValueId(); |
| ValueId maxId = maxVal->getValueId(); |
| |
| Attributes * mapAttr; |
| |
| // Map the min aggregate to be the same as the min placeholder |
| // (system generated hostvar). Set the ATP/ATPIndex to refer to |
| // the min/max tuple in the workAtp |
| mapAttr = (generator->getMapInfo(getMinMaxVals()[(mmCol*2)]))->getAttr(); |
| mapAttr = (generator->addMapInfo(minId, mapAttr))->getAttr(); |
| mapAttr->setAtp(workAtpPos); |
| mapAttr->setAtpIndex(minMaxValsAtpIndex); |
| |
| // Map the max aggregate to be the same as the min placeholder |
| // (system generated hostvar). Set the ATP/ATPIndex to refer to |
| // the min/max tuple in the workAtp |
| mapAttr = (generator->getMapInfo(getMinMaxVals()[((mmCol*2)+1)]))->getAttr(); |
| mapAttr = (generator->addMapInfo(maxId, mapAttr))->getAttr(); |
| mapAttr->setAtp(workAtpPos); |
| mapAttr->setAtpIndex(minMaxValsAtpIndex); |
| |
| // Insert into list |
| mmPairs.insert(minId); |
| mmPairs.insert(maxId); |
| |
| } |
| |
| // Generate the min/max expression. |
| expGen->generateAggrExpr(mmPairs, ex_expr::exp_AGGR, &minMaxExpr, |
| 0, true); |
| |
| // No longer need the right child map table. |
| generator->unlinkNext(lastMap); |
| } |
| |
| ex_cri_desc * returnedDesc = new(space) ex_cri_desc(returnedTuples, space); |
| |
| // now the unchain/chain business described above. First, unchain the |
| // left child map table |
| generator->unlinkNext(myMapTable0); |
| // add the right child map table |
| generator->appendAtEnd(myMapTable1); |
| // and finaly add the left child map table |
| generator->appendAtEnd(myMapTable2); |
| // Here is how the map table list looks like now: |
| // MT -> MT0 -> MT1 -> RC MT -> MT2 -> MT3 -> LC MT |
| |
| ex_cri_desc * workCriDesc = new(space) ex_cri_desc(numOfATPIndexes, space); |
| |
| // This value was originally set inside generator by my parent exchange node |
| // as a global variable. Now need to reset the saveNumEsps value back into |
| // generator since codegen of my child(0) may have changed it. |
| generator->setNumESPs(saveNumEsps); |
| |
| // make sure the expressions that are inserted into the hash table |
| // include the expression(s) to be hashed (this restriction may be |
| // lifted some day in the future, but in all "normal" cases this happens |
| // anyway) |
| |
| LIST(CollIndex) hashKeyColumns(generator->wHeap()); |
| |
| //////////////////////////////////////////////////////////// |
| // Before generating any expression for this node, set the |
| // the expression generation flag not to generate float |
| // validation PCode. This is to speed up PCode evaluation |
| //////////////////////////////////////////////////////////// |
| generator->setGenNoFloatValidatePCode(TRUE); |
| |
| //////////////////////////////////////////////////////////// |
| // Generate the hash computation expression for the left |
| // and right children. |
| // |
| // The hash value is computed as: |
| // hash_value = Hash(col1) + Hash(col2) .... + Hash(colN) |
| //////////////////////////////////////////////////////////// |
| ItemExpr * buildHashTree = NULL; |
| ItemExpr * probeHashTree = NULL; |
| |
| // construct the rightHashExpr and leftHashExpr if |
| // it's a full outer join and the right rows |
| // have to be returned as well. During the probe |
| // for every left row, we mark the right row |
| // that matches. At the end (after returning all left rows for left joins) |
| // we go thro all the clusters and null instantiate the left row |
| // for every right row that is not marked. For this reason, we need |
| // the left and right rows into the cluster. For getting the row into a cluster |
| // we need the hashValue and hence rightHashExpr and leftHashExpr. |
| // TBD Hema |
| // || |
| // (nullInstantiatedForRightJoinOutput().entries() > 0)) |
| |
| if (! getEquiJoinPredicates().isEmpty()) { |
| /////////////////////////////////////////////////////////////////////// |
| // getEquiJoinPredicates() is a set containing predicates of the form: |
| // <left value1> '=' <right value1>, <left value2> '=' <right value2> .. |
| // |
| // The right values are the hash table key values |
| /////////////////////////////////////////////////////////////////////// |
| for (valId = getEquiJoinPredicates().init(); |
| getEquiJoinPredicates().next(valId); |
| getEquiJoinPredicates().advance(valId)) { |
| ItemExpr * itemExpr = valId.getItemExpr(); |
| |
| // call the pre-code generator to make sure we handle cases of |
| // more difficult type conversion, such as complex types. |
| itemExpr->preCodeGen(generator); |
| |
| // get the left (probing) and the right (building) column value |
| // to be hashed and convert them to a common data type |
| ItemExpr * buildHashCol = itemExpr->child(1); |
| ItemExpr * probeHashCol = itemExpr->child(0); |
| |
| // Search for the expression to be hashed in the build table and |
| // make a lookup table that links hash key columns to columns in |
| // the hash table. For now, if the expression to be hashed is not |
| // yet part of the hash table, then add it. This should only happen |
| // for join predicates involving expressions, e.g. t1.a = t2.b + 5. |
| // The lookup table and the list rightBufferValIds is later used when |
| // moving the building tuple into the hash table and for |
| // generating the build search expression. |
| NABoolean found = FALSE; |
| ValueId buildHashColValId = buildHashCol->getValueId(); |
| for (CollIndex ix = 0; ix < rightBufferValIds.entries() AND NOT found; ix++) { |
| if (rightBufferValIds[ix] == buildHashColValId) { |
| // found the build hash column in the column layout of the |
| // hash table, remember that hash key column |
| // "hashKeyColumns.entries()" can be found in column "ix" of |
| // the hash table. |
| hashKeyColumns.insert(ix); |
| found = TRUE; |
| } |
| } |
| |
| if (NOT found) { |
| // hash value is not yet contained in hash table, add it and |
| // remember that it is stored in column "rightOutputValIds.entries()" |
| hashKeyColumns.insert(rightBufferValIds.entries()); |
| rightBufferValIds.insert(buildHashColValId); |
| } |
| |
| // now finish the job by adding type conversion to a common type |
| // even for cases that use specialized comparison operators for |
| // slightly mismatching types (e.g. the above procedure will not |
| // create a type conversion operator for a comparison between a |
| // 16 bit integer and a 32 bit integer) |
| const NAType & buildType = buildHashCol->getValueId().getType(); |
| const NAType & probeType = probeHashCol->getValueId().getType(); |
| |
| if (NOT (buildType == probeType)) { |
| // seems like the executor would use a special-purpose |
| // comparison operator between two different data types, |
| // but this isn't possible for this case where we hash |
| // both sides separately |
| |
| // find the common datatype that fits both columns |
| UInt32 flags = |
| ((CmpCommon::getDefault(LIMIT_MAX_NUMERIC_PRECISION) == DF_ON) |
| ? NAType::LIMIT_MAX_NUMERIC_PRECISION : 0); |
| |
| const NAType *resultType = buildType.synthesizeType( |
| SYNTH_RULE_UNION, |
| buildType, |
| probeType, |
| generator->wHeap(), |
| &flags); |
| CMPASSERT(resultType); |
| |
| // matchScales() has been called in preCodeGen() |
| |
| // add type conversion operators if necessary |
| if (NOT (buildType == *resultType)) { |
| buildHashCol = new(generator->wHeap()) Cast(buildHashCol,resultType); |
| } |
| if (NOT (probeType == *resultType)) { |
| probeHashCol = new(generator->wHeap()) Cast(probeHashCol,resultType); |
| } |
| } |
| |
| NABoolean doCasesensitiveHash = FALSE; |
| |
| if (buildType.getTypeQualifier() == NA_CHARACTER_TYPE && |
| probeType.getTypeQualifier() == NA_CHARACTER_TYPE) { |
| |
| const CharType &cBuildType = (CharType&)buildType; |
| const CharType &cProbeType = (CharType&)probeType; |
| |
| if (cBuildType.isCaseinsensitive() != cProbeType.isCaseinsensitive()) |
| doCasesensitiveHash = TRUE; |
| } |
| |
| |
| // make the hash function for the right value, to be hashed |
| // into the hash table while building the hash table. |
| buildHashCol->bindNode(generator->getBindWA()); |
| BuiltinFunction * hashFunction = |
| new(generator->wHeap()) Hash(buildHashCol); |
| if (buildHashTree) { |
| buildHashTree = new(generator->wHeap()) HashComb(buildHashTree, |
| hashFunction); |
| } |
| else { |
| buildHashTree = hashFunction; |
| } |
| |
| if (doCasesensitiveHash) |
| { |
| ((Hash*)hashFunction)->setCasesensitiveHash(TRUE); |
| |
| ((HashComb*)buildHashTree)->setCasesensitiveHash(TRUE); |
| } |
| |
| // make the hash function for the left value. This value is to |
| // be hashed into the hash table while probing the hash table. |
| hashFunction = new(generator->wHeap()) Hash(probeHashCol); |
| if (probeHashTree) { |
| probeHashTree = new(generator->wHeap()) HashComb(probeHashTree, |
| hashFunction); |
| } |
| else { |
| probeHashTree = hashFunction; |
| } |
| |
| if (doCasesensitiveHash) |
| { |
| ((Hash*)hashFunction)->setCasesensitiveHash(TRUE); |
| |
| ((HashComb*)probeHashTree)->setCasesensitiveHash(TRUE); |
| } |
| } |
| |
| // hash value is an unsigned long. (compare typedef SimpleHashValue in |
| // common/BaseType.h). It could be made a bigger datatype, |
| // if need be. |
| buildHashTree = new (generator->wHeap()) |
| Cast(buildHashTree, new (generator->wHeap()) SQLInt(generator->wHeap(), FALSE, FALSE)); |
| probeHashTree = new (generator->wHeap()) |
| Cast(probeHashTree, new (generator->wHeap()) SQLInt(generator->wHeap(), FALSE, FALSE)); |
| buildHashTree->setConstFoldingDisabled(TRUE); |
| probeHashTree->setConstFoldingDisabled(TRUE); |
| |
| // bind/type propagate the hash evaluation tree |
| buildHashTree->bindNode(generator->getBindWA()); |
| probeHashTree->bindNode(generator->getBindWA()); |
| |
| // add the build root value id to the map table. This is the hash value. |
| Attributes * mapAttr; |
| mapAttr = (generator->addMapInfo(buildHashTree->getValueId(), 0))-> |
| getAttr(); |
| mapAttr->setAtp(workAtpPos); |
| mapAttr->setAtpIndex(hashValueAtpIndex); |
| ULng32 len; |
| ExpTupleDesc::computeOffsets(mapAttr, |
| ExpTupleDesc::SQLARK_EXPLODED_FORMAT, len); |
| |
| // add the probe root value id to the map table. This is the hash value. |
| mapAttr = (generator->addMapInfo(probeHashTree->getValueId(), 0))-> |
| getAttr(); |
| |
| mapAttr->copyLocationAttrs(generator-> |
| getMapInfo(buildHashTree-> |
| getValueId())->getAttr()); |
| |
| // generate code to evaluate the hash expression |
| expGen->generateArithExpr(buildHashTree->getValueId(), |
| ex_expr::exp_ARITH_EXPR, |
| &rightHashExpr); |
| |
| // generate the probe hash expression |
| expGen->generateArithExpr(probeHashTree->getValueId(), |
| ex_expr::exp_ARITH_EXPR, |
| &leftHashExpr); |
| }; |
| |
| |
| // only the case of single column in the NOT IN is handled. |
| // these 2 expression (checkInnerNullExpr_ and checkOuterNullExpr_) |
| // will be empty for the multi-column case |
| // generate the check inner null expression |
| ex_expr *checkInnerNullExpression = 0; |
| if (!(getCheckInnerNullExpr().isEmpty())) |
| { |
| ItemExpr * newExprTree = getCheckInnerNullExpr().rebuildExprTree(ITM_AND,TRUE,TRUE); |
| expGen->generateExpr(newExprTree->getValueId(),ex_expr::exp_SCAN_PRED, |
| &checkInnerNullExpression); |
| |
| } |
| |
| // generate the check outer null expression |
| ex_expr *checkOuterNullExpression = 0; |
| if (!(getCheckOuterNullExpr().isEmpty())) |
| { |
| ItemExpr * newExprTree = getCheckOuterNullExpr().rebuildExprTree(ITM_AND,TRUE,TRUE); |
| expGen->generateExpr(newExprTree->getValueId(),ex_expr::exp_SCAN_PRED, |
| &checkOuterNullExpression); |
| |
| } |
| |
| // allocate two map tables, so that we later can remove the left childs map |
| // table |
| MapTable * myMapTable4 = generator->appendAtEnd(); |
| MapTable * myMapTable5 = generator->appendAtEnd(); |
| // MT -> MT0 -> MT1 -> RC MT -> MT2 -> MT3 -> LC MT -> MT4 -> MT5 |
| |
| //determine the tuple format and whether we want to resize rows or not |
| // base the decision on the right side and left side |
| NABoolean bmo_affinity = (CmpCommon::getDefault(COMPRESSED_INTERNAL_FORMAT_BMO_AFFINITY) == DF_ON); |
| NABoolean resizeCifRecord = FALSE; |
| ExpTupleDesc::TupleDataFormat tupleFormat ; |
| NABoolean considerBufferDefrag = FALSE; |
| |
| if (! bmo_affinity && |
| getCachedTupleFormat() != ExpTupleDesc::UNINITIALIZED_FORMAT && |
| CmpCommon::getDefault(COMPRESSED_INTERNAL_FORMAT) == DF_SYSTEM && |
| CmpCommon::getDefault(COMPRESSED_INTERNAL_FORMAT_BMO) == DF_SYSTEM) |
| { |
| resizeCifRecord = getCachedResizeCIFRecord(); |
| tupleFormat = getCachedTupleFormat(); |
| considerBufferDefrag = getCachedDefrag() && resizeCifRecord; |
| } |
| else |
| { |
| tupleFormat = determineInternalFormat( rightBufferValIds, |
| leftOutputValIds, |
| this, |
| resizeCifRecord, |
| generator, |
| bmo_affinity, |
| considerBufferDefrag, |
| useUniqueHashJoin); |
| considerBufferDefrag = considerBufferDefrag && resizeCifRecord; |
| } |
| // generate the rightMoveInExpr |
| ValueIdList rightMoveTargets1; |
| MapTable *rightExtValMapTable = NULL; |
| ULng32 rightRowLength = 0; |
| if (rightBufferValIds.entries() > 0) { |
| // Offsets are based on the row starting after the HashRow structure. |
| expGen->generateContiguousMoveExpr(rightBufferValIds, |
| -1, // add convert nodes |
| workAtpPos, |
| extRightRowAtpIndex1, |
| tupleFormat, |
| rightRowLength, |
| &rightMoveInExpr, |
| 0, // tuple descriptor |
| ExpTupleDesc::SHORT_FORMAT, |
| &rightExtValMapTable, |
| &rightMoveTargets1); |
| }; |
| |
| ULng32 extRightRowLength = rightRowLength + sizeof(HashRow); |
| |
| // MT -> MT0 -> MT1 -> RC MT -> MT2 -> MT3 -> LC MT |
| // -> MT4 -> MT5 -> ERC (new valIds) |
| |
| // remove the map table from the right child. We don't need it anymore |
| // first un-chain the left child's map table |
| generator->unlinkNext(myMapTable2); |
| // now delete the right child's map table |
| // This will delete MT1 -> RC MT and MT2, so we cannot reference those |
| // any more.. |
| generator->removeAll(myMapTable0); |
| // make sure we cannot reference them anymore.. |
| myMapTable1 = NULL; |
| myMapTable2 = NULL; |
| // and append the left childs map table again |
| generator->appendAtEnd(myMapTable3); |
| // MT -> MT0 -> MT3 -> LC MT -> MT4 -> MT5 -> ERC (new valIds) |
| |
| |
| // generate leftMoveExpr to move a left child row directly to the |
| // parents buffer |
| ULng32 leftRowLength = 0; |
| if (leftOutputValIds.entries() > 0) { |
| expGen->generateContiguousMoveExpr(leftOutputValIds, |
| -1, |
| workAtpPos, |
| leftRowAtpIndex, |
| tupleFormat, |
| leftRowLength, |
| &leftMoveExpr); |
| // get rid of the map table which was just appended by the last call |
| generator->removeLast(); |
| }; |
| // MT -> MT0 -> MT3 -> LC MT -> MT4 -> MT5 -> ERC (new valIds) |
| // generate the leftMoveInExpr |
| ValueIdList leftMoveTargets; |
| MapTable *leftExtValMapTable = NULL; |
| if (leftOutputValIds.entries() > 0) { |
| // Offsets are based on the row starting after the HashRow structure. |
| expGen->generateContiguousMoveExpr(leftOutputValIds, |
| -1, // add convert nodes |
| workAtpPos, |
| extLeftRowAtpIndex, |
| tupleFormat, |
| leftRowLength, |
| &leftMoveInExpr, |
| 0, |
| ExpTupleDesc::SHORT_FORMAT, |
| &leftExtValMapTable, |
| &leftMoveTargets); |
| }; |
| |
| ULng32 extLeftRowLength = leftRowLength + sizeof(HashRow); |
| |
| // MT -> MT0 -> MT3 -> LC MT -> MT4 -> MT5 |
| // -> ERC (new valIds) -> ELC (new valIds) |
| // add the map table of the "extended" right row |
| if(rightExtValMapTable) { |
| generator->appendAtEnd(rightExtValMapTable); |
| } |
| // MT -> MT0 -> MT3 -> LC MT -> MT4 -> MT5 |
| // -> ERC (new valIds) -> ELC (new valIds) -> ERC (old Ids) |
| // generate probeSearchExpr1 |
| if (! getEquiJoinPredicates().isEmpty()) { |
| ItemExpr * newPredTree; |
| newPredTree = getEquiJoinPredicates().rebuildExprTree(ITM_AND,TRUE,TRUE); |
| expGen->generateExpr(newPredTree->getValueId(), ex_expr::exp_SCAN_PRED, |
| &probeSearchExpr1); |
| } |
| |
| // generate beforeJoinPred1 |
| if (! joinPred().isEmpty()) { |
| ItemExpr * newPredTree; |
| newPredTree = joinPred().rebuildExprTree(ITM_AND,TRUE,TRUE); |
| expGen->generateExpr(newPredTree->getValueId(), ex_expr::exp_SCAN_PRED, |
| &beforeJoinPred1); |
| } |
| |
| // generate afterJoinPred1 |
| if (! selectionPred().isEmpty()) { |
| ItemExpr * newPredTree; |
| newPredTree = selectionPred().rebuildExprTree(ITM_AND,TRUE, TRUE); |
| expGen->generateExpr(newPredTree->getValueId(), ex_expr::exp_SCAN_PRED, |
| &afterJoinPred1); |
| } |
| |
| // MT -> MT0 -> MT3 -> LC MT -> MT4 -> MT5 |
| // -> ERC (new valIds) -> ELC (new valIds) -> ERC (old Ids) |
| // remove MapTable for left child row. First un-chain the extended |
| // right row tupps |
| generator->unlinkNext(myMapTable4); |
| // now we can savely delete the MapTable for the left child row |
| generator->unlinkNext(myMapTable0); |
| // add the MapTable for the "extended" right row again |
| generator->appendAtEnd(myMapTable5); |
| |
| |
| // For unique Hash Join, there is no leftExtValMapTable. |
| // |
| if(!useUniqueHashJoin) { |
| // add the MapTable for the "extended" left row |
| // if it exists. It will not exist for Unique Hash Join |
| generator->appendAtEnd(leftExtValMapTable); |
| } |
| |
| // MT -> MT0 -> MT5 -> ERC (new valIds) -> ELC (new valIds) |
| // -> ERC (old Ids) -> ELC (old Ids) |
| |
| if (! getEquiJoinPredicates().isEmpty()) { |
| // Generate rightSearchExpr for the build table. This expression |
| // compares two "extended" right rows. The MapTable contains |
| // only one of these rows (extRightRowAtpIndex1). To get the |
| // MapTable and ValueIdList for the second row, we go thru the |
| // rightBufferValIds and create new itemexpressions from this list. |
| ValueIdList rightMoveTargets2; |
| CollIndex i = 0; |
| for (i = 0; i < rightBufferValIds.entries(); i++) { |
| // create the new item xpression |
| ItemExpr * newCol = new(generator->wHeap()) |
| NATypeToItem((NAType *)&(rightBufferValIds[i].getType())); |
| newCol->synthTypeAndValueId(); |
| |
| // copy the attributes from the first entended row |
| Attributes * originalAttr = |
| generator->getMapInfo(rightMoveTargets1[i])->getAttr(); |
| |
| Attributes * newAttr = |
| generator->addMapInfo(newCol->getValueId(), 0)->getAttr(); |
| newAttr->copyLocationAttrs(originalAttr); |
| |
| // only atpindex is different |
| newAttr->setAtpIndex(extRightRowAtpIndex2); |
| |
| // add the new valueId to the list of movetargets |
| rightMoveTargets2.insert(newCol->getValueId()); |
| }; |
| |
| // MT -> MT0 -> MT5 -> ERC (new valIds) -> ELC (new valIds) |
| // -> ERC (old Ids) -> ELC (old Ids) -> ERC2 (new valIds) |
| ValueIdSet searchExpr; |
| |
| for (i = 0; i < hashKeyColumns.entries(); i++) { |
| // hashKeyColumns[i] remembers which column in the hash table |
| // the i-th hash key column is. |
| CollIndex hashColNum = hashKeyColumns[i]; |
| ItemExpr *eqNode = |
| new(generator->wHeap()) BiRelat(ITM_EQUAL, |
| rightMoveTargets1[hashColNum].getItemExpr(), |
| rightMoveTargets2[hashColNum].getItemExpr(), |
| // specialNulls == TRUE means that the right search |
| // expression would treat NULL values as identical (when |
| // a new row is inserted into the hash-table); hence such |
| // rows would be chained. |
| // Note: NULL values in the hash table are treated as |
| // non-identical by the probe search expressions (when |
| // probing with left rows); i.e., the above chain of right |
| // rows with NULLs would never be matched! |
| TRUE); |
| eqNode->bindNode(generator->getBindWA()); |
| // collect all the comparison preds in a value id set |
| searchExpr += eqNode->getValueId(); |
| } |
| |
| // AND the individual parts of the search expression together and |
| // generate the expression (note that code for the build table columns |
| // and for the move targets has been generated before, only the |
| // comparison code itself should be generated here) |
| ItemExpr * newPredTree = searchExpr.rebuildExprTree(ITM_AND,TRUE,TRUE); |
| newPredTree->bindNode(generator->getBindWA()); |
| expGen->generateExpr(newPredTree->getValueId(), |
| ex_expr::exp_SCAN_PRED, |
| &rightSearchExpr); |
| } |
| |
| // The Unique Hash Join does not use the probeSearchExpr2 since it |
| // does not support Overflow |
| // |
| if(!useUniqueHashJoin) { |
| // generate probeSearchExpr2 |
| if (! getEquiJoinPredicates().isEmpty()) { |
| ItemExpr * newPredTree; |
| newPredTree = getEquiJoinPredicates().rebuildExprTree(ITM_AND,TRUE,TRUE); |
| expGen->generateExpr(newPredTree->getValueId(), ex_expr::exp_SCAN_PRED, |
| &probeSearchExpr2); |
| } |
| } |
| |
| // generate beforeJoinPred2 |
| if (! joinPred().isEmpty()) { |
| ItemExpr * newPredTree; |
| newPredTree = joinPred().rebuildExprTree(ITM_AND,TRUE,TRUE); |
| expGen->generateExpr(newPredTree->getValueId(), ex_expr::exp_SCAN_PRED, |
| &beforeJoinPred2); |
| } |
| |
| // generate afterJoinPred2 |
| if (! selectionPred().isEmpty()) { |
| ItemExpr * newPredTree; |
| newPredTree = selectionPred().rebuildExprTree(ITM_AND,TRUE, TRUE); |
| expGen->generateExpr(newPredTree->getValueId(), ex_expr::exp_SCAN_PRED, |
| &afterJoinPred2); |
| } |
| |
| // generate the rightMoveOutExpr |
| rightRowLength = 0; |
| MapTable * rightValMapTable = NULL; |
| Int32 bulkMoveOffset = -2; // try to generate a bulk move |
| |
| if ( rightOutputValIds.entries() > 0 && rightOutputNeeded ) { |
| ValueIdList *rmo; |
| if(tupleFormat == ExpTupleDesc::SQLMX_ALIGNED_FORMAT) { |
| // For aligned rows, we cannot return just the prefix of the row. |
| // This is because the columns in the row may have beed rearraged and |
| // the values may not all be at fixed offsets. |
| // |
| // So for aligned rows, return the whole right buffer. |
| rmo = &rightBufferValIds; |
| } else { |
| // For exploded rows, we can return just the prefix. |
| // So, return just the values that are needed above. |
| rmo = &rightOutputValIds; |
| } |
| |
| expGen->generateContiguousMoveExpr(*rmo, |
| -1, // add convert nodes |
| workAtpPos, |
| rightRowAtpIndex, |
| tupleFormat, |
| rightRowLength, |
| &rightMoveOutExpr, |
| 0, |
| ExpTupleDesc::SHORT_FORMAT, |
| &rightValMapTable, |
| NULL, // target Value Id List |
| 0, // start offset |
| &bulkMoveOffset); |
| } |
| |
| // generate the leftMoveOutExpr |
| MapTable * leftValMapTable = NULL; |
| if (leftOutputValIds.entries() > 0) { |
| expGen->generateContiguousMoveExpr(leftOutputValIds, |
| -1, // add convert nodes |
| workAtpPos, |
| leftRowAtpIndex, |
| tupleFormat, |
| leftRowLength, |
| &leftMoveOutExpr, |
| 0, |
| ExpTupleDesc::SHORT_FORMAT, |
| &leftValMapTable); |
| } |
| |
| // remove the MapTables describing the extended rows |
| if (rightExtValMapTable) { |
| // Unlinks leftExtValMapTable |
| generator->unlinkNext(rightExtValMapTable); |
| } else { |
| // If we do not have a rightExtValMapTable, the code below will remove |
| // the leftExtValMapTable, and whatever follows, from the main maptable |
| // chain. |
| // If we don't do this the removeAll() 4 lines below, will delete the |
| // leftExtValMapTable (if it existed) and we need it later on.. |
| if (leftExtValMapTable) { |
| // Note that leftExtValMapTable may now have ERC2 (new valIds) as a child |
| // at this point.... |
| // If rightExtValMapTable, existsd we now are on a separate chain... |
| generator->unlinkMe(leftExtValMapTable); |
| } |
| } |
| // At this poin we have something like this.. |
| // MT -> MT0 -> MT5 -> ERC (new valIds) -> ELC (new valIds) |
| // -> ERC (old Ids) -> ELC (old Ids) -> XX potentialy more nodes here XX |
| // and everything from MT5 and onwards will now be deleted!! |
| generator->removeAll(myMapTable0); |
| myMapTable5 = NULL; // make sure we cannot use the stale data anymore.. |
| rightExtValMapTable = NULL; // make sure we cannot use the stale data anymore. |
| // Here is how the map table list looks like now: |
| // MT -> MT0 |
| |
| ULng32 instRowLength = 0; |
| ULng32 instRowForRightJoinLength = 0; |
| MapTable *instNullForLeftJoinMapTable = NULL; |
| // add MapTable for the right row |
| if ( rightOutputNeeded ) { |
| generator->appendAtEnd(rightValMapTable); |
| // Here is how the map table list looks like now: |
| // MT -> MT0 -> RV |
| |
| // generate nullInstExpr. instantiateValuesForLeftJoin generates |
| // 2 expressions. The first one is to evaluate the right expression |
| // and move the result into the null instantiated row. The second one |
| // is to initialize the instantiated row with null values. |
| // instantiateValuesForLeftJoin also adds info for the instatiated |
| // null row to the MapTable. |
| if (nullInstantiatedOutput().entries() > 0) { |
| instantiateValuesForLeftJoin(generator, |
| workAtpPos, instRowForLeftJoinAtpIndex, |
| &leftJoinExpr, &nullInstForLeftJoinExpr, |
| &instRowLength, |
| &instNullForLeftJoinMapTable, |
| tupleFormat); |
| }; |
| |
| // Check point. |
| if (isLeftJoin() && !selectionPred().isEmpty()) |
| { |
| MapTable * myMapTableX = generator->appendAtEnd(); |
| |
| // Add back the left map table temporarily for generating the sel pred. |
| // for Phase 2. |
| generator->appendAtEnd(myMapTable3); |
| // XXX -> MT3 -> LC MT -> MT4 -> MT5 -> ERC (new valIds) |
| |
| // generate afterJoinPred3 |
| ItemExpr * newPredTree; |
| newPredTree = selectionPred().rebuildExprTree(ITM_AND,TRUE,TRUE); |
| expGen->generateExpr(newPredTree->getValueId(), ex_expr::exp_SCAN_PRED, |
| &afterJoinPred3); |
| |
| // myMapTable3 may be needed later on, unlink before we remove myMapTableX |
| |
| // For those that might use myMapTable3 below, beware that the chain |
| // starting with myMapTable3 now may have additional map nodes appended |
| // as a result of the call to generateExpr() above, as compared to what |
| // looked like before when we unlinked myMapTable3 from the main chain. |
| // At this point the myMapTable3 chain will look something like this: |
| // MT3 -> LC MT -> MT4 -> MT5 -> ERC (new valIds) -> XX |
| // where XX represents whatever mapTables got added above |
| |
| generator->unlinkMe(myMapTable3); |
| |
| // This is how the check point is made use of. |
| generator->removeAll(myMapTableX); |
| |
| // For Left Joins (including full joins), generate afterJoinPred4 |
| // |
| { |
| // Add back the left extended map table temporarily for |
| // generating the selection predicate for Phase 3. |
| |
| generator->appendAtEnd(leftExtValMapTable); |
| |
| // generate afterJoinPred4 |
| newPredTree = selectionPred().rebuildExprTree(ITM_AND,TRUE,TRUE); |
| expGen->generateExpr(newPredTree->getValueId(), |
| ex_expr::exp_SCAN_PRED, |
| &afterJoinPred4); |
| |
| // This is how the check point is made use of. |
| generator->removeAll(myMapTableX); |
| // we just deleted leftExtValMapTable... |
| // make sure we don't reference it |
| leftExtValMapTable = NULL; |
| |
| } |
| |
| generator->removeLast(); // It should actually remove myMapTableX. |
| } //if (isLeftJoin() && !selectionPred().isEmpty()) |
| |
| // set the atp for right row values back to 0. |
| // Set the atp_index to the last returned tupp. |
| if (rightOutputValIds.entries()) { |
| for (CollIndex ix = 0; ix < rightOutputValIds.entries(); ix++) { |
| valId = rightOutputValIds[ix]; |
| // do this only, if the valueId is not input to this node |
| if (NOT getGroupAttr()->getCharacteristicInputs().contains(valId)) { |
| MapInfo * map_info = generator->getMapInfoAsIs(valId); |
| if (map_info) { |
| Attributes * attr = map_info->getAttr(); |
| attr->setAtp(0); |
| attr->setAtpIndex(returnedRightRowAtpIndex); |
| }; |
| }; |
| }; |
| }; |
| |
| // set the atp index for values in the instantiated row. |
| if (nullInstantiatedOutput().entries()) { |
| for (CollIndex ix = 0; ix < nullInstantiatedOutput().entries(); ix++) { |
| valId = nullInstantiatedOutput()[ix]; |
| |
| Attributes * attr = generator->getMapInfo(valId)->getAttr(); |
| attr->setAtp(0); |
| attr->setAtpIndex(returnedInstRowAtpIndex); |
| } |
| }; |
| }; // if ( rightOutputNeeded ) |
| |
| |
| if(useUniqueHashJoin) { |
| // Add the MapTable for the left child |
| // unique Hash Join passes the left child values as is (copyAtp()). |
| // |
| generator->appendAtEnd(myMapTable3); |
| } else { |
| // add the MapTable for the left row |
| // |
| generator->appendAtEnd(leftValMapTable); |
| } |
| |
| // Here is how the map table list looks like now: |
| // MT -> MT0 -> RV -> LV |
| |
| /***************** Generate the nullInstForRightJoinExprs *************/ |
| |
| // generate nullInstForRightJoinExpr. instantiateValuesForRightJoin |
| // generates 2 expressions. The first one is to evaluate the right |
| // expression and move the result into the null instantiated row. |
| // The second one is to initialize the instantiated row with null values. |
| // instantiateValuesForRightJoin also adds info for the instatiated |
| // null row to the MapTable. |
| |
| if (nullInstantiatedForRightJoinOutput().entries() > 0) { |
| instantiateValuesForRightJoin(generator, |
| workAtpPos, instRowForRightJoinAtpIndex, |
| &rightJoinExpr, &nullInstForRightJoinExpr, |
| &instRowForRightJoinLength, |
| NULL, // Don't need a MapTable back. At |
| // this point, we have generated all |
| // the necessary expressions. This |
| // code is here to be consistent with the |
| // this one's counterpart |
| // - instantiateValuesForLeftJoin |
| tupleFormat); |
| } // nullInstantiatedForRightJoinOutput() |
| |
| if (isRightJoin()&& !selectionPred().isEmpty()) |
| { |
| // Use the check point technique that is used in |
| // generating the afterJoinPred3 & afterJoinPred4 |
| // for isLeftJoin() |
| MapTable * myMapTableX = generator->appendAtEnd(); |
| |
| // add the null instantitated columns maptable back |
| // to generate the after join selection predicted. |
| // For this expression, the values should all be |
| // available at instNullForLeftJoinMapTable and |
| // instRowForLeftJoinAtpIndex |
| generator->appendAtEnd(instNullForLeftJoinMapTable); |
| |
| // generate afterJoinPred5 |
| // We only need one predicate here, since the nullInstantiated |
| // versions of both the left row and the right row are |
| // available in their respective nullInstantiated tupp. |
| // Note that the left join case will needs both afterJoinPred3 and |
| // afterJoinPred4. |
| ItemExpr * newPredTree; |
| newPredTree = selectionPred().rebuildExprTree(ITM_AND,TRUE,TRUE); |
| expGen->generateExpr(newPredTree->getValueId(), ex_expr::exp_SCAN_PRED, |
| &afterJoinPred5); |
| |
| // This is how the check point is made use of. |
| generator->removeAll(myMapTableX); |
| |
| // We just deleted instNullForLeftJoinMapTable, make sure we don't use |
| // it any more.. |
| instNullForLeftJoinMapTable = NULL; |
| |
| generator->removeLast(); // It should actually remove myMapTableX. |
| } |
| |
| // set the atp for the left row values back to 0 |
| if (leftOutputValIds.entries()) { |
| for (CollIndex ix = 0; ix < leftOutputValIds.entries(); ix++) { |
| valId = leftOutputValIds[ix]; |
| |
| MapInfo * map_info = |
| generator->getMapInfoFromThis(leftValMapTable, valId); |
| if (map_info) { |
| Attributes * attr = map_info->getAttr(); |
| attr->setAtp(0); |
| attr->setAtpIndex(returnedLeftRowAtpIndex); |
| }; |
| }; |
| }; |
| |
| // set the atp index for values in the instantiated row. |
| if (nullInstantiatedForRightJoinOutput().entries()) { |
| for (CollIndex ix = 0; ix < nullInstantiatedForRightJoinOutput().entries(); ix++) { |
| valId = nullInstantiatedForRightJoinOutput()[ix]; |
| |
| Attributes * attr = generator->getMapInfo(valId)->getAttr(); |
| attr->setAtp(0); |
| attr->setAtpIndex(returnedInstRowForRightJoinAtpIndex); |
| } |
| }; |
| |
| // determine the expected size of the inner table |
| Cardinality innerExpectedRows = (Cardinality) child(1)->getGroupAttr()-> |
| getOutputLogPropList()[0]->getResultCardinality().value(); |
| |
| // If this HJ is performed within ESPs, then number of records |
| // processed by each ESP is a subset of total records. |
| // Inner side -- divide only for type 1 join !! (type 2 join sends all the |
| // rows to each ESP, and for the rest we assume the same as a worst case). |
| if ( saveNumEsps > 0 && getParallelJoinType() == 1 ) |
| innerExpectedRows /= (Cardinality) saveNumEsps ; |
| |
| // determine the expected size of the outer table |
| Cardinality outerExpectedRows = (Cardinality) child(0)->getGroupAttr()-> |
| getOutputLogPropList()[0]->getResultCardinality().value(); |
| |
| // If this HJ is performed within ESPs, then number of records |
| // processed by each ESP is a subset of total records. |
| if ( saveNumEsps > 0 ) |
| outerExpectedRows /= (Cardinality) saveNumEsps ; |
| |
| // determine the size of the HJ buffers. This hash buffer is used to store |
| // the incoming (inner or outer) rows, and may be written to disk (overflow) |
| |
| // first determine the minimum size for the hash table buffers. |
| // a buffer has to store at least one extended inner or outer row |
| // plus overhead such as hash buffer header etc |
| ULng32 minHBufferSize = MAXOF(extLeftRowLength, extRightRowLength) + |
| ROUND8(sizeof(HashBufferHeader)) + 8; |
| |
| // determine the minimum result sql buffer size (may need to store result |
| // rows comprising of both inner and outer incoming rows) |
| ULng32 minResBufferSize = leftRowLength + sizeof(tupp_descriptor); |
| if ( rightOutputNeeded ) |
| minResBufferSize += rightRowLength + sizeof(tupp_descriptor); |
| |
| // get the default value for the (either hash or result) buffer size |
| ULng32 bufferSize = (ULng32) getDefault(GEN_HSHJ_BUFFER_SIZE); |
| |
| // currently the default hash buffer size is 56K (DP2 can not take anything |
| // larger), so if this Hash-Join may overflow, and the input row exceeds |
| // this size, we issue an error. If overflow is not allowed, then we may |
| // resize the hash buffer to accomodate the larger input row. |
| ULng32 hashBufferSize = bufferSize ; |
| if ( minHBufferSize > bufferSize ) { |
| // On linux we can handle any size of overflow buffer |
| hashBufferSize = minHBufferSize ; // use a larger Hash-Buffer |
| } |
| |
| // adjust up the result buffer size, if needed |
| bufferSize = MAXOF(minResBufferSize, bufferSize); |
| |
| // determione the memory usage (amount of memory as percentage from total |
| // physical memory used to initialize data structures) |
| unsigned short memUsagePercent = |
| (unsigned short) getDefault(BMO_MEMORY_USAGE_PERCENT); |
| |
| // determine the size of the up queue. We should be able to keep at least |
| // result buffer worth od data in the up queue |
| queue_index upQueueSize = (queue_index)(bufferSize / minResBufferSize); |
| // we want at least 4 entries in the up queue |
| upQueueSize = MAXOF(upQueueSize,(queue_index) 4); |
| // the default entry might be even larger |
| upQueueSize = MAXOF(upQueueSize, (queue_index)getDefault(GEN_HSHJ_SIZE_UP)); |
| |
| // Support for a RE-USE of the hash table, in case the input values for |
| // the right/inner child are the same as in the previous input. |
| ex_expr * moveInputExpr = 0; |
| ex_expr * checkInputPred = 0; |
| ULng32 inputValuesLen = 0; |
| |
| if ( isReuse() ) { // only if testing is needed |
| // Create a copy of the input values. This set represent the saved input |
| // values which are compared to the incoming values. |
| |
| // Generate expression to move the relevant input values |
| if (! moveInputValues().isEmpty() ) { |
| ValueIdList vid_list( moveInputValues() ); |
| expGen->generateContiguousMoveExpr(vid_list, |
| 0, // dont add conv nodes |
| workAtpPos, |
| prevInputTuppIndex, |
| tupleFormat, |
| inputValuesLen, &moveInputExpr); |
| } |
| |
| // generate expression to see if the relevant input values have changed. |
| // If changed, then we need to rebuild the hash table. |
| if (! checkInputValues().isEmpty() ) { |
| ItemExpr * newPredTree = |
| checkInputValues().rebuildExprTree(ITM_AND,TRUE,TRUE); |
| expGen->generateExpr(newPredTree->getValueId(), ex_expr::exp_SCAN_PRED, |
| &checkInputPred); |
| } |
| } |
| |
| |
| if(useUniqueHashJoin) { |
| // The unique hash join does not use the rightMoveOutExpr, |
| // however, it was generated to get the mapTable (could use |
| // processAttributes()). |
| // Set it to NULL here. |
| // |
| rightMoveOutExpr = NULL; |
| rightRowAtpIndex = -1; |
| |
| // The Unique Hash Join does not have an extended left row. |
| // However, extLeftRowLength was set to a non-zero value above. |
| // |
| extLeftRowLength = 0; |
| |
| // Check to make sure things are as we expect for the unique hash join |
| // |
| GenAssert(!rightMoveOutExpr, "Bad Unique Hash Join: rightMoveOutExpr"); |
| GenAssert(!leftMoveExpr, "Bad Unique Hash Join: leftMoveExpr"); |
| GenAssert(!leftMoveInExpr, "Bad Unique Hash Join: leftMoveInExpr"); |
| GenAssert(!leftMoveOutExpr, "Bad Unique Hash Join: leftMoveOutExpr"); |
| GenAssert(!probeSearchExpr2, "Bad Unique Hash Join: probeSearchExpr2"); |
| GenAssert(!leftJoinExpr, "Bad Unique Hash Join: leftJoinExpr"); |
| GenAssert(!nullInstForLeftJoinExpr, |
| "Bad Unique Hash Join: nullInstForLeftJoinExpr"); |
| GenAssert(!beforeJoinPred1, "Bad Unique Hash Join: beforeJoinPred1"); |
| GenAssert(!beforeJoinPred2, "Bad Unique Hash Join: beforeJoinPred2"); |
| GenAssert(!afterJoinPred1, "Bad Unique Hash Join: afterJoinPred1"); |
| GenAssert(!afterJoinPred2, "Bad Unique Hash Join: afterJoinPred2"); |
| GenAssert(!afterJoinPred3, "Bad Unique Hash Join: afterJoinPred3"); |
| GenAssert(!afterJoinPred4, "Bad Unique Hash Join: afterJoinPred4"); |
| GenAssert(leftRowLength == 0, "Bad Unique Hash Join: leftRowLength"); |
| GenAssert(extLeftRowLength == 0, "Bad Unique Hash Join: extLeftRowLength"); |
| GenAssert(instRowLength == 0, "Bad Unique Hash Join: instRowLength"); |
| GenAssert(leftRowAtpIndex == -1, "Bad Unique Hash Join: leftRowAtpIndex"); |
| GenAssert(rightRowAtpIndex == -1, "Bad Unique Hash Join: rightRowAtpIndex"); |
| GenAssert(instRowForLeftJoinAtpIndex == -1, |
| "Bad Unique Hash Join: instRowForLeftJoinAtpIndex"); |
| GenAssert(returnedLeftRowAtpIndex == -1, |
| "Bad Unique Hash Join: returnedLeftRowAtpIndex"); |
| GenAssert(returnedInstRowAtpIndex == -1, |
| "Bad Unique Hash Join: returnedInstRowAtpIndex"); |
| GenAssert(!rightJoinExpr, "Bad Unique Hash Join: rightJoinExpr"); |
| GenAssert(!nullInstForRightJoinExpr, |
| "Bad Unique Hash Join: nullInstForRightJoinExpr"); |
| GenAssert(instRowForRightJoinAtpIndex == -1, |
| "Bad Unique Hash Join: instRowForRightJoinAtpIndex"); |
| GenAssert(returnedInstRowForRightJoinAtpIndex == -1, |
| "Bad Unique Hash Join: returnedInstRowForRightJoinAtpIndex"); |
| GenAssert(instRowForRightJoinLength == 0, |
| "Bad Unique Hash Join: instRowForRightJoinLength"); |
| } |
| |
| |
| short scrthreshold = (short) CmpCommon::getDefaultLong(SCRATCH_FREESPACE_THRESHOLD_PERCENT); |
| short hjGrowthPercent = |
| getGroupAttr()->getOutputLogPropList()[0]->getBMOgrowthPercent(); |
| // now we have generated all the required expressions and the MapTable |
| // reflects the returned rows. Let's generate the hash join TDB now |
| ComTdbHashj * hashj_tdb = |
| new(space) ComTdbHashj(leftChildTdb, |
| rightChildTdb, |
| givenDesc, |
| returnedDesc, |
| rightHashExpr, |
| rightMoveInExpr, |
| rightMoveOutExpr, |
| rightSearchExpr, |
| leftHashExpr, |
| leftMoveExpr, |
| leftMoveInExpr, |
| leftMoveOutExpr, |
| probeSearchExpr1, |
| probeSearchExpr2, |
| leftJoinExpr, |
| nullInstForLeftJoinExpr, |
| beforeJoinPred1, |
| beforeJoinPred2, |
| afterJoinPred1, |
| afterJoinPred2, |
| afterJoinPred3, |
| afterJoinPred4, |
| afterJoinPred5, |
| checkInputPred, |
| moveInputExpr, |
| (Lng32)inputValuesLen, |
| prevInputTuppIndex, |
| rightRowLength, |
| extRightRowLength, |
| leftRowLength, |
| extLeftRowLength, |
| instRowLength, |
| workCriDesc, |
| leftRowAtpIndex, |
| extLeftRowAtpIndex, |
| rightRowAtpIndex, |
| extRightRowAtpIndex1, |
| extRightRowAtpIndex2, |
| hashValueAtpIndex, |
| instRowForLeftJoinAtpIndex, |
| returnedLeftRowAtpIndex, |
| returnedRightRowAtpIndex, |
| returnedInstRowAtpIndex, |
| memUsagePercent, |
| (short)getDefault(GEN_MEM_PRESSURE_THRESHOLD), |
| scrthreshold, |
| (queue_index)getDefault(GEN_HSHJ_SIZE_DOWN), |
| upQueueSize, |
| isSemiJoin(), |
| isLeftJoin(), |
| isAntiSemiJoin(), |
| useUniqueHashJoin, |
| (isNoOverflow() || |
| (CmpCommon::getDefault(EXE_BMO_DISABLE_OVERFLOW) |
| == DF_ON)), |
| isReuse(), |
| (Lng32)getDefault(GEN_HSHJ_NUM_BUFFERS), |
| bufferSize, |
| hashBufferSize, |
| (Cardinality) getGroupAttr()-> |
| getOutputLogPropList()[0]-> |
| getResultCardinality().value(), |
| innerExpectedRows, |
| outerExpectedRows, |
| isRightJoin(), |
| rightJoinExpr, |
| nullInstForRightJoinExpr, |
| instRowForRightJoinAtpIndex, |
| returnedInstRowForRightJoinAtpIndex, |
| instRowForRightJoinLength, |
| // To get the min number of buffers per a flushed |
| // cluster before is can be flushed again |
| (unsigned short) |
| getDefault(EXE_NUM_CONCURRENT_SCRATCH_IOS) |
| + (short)getDefault(COMP_INT_66), // for testing |
| (UInt32) getDefault(COMP_INT_67), // for batch num |
| //+ (short)getDefault(COMP_INT_66), // for testing |
| checkInnerNullExpression, |
| checkOuterNullExpression, |
| hjGrowthPercent, |
| |
| // For min/max optimization. |
| minMaxValsAtpIndex, |
| minMaxRowLength, |
| minMaxExpr, |
| leftDownDesc |
| ); |
| |
| generator->initTdbFields(hashj_tdb); |
| hashj_tdb->setOverflowMode(generator->getOverflowMode()); |
| if (CmpCommon::getDefault(EXE_BMO_SET_BUFFERED_WRITES) == DF_ON) |
| hashj_tdb->setBufferedWrites(TRUE); |
| if (CmpCommon::getDefault(EXE_DIAGNOSTIC_EVENTS) == DF_ON) |
| hashj_tdb->setLogDiagnostics(TRUE); |
| if (useUniqueHashJoin || // UHJ avoids check for early overflow |
| CmpCommon::getDefault(EXE_BMO_DISABLE_CMP_HINTS_OVERFLOW_HASH) == DF_ON |
| // If CQD value is SYSTEM, then no compiler hints checks for HDD |
| || |
| (((generator->getOverflowMode() == ComTdb::OFM_DISK ) || |
| ( generator->getOverflowMode() == ComTdb::OFM_MMAP)) |
| && |
| CmpCommon::getDefault(EXE_BMO_DISABLE_CMP_HINTS_OVERFLOW_HASH) == |
| DF_SYSTEM )) |
| hashj_tdb->setDisableCmpHintsOverflow(TRUE); |
| hashj_tdb->setBmoMinMemBeforePressureCheck((Int16)getDefault(EXE_BMO_MIN_SIZE_BEFORE_PRESSURE_CHECK_IN_MB)); |
| |
| if(generator->getOverflowMode() == ComTdb::OFM_SSD ) |
| hashj_tdb->setBMOMaxMemThresholdMB((UInt16)(ActiveSchemaDB()-> |
| getDefaults()). |
| getAsLong(SSD_BMO_MAX_MEM_THRESHOLD_IN_MB)); |
| else |
| hashj_tdb->setBMOMaxMemThresholdMB((UInt16)(ActiveSchemaDB()-> |
| getDefaults()). |
| getAsLong(EXE_MEMORY_AVAILABLE_IN_MB)); |
| |
| hashj_tdb->setScratchIOVectorSize((Int16)getDefault(SCRATCH_IO_VECTOR_SIZE_HASH)); |
| hashj_tdb-> |
| setForceOverflowEvery((UInt16)(ActiveSchemaDB()-> |
| getDefaults()). |
| getAsULong(EXE_TEST_HASH_FORCE_OVERFLOW_EVERY)); |
| |
| hashj_tdb-> |
| setForceHashLoopAfterNumBuffers((UInt16)(ActiveSchemaDB()-> |
| getDefaults()). |
| getAsULong(EXE_TEST_FORCE_HASH_LOOP_AFTER_NUM_BUFFERS)); |
| |
| hashj_tdb-> |
| setForceClusterSplitAfterMB((UInt16) (ActiveSchemaDB()->getDefaults()). |
| getAsULong(EXE_TEST_FORCE_CLUSTER_SPLIT_AFTER_MB)); |
| |
| ((ComTdb*)hashj_tdb)->setCIFON((tupleFormat == ExpTupleDesc::SQLMX_ALIGNED_FORMAT)); |
| |
| // The CQD EXE_MEM_LIMIT_PER_BMO_IN_MB has precedence over the mem quota sys |
| NADefaults &defs = ActiveSchemaDB()->getDefaults(); |
| |
| UInt16 mmu = (UInt16)(defs.getAsDouble(EXE_MEM_LIMIT_PER_BMO_IN_MB)); |
| |
| UInt16 numBMOsInFrag = (UInt16)generator->getFragmentDir()->getNumBMOs(); |
| |
| double memQuota = 0; |
| double memQuotaRatio; |
| Lng32 numStreams; |
| double bmoMemoryUsagePerNode = generator->getEstMemPerNode(getKey(), numStreams); |
| if (mmu != 0) { |
| memQuota = mmu; |
| hashj_tdb->setMemoryQuotaMB(mmu); |
| } else { |
| // Apply quota system if either one the following two is true: |
| // 1. the memory limit feature is turned off and more than one BMOs |
| // 2. the memory limit feature is turned on |
| NABoolean mlimitPerNode = defs.getAsDouble(BMO_MEMORY_LIMIT_PER_NODE_IN_MB) > 0; |
| |
| if ( mlimitPerNode || numBMOsInFrag > 1 || |
| (numBMOsInFrag == 1 && CmpCommon::getDefault(EXE_SINGLE_BMO_QUOTA) == DF_ON)) { |
| memQuota = |
| computeMemoryQuota(generator->getEspLevel() == 0, |
| mlimitPerNode, |
| generator->getBMOsMemoryLimitPerNode().value(), |
| generator->getTotalNumBMOs(), |
| generator->getTotalBMOsMemoryPerNode().value(), |
| numBMOsInFrag, |
| bmoMemoryUsagePerNode, |
| numStreams, |
| memQuotaRatio |
| ); |
| } |
| Lng32 hjMemoryLowbound = defs.getAsLong(BMO_MEMORY_LIMIT_LOWER_BOUND_HASHJOIN); |
| Lng32 memoryUpperbound = defs.getAsLong(BMO_MEMORY_LIMIT_UPPER_BOUND); |
| |
| if ( memQuota < hjMemoryLowbound ) { |
| memQuota = hjMemoryLowbound; |
| memQuotaRatio = BMOQuotaRatio::MIN_QUOTA; |
| } |
| else if (memQuota > memoryUpperbound) |
| memQuota = memoryUpperbound; |
| memQuotaRatio = BMOQuotaRatio::MIN_QUOTA; |
| hashj_tdb->setMemoryQuotaMB( UInt16(memQuota) ); |
| hashj_tdb->setBmoQuotaRatio(memQuotaRatio); |
| } |
| |
| if (beforeJoinPredOnOuterOnly()) |
| hashj_tdb->setBeforePredOnOuterOnly(); |
| |
| double hjMemEst = generator->getEstMemPerInst(getKey()); |
| hashj_tdb->setEstimatedMemoryUsage(hjMemEst / 1024); |
| generator->addToTotalEstimatedMemory(hjMemEst); |
| |
| if ( generator->getRightSideOfFlow() ) |
| hashj_tdb->setPossibleMultipleCalls(TRUE); |
| |
| // Internal CQD -- if set, enforce a minimum number of clusters |
| UInt16 nc = |
| (UInt16)(ActiveSchemaDB()-> |
| getDefaults()).getAsDouble(EXE_HJ_MIN_NUM_CLUSTERS); |
| if (nc != 0) |
| hashj_tdb->setNumClusters(nc); |
| |
| hashj_tdb->setMemoryContingencyMB(getDefault(PHY_MEM_CONTINGENCY_MB)); |
| float bmoCtzFactor; |
| defs.getFloat(BMO_CITIZENSHIP_FACTOR, bmoCtzFactor); |
| hashj_tdb->setBmoCitizenshipFactor((Float32)bmoCtzFactor); |
| |
| |
| // For now, use variable for all CIF rows based on resizeCifRecord |
| if(resizeCifRecord){ //tupleFormat == ExpTupleDesc::SQLMX_ALIGNED_FORMAT) { |
| hashj_tdb->setUseVariableLength(); |
| if(considerBufferDefrag) |
| { |
| hashj_tdb->setConsiderBufferDefrag(); |
| } |
| } |
| |
| hashj_tdb->setHjMemEstInKBPerNode(bmoMemoryUsagePerNode / 1024); |
| if (!generator->explainDisabled()) { |
| |
| generator->setExplainTuple( |
| addExplainInfo(hashj_tdb, leftExplainTuple, rightExplainTuple, generator)); |
| |
| } |
| |
| hashj_tdb->setReturnRightOrdered( returnRightOrdered() ); |
| |
| // Only for anti-semi-join with no search expr |
| // Make a guess about the likelihood of any row from the right, in which case |
| // delay requesting left rows, that are probably then not needed. |
| hashj_tdb->setDelayLeftRequest( innerExpectedRows > 100 || |
| outerExpectedRows > 100000 ); |
| |
| // If using the min/max optimization, must delay the left request. |
| // This is because we send the min/max values with the left request |
| // and they are not available until phase1 is complete. |
| if(minMaxExpr) |
| hashj_tdb->setDelayLeftRequest(true); |
| |
| // Query limits. |
| if ((afterJoinPred1 != NULL) || (afterJoinPred2 != NULL)) |
| { |
| ULng32 joinedRowsBeforePreempt = |
| (ULng32)getDefault(QUERY_LIMIT_SQL_PROCESS_CPU_XPROD); |
| |
| if ((joinedRowsBeforePreempt > 0)) |
| hashj_tdb->setXproductPreemptMax(joinedRowsBeforePreempt); |
| } |
| |
| // if an Insert/Update/Delete operation exists below the left |
| // child, we need to turn off a hash join optimization which |
| // cancels the left side if inner table is empty |
| if( child(0)->seenIUD() ) |
| { |
| hashj_tdb->setLeftSideIUD(); |
| } |
| |
| |
| // restore the original down cri desc since this node changed it. |
| generator->setCriDesc(givenDesc, Generator::DOWN); |
| |
| // set the new up cri desc. |
| generator->setCriDesc(returnedDesc, Generator::UP); |
| |
| generator->setGenObj(this, hashj_tdb); |
| |
| // reset the expression generation flag to generate float validation pcode |
| generator->setGenNoFloatValidatePCode(FALSE); |
| |
| // reset the handleIndirectVC flag to its initial value |
| expGen->setHandleIndirectVC( vcflag ); |
| |
| return 0; |
| |
| } |
| |
| |
| ExpTupleDesc::TupleDataFormat HashJoin::determineInternalFormat( const ValueIdList & rightList, |
| const ValueIdList & leftList, |
| RelExpr * relExpr, |
| NABoolean & resizeCifRecord, |
| Generator * generator, |
| NABoolean bmo_affinity, |
| NABoolean & considerBufferDefrag, |
| NABoolean uniqueHJ) |
| { |
| RelExpr::CifUseOptions bmo_cif = RelExpr::CIF_SYSTEM; |
| |
| considerBufferDefrag = FALSE; |
| |
| if (CmpCommon::getDefault(COMPRESSED_INTERNAL_FORMAT_BMO) == DF_OFF) |
| { |
| bmo_cif = RelExpr::CIF_OFF; |
| resizeCifRecord = FALSE; |
| return ExpTupleDesc::SQLARK_EXPLODED_FORMAT; |
| } |
| |
| UInt32 maxRowSize = 0; |
| //determine whether we want to defragment the buffers or not based on the average row size |
| double ratio = CmpCommon::getDefaultNumeric(COMPRESSED_INTERNAL_FORMAT_DEFRAG_RATIO); |
| double avgRowSize = getGroupAttr()->getAverageVarcharSize(rightList, maxRowSize); |
| considerBufferDefrag = ( maxRowSize >0 && avgRowSize/maxRowSize < ratio); |
| |
| if (!uniqueHJ) |
| { |
| avgRowSize = getGroupAttr()->getAverageVarcharSize(leftList, maxRowSize); |
| considerBufferDefrag = considerBufferDefrag && ( maxRowSize >0 && avgRowSize/maxRowSize < ratio); |
| } |
| |
| if (CmpCommon::getDefault(COMPRESSED_INTERNAL_FORMAT_BMO) == DF_ON) |
| { |
| bmo_cif = RelExpr::CIF_ON; |
| resizeCifRecord = (rightList.hasVarChars() || leftList.hasVarChars()); |
| return ExpTupleDesc::SQLMX_ALIGNED_FORMAT; |
| } |
| |
| |
| //CIF_SYSTEM |
| |
| |
| if (bmo_affinity == TRUE) |
| { |
| if (generator->getInternalFormat() == ExpTupleDesc::SQLMX_ALIGNED_FORMAT) |
| { |
| resizeCifRecord = (rightList.hasVarChars() || leftList.hasVarChars()); |
| return ExpTupleDesc::SQLMX_ALIGNED_FORMAT; |
| } |
| else |
| { |
| CMPASSERT(generator->getInternalFormat() == ExpTupleDesc::SQLARK_EXPLODED_FORMAT); |
| resizeCifRecord = FALSE; |
| return ExpTupleDesc::SQLARK_EXPLODED_FORMAT; |
| |
| } |
| } |
| |
| ExpTupleDesc::TupleDataFormat lTupleFormat = generator->getInternalFormat(); |
| UInt32 lAlignedHeaderSize= 0; |
| UInt32 lAlignedVarCharSize = 0; |
| UInt32 lExplodedLength = 0; |
| UInt32 lAlignedLength = 0; |
| double lAvgVarCharUsage = 1; |
| NABoolean lResizeRecord = FALSE; |
| |
| ExpTupleDesc::TupleDataFormat rTupleFormat = generator->getInternalFormat(); |
| UInt32 rAlignedHeaderSize= 0; |
| UInt32 rAlignedVarCharSize = 0; |
| UInt32 rExplodedLength = 0; |
| UInt32 rAlignedLength = 0; |
| double rAvgVarCharUsage = 1; |
| NABoolean rResizeRecord = FALSE; |
| |
| rTupleFormat = generator->determineInternalFormat(rightList, |
| relExpr, |
| rResizeRecord, |
| bmo_cif, |
| bmo_affinity, |
| rAlignedLength, |
| rExplodedLength, |
| rAlignedVarCharSize, |
| rAlignedHeaderSize, |
| rAvgVarCharUsage); |
| |
| lTupleFormat = generator->determineInternalFormat(leftList, |
| relExpr, |
| lResizeRecord, |
| bmo_cif, |
| bmo_affinity, |
| lAlignedLength, |
| lExplodedLength, |
| lAlignedVarCharSize, |
| lAlignedHeaderSize, |
| lAvgVarCharUsage); |
| |
| |
| if (rTupleFormat == ExpTupleDesc::SQLARK_EXPLODED_FORMAT && |
| lTupleFormat == ExpTupleDesc::SQLARK_EXPLODED_FORMAT) |
| { |
| resizeCifRecord = FALSE; |
| return ExpTupleDesc::SQLARK_EXPLODED_FORMAT; |
| } |
| |
| UInt32 rAlignedNonVarSize = rAlignedLength - rAlignedVarCharSize; |
| UInt32 lAlignedNonVarSize = lAlignedLength - lAlignedVarCharSize; |
| |
| if (rTupleFormat == ExpTupleDesc::SQLMX_ALIGNED_FORMAT && |
| lTupleFormat == ExpTupleDesc::SQLMX_ALIGNED_FORMAT) |
| { |
| resizeCifRecord = (rResizeRecord || lResizeRecord); |
| return ExpTupleDesc::SQLMX_ALIGNED_FORMAT; |
| } |
| //at this point one is aligned the other is exploded |
| double cifRowSizeAdj = CmpCommon::getDefaultNumeric(COMPRESSED_INTERNAL_FORMAT_ROW_SIZE_ADJ); |
| double lEstRowCount = 1; |
| double rEstRowCount = 1; |
| |
| lEstRowCount = child(0)->getGroupAttr()->getResultCardinalityForEmptyInput().value(); |
| rEstRowCount = child(1)->getGroupAttr()->getResultCardinalityForEmptyInput().value(); |
| |
| |
| |
| if ( (rAlignedVarCharSize > rAlignedHeaderSize || |
| lAlignedVarCharSize > lAlignedHeaderSize) && |
| |
| (((rAlignedNonVarSize + rAvgVarCharUsage * rAlignedVarCharSize ) * rEstRowCount + |
| (lAlignedNonVarSize + lAvgVarCharUsage * lAlignedVarCharSize ) * lEstRowCount) < |
| (rExplodedLength * rEstRowCount + lExplodedLength * lEstRowCount) * cifRowSizeAdj)) |
| { |
| resizeCifRecord = (rResizeRecord || lResizeRecord); |
| return ExpTupleDesc::SQLMX_ALIGNED_FORMAT; |
| } |
| |
| resizeCifRecord = FALSE; |
| return ExpTupleDesc::SQLARK_EXPLODED_FORMAT; |
| |
| } |
| |
| |
| CostScalar HashJoin::getEstimatedRunTimeMemoryUsage(Generator *generator, NABoolean perNode, Lng32 *numStreams) |
| { |
| GroupAttributes * childGroupAttr = child(1).getGroupAttr(); |
| const CostScalar childRecordSize = childGroupAttr->getCharacteristicOutputs().getRowLength(); |
| const CostScalar childRowCount = child(1).getPtr()->getEstRowsUsed(); |
| //TODO: Line below dumps core at times |
| //const CostScalar maxCard = childGroupAttr->getResultMaxCardinalityForEmptyInput(); |
| const CostScalar maxCard = 0; |
| // Each record also uses a header (HashRow) in memory (8 bytes for 32bit). |
| // Hash tables also take memory -- they are about %50 longer than the |
| // number of entries. |
| const ULng32 |
| memOverheadPerRecord = sizeof(HashRow) + sizeof(HashTableHeader) * 3 / 2 ; |
| |
| CostScalar estMemPerNode; |
| CostScalar estMemPerInst; |
| |
| CostScalar totalHashTableMemory = |
| childRowCount * (childRecordSize + memOverheadPerRecord); |
| // one buffer for the outer table |
| totalHashTableMemory += ActiveSchemaDB()->getDefaults().getAsLong(GEN_HSHJ_BUFFER_SIZE); |
| |
| const PhysicalProperty* const phyProp = getPhysicalProperty() ; |
| Lng32 numOfStreams = 1; |
| PartitioningFunction * partFunc = NULL; |
| if (phyProp) |
| { |
| partFunc = phyProp -> getPartitioningFunction() ; |
| numOfStreams = partFunc->getCountOfPartitions(); |
| if (numOfStreams <= 0) |
| numOfStreams = 1; |
| if ( partFunc -> isAReplicationPartitioningFunction() == TRUE ) |
| totalHashTableMemory *= numOfStreams; |
| } |
| if (numStreams != NULL) |
| *numStreams = numOfStreams; |
| estMemPerNode = totalHashTableMemory / MINOF(MAXOF(gpClusterInfo->getTotalNumberOfCPUs(), 1), numOfStreams); |
| estMemPerInst = totalHashTableMemory / numOfStreams; |
| OperBMOQuota *operBMOQuota = new (generator->wHeap()) OperBMOQuota(getKey(), numOfStreams, |
| estMemPerNode, estMemPerInst, childRowCount, maxCard); |
| generator->getBMOQuotaMap()->insert(operBMOQuota); |
| if (perNode) |
| return estMemPerNode; |
| else |
| return estMemPerInst; |
| } |
| |
| // NABoolean HashJoin::canUseUniqueHashJoin() |
| // Decide if this join can use the Unique Hash Join option. This |
| // option can be significantly faster than the regular hash join, but |
| // does not support many features. First, the unique hash join does |
| // not support overflow, so we must ensure that the inner side can fit |
| // int memory. The Unique Hash Join does not support: |
| // - Overflow |
| // - Outer joins (left, right or full) |
| // - selection or join predicates |
| // - anti semi join |
| // |
| // The Unique Hash Join only supports |
| // - unique joins (at most one row per probe, exception semi joins) |
| // Note that the method rowsFromLeftHaveUniqueMatch() used below |
| // will return TRUE for Semi-Joins regardless of the uniqueness of |
| // the join keys |
| // |
| // - joins with equi join predicates (cross products are not supported) |
| // |
| // Semi joins are supported by the Unique Hash Join even if the inner |
| // table contains duplicates. It actually does this naturally with no |
| // special code for semi joins. This works because the unique hash |
| // join implementation expects to find only one match and will not |
| // look for additional matches after finding the first. This is the |
| // exact behavior required by the semi join. The Unique Hash Join |
| // could elimiate these duplicates in the build phase, but does not |
| // currently do this. |
| // |
| NABoolean HashJoin::canUseUniqueHashJoin() |
| { |
| |
| // Do not use Unique Hash Join if it is turned OFF |
| if(CmpCommon::getDefault(UNIQUE_HASH_JOINS) == DF_OFF) |
| { |
| return FALSE; |
| } |
| |
| |
| if(!isLeftJoin() && |
| !isRightJoin() && |
| joinPred().isEmpty() && |
| selectionPred().isEmpty() && |
| !isAntiSemiJoin() && |
| rowsFromLeftHaveUniqueMatch() && |
| !getEquiJoinPredicates().isEmpty() && |
| (CmpCommon::context()->internalCompile() != CmpContext::INTERNAL_MODULENAME) && |
| !(CmpCommon::statement()->isSMDRecompile()) |
| ) |
| { |
| // If Unique Hash Joins are turned ON, use for all that qualify |
| // regardless of cardinalities. |
| // |
| if(CmpCommon::getDefault(UNIQUE_HASH_JOINS) == DF_ON) |
| { |
| return TRUE; |
| } |
| |
| // Otherwise, for UNIQUE_HASH_JOINS == 'SYSTEM', decide based on |
| // cardinalities. |
| |
| // Make sure Inner side of join is suitable for Unique Hash Join. |
| // |
| GroupAttributes *rightChildGA = child(1)->getGroupAttr(); |
| |
| if(rightChildGA->getGroupAnalysis()) |
| { |
| const CANodeIdSet &nodes = |
| rightChildGA->getGroupAnalysis()->getAllSubtreeTables(); |
| |
| UInt32 innerTables = |
| (UInt32) getDefault(UNIQUE_HASH_JOIN_MAX_INNER_TABLES); |
| |
| // Default is 1GB |
| UInt32 innerTableSizeLimitInMB = |
| (UInt32) getDefault(UNIQUE_HASH_JOIN_MAX_INNER_SIZE); |
| |
| // Default is 100MB |
| UInt32 innerTableSizePerInstanceLimitInMB = |
| (UInt32) getDefault(UNIQUE_HASH_JOIN_MAX_INNER_SIZE_PER_INSTANCE); |
| |
| RowSize rowSize = rightChildGA->getRecordLength(); |
| |
| // The extended size |
| rowSize += sizeof(HashRow); |
| |
| // The hash table entries are rounded up. |
| // So do the same here. |
| // |
| rowSize = ROUND8(rowSize); |
| |
| Lng32 numPartitions = 1; |
| if(getParallelJoinType() == 1) |
| { |
| const PhysicalProperty* physProp = child(1)->getPhysicalProperty(); |
| PartitioningFunction *partFunc = physProp->getPartitioningFunction(); |
| |
| Lng32 numPartitions = partFunc->getCountOfPartitions(); |
| } |
| |
| if(nodes.entries() == 1) |
| { |
| TableAnalysis *tabAnalysis = |
| nodes.getFirst().getNodeAnalysis()->getTableAnalysis(); |
| |
| if(tabAnalysis) |
| { |
| CostScalar numRows = tabAnalysis->getCardinalityOfBaseTable(); |
| CostScalar estRows = child(1)->getEstRowsUsed(); |
| |
| if(numRows >= estRows) |
| { |
| CostScalar innerTableSize = numRows * rowSize; |
| CostScalar innerTableSizePerInstance = innerTableSize / |
| numPartitions; |
| |
| // Convert to MBs |
| innerTableSize /= (1024 * 1024); |
| |
| innerTableSizePerInstance /= (1024 * 1024); |
| |
| if(innerTableSize < innerTableSizeLimitInMB && |
| innerTableSizePerInstance < innerTableSizePerInstanceLimitInMB) |
| { |
| |
| // Use the Unique Hash Join Implementation. |
| // |
| return TRUE; |
| } |
| } |
| } |
| } |
| // single inner table did not qualify for unique hash join. |
| // give unique hash join another chance using max cardinality |
| // estimate to determine if inner hash table fits in memory. |
| if (nodes.entries() <= innerTables) |
| { |
| CostScalar maxRows = rightChildGA-> |
| getResultMaxCardinalityForEmptyInput(); |
| CostScalar innerTableMaxSize = maxRows * rowSize |
| / (1024 * 1024); |
| CostScalar innerTableMaxSizePerInstance = innerTableMaxSize / |
| numPartitions; |
| if (innerTableMaxSize < innerTableSizeLimitInMB && |
| innerTableMaxSizePerInstance < innerTableSizePerInstanceLimitInMB) |
| // Use the Unique Hash Join Implementation. |
| // |
| return TRUE; |
| } |
| } |
| } |
| |
| return FALSE; |
| } |
| |
| // case of hash anti semi join optimization (NOT IN) |
| // add/build expression to detect inner and outer null : |
| // checkOuteNullexpr_ : <outer> IS NULL |
| // checkInnerNullExpr_: <inner} IS NULL |
| |
| void HashJoin::addCheckNullExpressions(CollHeap * wHeap) |
| { |
| if(!getIsNotInSubqTransform() ) |
| { |
| return; |
| } |
| |
| |
| ValueId valId; |
| |
| Int32 notinCount = 0; |
| |
| for ( valId = getEquiJoinPredicates().init(); |
| getEquiJoinPredicates().next(valId); |
| getEquiJoinPredicates().advance(valId)) |
| { |
| ItemExpr * itemExpr = valId.getItemExpr(); |
| |
| if ((itemExpr->getOperatorType() == ITM_EQUAL) && |
| ((BiRelat *)itemExpr)->getIsNotInPredTransform()) |
| { |
| notinCount++; |
| |
| ItemExpr * child0 = itemExpr->child(0); |
| ItemExpr * child1 = itemExpr->child(1); |
| |
| const NAType &outerType = child0->getValueId().getType(); |
| const NAType &innerType = child1->getValueId().getType(); |
| |
| if (innerType.supportsSQLnull() && |
| !((BiRelat*)itemExpr)->getInnerNullFilteringDetected()) |
| { |
| ItemExpr * itm = new (wHeap) UnLogic(ITM_IS_NULL, child1); |
| |
| itm->synthTypeAndValueId(TRUE); |
| |
| getCheckInnerNullExpr().insert(itm->getValueId()); |
| // reuse is disabled in this phase on not in optimization |
| setReuse(FALSE); |
| |
| } |
| |
| //outer column |
| if (outerType.supportsSQLnull() && |
| !((BiRelat*)itemExpr)->getOuterNullFilteringDetected()) |
| { |
| ItemExpr * itm = new (wHeap) UnLogic(ITM_IS_NULL, child0); |
| itm->synthTypeAndValueId(TRUE); |
| getCheckOuterNullExpr().insert(itm->getValueId()); |
| } |
| |
| } |
| } |
| // assert if more than one notin found |
| DCMPASSERT(notinCount = 1); |
| |
| } |
| |
| //10-060710-7606(port of 10-050706-9430) - Begin |
| // This function was Reimplemented (7/1/08) soln 10-080518-3261 |
| |
| // This function Recursively navigates the item expression |
| // tree to collect all the value ids needed for expression |
| // evaluation. |
| |
| void gatherValuesFromExpr(ValueIdList &vs, |
| ItemExpr *ie, |
| Generator *generator) |
| { |
| ValueId valId(ie->getValueId()); |
| if(generator->getMapInfoAsIs(valId)) |
| { |
| // If it is directly in the mapTable record then it is an |
| // output of the right child. |
| vs.insert(valId); |
| } |
| else |
| { |
| // Check for a special case like a CAST(<x>) where <x> is |
| // available in the MapTable. |
| for (Int32 i=0; i < ie->getArity(); i++) |
| { |
| gatherValuesFromExpr(vs, ie->child(i), generator); |
| } |
| } |
| } |
| |
| short MergeJoin::codeGen(Generator * generator) |
| { |
| ExpGenerator * exp_gen = generator->getExpGenerator(); |
| Space * space = generator->getSpace(); |
| MapTable * my_map_table = generator->appendAtEnd(); |
| |
| // set flag to enable pcode for indirect varchar |
| NABoolean vcflag = exp_gen->handleIndirectVC(); |
| if (CmpCommon::getDefault(VARCHAR_PCODE) == DF_ON) { |
| exp_gen->setHandleIndirectVC( TRUE ); |
| } |
| |
| NABoolean is_semijoin = isSemiJoin(); |
| NABoolean is_leftjoin = isLeftJoin(); |
| NABoolean is_anti_semijoin = isAntiSemiJoin(); |
| |
| // find if the left child and/or the right child will have atmost |
| // one matching row. If so, an faster merge join implementation |
| // will be used at runtime. |
| // This optimization is not used for left or semi joins. |
| NABoolean isLeftUnique = FALSE; |
| NABoolean isRightUnique = FALSE; |
| NABoolean fastMJEval = FALSE; |
| if ((! is_semijoin) && |
| (! is_anti_semijoin) && |
| (! is_leftjoin)) |
| { |
| #ifdef _DEBUG |
| isLeftUnique = (getenv("LEFT_UNIQUE_MJ") ? TRUE : leftUnique()); |
| isRightUnique = (getenv("RIGHT_UNIQUE_MJ") ? TRUE : rightUnique()); |
| #else |
| isLeftUnique = leftUnique(); |
| isRightUnique = rightUnique(); |
| #endif |
| if (isLeftUnique || isRightUnique) |
| fastMJEval = TRUE; |
| } |
| |
| ex_expr * merge_expr = 0; |
| ex_expr * comp_expr = 0; |
| ex_expr * pre_join_expr = 0; |
| ex_expr * post_join_expr = 0; |
| |
| ex_expr * left_check_dup_expr = 0; |
| ex_expr * right_check_dup_expr = 0; |
| |
| ex_expr * left_encoded_key_expr = NULL; |
| ex_expr * right_encoded_key_expr = NULL; |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // |
| // Layout of row returned by this node. |
| // |
| // |------------------------------------------------------------------------| |
| // | input data | left child's data | right child's data | instantiated row | |
| // | ( I tupps) | ( L tupps ) | ( R tupp ) | ( 1 tupp ) | |
| // |------------------------------------------------------------------------| |
| // |
| // input data: the atp input to this node by its parent. This is given |
| // to both children as input. |
| // left child data: tupps appended by the left child |
| // right child data: tupps appended by the left child |
| // instantiated row: For some left join cases, the |
| // null values are instantiated. See proc |
| // Join::instantiateValuesForLeftJoin for details at the |
| // end of this file. |
| // |
| // Returned row to parent contains: |
| // |
| // I + L tupps, if this is a semi join. Rows from right are not returned. |
| // |
| // If this is not a semi join, then: |
| // I + L + R tupps, if instantiation is not done. |
| // I + L + R + 1 tupps, if instantiation is done. |
| // |
| //////////////////////////////////////////////////////////////////////////// |
| ex_cri_desc * given_desc = generator->getCriDesc(Generator::DOWN); |
| |
| generator->setCriDesc(given_desc, Generator::DOWN); |
| child(0)->codeGen(generator); |
| ComTdb * child_tdb1 = (ComTdb *)(generator->getGenObj()); |
| ExplainTuple *leftExplainTuple = generator->getExplainTuple(); |
| ex_cri_desc * left_child_desc = generator->getCriDesc(Generator::UP); |
| |
| generator->setCriDesc(left_child_desc, Generator::DOWN); |
| child(1)->codeGen(generator); |
| ComTdb * child_tdb2 = (ComTdb *)(generator->getGenObj()); |
| ExplainTuple *rightExplainTuple = generator->getExplainTuple(); |
| ex_cri_desc * right_child_desc = generator->getCriDesc(Generator::UP); |
| |
| unsigned short returned_tuples; |
| short returned_instantiated_row_atp_index = -1; |
| short returned_right_row_atp_index = -1; |
| |
| ex_cri_desc * work_cri_desc = NULL; |
| short instantiated_row_atp_index = -1; |
| short encoded_key_atp_index = -1; |
| |
| work_cri_desc = new(space) ex_cri_desc(3, space); |
| |
| if (is_semijoin || is_anti_semijoin) |
| returned_tuples = left_child_desc->noTuples(); |
| else |
| { |
| // not a semi join. |
| |
| // if right side can return atmost one rows, then no need to |
| // save dups. No new row is created at returned_right_row_atp_index |
| // in this case. |
| if (fastMJEval) |
| returned_tuples = right_child_desc->noTuples(); |
| else |
| { |
| returned_tuples = (unsigned short)(left_child_desc->noTuples() + 1); |
| returned_right_row_atp_index = returned_tuples - 1; |
| } |
| |
| if (nullInstantiatedOutput().entries() > 0) |
| { |
| instantiated_row_atp_index = 3; |
| |
| returned_instantiated_row_atp_index = (short) returned_tuples++; |
| } |
| } |
| |
| ex_cri_desc * returned_desc = new(space) ex_cri_desc(returned_tuples, space); |
| GenAssert(!getOrderedMJPreds().isEmpty(),"getOrderedMJPreds().isEmpty()"); |
| |
| //////////////////////////////////////////////////////////// |
| // Before generating any expression for this node, set the |
| // the expression generation flag not to generate float |
| // validation PCode. This is to speed up PCode evaluation |
| //////////////////////////////////////////////////////////// |
| generator->setGenNoFloatValidatePCode(TRUE); |
| |
| NABoolean doEncodedKeyCompOpt = FALSE; |
| doEncodedKeyCompOpt = TRUE; |
| encoded_key_atp_index = 2; |
| |
| // generate expressions to find out if left or right rows are duplicate |
| // of the previous rows. |
| ValueIdSet right_dup_val_id_set; |
| ValueIdList leftChildOfMJPList;//list of left children of orderedMJPreds() |
| ValueIdList rightChildOfMJPList;//list of right children of orderedMJPreds() |
| ValueId val_id; |
| CollIndex i; |
| |
| for (i = 0; i < orderedMJPreds().entries(); i++) |
| { |
| // create a place holder node to represent the previous row, |
| // which is exactly the same as |
| // the child values except for its atp. At runtime, the previous |
| // value is passed to the expression evaluator as the second atp. |
| |
| // Usually, the child RelExpr values are the immediate children |
| // of the orderedMJPreds. However, in some cases an immediate |
| // child is an expression made up of values coming from the |
| // child RelExpr. Typically, this is a Cast expression |
| // introduced by a MapValueId node. Here, we handle the usual |
| // case and the case of the Cast expression. Other expressions |
| // that are not evaluated by the child RelExpr will result in an |
| // Assertion. If we ever trigger this assertion, we may need to |
| // make this code more general. |
| |
| |
| val_id = orderedMJPreds()[i]; |
| |
| // Do the right row. |
| ValueId child1Vid = |
| val_id.getItemExpr()->child(1)->castToItemExpr()->getValueId(); |
| |
| // Place holder for right values. |
| // |
| Cast *ph = NULL; |
| |
| // Attributes of the right child. |
| // |
| Attributes *childAttr = NULL; |
| |
| // ValueId of value actually supplied by child RelExpr. |
| // |
| ValueId childOutputVid; |
| |
| MapInfo *child1MapInfo = generator->getMapInfoAsIs(child1Vid); |
| |
| // If there is no mapInfo for the immediate child, then it is |
| // not directly supplied by the child RelExpr. Check for the |
| // case when the immediate child is a CAST and the child of the |
| // CAST is supplied directly by the child RelExpr. |
| // |
| if(!child1MapInfo) |
| { |
| |
| // If this is a CAST AND ... |
| // |
| if((child1Vid.getItemExpr()->getOperatorType() == ITM_CAST) || |
| (child1Vid.getItemExpr()->getOperatorType() == ITM_TRANSLATE) || |
| (child1Vid.getItemExpr()->getOperatorType() == ITM_NOTCOVERED)) |
| { |
| ValueId nextChild0Vid = |
| child1Vid.getItemExpr()->child(0)->castToItemExpr()->getValueId(); |
| |
| // If the child of the CAST is in the mapTable (supplied by |
| // the child RelExpr) |
| // |
| if (generator->getMapInfoAsIs(nextChild0Vid)) |
| { |
| |
| // Remember the actual value supplied by the child RelExpr. |
| // |
| childOutputVid = nextChild0Vid; |
| |
| // Create place holder node. |
| // |
| ph = new (generator->wHeap()) |
| Cast(child1Vid.getItemExpr()->child(0), |
| &(nextChild0Vid.getType())); |
| |
| // Attributes for this place holder node. Same as the |
| // child value, but we will later change the ATP. |
| // |
| childAttr = generator->getMapInfo(nextChild0Vid)->getAttr(); |
| } |
| } |
| } |
| else |
| { |
| |
| // The immediate child is supplied by the child RelExpr. |
| // |
| |
| // Remember the actual value supplied by the child RelExpr. |
| // |
| childOutputVid = child1Vid; |
| |
| // Create place holder node. |
| // |
| ph = new(generator->wHeap()) |
| Cast(val_id.getItemExpr()->child(1), |
| &(child1Vid.getType())); |
| |
| // Attributes for this place holder node. Same as the |
| // child value, but we will later change the ATP. |
| // |
| childAttr = generator->getMapInfo(child1Vid)->getAttr(); |
| } |
| |
| // If we did not find a childAttr, then neither the immediate |
| // child nor the child of an immediate CAST is supplied by the |
| // child RelExpr. We need to be more general here. |
| // |
| GenAssert(childAttr, "Merge Join: expression not found"); |
| |
| |
| ph->bindNode(generator->getBindWA()); |
| |
| |
| if ( childAttr->getAtpIndex() > 1) |
| { |
| // Make a mapTable entry for the place holder, just like the |
| // child value |
| // |
| MapInfo * map_info = generator->addMapInfo(ph->getValueId(), |
| childAttr); |
| // Make this mapTable entry refer to ATP 1. |
| // |
| map_info->getAttr()->setAtp(1); |
| |
| // mark ph as code generated node since we don't want the |
| // Cast to actually do a conversion. |
| map_info->codeGenerated(); |
| } |
| |
| if(!child1MapInfo) |
| { |
| |
| // If the immediate child is not supplied by the child RelExpr |
| // and it is a CAST node, we need to add the equivalent CAST |
| // node for the left side of the DUP expression. |
| // Here is the expression we need to generate in this case: |
| // EQUAL |
| // | |
| // /------------\ |
| // CAST CAST must create equiv node here |
| // | | |
| // supplied by Child A PlaceHolder-For-A |
| // |
| ph = |
| new(generator->wHeap()) Cast(ph, |
| &(child1Vid.getType())); |
| } |
| |
| BiRelat * bi_relat = |
| new(generator->wHeap()) BiRelat(ITM_EQUAL, |
| val_id.getItemExpr()->child(1), |
| ph); |
| // for the purpose of checking duplicates, nulls are equal |
| // to other nulls. Mark them so. |
| bi_relat->setSpecialNulls(-1); |
| |
| bi_relat->bindNode(generator->getBindWA()); |
| |
| leftChildOfMJPList.insert(val_id.getItemExpr()->child(0)->getValueId()); |
| |
| // This must be the actual values supplied by Child RelExpr. |
| rightChildOfMJPList.insert(childOutputVid); |
| right_dup_val_id_set.insert(bi_relat->getValueId()); |
| |
| if ((val_id.getItemExpr()->child(0)->getValueId().getType().supportsSQLnull()) || |
| (val_id.getItemExpr()->child(1)->getValueId().getType().supportsSQLnull())) |
| doEncodedKeyCompOpt = FALSE; |
| } |
| |
| // now generate an expression to see if the left values are less |
| // than the right values. This is needed to advance the left child |
| // if the expression is true. |
| // Note: later, only generate one expression for merge and comp |
| // and have it return status indicating if the left row is less than, |
| // equal to or greated than the right. Use a CASE statement to do that. |
| |
| ExprValueId left_tree = (ItemExpr *) NULL; |
| ExprValueId right_tree = (ItemExpr *) NULL; |
| |
| ItemExprTreeAsList * left_list = NULL; |
| ItemExprTreeAsList * right_list = NULL; |
| |
| ValueIdList leftEncodedValIds; |
| ValueIdList rightEncodedValIds; |
| ULng32 encoded_key_len = 0; |
| |
| if (NOT doEncodedKeyCompOpt) |
| { |
| left_list = new(generator->wHeap()) ItemExprTreeAsList( |
| &left_tree, |
| ITM_ITEM_LIST, |
| RIGHT_LINEAR_TREE); |
| right_list = new(generator->wHeap()) ItemExprTreeAsList( |
| &right_tree, |
| ITM_ITEM_LIST, |
| RIGHT_LINEAR_TREE); |
| } |
| |
| for (i = 0; i < orderedMJPreds().entries(); i++) |
| { |
| val_id = orderedMJPreds()[i]; |
| |
| ItemExpr * left_val = val_id.getItemExpr()->child(0); |
| ItemExpr * right_val = val_id.getItemExpr()->child(1); |
| |
| // if the left and right values do not have the same type, |
| // then convert them to a common super type before encoding. |
| const NAType & leftType = left_val->getValueId().getType(); |
| const NAType & rightType = right_val->getValueId().getType(); |
| if (NOT (leftType == rightType)) |
| { |
| UInt32 flags = |
| ((CmpCommon::getDefault(LIMIT_MAX_NUMERIC_PRECISION) == DF_ON) |
| ? NAType::LIMIT_MAX_NUMERIC_PRECISION : 0); |
| |
| // find the common super datatype xs |
| const NAType *resultType = |
| leftType.synthesizeType( |
| SYNTH_RULE_UNION, |
| leftType, |
| rightType, |
| generator->wHeap(), |
| &flags); |
| |
| CMPASSERT(resultType); |
| |
| // add type conversion operators if necessary |
| if (NOT (leftType == *resultType)) |
| { |
| left_val = new(generator->wHeap()) Cast(left_val,resultType); |
| } |
| |
| if (NOT (rightType == *resultType)) |
| { |
| right_val = new(generator->wHeap()) Cast(right_val,resultType); |
| } |
| } |
| |
| // encode the left and right values before doing the comparison. |
| short desc_flag = FALSE; |
| if (getLeftSortOrder()[i].getItemExpr()->getOperatorType() == ITM_INVERSE) |
| desc_flag = TRUE; |
| |
| CompEncode * encoded_left_val |
| = new(generator->wHeap()) CompEncode(left_val, desc_flag); |
| CompEncode * encoded_right_val |
| = new(generator->wHeap()) CompEncode(right_val, desc_flag); |
| |
| encoded_left_val->bindNode(generator->getBindWA()); |
| encoded_right_val->bindNode(generator->getBindWA()); |
| |
| if (doEncodedKeyCompOpt) |
| { |
| leftEncodedValIds.insert(encoded_left_val->getValueId()); |
| rightEncodedValIds.insert(encoded_right_val->getValueId()); |
| } |
| else |
| { |
| // add the search condition |
| left_list->insert(encoded_left_val); |
| right_list->insert(encoded_right_val); |
| } |
| } |
| |
| ItemExpr * compTree = 0; |
| |
| if (NOT doEncodedKeyCompOpt) |
| { |
| compTree = new(generator->wHeap()) BiRelat(ITM_LESS, left_tree, right_tree); |
| |
| compTree = new(generator->wHeap()) BoolResult(compTree); |
| |
| // bind/type propagate the comp tree |
| compTree->bindNode(generator->getBindWA()); |
| } |
| |
| // At runtime when this merge join expression is evaluated, the left child |
| // values are passed in the first atp, and the right child values |
| // are passed in the second atp. Change the atp value of right child's output |
| // to 1, if it is not already an input value. |
| const ValueIdSet & child1CharacteristicOutputs = |
| child(1)->castToRelExpr()->getGroupAttr()->getCharacteristicOutputs(); |
| |
| // create a list of all values returned from right child that are |
| // not input to this node. |
| ValueIdList rightChildOutput; |
| |
| for (val_id = child1CharacteristicOutputs.init(); |
| child1CharacteristicOutputs.next(val_id); |
| child1CharacteristicOutputs.advance(val_id)) |
| { |
| // If it is part of my input or not in the mapTable... The |
| // assumption is that if it is not in the mapTable and it is not |
| // directly in the inputs, it can be derived from the inputs |
| // |
| if (! getGroupAttr()->getCharacteristicInputs().contains(val_id)) |
| { |
| // This new function takes care of the CAST(<x>) function as well |
| gatherValuesFromExpr(rightChildOutput, val_id.getItemExpr(), generator); |
| } |
| } |
| |
| // Change the atp value of right child's output to 1 |
| // Atpindex of -1 means leave atpindex as is. |
| // |
| exp_gen->assignAtpAndAtpIndex(rightChildOutput, 1, -1); |
| |
| ExpTupleDesc::TupleDataFormat tupleFormat = generator->getInternalFormat(); |
| |
| ItemExpr * newPredTree = NULL; |
| if (NOT doEncodedKeyCompOpt) |
| { |
| // orderedMJPreds() is a list containing predicates of the form: |
| // <left value1> '=' <right value1>, <left value2> '=' <right value2> ... |
| // Generate the merge expression. This is used to find out if |
| // the left and right rows match for equi join. |
| newPredTree = orderedMJPreds().rebuildExprTree(ITM_AND,TRUE,TRUE); |
| exp_gen->generateExpr(newPredTree->getValueId(), ex_expr::exp_SCAN_PRED, |
| &merge_expr); |
| |
| |
| // generate the comp expression. This expression is used |
| // to look for a matching value in the hash table. |
| exp_gen->generateExpr(compTree->getValueId(), ex_expr::exp_SCAN_PRED, |
| &comp_expr); |
| } |
| else |
| { |
| // generate expression to create encoded left key buffer. |
| // The work atp where encoded is created is passed in as atp1 at runtime. |
| exp_gen->generateContiguousMoveExpr(leftEncodedValIds, |
| 0, // don't add convert nodes |
| 1, // atp 1 |
| encoded_key_atp_index, |
| ExpTupleDesc::SQLARK_EXPLODED_FORMAT, |
| encoded_key_len, |
| &left_encoded_key_expr, |
| 0, |
| ExpTupleDesc::SHORT_FORMAT); |
| |
| // generate expression to create encoded right key buffer |
| // The work atp where encoded is created is passed in as atp0 at runtime. |
| exp_gen->generateContiguousMoveExpr(rightEncodedValIds, |
| 0, // don't add convert nodes |
| 0, // atp 0 |
| encoded_key_atp_index, |
| ExpTupleDesc::SQLARK_EXPLODED_FORMAT, |
| encoded_key_len, |
| &right_encoded_key_expr, |
| 0, |
| ExpTupleDesc::SHORT_FORMAT); |
| } |
| |
| /* |
| // generate expression to evaluate the |
| // non-equi join predicates applied before NULL-instantiation |
| if (! joinPred().isEmpty()) |
| { |
| ItemExpr * newPredTree; |
| newPredTree = joinPred().rebuildExprTree(ITM_AND,TRUE,TRUE); |
| exp_gen->generateExpr(newPredTree->getValueId(), ex_expr::exp_SCAN_PRED, |
| &pre_join_expr); |
| } |
| */ |
| |
| // Change the atp value of right child's output to 0 |
| // Second argument -1 means leave atpindex as is. |
| exp_gen->assignAtpAndAtpIndex(rightChildOutput, 0, -1); |
| |
| // generate expression to save the duplicate rows returned from right |
| // child. Do it if rightUnique is false. |
| ex_expr * right_copy_dup_expr = NULL; |
| ULng32 right_row_len = 0; |
| if (NOT fastMJEval) |
| { |
| ValueIdList resultValIdList; |
| exp_gen->generateContiguousMoveExpr(rightChildOutput, |
| -1, // add convert nodes |
| 1 /*atp*/, 2 /*atpindex*/, |
| tupleFormat, |
| right_row_len, |
| &right_copy_dup_expr, |
| NULL, ExpTupleDesc::SHORT_FORMAT, |
| NULL, |
| &resultValIdList); |
| |
| ValueIdList prevRightValIds; // the secend operand of dup comparison |
| CollIndex i = 0; |
| for (i = 0; i < resultValIdList.entries(); i++) |
| { |
| // create the new item xpression |
| ItemExpr * newCol = new(generator->wHeap()) |
| NATypeToItem((NAType *)&(resultValIdList[i].getType())); |
| newCol->synthTypeAndValueId(); |
| |
| // copy the attributes |
| Attributes * originalAttr = |
| generator->getMapInfo(resultValIdList[i])->getAttr(); |
| |
| Attributes * newAttr = |
| generator->addMapInfo(newCol->getValueId(), 0)->getAttr(); |
| newAttr->copyLocationAttrs(originalAttr); |
| |
| // set atp |
| newAttr->setAtp(1); |
| |
| // add the new valueId to the list of 2nd operand |
| prevRightValIds.insert(newCol->getValueId()); |
| } |
| |
| // At runtime, duplicate right rows in right child up queue are checked |
| // by comparing that row with one of the saved right dup rows. |
| ValueIdSet right_dup_val_id_set; |
| for (i = 0; i < rightChildOfMJPList.entries(); i++) |
| { |
| val_id = rightChildOfMJPList[i]; |
| CollIndex index = rightChildOutput.index(val_id); |
| |
| BiRelat * bi_relat = |
| new(generator->wHeap()) BiRelat(ITM_EQUAL, |
| rightChildOfMJPList[i].getItemExpr(), |
| prevRightValIds[index].getItemExpr()); |
| // for the purpose of checking duplicates, nulls are equal |
| // to other nulls. Mark them so. |
| bi_relat->setSpecialNulls(-1); |
| |
| bi_relat->bindNode(generator->getBindWA()); |
| |
| right_dup_val_id_set.insert(bi_relat->getValueId()); |
| } |
| |
| // generate expressions to do the duplicate row checks for right child. |
| // The row returned from right child is compared to the saved row returned |
| // by the right child. |
| newPredTree = right_dup_val_id_set.rebuildExprTree(ITM_AND,TRUE,TRUE); |
| exp_gen->generateExpr(newPredTree->getValueId(), ex_expr::exp_SCAN_PRED, |
| &right_check_dup_expr); |
| |
| for (i = 0; i < resultValIdList.entries(); i++) |
| { |
| ValueId resultValId = resultValIdList[i]; |
| ValueId rightChildOutputValId = rightChildOutput[i]; |
| Attributes * resultAttr = generator->getMapInfo(resultValId)->getAttr(); |
| Attributes * rightChildAttr = generator->getMapInfo(rightChildOutputValId)->getAttr(); |
| Int32 rightChildAtpIndex = rightChildAttr->getAtpIndex(); |
| rightChildAttr->copyLocationAttrs(resultAttr); |
| } |
| |
| // at runtime, duplicate left rows are checked by comparing the |
| // left row with one of the saved right dup rows. |
| ValueIdSet left_dup_val_id_set; |
| for (i = 0; i < rightChildOfMJPList.entries(); i++) |
| { |
| val_id = rightChildOfMJPList[i]; |
| CollIndex index = rightChildOutput.index(val_id); |
| |
| BiRelat * bi_relat = |
| new(generator->wHeap()) BiRelat(ITM_EQUAL, |
| leftChildOfMJPList[i].getItemExpr(), |
| resultValIdList[index].getItemExpr()); |
| // for the purpose of checking duplicates, nulls are equal |
| // to other nulls. Mark them so. |
| bi_relat->setSpecialNulls(-1); |
| |
| bi_relat->bindNode(generator->getBindWA()); |
| |
| left_dup_val_id_set.insert(bi_relat->getValueId()); |
| } |
| |
| // generate expressions to do the duplicate row checks for left child. |
| // The row returned from left child is compared to the saved row returned |
| // by the right child. |
| newPredTree = left_dup_val_id_set.rebuildExprTree(ITM_AND,TRUE,TRUE); |
| exp_gen->generateExpr(newPredTree->getValueId(), ex_expr::exp_SCAN_PRED, |
| &left_check_dup_expr); |
| } |
| |
| // generate expression to evaluate the |
| // non-equi join predicates applied before NULL-instantiation |
| if (! joinPred().isEmpty()) |
| { |
| ItemExpr * newPredTree; |
| newPredTree = joinPred().rebuildExprTree(ITM_AND,TRUE,TRUE); |
| exp_gen->generateExpr(newPredTree->getValueId(), ex_expr::exp_SCAN_PRED, |
| &pre_join_expr); |
| } |
| |
| // now change the atp to 0 and atpindex to returned_right_row_atp_index. |
| if (NOT fastMJEval) |
| { |
| exp_gen->assignAtpAndAtpIndex(rightChildOutput, 0, returned_right_row_atp_index); |
| } |
| |
| ex_expr * lj_expr = 0; |
| ex_expr * ni_expr = 0; |
| ULng32 rowlen = 0; |
| |
| if (nullInstantiatedOutput().entries() > 0) |
| { |
| instantiateValuesForLeftJoin(generator, |
| 0, returned_instantiated_row_atp_index, |
| &lj_expr, &ni_expr, |
| &rowlen, |
| NULL // No MapTable required |
| ); |
| } |
| |
| // set the atp index for values in the instantiated row. |
| for (i = 0; i < nullInstantiatedOutput().entries(); i++) |
| { |
| ValueId val_id = nullInstantiatedOutput()[i]; |
| |
| Attributes * attr = generator->getMapInfo(val_id)->getAttr(); |
| attr->setAtp(0); |
| attr->setAtpIndex(returned_instantiated_row_atp_index); |
| // Do not do bulk move because null instantiate expression |
| // is not set in TDB to save execution time, see ComTdbMj below |
| attr->setBulkMoveable(FALSE); |
| } |
| |
| // generate any expression to be applied after the join |
| if (! selectionPred().isEmpty()) |
| { |
| ItemExpr * newPredTree = selectionPred().rebuildExprTree(ITM_AND,TRUE,TRUE); |
| exp_gen->generateExpr(newPredTree->getValueId(), ex_expr::exp_SCAN_PRED, |
| &post_join_expr); |
| } |
| |
| bool isOverflowEnabled = (CmpCommon::getDefault(MJ_OVERFLOW) == DF_ON); |
| UInt16 scratchThresholdPct |
| = (UInt16) getDefault(SCRATCH_FREESPACE_THRESHOLD_PERCENT); |
| |
| // Big Memory Operator (BMO) settings |
| // Use memory quota only if fragment has more than one BMO. |
| UInt16 numBMOsInFrag = (UInt16)generator->getFragmentDir()->getNumBMOs(); |
| |
| double BMOsMemoryLimit = 0; |
| UInt16 quotaMB = 0; |
| Lng32 numStreams; |
| double memQuotaRatio; |
| double bmoMemoryUsage = generator->getEstMemPerNode(getKey(), numStreams); |
| |
| NADefaults &defs = ActiveSchemaDB()->getDefaults(); |
| if ( CmpCommon::getDefaultLong(MJ_BMO_QUOTA_PERCENT) != 0) |
| { |
| // Apply quota system if either one the following two is true: |
| // 1. the memory limit feature is turned off and more than one BMOs |
| // 2. the memory limit feature is turned on |
| NABoolean mlimitPerNode = defs.getAsDouble(BMO_MEMORY_LIMIT_PER_NODE_IN_MB) > 0; |
| |
| if ( mlimitPerNode || numBMOsInFrag > 1 || |
| (numBMOsInFrag == 1 && CmpCommon::getDefault(EXE_SINGLE_BMO_QUOTA) == DF_ON)) { |
| |
| quotaMB = (UInt16) |
| computeMemoryQuota(generator->getEspLevel() == 0, |
| mlimitPerNode, |
| generator->getBMOsMemoryLimitPerNode().value(), |
| generator->getTotalNumBMOs(), |
| generator->getTotalBMOsMemoryPerNode().value(), |
| numBMOsInFrag, |
| bmoMemoryUsage, |
| numStreams, |
| memQuotaRatio |
| ); |
| } |
| Lng32 mjMemoryLowbound = defs.getAsLong(EXE_MEMORY_LIMIT_LOWER_BOUND_MERGEJOIN); |
| Lng32 memoryUpperbound = defs.getAsLong(BMO_MEMORY_LIMIT_UPPER_BOUND); |
| |
| if ( quotaMB < mjMemoryLowbound ) { |
| quotaMB = (UInt16)mjMemoryLowbound; |
| memQuotaRatio = BMOQuotaRatio::MIN_QUOTA; |
| } |
| else if (quotaMB > memoryUpperbound) |
| quotaMB = memoryUpperbound; |
| } else { |
| Lng32 quotaMB = defs.getAsLong(EXE_MEMORY_LIMIT_LOWER_BOUND_MERGEJOIN); |
| } |
| |
| |
| bool yieldQuota = !(generator->getRightSideOfFlow()); |
| UInt16 quotaPct = (UInt16) getDefault(MJ_BMO_QUOTA_PERCENT); |
| |
| ComTdbMj * mj_tdb = |
| new(space) ComTdbMj(child_tdb1, |
| child_tdb2, |
| given_desc, |
| returned_desc, |
| (NOT doEncodedKeyCompOpt |
| ? merge_expr : left_encoded_key_expr), |
| (NOT doEncodedKeyCompOpt |
| ? comp_expr : right_encoded_key_expr), |
| left_check_dup_expr, |
| right_check_dup_expr, |
| lj_expr, |
| 0, |
| right_copy_dup_expr, |
| right_row_len, |
| rowlen, |
| work_cri_desc, |
| instantiated_row_atp_index, |
| encoded_key_len, |
| encoded_key_atp_index, |
| pre_join_expr, |
| post_join_expr, |
| (queue_index)getDefault(GEN_MJ_SIZE_DOWN), |
| (queue_index)getDefault(GEN_MJ_SIZE_UP), |
| (Cardinality) getGroupAttr()-> |
| getOutputLogPropList()[0]->getResultCardinality().value(), |
| getDefault(GEN_MJ_NUM_BUFFERS), |
| getDefault(GEN_MJ_BUFFER_SIZE), |
| is_semijoin, |
| is_leftjoin, |
| is_anti_semijoin, |
| isLeftUnique, |
| isRightUnique, |
| isOverflowEnabled, |
| scratchThresholdPct, |
| quotaMB, |
| quotaPct, |
| yieldQuota); |
| generator->initTdbFields(mj_tdb); |
| |
| if (CmpCommon::getDefault(EXE_DIAGNOSTIC_EVENTS) == DF_ON) |
| { |
| mj_tdb->setLogDiagnostics(true); |
| } |
| mj_tdb->setOverflowMode(generator->getOverflowMode()); |
| |
| if (!generator->explainDisabled()) { |
| generator->setExplainTuple( |
| addExplainInfo(mj_tdb, leftExplainTuple, rightExplainTuple, generator)); |
| } |
| |
| generator->setGenObj(this, mj_tdb); |
| |
| // restore the original down cri desc since this node changed it. |
| generator->setCriDesc(given_desc, Generator::DOWN); |
| |
| // set the new up cri desc. |
| generator->setCriDesc(returned_desc, Generator::UP); |
| |
| // reset the expression generation flag to generate float validation pcode |
| generator->setGenNoFloatValidatePCode(FALSE); |
| |
| // reset the handleIndirectVC flag to its initial value |
| exp_gen->setHandleIndirectVC( vcflag ); |
| |
| return 0; |
| } // MergeJoin::codeGen |
| |
| short NestedJoin::codeGen(Generator * generator) |
| { |
| ExpGenerator * exp_gen = generator->getExpGenerator(); |
| Space * space = generator->getSpace(); |
| |
| // set flag to enable pcode for indirect varchar |
| NABoolean vcflag = exp_gen->handleIndirectVC(); |
| if (CmpCommon::getDefault(VARCHAR_PCODE) == DF_ON) { |
| exp_gen->setHandleIndirectVC( TRUE ); |
| } |
| |
| ex_expr * after_expr = 0; |
| |
| NABoolean is_semijoin = isSemiJoin(); |
| NABoolean is_antisemijoin = isAntiSemiJoin(); |
| NABoolean is_leftjoin = isLeftJoin(); |
| NABoolean is_undojoin = isTSJForUndo(); |
| NABoolean is_setnferror = isTSJForSetNFError(); |
| |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // |
| // Layout of row returned by this node. |
| // |
| // |------------------------------------------------------------------------| |
| // | input data | left child's data | right child's data| instantiated row | |
| // | ( I tupps ) | ( L tupps ) | ( R tupps ) | ( 1 tupp ) | |
| // |------------------------------------------------------------------------| |
| // |
| // <-- returned row from left -------> |
| // <------------------ returned row from right ----------> |
| // |
| // input data: the atp input to this node by its parent. |
| // left child data: tupps appended by the left child |
| // right child data: tupps appended by right child |
| // instantiated row: For some left join cases, the |
| // null values are instantiated. See proc |
| // Join::instantiateValuesForLeftJoin for details at the end of |
| // this file. |
| // |
| // Returned row to parent contains: |
| // |
| // I + L tupps, if this is a semi join. Rows from right are not returned. |
| // |
| // If this is not a semi join, then: |
| // I + L + R tupps, if instantiation is not done. |
| // I + L + R + 1 tupps, if instantiation is done. |
| // |
| //////////////////////////////////////////////////////////////////////////// |
| |
| |
| ex_cri_desc * given_desc = generator->getCriDesc(Generator::DOWN); |
| |
| // It is OK for neither child to exist when generating a merge union TDB |
| // for index maintenenace. The children are filled in at build time. |
| // |
| GenAssert((child(0) AND child(1)) OR (NOT child(0) AND NOT (child(1))), |
| "NestedJoin -- missing one child"); |
| |
| ComTdb * tdb1 = NULL; |
| ComTdb * tdb2 = NULL; |
| ExplainTuple *leftExplainTuple = NULL; |
| ExplainTuple *rightExplainTuple = NULL; |
| |
| //++Triggers |
| // insert a temporary map table, so that we can later delete the children's map |
| // tables in case the nested join doesn't return values. |
| MapTable * beforeLeftMapTable = generator->appendAtEnd(); |
| //--Triggers |
| |
| // MV -- |
| // We need to know if the right child is a VSBB Insert node. |
| NABoolean rightChildIsVsbbInsert = FALSE; |
| NABoolean leftChildIsVsbbInsert = FALSE; |
| |
| |
| |
| GenAssert(!generator->getVSBBInsert(), "Not expecting VSBBInsert flag from parent."); |
| |
| if(child(0) && child(1)) { |
| // generate code for left child tree |
| |
| // - MVs |
| child(0)->codeGen(generator); |
| leftChildIsVsbbInsert = generator->getVSBBInsert(); |
| generator->setVSBBInsert(FALSE); |
| tdb1 = (ComTdb *)(generator->getGenObj()); |
| leftExplainTuple = generator->getExplainTuple(); |
| |
| // Override the queue sizes for the left child, if |
| // GEN_ONLJ_SET_QUEUE_LEFT is on. |
| if (generator->getMakeOnljLeftQueuesBig()) |
| { |
| short queueResizeLimit = (short) getDefault(DYN_QUEUE_RESIZE_LIMIT); |
| short queueResizeFactor = (short) getDefault(DYN_QUEUE_RESIZE_FACTOR); |
| |
| queue_index downSize = generator->getOnljLeftDownQueue(); |
| queue_index upSize = generator->getOnljLeftUpQueue(); |
| |
| downSize = MAXOF(downSize, tdb1->getInitialQueueSizeDown()); |
| upSize = MAXOF(upSize, tdb1->getInitialQueueSizeUp()); |
| |
| tdb1->setQueueResizeParams(downSize, upSize, queueResizeLimit, queueResizeFactor); |
| } |
| } |
| |
| //////////////////////////////////////////////////////////// |
| // Before generating any expression for this node, set the |
| // the expression generation flag not to generate float |
| // validation PCode. This is to speed up PCode evaluation |
| //////////////////////////////////////////////////////////// |
| generator->setGenNoFloatValidatePCode(TRUE); |
| |
| child(0)->isRowsetIterator() ? setRowsetIterator(TRUE) : setRowsetIterator(FALSE); |
| NABoolean tolerateNonFatalError = FALSE; |
| if (child(0)->getTolerateNonFatalError() == RelExpr::NOT_ATOMIC_) |
| { |
| tolerateNonFatalError = TRUE; |
| generator->setTolerateNonFatalError(TRUE); |
| generator->setTolerateNonFatalErrorInFlowRightChild(TRUE); |
| } |
| |
| if (getTolerateNonFatalError() == RelExpr::NOT_ATOMIC_) |
| { |
| tolerateNonFatalError = TRUE; |
| } |
| |
| ex_expr * before_expr = 0; |
| // generate join expression, if present. |
| if (! joinPred().isEmpty()) |
| { |
| ItemExpr * newPredTree |
| = joinPred().rebuildExprTree(ITM_AND,TRUE,TRUE); |
| exp_gen->generateExpr(newPredTree->getValueId(), ex_expr::exp_SCAN_PRED, |
| &before_expr); |
| } |
| |
| ex_cri_desc * left_child_desc = generator->getCriDesc(Generator::UP); |
| |
| // if semi join, save the address of the last map table. |
| // This is used later to remove |
| // all map tables appended by the right child tree as the right child |
| // values are not visible above this node. |
| MapTable * save_map_table = 0; |
| if (is_semijoin || is_antisemijoin) |
| save_map_table = generator->getLastMapTable(); |
| |
| if(child(0) && child(1)) { |
| // give to the second child the returned descriptor from first child |
| generator->setCriDesc(left_child_desc, Generator::DOWN); |
| |
| // reset the expression generation flag |
| generator->setGenNoFloatValidatePCode(FALSE); |
| |
| // remember that we're code gen'ing the right side of a join. |
| NABoolean wasRightSideOfOnlj = generator->getRightSideOfOnlj(); |
| NABoolean savComputeRowsetRowsAffected = |
| generator->computeRowsetRowsAffected(); |
| if (getRowsetRowCountArraySize() > 0) |
| generator->setComputeRowsetRowsAffected(TRUE); |
| generator->setRightSideOfOnlj(TRUE); |
| |
| // RHS of NestedJoin starts with LargeQueueSizes not in use (0). |
| // If a SplitTop is found, it may set the largeQueueSize to |
| // an appropriate value. |
| ULng32 largeQueueSize = generator->getLargeQueueSize(); |
| generator->setLargeQueueSize(0); |
| |
| // generate code for right child tree |
| child(1)->codeGen(generator); |
| |
| // Above the NestedJoin, we restore the LargeQueueSize to what |
| // was in effect before. |
| generator->setLargeQueueSize(largeQueueSize); |
| |
| generator->setRightSideOfOnlj(wasRightSideOfOnlj); |
| generator->setComputeRowsetRowsAffected(savComputeRowsetRowsAffected); |
| |
| rightChildIsVsbbInsert = generator->getVSBBInsert(); |
| |
| // Because of the bushy tree optimizer rule, there is a chance that our |
| // left child is a VSBB Insert, so we need to pass the flag to our parent. |
| generator->setVSBBInsert(leftChildIsVsbbInsert); |
| |
| tdb2 = (ComTdb *)(generator->getGenObj()); |
| rightExplainTuple = generator->getExplainTuple(); |
| } |
| // turn of the the Right Child Only flag. Note we turn it off only after making sure |
| // that we are in the same NestedJoinFlow::codeGen method that turned it on in the |
| // first place. |
| if (tolerateNonFatalError) |
| generator->setTolerateNonFatalErrorInFlowRightChild(FALSE); |
| |
| ex_cri_desc * right_child_desc = generator->getCriDesc(Generator::UP); |
| |
| short returned_instantiated_row_atp_index = -1; |
| |
| // set the expression generation flag not to generate float |
| // validation PCode again, as it might be reset above |
| generator->setGenNoFloatValidatePCode(TRUE); |
| |
| // only the left child's rows are returned for semi join. |
| unsigned short returned_tuples = left_child_desc->noTuples(); |
| if (! is_semijoin) { |
| returned_tuples = right_child_desc->noTuples(); |
| if (nullInstantiatedOutput().entries() > 0) |
| returned_instantiated_row_atp_index = (short) returned_tuples++; |
| } |
| ex_cri_desc * returned_desc = new(space) ex_cri_desc(returned_tuples, space); |
| |
| ValueIdSet afterPredicates; |
| if ( is_semijoin || is_antisemijoin || is_leftjoin ) |
| { |
| afterPredicates = selectionPred(); |
| } |
| else |
| { |
| GenAssert(joinPred().isEmpty(),"NOT joinPred().isEmpty()"); |
| // Since this is a form of TSJ selectionPred() should also be empty |
| // Since this is a form of TSJ selectionPred() should also be empty |
| if (getGroupAttr()->isGenericUpdateRoot() AND (NOT selectionPred().isEmpty())) |
| afterPredicates = selectionPred(); |
| else |
| GenAssert(selectionPred().isEmpty(),"NOT selectionPred().isEmpty()"); |
| } |
| |
| ex_expr * lj_expr = 0; |
| ex_expr * ni_expr = 0; |
| ULng32 rowlen = 0; |
| |
| if (nullInstantiatedOutput().entries() > 0) |
| { |
| instantiateValuesForLeftJoin(generator, |
| 0, returned_instantiated_row_atp_index, |
| &lj_expr, &ni_expr, |
| &rowlen, |
| NULL // No MapTable required |
| ); |
| Attributes *attr = 0; |
| ItemExpr *itemExpr = 0; |
| |
| for (CollIndex i = 0; i < nullInstantiatedOutput().entries(); i++) |
| { |
| itemExpr = nullInstantiatedOutput()[i].getItemExpr(); |
| attr = (generator->getMapInfo( itemExpr->getValueId() ))->getAttr(); |
| // Do not do bulk move because null instantiate expression |
| // is not set in TDB to save execution time, see ComTdbOnlj below |
| attr->setBulkMoveable(FALSE); |
| } |
| } |
| |
| // right child's values are not returned for semi join. Remove them. |
| if (is_semijoin || is_antisemijoin) |
| generator->removeAll(save_map_table); |
| |
| // generate after join expression, if present. |
| if (! afterPredicates.isEmpty()) |
| { |
| ItemExpr * newPredTree = afterPredicates.rebuildExprTree(ITM_AND,TRUE,TRUE); |
| exp_gen->generateExpr(newPredTree->getValueId(), ex_expr::exp_SCAN_PRED, |
| &after_expr); |
| } |
| |
| //++Triggers |
| // no characteristic output, remove values that where generated by the children |
| if (!(getGroupAttr()->getCharacteristicOutputs().entries())) |
| generator->removeAll(beforeLeftMapTable); |
| //--Triggers |
| |
| // get the default value for the buffer size |
| ULng32 bufferSize = (ULng32) getDefault(GEN_ONLJ_BUFFER_SIZE); |
| |
| // adjust the default and compute the size of a buffer that can |
| // accommodate five rows. The number five is an arbitrary number. |
| // Too low a number means that at execution time row processing might |
| // be blocked waiting for an empty buffer and too large a number might imply |
| // waste of memory space |
| if (rowlen) |
| { |
| bufferSize = MAXOF(bufferSize, |
| SqlBufferNeededSize(5, (Lng32)rowlen, SqlBuffer::NORMAL_)); |
| } |
| |
| |
| |
| // is this join used to drive mv logging |
| RelExpr *MvLogExpr = this; |
| while ((MvLogExpr->child(0)->castToRelExpr()->getOperator() == REL_NESTED_JOIN) || |
| (MvLogExpr->child(0)->castToRelExpr()->getOperator() == REL_LEFT_NESTED_JOIN)) |
| MvLogExpr = MvLogExpr->child(0)->castToRelExpr(); |
| |
| while ((MvLogExpr->child(1)->castToRelExpr()->getOperator() == REL_NESTED_JOIN) || |
| (MvLogExpr->child(1)->castToRelExpr()->getOperator() == REL_LEFT_NESTED_JOIN) || |
| (MvLogExpr->child(1)->castToRelExpr()->getOperator() == REL_NESTED_JOIN_FLOW)) |
| MvLogExpr = MvLogExpr->child(1)->castToRelExpr(); |
| |
| RelExpr *rightChildExpr = MvLogExpr->child(1)->castToRelExpr(); |
| OperatorTypeEnum rightChildOp = rightChildExpr->getOperatorType(); |
| NABoolean usedForMvLogging = FALSE; |
| |
| ComTdbOnlj * nlj_tdb = |
| new(space) ComTdbOnlj(tdb1, |
| tdb2, |
| given_desc, |
| returned_desc, |
| (queue_index)getDefault(GEN_ONLJ_SIZE_DOWN), |
| (queue_index)getDefault(GEN_ONLJ_SIZE_UP), |
| (Cardinality) getGroupAttr()-> |
| getOutputLogPropList()[0]-> |
| getResultCardinality().value(), |
| getDefault(GEN_ONLJ_NUM_BUFFERS), |
| bufferSize, |
| before_expr, |
| after_expr, |
| lj_expr, 0, |
| 0, |
| 0, |
| rowlen, |
| is_semijoin, |
| is_antisemijoin, |
| is_leftjoin, |
| is_undojoin, |
| is_setnferror, |
| isRowsetIterator(), |
| isIndexJoin(), |
| rightChildIsVsbbInsert, |
| getRowsetRowCountArraySize(), |
| tolerateNonFatalError, |
| usedForMvLogging |
| ); |
| // getRowsetRowCountArraySize() should return positive values |
| // only if isRowsetIterator() returns TRUE. |
| GenAssert((((getRowsetRowCountArraySize() > 0) && isRowsetIterator()) || |
| (getRowsetRowCountArraySize() == 0)), |
| "Incorrect value returned by getRowsetRowCountArray()"); |
| |
| generator->initTdbFields(nlj_tdb); |
| |
| // Make sure that the LHS up queue can grow as large as the RHS down |
| // queue. |
| //We can't let upqueue of unpack to grow beyond what was calculated and set earlier in codeGen of the unpack operator |
| if (tdb1->getClassID() != ComTdb::ex_UNPACKROWS) |
| if(tdb1->getMaxQueueSizeUp() < tdb2->getInitialQueueSizeDown()) { |
| tdb1->setMaxQueueSizeUp(tdb2->getInitialQueueSizeDown()); |
| } |
| |
| // If this NestedJoin itself is not on the RHS of a Flow/NestedJoin, |
| // Then reset the largeQueueSize to 0. |
| if(NOT generator->getRightSideOfFlow()) { |
| generator->setLargeQueueSize(0); |
| } |
| |
| // If it does not have two children, this is index maintenance code and |
| // should not be Explained |
| if(!generator->explainDisabled()) { |
| generator->setExplainTuple( |
| addExplainInfo(nlj_tdb, leftExplainTuple, rightExplainTuple, generator)); |
| } |
| |
| // restore the original down cri desc since this node changed it. |
| generator->setCriDesc(given_desc, Generator::DOWN); |
| |
| // set the new up cri desc. |
| generator->setCriDesc(returned_desc, Generator::UP); |
| |
| generator->setGenObj(this, nlj_tdb); |
| |
| // reset the expression generation flag to generate float validation pcode |
| generator->setGenNoFloatValidatePCode(FALSE); |
| |
| // reset the handleIndirectVC flag to its initial value |
| exp_gen->setHandleIndirectVC( vcflag ); |
| |
| return 0; |
| } |
| |
| short NestedJoinFlow::codeGen(Generator * generator) |
| { |
| CostScalar numberOfInputRows = getInputCardinality(); |
| |
| if ((numberOfInputRows > 1) && |
| (child(1)) && |
| ((child(1)->getOperatorType() == REL_HBASE_DELETE) || |
| (child(1)->getOperatorType() == REL_HBASE_UPDATE)) && |
| (CmpCommon::getDefault(HBASE_SQL_IUD_SEMANTICS) == DF_ON) && |
| (CmpCommon::getDefault(HBASE_UPDEL_CURSOR_OPT) == DF_ON)) |
| { |
| setOperatorType(REL_NESTED_JOIN); |
| |
| return NestedJoin::codeGen(generator); |
| } |
| |
| ExpGenerator * exp_gen = generator->getExpGenerator(); |
| MapTable * map_table = generator->getMapTable(); |
| Space * space = generator->getSpace(); |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // |
| // Layout of row returned by this node. |
| // |
| // |---------------------------------| |
| // | input data | left child's data | |
| // | ( I tupps ) | ( L tupps ) | |
| // |---------------------------------| |
| // |
| // <-- returned row from left -------> |
| // <- returned row from right -------> |
| // |
| // input data: the atp input to this node by its parent. |
| // left child data: tupps appended by the left child |
| // |
| // Returned row to parent contains: |
| // |
| // I + L tupps, since this operator doesn't produce any output. |
| // |
| //////////////////////////////////////////////////////////////////////////// |
| |
| |
| ex_cri_desc * given_desc = generator->getCriDesc(Generator::DOWN); |
| |
| ComTdb * tdb1 = NULL; |
| ComTdb * tdb2 = NULL; |
| |
| ExplainTuple *leftExplainTuple = NULL; |
| ExplainTuple *rightExplainTuple = NULL; |
| |
| NABoolean tolerateNonFatalError = FALSE; |
| |
| if(child(0) && child(1)) { |
| // generate code for left child tree |
| child(0)->codeGen(generator); |
| tdb1 = (ComTdb *)(generator->getGenObj()); |
| leftExplainTuple = generator->getExplainTuple(); |
| if (child(0)->isRowsetIterator()) |
| { |
| setRowsetIterator(TRUE); |
| if (child(0)->getTolerateNonFatalError() == RelExpr::NOT_ATOMIC_) |
| { |
| tolerateNonFatalError = TRUE; |
| generator->setTolerateNonFatalError(TRUE); |
| generator->setTolerateNonFatalErrorInFlowRightChild(TRUE); |
| } |
| } |
| generator->setTupleFlowLeftChildAttrs(child(0)->getGroupAttr()); |
| } |
| |
| ex_cri_desc * left_child_desc = generator->getCriDesc(Generator::UP); |
| |
| if(child(0) && child(1)) { |
| // give to the second child the returned descriptor from first child |
| generator->setCriDesc(left_child_desc, Generator::DOWN); |
| |
| // remember that we're code gen'ing the right side of a tuple flow. |
| NABoolean wasRightSideOfFlow = generator->getRightSideOfTupleFlow(); |
| generator->setRightSideOfTupleFlow(TRUE); |
| |
| // RHS of Flow starts with LargeQueueSizes not in use (0). |
| // If a SplitTop is found, it may set the largeQueueSize to |
| // an appropriate value. |
| ULng32 largeQueueSize = generator->getLargeQueueSize(); |
| generator->setLargeQueueSize(0); |
| |
| // generate code for right child tree |
| child(1)->codeGen(generator); |
| |
| // Above the Flow, we restore the LargeQueueSize to what |
| // was in effect before. |
| generator->setLargeQueueSize(largeQueueSize); |
| generator->setRightSideOfTupleFlow(wasRightSideOfFlow); |
| |
| tdb2 = (ComTdb *)(generator->getGenObj()); |
| rightExplainTuple = generator->getExplainTuple(); |
| } |
| |
| // turn of the the Right Child Only flag. Note we turn it off only after making sure |
| // that we are in the same NestedJoinFlow::codeGen method that turned it on in the |
| // first place. |
| if (tolerateNonFatalError) |
| generator->setTolerateNonFatalErrorInFlowRightChild(FALSE); |
| |
| ex_cri_desc * right_child_desc = generator->getCriDesc(Generator::UP); |
| |
| // only the left child's rows are returned for semi join. |
| unsigned short returned_tuples = 0; |
| #ifdef _DEBUG |
| if (getenv("RI_DEBUG")) |
| returned_tuples = right_child_desc->noTuples(); |
| else |
| returned_tuples = left_child_desc->noTuples(); |
| #else |
| returned_tuples = left_child_desc->noTuples(); |
| #endif |
| |
| returned_tuples = right_child_desc->noTuples(); |
| |
| ex_cri_desc * returned_desc = new(space) ex_cri_desc(returned_tuples, space); |
| |
| ComTdbTupleFlow * tflow_tdb = |
| new(space) ComTdbTupleFlow(tdb1, |
| tdb2, |
| given_desc, |
| returned_desc, |
| 0, // no tgt Expr yet |
| 0, // no work cri desc yet |
| (queue_index)getDefault(GEN_TFLO_SIZE_DOWN), |
| (queue_index)getDefault(GEN_TFLO_SIZE_UP), |
| (Cardinality) getGroupAttr()-> |
| getOutputLogPropList()[0]-> |
| getResultCardinality().value(), |
| getDefault(GEN_TFLO_NUM_BUFFERS), |
| getDefault(GEN_TFLO_BUFFER_SIZE), |
| generator->getVSBBInsert(), |
| isRowsetIterator(), |
| tolerateNonFatalError); |
| generator->initTdbFields(tflow_tdb); |
| |
| // turn off the VSBB insert flag in the generator, it has been |
| // processed and we don't want other nodes to use it by mistake |
| generator->setVSBBInsert(FALSE); |
| |
| tflow_tdb->setUserSidetreeInsert(generator->getUserSidetreeInsert()); |
| |
| // If this Flow itself is not on the RHS of a Flow/NestedJoin, |
| // Then reset the largeQueueSize to 0. |
| if(NOT generator->getRightSideOfFlow()) { |
| generator->setLargeQueueSize(0); |
| } |
| tflow_tdb->setSendEODtoTgt(sendEODtoTgt_); |
| |
| if(!generator->explainDisabled()) |
| generator->setExplainTuple(addExplainInfo( |
| tflow_tdb, leftExplainTuple, rightExplainTuple, generator)); |
| |
| // restore the original down cri desc since this node changed it. |
| generator->setCriDesc(given_desc, Generator::DOWN); |
| |
| // set the new up cri desc. |
| generator->setCriDesc(returned_desc, Generator::UP); |
| |
| generator->setGenObj(this, tflow_tdb); |
| |
| return 0; |
| } |
| |
| short Join::instantiateValuesForLeftJoin(Generator * generator, |
| short atp, short atp_index, |
| ex_expr ** lj_expr, |
| ex_expr ** ni_expr, |
| ULng32 * rowlen, |
| MapTable ** newMapTable, |
| ExpTupleDesc::TupleDataFormat tdf) |
| { |
| ////////////////////////////////////////////////////////////////////////////// |
| // Special handling for left joins: |
| // A null instantiated row is represented as a missing entry at the |
| // atp_index of the row(s) coming up from the right child. Any use of |
| // a value from this (missing) row is treated as a null value. |
| // This works well for the case when the output of the right child |
| // preserves null. That is, the output becomes null if its operand |
| // from the right side is null. |
| // |
| // Nulls are not preserved in two cases: |
| // |
| // 1) If output of the right child depends upon its input. |
| // For example: |
| // select * from t1 left join (select 10 from t2) on ... |
| // In this example, the constant 10 is needed to evaluate the output coming |
| // in from the right child, but the constant 10 is an input to the right |
| // child and has space allocated at atp_index = 0. So even if the row from |
| // the right is 'missing', the output value will be 10. |
| // |
| // 2) If output of the right is involved in certain expressions which |
| // do not return null if their operand is null. |
| // For example: |
| // select * from t1 left join (select case when a is null then 10 end from t2) |
| // In this case, the output of right will become 10 if the column 'a' from |
| // right table t2 is missing. But that is not correct. The correct value |
| // is a null value for the whole expression, if left join doesn't find |
| // a match. |
| // |
| // To handle these cases, the rows from the right are instantiated before |
| // returning back from the Join node. |
| // Two expressions are generated to do this. One, for the case when a match |
| // is found. The right expression is evaluated and its result moved to a separate |
| // tupp. Two, for the case when a match is not found. Then, a null value is |
| // moved to the location of the expression result. |
| // |
| ////////////////////////////////////////////////////////////////////////////// |
| ExpGenerator * exp_gen = generator->getExpGenerator(); |
| MapTable * map_table = generator->getMapTable(); |
| Space * space = generator->getSpace(); |
| |
| |
| ExpTupleDesc::TupleDataFormat tupleFormat = generator->getInternalFormat(); |
| |
| if (tdf != ExpTupleDesc::UNINITIALIZED_FORMAT) |
| { |
| tupleFormat = tdf; |
| } |
| |
| exp_gen->generateContiguousMoveExpr(nullInstantiatedOutput(), |
| 0, // don't add convert nodes |
| atp, |
| atp_index, |
| tupleFormat, |
| *rowlen, |
| lj_expr, |
| 0, // no need for ExpTupleDesc * tupleDesc |
| ExpTupleDesc::SHORT_FORMAT, |
| newMapTable); |
| |
| // generate expression to move null values to instantiate buffer. |
| ValueIdSet null_val_id_set; |
| for (CollIndex i = 0; i < nullInstantiatedOutput().entries(); i++) |
| { |
| ValueId val_id = nullInstantiatedOutput()[i]; |
| |
| ConstValue * const_value = exp_gen->generateNullConst(val_id.getType()); |
| |
| ItemExpr * ie = new(generator->wHeap()) |
| Cast(const_value, &(val_id.getType())); |
| ie->bindNode(generator->getBindWA()); |
| |
| generator->addMapInfo(ie->getValueId(), |
| generator->getMapInfo(val_id)->getAttr()); |
| |
| null_val_id_set.insert(ie->getValueId()); |
| } |
| |
| ULng32 rowlen2=0; |
| exp_gen->generateContiguousMoveExpr(null_val_id_set, |
| 0, // don't add convert nodes |
| atp, atp_index, |
| tupleFormat, |
| rowlen2, |
| ni_expr, |
| 0, // no need for ExpTupleDesc * tupleDesc |
| ExpTupleDesc::SHORT_FORMAT); |
| |
| GenAssert(rowlen2 == *rowlen, "Unexpected row length from expression"); |
| |
| return 0; |
| } |
| short Join::instantiateValuesForRightJoin(Generator * generator, |
| short atp, short atp_index, |
| ex_expr ** rj_expr, |
| ex_expr ** ni_expr, |
| ULng32 * rowlen, |
| MapTable ** newMapTable, |
| ExpTupleDesc::TupleDataFormat tdf) |
| { |
| ExpGenerator * exp_gen = generator->getExpGenerator(); |
| MapTable * map_table = generator->getMapTable(); |
| Space * space = generator->getSpace(); |
| |
| // Don't need a MapTable back. At this point, we have generated all |
| // the necessary expressions. This code is here to be consistent with the |
| // this one's counterpart - instantiateValuesForLeftJoin |
| GenAssert((newMapTable == NULL), "Don't need a Maptable back"); |
| |
| ExpTupleDesc::TupleDataFormat tupleFormat = generator->getInternalFormat(); |
| |
| if (tdf != ExpTupleDesc::UNINITIALIZED_FORMAT) |
| { |
| tupleFormat = tdf; |
| } |
| |
| exp_gen->generateContiguousMoveExpr(nullInstantiatedForRightJoinOutput(), |
| 0, // don't add convert nodes |
| atp, atp_index, |
| tupleFormat, |
| *rowlen, |
| rj_expr, |
| 0, // no need for ExpTupleDesc * tupleDesc |
| ExpTupleDesc::SHORT_FORMAT, |
| newMapTable); |
| |
| |
| // generate expression to move null values to instantiate buffer. |
| ValueIdSet null_val_id_set; |
| for (CollIndex i = 0; i < nullInstantiatedForRightJoinOutput().entries(); i++) |
| { |
| ValueId val_id = nullInstantiatedForRightJoinOutput()[i]; |
| |
| ConstValue * const_value = exp_gen->generateNullConst(val_id.getType()); |
| |
| ItemExpr * ie = new(generator->wHeap()) |
| Cast(const_value, &(val_id.getType())); |
| ie->bindNode(generator->getBindWA()); |
| |
| generator->addMapInfo(ie->getValueId(), |
| generator->getMapInfo(val_id)->getAttr()); |
| |
| null_val_id_set.insert(ie->getValueId()); |
| } |
| |
| ULng32 rowlen2=0; |
| exp_gen->generateContiguousMoveExpr(null_val_id_set, |
| 0, // don't add convert nodes |
| atp, atp_index, |
| tupleFormat, |
| rowlen2, |
| ni_expr, |
| 0, // no need for ExpTupleDesc * tupleDesc |
| ExpTupleDesc::SHORT_FORMAT); |
| |
| GenAssert(rowlen2 == *rowlen, "Unexpected row length from expression"); |
| |
| return 0; |
| } |
| |