| /********************************************************************** |
| // @@@ 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: GenRelGrby.C |
| * Description: Aggregate and grouping operators |
| * |
| * |
| * Created: 5/17/94 |
| * Language: C++ |
| * |
| * |
| * |
| * |
| ****************************************************************************** |
| */ |
| #include "ComOptIncludes.h" |
| #include "GroupAttr.h" |
| #include "RelGrby.h" |
| #include "Generator.h" |
| #include "GenExpGenerator.h" |
| //#include "ex_stdh.h" |
| #include "ExpCriDesc.h" |
| #include "ComTdb.h" |
| //#include "ex_tcb.h" |
| #include "HashRow.h" |
| #include "hash_table.h" // for HashTableHeader |
| #include "ComTdbHashGrby.h" |
| #include "ComTdbSortGrby.h" |
| #include "DefaultConstants.h" |
| #include "ItmBitMuxFunction.h" |
| #include "ComUnits.h" |
| //#include "ExStats.h" |
| |
| ///////////////////////////////////////////////////////////////////// |
| // |
| // Contents: |
| // |
| // GroupByAgg::genAggrGrbyExpr() |
| // HashGroupBy::codeGen() |
| // GroupByAgg::codeGen() |
| // |
| ////////////////////////////////////////////////////////////////////// |
| |
| // Called by GroupByAgg::codeGen() |
| short GroupByAgg::genAggrGrbyExpr(Generator * generator, |
| ValueIdSet &aggregateExpr, |
| ValueIdSet &groupExpr, |
| ValueIdList &rollupGroupExprList, |
| ValueIdSet &selectionPred, |
| Int32 workAtp, |
| Int32 workAtpIndex, |
| short returnedAtpIndex, |
| ex_expr ** aggrExpr, |
| ex_expr ** grbyExpr, |
| ex_expr ** moveExpr, |
| ex_expr ** havingExpr, |
| ComTdb ** childTdb, |
| ExpTupleDesc ** tupleDesc) { |
| ExpGenerator * expGen = generator->getExpGenerator(); |
| Space * space = generator->getSpace(); |
| |
| // remember the end of the current MT |
| MapTable * lastMapTable = generator->getLastMapTable(); |
| |
| // create extra MT to manipultate the childs MT |
| MapTable * childMapTable = generator->appendAtEnd(); |
| |
| // generate code for child tree |
| child(0)->codeGen(generator); |
| *childTdb = (ComTdb *)(generator->getGenObj()); |
| |
| // "unlink" the childs MT |
| generator->unlinkNext(lastMapTable); |
| |
| //////////////////////////////////////////////////////////// |
| // 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); |
| |
| ULng32 recLen = 0; |
| ValueId valId; |
| |
| // find the number of aggregate and groupby entries |
| ULng32 numAttrs = 0; |
| if (NOT aggregateExpr.isEmpty()) |
| numAttrs += aggregateExpr.entries(); |
| if (isRollup() && (NOT rollupGroupExprList.isEmpty())) |
| numAttrs += rollupGroupExprList.entries(); |
| else if (NOT groupExpr.isEmpty()) |
| numAttrs += groupExpr.entries(); |
| |
| NABoolean isAggrOneRow_ = FALSE; |
| |
| // ITM_ONE_ROW can occur in a scalar groupby or in a groupBy with |
| // grouping columns. The second possibility occurs only when the |
| // subquery that contained the ITM_ONE_ROW predicate has been |
| // processed by subquery unnesting. |
| if (aggregateExpr.entries() == 1) |
| { |
| ValueId exprId; |
| aggregateExpr.getFirst(exprId); |
| |
| if ( exprId.getItemExpr()->getOperatorType() == ITM_ONE_ROW ) |
| { |
| isAggrOneRow_ = TRUE; |
| } |
| } |
| |
| // add the elements in the aggr list to the map table |
| Int32 i = 0; |
| Attributes ** attrs = NULL; |
| |
| if ( NOT isAggrOneRow_ ) |
| { |
| attrs = new(generator->wHeap()) Attributes * [numAttrs]; |
| if (NOT aggregateExpr.isEmpty()) { |
| for (valId = aggregateExpr.init(); |
| aggregateExpr.next(valId); |
| aggregateExpr.advance(valId), i++) { |
| attrs[i] = (generator->addMapInfo(valId, 0))->getAttr(); |
| } |
| } |
| } |
| |
| // ----------------------------------------------------------------------- |
| // The values of this moveSet has to be copied from the child's outputs |
| // into our buffer at run-time. The moveExpr is going to do this and it |
| // will be code-generated from this moveSet later in this method. |
| // ----------------------------------------------------------------------- |
| ValueIdSet moveSet; |
| |
| if ( isAggrOneRow_ ) |
| { |
| // --------------------------------------------------------------------- |
| // First, collect all the inst-null values under ITM_ONE_ROW. We should |
| // have the same number of values as the child's outputs. |
| // --------------------------------------------------------------------- |
| GenAssert(NOT aggregateExpr.isEmpty(), |
| "both groupby and aggregate are empty"); |
| ValueIdSet inulls; |
| for (ValueId vid = aggregateExpr.init(); |
| aggregateExpr.next(vid); |
| aggregateExpr.advance(vid)) |
| { |
| GenAssert(vid.getItemExpr()->getOperatorType() == ITM_ONE_ROW, |
| "aggregateExpr of one-row aggr must be rooted by ITM_ONE_ROW"); |
| |
| // The child of ITM_ONE_ROW might be an ITEM_LIST backbone. This code |
| // assumes that the LIST backbone is a left-linear tree. |
| // |
| ValueId listBackboneVid = vid.getItemExpr()->child(0)->getValueId(); |
| NABoolean moreToGo = TRUE; |
| do { |
| ValueId listItemVid; |
| if (listBackboneVid.getItemExpr()->getOperatorType() == ITM_ITEM_LIST) |
| listItemVid = listBackboneVid.getItemExpr()->child(1)->getValueId(); |
| else |
| { |
| // reach the end of the backbone. handle the remaining one item and |
| // exit. |
| listItemVid = listBackboneVid; |
| moreToGo = FALSE; |
| } |
| |
| if (listItemVid.getItemExpr()->getOperatorType() == ITM_CAST) |
| { |
| // The code above might have inserted a ITM_CAST on top of INST_NULL. |
| listItemVid = listItemVid.getItemExpr()->child(0)->getValueId(); |
| } |
| |
| GenAssert( |
| listItemVid.getItemExpr()->getOperatorType() == ITM_INSTANTIATE_NULL, |
| "ITM_ONE_ROW's logical leaf nodes can only be inst-null values"); |
| |
| inulls.insert(listItemVid); // collect the value id. |
| |
| // Go to the next item. Here is where we assume the tree is left linear. |
| if (moreToGo) |
| listBackboneVid = listBackboneVid.getItemExpr()->child(0)->getValueId(); |
| } while (moreToGo); |
| } |
| |
| /* this might not hold due to veg rewrite. for example inst-null(1) can |
| be a result of rewriting inst-null(veg{x.a,1}). child will no longer |
| produce x.a if x.a = 1 has been evaluated at it. In that case, child's |
| output set can be empty while we still find inull(1) under the aggr. |
| |
| GenAssert(inulls.entries() == |
| child(0)->getGroupAttr()->getCharacteristicOutputs().entries(), |
| "inst-null values under ITM_ONE_ROW not same as child's outputs"); |
| */ |
| |
| moveSet += inulls; |
| |
| // --------------------------------------------------------------------- |
| // Also add my own outputs. |
| // --------------------------------------------------------------------- |
| // |
| // sol 10-070112-1749 (wangth) |
| // since isAggrOneRow_ is true we know the child node of sort_grby will |
| // either return only one row or no row at all. at runtime the executor |
| // does following (see ex_sort_grby_tcb::work in ex_sort_grby.cpp): |
| // |
| // - if one row is returned from child node, evaluate it with |
| // aggrExpr.moveExpr_ and return the results upstream. |
| // - if no row is returned from child node, don't do any evaluation |
| // but simply return NULL upstream. this can cause problem if |
| // the select object is not of column type. for example, if select |
| // contains expression such as ISNULL(), the evaluation of the ISNULL |
| // expression with no row may result in a not-NULL value. |
| // |
| // to solve above problem we have two options: |
| // |
| // 1. generate two moveExpr_ expressions, one used for one row scenario, |
| // and the other used for no row scenario. |
| // 2. if select contains expressions other than column, do not include |
| // those expressions in moveExpr_. instead, let the parent node of |
| // sort_grby do code generation for those expressions. this won't |
| // cause any performance setback because the parent node will evaluate |
| // the expressions (such as ISNULL) only once. |
| // |
| // we choose the second option. the first option is a lot more complex |
| // and risky. |
| // |
| const ValueIdSet & charOutputs = getGroupAttr()->getCharacteristicOutputs(); |
| for (valId = charOutputs.init(); |
| charOutputs.next(valId); |
| charOutputs.advance(valId)) |
| { |
| OperatorTypeEnum opType = valId.getValueDesc()->getItemExpr()->getOperatorType(); |
| if (opType == ITM_BASECOLUMN || opType == ITM_INDEXCOLUMN || |
| opType == ITM_REFERENCE || opType == ITM_BASECOL) |
| // only do code gen for columns (but not other expressions) |
| |
| // add only those value ids that are not already in the groupExpr |
| if (NOT groupExpr.contains(valId)) |
| moveSet.insert(valId); |
| } |
| |
| // ---------------------------------------------------------------------- |
| // Set up the destinations of this moveSet (if any). It's going to reside |
| // in the workAtp together with the aggregates. |
| // ---------------------------------------------------------------------- |
| attrs = new(generator->wHeap()) Attributes * [numAttrs+moveSet.entries()]; |
| |
| for (valId = aggregateExpr.init(); |
| aggregateExpr.next(valId); |
| aggregateExpr.advance(valId), i++) |
| { |
| attrs[i] = (generator->addMapInfo(valId, 0))->getAttr(); |
| } |
| |
| for (valId = moveSet.init(); |
| moveSet.next(valId); |
| moveSet.advance(valId), i++) |
| { |
| attrs[i] = (generator->addMapInfo(valId, 0))->getAttr(); |
| } |
| } // aggregate is ITM_ONE_ROW |
| |
| // create a copy of the group by set. This set is used |
| // to move the incoming values to current group buffer. |
| ValueIdList moveValIdList; |
| ValueIdList gbyValIdList; |
| ValueIdSet searchValIdSet; |
| |
| if (isRollup() && (NOT rollupGroupExprList.isEmpty())) { |
| for (CollIndex j = 0; j < rollupGroupExprList.entries(); j++) { |
| valId = rollupGroupExprList[j]; |
| |
| ItemExpr * itemExpr = valId.getItemExpr(); |
| |
| // add this converted value to the map table. |
| ItemExpr * convNode = NULL; |
| convNode = new(generator->wHeap()) Convert (itemExpr); |
| |
| // bind/type propagate the new node |
| convNode->bindNode(generator->getBindWA()); |
| |
| attrs[i++] = |
| (generator->addMapInfo(convNode->getValueId(), 0))->getAttr(); |
| moveValIdList.insert(convNode->getValueId()); |
| gbyValIdList.insert(valId); |
| |
| // add the search condition |
| BiRelat * biRelat = new(generator->wHeap()) |
| BiRelat(ITM_EQUAL, itemExpr, convNode); |
| biRelat->setSpecialNulls(-1); |
| biRelat->bindNode(generator->getBindWA()); |
| |
| biRelat->rollupColumnNum() = j+1; |
| |
| searchValIdSet.insert(biRelat->getValueId()); |
| } |
| } |
| else if (NOT groupExpr.isEmpty()) { |
| for (valId = groupExpr.init(); |
| groupExpr.next(valId); |
| groupExpr.advance(valId), i++) { |
| |
| ItemExpr * itemExpr = valId.getItemExpr(); |
| |
| // add this converted value to the map table. |
| Convert * convNode = new(generator->wHeap()) Convert (itemExpr); |
| |
| // bind/type propagate the new node |
| convNode->bindNode(generator->getBindWA()); |
| |
| attrs[i] = |
| (generator->addMapInfo(convNode->getValueId(), 0))->getAttr(); |
| moveValIdList.insert(convNode->getValueId()); |
| gbyValIdList.insert(valId); |
| |
| // add the search condition |
| BiRelat * biRelat = new(generator->wHeap()) |
| BiRelat(ITM_EQUAL, itemExpr, convNode); |
| biRelat->setSpecialNulls(-1); |
| biRelat->bindNode(generator->getBindWA()); |
| |
| searchValIdSet.insert(biRelat->getValueId()); |
| } |
| } |
| |
| numAttrs = ( isAggrOneRow_ ? numAttrs + moveSet.entries() : numAttrs ); |
| |
| // Create the descriptor describing the aggr row and assign offset to attrs. |
| expGen->processAttributes(numAttrs, |
| attrs, |
| ExpTupleDesc::SQLARK_EXPLODED_FORMAT, |
| recLen, |
| workAtp, |
| workAtpIndex, |
| tupleDesc, |
| (isRollup() ? ExpTupleDesc::LONG_FORMAT : ExpTupleDesc::SHORT_FORMAT)); |
| |
| NADELETEBASIC(attrs, generator->wHeap()); |
| |
| // add the childs MT again, we need it to generate the following expressions |
| generator->appendAtEnd(childMapTable); |
| |
| if (NOT aggregateExpr.isEmpty()) |
| { |
| if (isAggrOneRow_) |
| { |
| // -------------------------------------------------------------------- |
| // generate aggregate expression now. The function will also generate |
| // an initializeExpr to initialize all the values in moveSet to null. |
| // -------------------------------------------------------------------- |
| expGen->generateAggrExpr(aggregateExpr, ex_expr::exp_AGGR, |
| aggrExpr, 0, |
| (NOT groupExpr.isEmpty()), &moveSet); |
| |
| ((AggrExpr *)*(aggrExpr))->setOneRowAggr(); |
| } |
| else |
| { |
| // generate aggregate expression |
| expGen->generateAggrExpr(aggregateExpr, ex_expr::exp_AGGR, aggrExpr, |
| 0, (NOT groupExpr.isEmpty())); |
| } |
| } |
| |
| MapTable *returnedMapTable_ = NULL; |
| |
| if (NOT groupExpr.isEmpty()) { |
| // generate the move expression. This is used to move the incoming |
| // grouping values to the aggr buffer. |
| expGen->generateListExpr(moveValIdList, |
| ex_expr::exp_ARITH_EXPR, |
| moveExpr); |
| |
| // generate the search expression. This expression is used |
| // to look for a matching value in the hash table. |
| ItemExpr * newPredTree = |
| searchValIdSet.rebuildExprTree(ITM_AND, TRUE, TRUE); |
| |
| expGen->generateExpr(newPredTree->getValueId(), |
| ex_expr::exp_SCAN_PRED, |
| grbyExpr); |
| } |
| |
| if ( isAggrOneRow_ && (NOT moveSet.isEmpty()) ) |
| { |
| // -------------------------------------------------------------------- |
| // Generate the moveExpr which moves values from the child's outputs |
| // (atp0) to the workAtp (atp1). |
| // -------------------------------------------------------------------- |
| expGen->generateSetExpr(moveSet, |
| ex_expr::exp_ARITH_EXPR, |
| moveExpr); |
| } |
| |
| // Change the atp of aggregate values to 0. |
| // All references to these values from this point on |
| // will be at atp = 0, atp_index = last entry in returned desc. |
| // Offset will be the same as in the workAtp. |
| if (NOT aggregateExpr.isEmpty()) { |
| for (valId = aggregateExpr.init(); |
| aggregateExpr.next(valId); |
| aggregateExpr.advance(valId)) { |
| Attributes * attr = generator->getMapInfo(valId)->getAttr(); |
| attr->setAtp(0); |
| attr->setAtpIndex(returnedAtpIndex); |
| } |
| } |
| |
| // remove the child's map table. Nothing from child's context |
| // should be visible from here on upwards. |
| generator->removeAll(lastMapTable); |
| |
| if ( returnedMapTable_ ) |
| generator->appendAtEnd( returnedMapTable_ ); |
| |
| if ( isAggrOneRow_ ) |
| { |
| // ------------------------------------------------------------------- |
| // Both the selection predicates and the parent expects outputs in |
| // atp0. Set the values in moveSet to reference atp0, so that the |
| // selection predicates and parent could be generated properly. |
| // ------------------------------------------------------------------- |
| for (valId = moveSet.init(); |
| moveSet.next(valId); |
| moveSet.advance(valId)) |
| { |
| |
| MapInfo * mapInfo = generator->getMapInfo(valId); |
| Attributes * colAttr = mapInfo->getAttr(); |
| colAttr->setAtp(0); |
| |
| // This code assumes that returnedAtpIndex == workAtpIndex. |
| colAttr->setAtpIndex(returnedAtpIndex); |
| |
| // code has been generated for valId and a value is available. |
| // Mark it so. The valId should have already been marked "generated" |
| // while we were generating the moveExpr. |
| // |
| mapInfo->codeGenerated(); |
| } |
| } // if ITM_ONE_ROW and outputs exist |
| |
| // the moveValIdList (set of convert nodes) was used to move the incoming |
| // group by values to the aggregate buffer. The child of the convert node |
| // is the original grouping column. From this point on, the grouping value |
| // is available in the aggregate buffer. Change the buffer attributes of |
| // the grouping values in the map table to point to this new location. |
| // Also, the atp value of the grouping columns is now 0. |
| // This change of attributes is not done if the grouping column |
| // is a 'constant'. |
| if (NOT groupExpr.isEmpty()) { |
| ValueId convValId; |
| |
| for (CollIndex i = 0; i < moveValIdList.entries(); i++) { |
| convValId = moveValIdList[i]; |
| Attributes * newGroupColAttr = |
| (generator->getMapInfo(convValId))->getAttr(); |
| |
| ValueId valId = gbyValIdList[i]; |
| |
| // change the location of the grouping columns, unless they |
| // are input to this node. Input values already have location |
| // assigned to them. |
| if ((NOT getGroupAttr()->getCharacteristicInputs().contains(valId)) && |
| (valId.getItemExpr()->getOperatorType() != ITM_CONSTANT)) |
| { |
| MapInfo * mapInfo = generator->addMapInfo(valId, 0); |
| Attributes * oldGroupColAttr = mapInfo->getAttr(); |
| |
| oldGroupColAttr->copyLocationAttrs(newGroupColAttr); |
| oldGroupColAttr->setAtp(0); |
| oldGroupColAttr->setAtpIndex(returnedAtpIndex); |
| |
| // code has been generated for valId and a value is available. |
| // Mark it so. |
| mapInfo->codeGenerated(); |
| } |
| } |
| } |
| |
| // generate having expression, if present |
| if (NOT selectionPred.isEmpty()) { |
| ItemExpr * newPredTree = |
| selectionPred.rebuildExprTree(ITM_AND,TRUE,TRUE); |
| expGen->generateExpr(newPredTree->getValueId(), |
| ex_expr::exp_SCAN_PRED, |
| havingExpr); |
| } |
| |
| // reset the expression generation flag to generate float validation pcode |
| generator->setGenNoFloatValidatePCode(FALSE); |
| |
| return 0; |
| } // GroupByAgg::genAggrGrbyExpr() |
| |
| ///////////////////////////////////////////////////////// |
| // |
| // HashGroupBy::codeGen() |
| // |
| ///////////////////////////////////////////////////////// |
| short HashGroupBy::codeGen(Generator * generator) { |
| Space * space = generator->getSpace(); |
| |
| // create a map tables for returned group by and aggregate values |
| MapTable * myMapTable1 = generator->appendAtEnd(); |
| MapTable * myMapTable2 = generator->appendAtEnd(); |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // |
| // Layout at this node: |
| // |
| // |-------------------------------------------------| |
| // | input data | Grouped data | child's data | |
| // | ( I tupps ) | ( 1 tupp ) | ( C tupps ) | |
| // |-------------------------------------------------| |
| // <-- returned row to parent ---> |
| // <------------ returned row from child ------------> |
| // |
| // input data: the atp input to this node by its parent. |
| // grouped data: tupp where the aggr/grouped result is moved |
| // child data: tupps appended by the left child |
| // |
| // Input to child: I + 1 tupps |
| // |
| // Tupps returned from child are only used to create the |
| // grouped data. They are not returned to parent. |
| // |
| //////////////////////////////////////////////////////////////////////////// |
| |
| ex_cri_desc * givenDesc |
| = generator->getCriDesc(Generator::DOWN); |
| |
| ex_cri_desc * returnedDesc |
| = new(space) ex_cri_desc(givenDesc->noTuples() + 1, space); |
| |
| generator->setCriDesc(returnedDesc, Generator::DOWN); |
| |
| short returnedAtpIndex = (short) (returnedDesc->noTuples() - 1); |
| |
| /////////////////////////////////////////////////////////////////////////// |
| // in all the computation below, the grouped/aggregated rows is available |
| // in a temporary work atp. They are used from there to aggregate / hash / |
| // group values. |
| // At runtime, the work atp is passed as the second atp to the |
| // expression evaluation procedures. So, the atp value for all group / |
| // aggregate values ids are set to 1 (which corresponds to the second |
| // atp -- it is zero based). |
| // Before returning from this proc, these value ids are moved to the |
| // map table that is being returned. |
| // The work atp contains 6 entries: |
| // index what |
| // ------------------------------------------------- |
| // 0 constants |
| // 1 temps |
| // 2 grouped row in the hash buffer |
| // 3 another grouped row in the hash buffer, this time |
| // interpreted as an overflow buffer |
| // 4 the calculated hash value |
| // 5 grouped row in the result buffer |
| ////////////////////////////////////////////////////////////////////////// |
| short workAtpPos = 1; // second atp |
| short hbRowAtpIndex = 2; |
| short ofRowAtpIndex = 3; |
| short hashValueAtpIndex = 4; |
| short resultRowAtpIndex = 5; |
| short bitMuxAtpIndex = 6; |
| |
| ex_cri_desc * workCriDesc = new(space) ex_cri_desc(7, space); |
| |
| ComTdb * childTdb = 0; |
| |
| // The hashExpr takes a child row and calculates the hash value. The hash |
| // value is stored in the hashValueTupp_ of the hash_grby_tcb. |
| ex_expr * hashExpr = 0; |
| |
| // The bitMuxExpr takes a child row and computes the entry in the |
| // bitMuxTable of the row's group. The entry is stored in the |
| // hashValueTupp_ of the hash_grby_tcb (like hashExpr above). |
| // |
| ex_expr * bitMuxExpr = 0; |
| |
| // The bitMuxAggrExpr is equivalent to hbAggrExpr except that since |
| // the BitMux table automatically computes some aggregates (i.e. count), |
| // the bitMuxAggrExpr does not recompute these aggregates. |
| // |
| ex_expr * bitMuxAggrExpr = 0; |
| |
| // The hbMoveInExpr is used to move the grouping columns from the child |
| // row into the hash buffer. This is done if a new group is started. |
| ex_expr * hbMoveInExpr = 0; |
| |
| // The ofMoveInExpr is used to move the grouping columns from the overflow |
| // buffer into the hash buffer. This is done if a new group is started. |
| // In case of a partial hash group this expression is not generated. |
| // Note that the overflow buffer and the hash buffer contain rows with |
| // identical format. |
| ex_expr * ofMoveInExpr = 0; |
| |
| // The resMoveInExpr is used to move the grouping columns from the child |
| // row into the result buffer. This is done in case of partial hash groups |
| // if the current row doesn't belong to any group in the hash buffer. In |
| // case the row estabilshes a new (partial) group in the result buffer. The |
| // parent takes care of merging partial groups. Partial groups are used to |
| // avoid overflow handling (overflow handling can't be used in case the |
| // hash grouping is executed in DP2) or in case of parallel execution. |
| // This expression is also generated in the case of no aggregates, where |
| // the root node (non partial) also needs to copy rows from child input |
| // to the result buffer (that row/group is returned up immediately.) |
| ex_expr * resMoveInExpr = 0; |
| |
| // The hbAggrExpr is used to aggregate a child row into an existing group |
| // in the hash buffer. |
| ex_expr * hbAggrExpr = 0; |
| |
| // The ofAggrExpr is used to aggregate a row from the overflow buffer into |
| // an existing group in the hash buffer. |
| // In case of a partial hash group this expression is not generated. |
| ex_expr * ofAggrExpr = 0; |
| |
| // The resAggrExpr is used to aggregate a child row into a partial group |
| // in the result buffer. |
| // This expression is only generated in case of a partial hash group. |
| ex_expr * resAggrExpr = 0; |
| |
| // The havingExpr is used to decide whether a a group in the hash buffer |
| // qualifies and is therefore moved into the result buffer. |
| ex_expr * havingExpr = 0; |
| |
| // The moveOutExpr is used to move a qualifiying group from the hash |
| // buffer to the result buffer |
| ex_expr * moveOutExpr = 0; |
| |
| // The hbSearchExpr is used to serach the hash table for the group to which |
| // the child row belongs. |
| ex_expr * hbSearchExpr = 0; |
| |
| // The ofSearchExpr is used to search the hash table for the group to which |
| // a row in a overflow buffer belongs |
| ex_expr * ofSearchExpr = 0; |
| |
| ExpTupleDesc * tupleDesc = 0; |
| |
| ExpGenerator * expGen = generator->getExpGenerator(); |
| |
| // if the hash grouping is executed in DP2, we don't do overflow |
| // handling. This also means, that it is a partial group by |
| // Do not do overflow handling for any partial groupby. |
| // |
| NABoolean isPartialGroupBy = (isAPartialGroupByNonLeaf() || |
| isAPartialGroupByLeaf()); |
| |
| // The old way, only groupbys in DP2 are considered partial |
| // |
| if (CmpCommon::getDefault(COMP_BOOL_152) == DF_ON) { |
| isPartialGroupBy = executeInDP2(); |
| } |
| |
| // set flag to enable pcode for indirect varchar |
| NABoolean vcflag = expGen->handleIndirectVC(); |
| if (CmpCommon::getDefault(VARCHAR_PCODE) == DF_ON) { |
| expGen->setHandleIndirectVC( TRUE ); |
| } |
| |
| // If executing in DP2, then it should be a partial |
| // |
| GenAssert(!executeInDP2() || isPartialGroupBy, "Invalid Groupby in DP2"); |
| |
| // Get the value from the defaults table that specifies |
| // whether to use BitMux or not. |
| NABoolean useBitMux = (CmpCommon::getDefault(HGB_BITMUX) == DF_ON); |
| |
| useBitMux = FALSE; |
| |
| // split the aggregate into a lower and an upper aggregate function. |
| // Impotant assumption for this code: The lower and the upper aggregate |
| // have identical format. |
| ValueId valId; |
| ValueIdList originalAggrValIds(aggregateExpr()); |
| |
| GenAssert(aggregateEvaluationCanBeStaged(), |
| "illegal aggregate function for hash grouping"); |
| |
| ValueIdList lowerAggrValIdList; |
| ValueIdList upperAggrValIdList; |
| for (valId = aggregateExpr().init(); |
| aggregateExpr().next(valId); |
| aggregateExpr().advance(valId)) { |
| Aggregate *a = (Aggregate *) valId.getItemExpr(); |
| ItemExpr * newResult = |
| a->rewriteForStagedEvaluation(lowerAggrValIdList, |
| upperAggrValIdList, TRUE); |
| }; |
| |
| ValueIdSet lowerAggrValIds(lowerAggrValIdList); |
| ValueIdSet upperAggrValIds(upperAggrValIdList); |
| |
| // the value Ids of the grouping expression. |
| ValueIdSet &groupValIds = groupExpr(); |
| |
| // the size of the grouped row in the hash buffer (not including the |
| // HashRow header). |
| ULng32 groupedRowLength = 0; |
| |
| // find the number of aggregate and groupby entries |
| ULng32 numAttrs |
| = ((NOT lowerAggrValIds.isEmpty() ) ? lowerAggrValIds.entries() : 0) |
| + ((NOT groupValIds.isEmpty() ) ? groupValIds.entries() : 0); |
| |
| Attributes ** attrs = new(generator->wHeap()) Attributes * [numAttrs]; |
| |
| // add the elements in the aggr list to the map table |
| Int32 i = 0; |
| if (NOT lowerAggrValIds.isEmpty()) { |
| for (valId = lowerAggrValIds.init(); |
| lowerAggrValIds.next(valId); |
| lowerAggrValIds.advance(valId), i++) { |
| attrs[i] = (generator->addMapInfo(valId, 0))->getAttr(); |
| |
| // For any varchar aggregates to be treated as fixed values in |
| // Aligned format (CIF) |
| attrs[i]->setForceFixed(); |
| } |
| }; |
| |
| // create a copy of the group by set. This set is used |
| // to move the incoming values to the hash buffer. It is also |
| // used to generate the search expression to decide if an incoming |
| // row belongs to an existing group |
| ValueIdList hbMoveValIds; |
| ValueIdList gbyValIdList; |
| ValueIdSet hbSearchValIds; |
| |
| // remember index of first group attr |
| Int32 attrIdx = i; |
| |
| if (NOT groupValIds.isEmpty()) { |
| for (valId = groupValIds.init(); |
| groupValIds.next(valId); |
| groupValIds.advance(valId), i++) { |
| |
| ItemExpr * itemExpr = valId.getItemExpr(); |
| |
| // add this converted value to the map table. |
| Convert * convNode = new(generator->wHeap()) Convert (itemExpr); |
| |
| // bind/type propagate the new node |
| convNode->bindNode(generator->getBindWA()); |
| |
| attrs[i] = |
| (generator->addMapInfo(convNode->getValueId(), 0))->getAttr(); |
| hbMoveValIds.insert(convNode->getValueId()); |
| gbyValIdList.insert(valId); |
| |
| // add the search condition |
| BiRelat * biRelat = new(generator->wHeap()) |
| BiRelat(ITM_EQUAL, itemExpr, convNode); |
| biRelat->setSpecialNulls(-1); |
| biRelat->bindNode(generator->getBindWA()); |
| if (generator->getBindWA()->errStatus()) |
| GenExit(); |
| hbSearchValIds.insert(biRelat->getValueId()); |
| } |
| }; |
| |
| // Incoming records will be divided equally (sans data skew) among the ESPs |
| // so each HGB instance will handle only its share (i.e. divide by #esps) |
| Lng32 saveNumEsps = generator->getNumESPs(); |
| |
| // generate code for child tree |
| child(0)->codeGen(generator); |
| childTdb = (ComTdb *)(generator->getGenObj()); |
| |
| // 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 children exchange nodes may have changed it. |
| generator->setNumESPs(saveNumEsps); |
| |
| //////////////////////////////////////////////////////////// |
| // 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); |
| |
| ExpTupleDesc::TupleDataFormat tupleFormat = generator->getInternalFormat(); |
| |
| //determine the tuple format and whether we want to resize rows or not |
| NABoolean bmo_affinity = (CmpCommon::getDefault(COMPRESSED_INTERNAL_FORMAT_BMO_AFFINITY) == DF_ON); |
| NABoolean resizeCifRecord = FALSE; |
| 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(hbMoveValIds, |
| this, |
| resizeCifRecord, |
| generator, |
| bmo_affinity, |
| considerBufferDefrag); |
| considerBufferDefrag = considerBufferDefrag && resizeCifRecord; |
| } |
| |
| // If generating a contiguous move expression where the target tuple data |
| // format is a disk format - Packed or Aligned - then header information |
| // must be gathered during processing the attributes. This allows a new |
| // header clause to be generated during endExprGen() |
| ExpHdrInfo *hdrInfo = NULL; |
| if ( ExpTupleDesc::isDiskFormat(tupleFormat) ) |
| hdrInfo = new( generator->wHeap() )ExpHdrInfo(); |
| |
| // generate resMoveInExpr and resAggrExpr required in case of |
| // partial group by |
| if ( isPartialGroupBy || upperAggrValIds.isEmpty() ) { |
| // first add a maptable, so that we can get rid of all the |
| // stuff added to the map table by the following statements |
| MapTable * tempMapTable = generator->appendAtEnd(); |
| MapTable * tempMapTable1 = generator->appendAtEnd(); |
| expGen->processAttributes(numAttrs, |
| attrs, |
| tupleFormat, |
| groupedRowLength, |
| workAtpPos, |
| resultRowAtpIndex, |
| 0, |
| ExpTupleDesc::SHORT_FORMAT, |
| 0, |
| hdrInfo); |
| |
| // generate resAggrExpr |
| if (NOT lowerAggrValIds.isEmpty()) { |
| expGen->generateAggrExpr(lowerAggrValIds, ex_expr::exp_AGGR, |
| &resAggrExpr, 0, (NOT groupValIds.isEmpty())); |
| } |
| |
| // create a copy of the group by set. This set is used |
| // to move the incoming values to the result buffer. |
| ValueIdSet resMoveValIds; |
| |
| if (NOT groupValIds.isEmpty()) { |
| for (valId = groupValIds.init(); |
| groupValIds.next(valId); |
| groupValIds.advance(valId), attrIdx++) { |
| |
| ItemExpr * itemExpr = valId.getItemExpr(); |
| |
| // add this converted value to the map table. |
| Convert * convNode = new(generator->wHeap()) Convert (itemExpr); |
| |
| // bind/type propagate the new node |
| convNode->bindNode(generator->getBindWA()); |
| |
| generator->addMapInfo(convNode->getValueId(), attrs[attrIdx]); |
| resMoveValIds.insert(convNode->getValueId()); |
| |
| } |
| |
| // generate the move expression. This is used to move the incoming |
| // grouping values to the result buffer. |
| expGen->generateSetExpr(resMoveValIds, |
| ex_expr::exp_ARITH_EXPR, |
| &resMoveInExpr, |
| -1, |
| hdrInfo); |
| }; |
| |
| // the extra extressions are generated. Lets get rid of the additional |
| // map table entries |
| generator->removeAll(tempMapTable); |
| }; |
| |
| // change attributes and offsets to represent the rows in the |
| // hash buffer |
| // Offsets are based on the row starting after the HashRow structure. |
| expGen->processAttributes(numAttrs, |
| attrs, |
| tupleFormat, |
| groupedRowLength, |
| workAtpPos, |
| hbRowAtpIndex, |
| 0, |
| ExpTupleDesc::SHORT_FORMAT, |
| 0, |
| hdrInfo); |
| |
| // the size of the grouped row in the hash buffer |
| ULng32 extGroupRowLength = groupedRowLength + sizeof(HashRow); |
| |
| NADELETEBASIC(attrs, generator->wHeap()); |
| |
| // generate the hash computation function |
| ItemExpr * leftExpr = 0; |
| for (valId = groupValIds.init(); |
| groupValIds.next(valId); |
| groupValIds.advance(valId)) { |
| |
| ItemExpr * itemExpr = valId.getItemExpr(); |
| |
| BuiltinFunction * hashFunction = new(generator->wHeap()) Hash(itemExpr); |
| |
| if (leftExpr) |
| leftExpr = new(generator->wHeap()) HashComb(leftExpr, hashFunction); |
| else |
| leftExpr = hashFunction; |
| } |
| |
| leftExpr = new (generator->wHeap()) Cast(leftExpr, new (generator->wHeap()) |
| SQLInt(generator->wHeap(), FALSE, FALSE)); |
| leftExpr->setConstFoldingDisabled(TRUE); |
| |
| // bind/type propagate the hash evaluation tree |
| leftExpr->bindNode(generator->getBindWA()); |
| |
| // add the root value id to the map table. This is the hash value. |
| Attributes * mapAttr |
| = (generator->addMapInfo(leftExpr->getValueId(), 0))->getAttr(); |
| mapAttr->setAtp(workAtpPos); |
| mapAttr->setAtpIndex(hashValueAtpIndex); |
| ULng32 len; |
| ExpTupleDesc::computeOffsets(mapAttr, |
| ExpTupleDesc::SQLARK_EXPLODED_FORMAT, |
| len); |
| |
| // generate code to evaluate the hash expression |
| expGen->generateArithExpr(leftExpr->getValueId(), |
| ex_expr::exp_ARITH_EXPR, |
| &hashExpr); |
| |
| // Construct the BitMux expression. The BitMux expression computes the |
| // binary encoded key for the grouping operation. The key is stored in |
| // the bitMuxTupp_ of the workAtp. |
| // |
| // First, construct a list of the item expressions representing the |
| // grouping attributes. |
| // |
| LIST(ItemExpr*) bitMuxAttrList(generator->wHeap()); |
| for(valId = groupValIds.init(); |
| groupValIds.next(valId); |
| groupValIds.advance(valId)) { |
| |
| ItemExpr * ie = valId.getItemExpr(); |
| const NAType &valType = valId.getType(); |
| |
| if(valType.getTypeQualifier() == NA_CHARACTER_TYPE) { |
| |
| const CharType &chType = (CharType&)valType; |
| |
| if ((chType.isCaseinsensitive()) && |
| (NOT chType.isUpshifted())) { |
| ie = new (generator->wHeap()) Upper(ie); |
| ie = ie->bindNode(generator->getBindWA()); |
| } |
| // At least for now, don't use BitMux if CZECH collation is involved. |
| if (chType.getCollation() == CharInfo::CZECH_COLLATION ) |
| useBitMux = FALSE; |
| } |
| |
| bitMuxAttrList.insert(ie); |
| } |
| |
| // Allocate and bind the bit muxing item expression |
| // |
| ItemExpr *bitMuxItemExpr = new(generator->wHeap()) |
| ItmBitMuxFunction(bitMuxAttrList); |
| bitMuxItemExpr->setConstFoldingDisabled(TRUE); |
| |
| bitMuxItemExpr->bindNode(generator->getBindWA()); |
| |
| // Alter the map table so that the result of the bitMux expression |
| // side effects the bitMuxAtpIndex of the workAtp. |
| // |
| ULng32 keyLength; |
| mapAttr = generator->addMapInfo(bitMuxItemExpr->getValueId(), 0)->getAttr(); |
| mapAttr->setAtp(workAtpPos); |
| mapAttr->setAtpIndex(bitMuxAtpIndex); |
| ExpTupleDesc::computeOffsets(mapAttr, |
| ExpTupleDesc::SQLARK_EXPLODED_FORMAT, |
| keyLength); |
| |
| // a simple heuristic for now until BitMux limitations are better understood |
| if (keyLength > 12 || |
| |
| // BitMux becomes a burden once its table gets full, as it adds unneeded |
| // checks for every newly incoming group. Don't use BitMux when the |
| // groups held in memory are expected to exceed the bitmux table size! |
| |
| 1000 * 1024 < // bitmux table size (taken from ex_hash_grby.cpp) |
| (Cardinality) getEstRowsUsed().getValue() * // expected #groups |
| // Approximate memory used per each group in the BitMux table |
| // (Detail in ExBitMapTable.cpp; "+3+7" is a cheap ROUND4/8 substitute) |
| ( extGroupRowLength + keyLength + 2*sizeof(char *) + 3 + 7 ) |
| |
| ) |
| useBitMux = FALSE; |
| |
| // There is an arbitrary limit in the standard expression evaluation |
| // scheme of MAX_OPERANDS arguments (including the result) for any |
| // operation. Thus, the number of items in the BitMux attribute list |
| // must be less than MAX_OPERANDS. |
| // |
| if(bitMuxAttrList.entries() < ex_clause::MAX_OPERANDS && useBitMux) { |
| // Finally, generate the clauses for the bit muxing expression. |
| // |
| expGen->generateArithExpr(bitMuxItemExpr->getValueId(), |
| ex_expr::exp_ARITH_EXPR, |
| &bitMuxExpr); |
| } |
| |
| // If the BitMux expression is successfully generated. |
| // |
| // countOffset is the offset within the BitMux buffer to store the count |
| // for the row. By default, this is at offset zero which is where the |
| // hash value is normally stored for hash buffers. If there is a count(*) |
| // aggregate is the lower aggregates, then the countOffset will point |
| // to that location instead. |
| // |
| short bitMuxCountOffset = 0; |
| |
| if (NOT lowerAggrValIds.isEmpty()) { |
| // generate the hash buffer aggregate expression |
| expGen->generateAggrExpr(lowerAggrValIds, ex_expr::exp_AGGR, |
| &hbAggrExpr, 0, |
| (NOT groupValIds.isEmpty())); |
| |
| if (bitMuxExpr) { |
| // Generate the BitMux table aggregate expression. For now the only |
| // difference is that the BitMux table already has the count available |
| // so the BitMux aggregate expression does not need to recompute the |
| // count. |
| // |
| // First, duplicate the aggregate value id set except for any |
| // count(*) aggregates. |
| // |
| ValueIdSet bitMuxAggrValIds; |
| for (valId = lowerAggrValIds.init(); |
| lowerAggrValIds.next(valId); |
| lowerAggrValIds.advance(valId), i++) { |
| Aggregate *aggr = (Aggregate*)valId.getItemExpr(); |
| |
| if(aggr->getOperatorType() == ITM_COUNT) { |
| Attributes * attr = generator->getMapInfo(valId)->getAttr(); |
| if(bitMuxCountOffset == 0) { |
| bitMuxCountOffset = (short)attr->getOffset(); |
| } else { |
| attr->setOffset(bitMuxCountOffset); |
| } |
| continue; |
| } |
| |
| bitMuxAggrValIds += valId; |
| } |
| |
| // If there are still aggregates, generate the BitMux aggregate |
| // expression. |
| // |
| if(NOT bitMuxAggrValIds.isEmpty()) |
| expGen->generateAggrExpr(bitMuxAggrValIds, ex_expr::exp_AGGR, |
| &bitMuxAggrExpr, 0, |
| (NOT groupValIds.isEmpty())); |
| } |
| } |
| |
| |
| if (NOT groupValIds.isEmpty()) { |
| |
| // generate the move expression. This is used to move the incoming |
| // grouping values to the hash buffer. |
| expGen->generateListExpr(hbMoveValIds, |
| ex_expr::exp_ARITH_EXPR, |
| &hbMoveInExpr, |
| -1, -1, |
| hdrInfo |
| ); |
| // generate the search expression. This expression is used |
| // to look for a matching value in the hash table. |
| ItemExpr * newPredTree = |
| hbSearchValIds.rebuildExprTree(ITM_AND, TRUE, TRUE); |
| |
| expGen->generateExpr(newPredTree->getValueId(), |
| ex_expr::exp_SCAN_PRED, |
| &hbSearchExpr); |
| |
| }; |
| |
| // remove all the map tables generated by the child. This leaves us |
| // with the map table describing the grouped row in the hash buffer |
| generator->removeAll(myMapTable2); |
| |
| // generate the expression which moves a result row to the result buffer |
| ValueIdList resultValIds; |
| ULng32 resultRowLength; |
| MapTable * resultValMapTable = NULL; |
| ValueIdList moveOutValIdList; |
| ValueIdList gendAggrValIdList; |
| |
| // add the valIds of the aggregate expressions |
| if (NOT lowerAggrValIds.isEmpty()) { |
| |
| for (CollIndex i = 0; i < lowerAggrValIdList.entries(); i++) |
| { |
| valId = lowerAggrValIdList[i]; |
| ValueId origValId = originalAggrValIds[i]; |
| |
| // convert lowerAggrValIds to original aggr type before |
| // adding to the result row, if the two types are not the same. |
| ValueId moveOutValId; |
| if (! (valId.getType() == origValId.getType())) |
| { |
| ItemExpr * item_expr = new(generator->wHeap()) Cast(valId.getItemExpr(), |
| &(origValId.getType())); |
| item_expr->bindNode(generator->getBindWA()); |
| moveOutValId = item_expr->getValueId(); |
| } |
| else |
| moveOutValId = valId; |
| |
| resultValIds.insert(valId); |
| moveOutValIdList.insert(moveOutValId); |
| gendAggrValIdList.insert(moveOutValId); |
| } |
| }; |
| |
| // add the valIds for grouping |
| if (NOT hbMoveValIds.isEmpty()) { |
| for (CollIndex i = 0; i < hbMoveValIds.entries(); i++) |
| { |
| valId = hbMoveValIds[i]; |
| moveOutValIdList.insert(valId); |
| resultValIds.insert(valId); |
| } |
| }; |
| |
| expGen->generateContiguousMoveExpr(moveOutValIdList, //resultValIds, |
| -1, // add convert nodes |
| workAtpPos, |
| resultRowAtpIndex, |
| tupleFormat, |
| resultRowLength, |
| &moveOutExpr, |
| &tupleDesc, |
| ExpTupleDesc::SHORT_FORMAT, |
| &resultValMapTable); |
| |
| // if we do overflow handling, we have to generate ofMoveInExpr, |
| // ofSearchExpr, and ofAggrExpr |
| if (!isPartialGroupBy) { |
| // the map table describes a row in the hash buffer. This is the |
| // row we read in from the temporary file. Thus, we change the |
| // atp index accordingly. This guarantees, that the row in the |
| // hash buffer always has atpindex hbRowAtpIndex. |
| // The row from the overflow buffer is always the first parameter |
| // for expressions. Thus, the atp is 0. |
| for (CollIndex ix = 0; ix < resultValIds.entries(); ix++) { |
| valId = resultValIds[ix]; |
| Attributes * attr = generator->getMapInfo(valId)->getAttr(); |
| attr->setAtpIndex(ofRowAtpIndex); |
| attr->setAtp(0); |
| }; |
| |
| // add the elements of the upper aggregate to the map table |
| attrs = new(generator->wHeap()) Attributes * [numAttrs]; |
| Int32 i = 0; |
| if (NOT upperAggrValIds.isEmpty()) { |
| for (valId = upperAggrValIds.init(); |
| upperAggrValIds.next(valId); |
| upperAggrValIds.advance(valId), i++) { |
| attrs[i] = (generator->addMapInfo(valId, 0))->getAttr(); |
| |
| // For any varchar aggregates to be treated as fixed values in |
| // Aligned format (CIF) |
| attrs[i]->setForceFixed(); |
| } |
| }; |
| |
| // create a copy of the group by set. This set is used |
| // to move the incoming values from the overflow buffer to the |
| // hash buffer. It is also used to generate the search expression |
| // to decide if a row from the overflow buffer belongs to an |
| // existing group |
| ValueIdSet ofMoveValIds; |
| ValueIdSet ofSearchValIds; |
| |
| if (NOT hbMoveValIds.isEmpty()) { |
| for (CollIndex j = 0; j < hbMoveValIds.entries(); j++,i++) { |
| valId = hbMoveValIds[j]; |
| |
| ItemExpr * itemExpr = valId.getItemExpr(); |
| Attributes * hbAttr = (generator->getMapInfo(valId))->getAttr(); |
| |
| // copy this value and add it to the map table. |
| Convert * newItemExpr = new(generator->wHeap()) Convert (itemExpr); |
| |
| // bind/type propagate the new node |
| newItemExpr->bindNode(generator->getBindWA()); |
| |
| attrs[i] = |
| (generator->addMapInfo(newItemExpr->getValueId(), 0))->getAttr(); |
| ofMoveValIds.insert(newItemExpr->getValueId()); |
| |
| // add the search condition |
| BiRelat * biRelat = new(generator->wHeap()) |
| BiRelat(ITM_EQUAL, itemExpr, newItemExpr); |
| biRelat->setSpecialNulls(-1); |
| biRelat->bindNode(generator->getBindWA()); |
| ofSearchValIds.insert(biRelat->getValueId()); |
| } |
| }; |
| |
| |
| // set the attributes and offsets to represent the rows in the |
| // hash buffer |
| // Offsets are based on the row starting after the HashRow structure. |
| expGen->processAttributes(numAttrs, |
| attrs, |
| tupleFormat, |
| groupedRowLength, |
| workAtpPos, |
| hbRowAtpIndex, |
| 0, |
| ExpTupleDesc::SHORT_FORMAT, |
| 0, |
| hdrInfo); |
| |
| NADELETEBASIC(attrs, generator->wHeap()); |
| |
| // generate the overflow buffer aggregate expression which aggregates |
| // rows from the overflow buffer into groups in the hash buffer |
| if (NOT upperAggrValIds.isEmpty()) |
| expGen->generateAggrExpr(upperAggrValIds, ex_expr::exp_AGGR, |
| &ofAggrExpr, 0, |
| (NOT hbMoveValIds.isEmpty())); |
| |
| if (NOT hbMoveValIds.isEmpty()) { |
| // generate the move expression. This is used to move the |
| // grouping values from the overflow buffer to the hash buffer. |
| expGen->generateSetExpr(ofMoveValIds, |
| ex_expr::exp_ARITH_EXPR, |
| &ofMoveInExpr); |
| |
| // generate the search expression. This expression is used |
| // to look for a matching value in the hash table. |
| ItemExpr * newPredTree = |
| ofSearchValIds.rebuildExprTree(ITM_AND, TRUE, TRUE); |
| |
| expGen->generateExpr(newPredTree->getValueId(), |
| ex_expr::exp_SCAN_PRED, |
| &ofSearchExpr ); |
| }; |
| }; |
| |
| if (hdrInfo) |
| NADELETEBASIC( hdrInfo, generator->wHeap() ); |
| |
| // remove all the map tables generated by this node so far |
| generator->removeAll(myMapTable1); |
| |
| // add the resultMapTable |
| generator->appendAtEnd(resultValMapTable); |
| |
| // insert the original valueIds into the map table and |
| // change the atp of aggregate values to 0. |
| // All references to these values from this point on |
| // will be at atp = 0, atp_index = resultRowAtpIndex. |
| for (CollIndex ix = 0; ix < lowerAggrValIdList.entries(); ix++) { |
| // valId = lowerAggrValIdList[ix]; |
| valId = gendAggrValIdList[ix]; |
| |
| // get the "new" attributes |
| Attributes * newAggrAttr = |
| (generator->getMapInfo(valId))->getAttr(); |
| |
| // insert the original valIds into the map table |
| ValueId originalValId = originalAggrValIds[ix]; |
| MapInfo * originalMapInfo = generator->addMapInfo(originalValId, 0); |
| Attributes * originalAggrAttr = originalMapInfo->getAttr(); |
| |
| // set the attributes of the original valIds |
| originalAggrAttr->copyLocationAttrs(newAggrAttr); |
| originalAggrAttr->setAtp(0); |
| originalAggrAttr->setAtpIndex(returnedAtpIndex); |
| |
| // code has been generated for originalValId and a |
| // value is available. Mark it so. |
| originalMapInfo->codeGenerated(); |
| }; |
| |
| // the hbMoveValIds (set of convert nodes) was used to move the incoming |
| // group by values to the aggregate buffer. The child of the convert node |
| // is the original grouping column. From this point on, the grouping value |
| // is available in the result buffer. Change the buffer attributes of |
| // the grouping values in the map table to point to this new location. |
| // Also, the atp value of the grouping columns is now 0. |
| // This change of attributes is not done if the grouping column |
| // is a 'constant'. |
| if (NOT hbMoveValIds.isEmpty()) { |
| ValueId convValId; |
| |
| for (CollIndex i = 0; i < hbMoveValIds.entries(); i++) { |
| convValId = hbMoveValIds[i]; |
| Attributes * newGroupColAttr = |
| (generator->getMapInfo(convValId))->getAttr(); |
| |
| ValueId valId = gbyValIdList[i]; |
| |
| // change the location of the grouping columns, unless they |
| // are input to this node. Input values already have location |
| // assigned to them. |
| if ((NOT getGroupAttr()->getCharacteristicInputs().contains(valId)) && |
| (valId.getItemExpr()->getOperatorType() != ITM_CONSTANT)) { |
| MapInfo * mapInfo = generator->addMapInfo(valId, 0); |
| Attributes * oldGroupColAttr = mapInfo->getAttr(); |
| |
| oldGroupColAttr->copyLocationAttrs(newGroupColAttr); |
| oldGroupColAttr->setAtp(0); |
| oldGroupColAttr->setAtpIndex(returnedAtpIndex); |
| |
| // code has been generated for valId and a value is available. |
| // Mark it so. |
| mapInfo->codeGenerated(); |
| } |
| } |
| }; |
| |
| // generate having expression, if present |
| if (NOT selectionPred().isEmpty()) { |
| ItemExpr * newPredTree = |
| selectionPred().rebuildExprTree(ITM_AND,TRUE,TRUE); |
| expGen->generateExpr(newPredTree->getValueId(), |
| ex_expr::exp_SCAN_PRED, &havingExpr); |
| |
| }; |
| |
| ExplainTuple *childExplainTuple = generator->getExplainTuple(); |
| |
| returnedDesc-> |
| setTupleDescriptor(returnedDesc->noTuples() - 1, tupleDesc); |
| |
| // This estimate on the number of groups should work correctly even when |
| // under the right side of a NestedLoopJoin (i.e., multiple probes are |
| // sent to this HGB, but the estimate is "per a single probe".) |
| // This estimate is only used to determine the number of clusters; the |
| // hash-table would adapt dynamically to the number of groups. |
| Cardinality expectedRows = (Cardinality) getEstRowsUsed().getValue() ; |
| |
| // If this HGB is performed within ESPs, then number of records |
| // processed by each ESP is a subset of total records. |
| if ( saveNumEsps > 0 ) |
| expectedRows /= (Cardinality) saveNumEsps ; |
| |
| // also, make sure that we don't run into overflow problems. |
| // estimatedRowCount_ is an estimate. Therfore it is ok to limit it |
| |
| // the c89 doesn't handle the direct comparison of float and |
| // UINT_MAX correctly. For now we use 4294967295.0!!!!!!! |
| if (expectedRows > 4294967295.0) |
| expectedRows = (float)4294967295.0; |
| |
| // determine the size of the HGB buffers. This buffer size is used for |
| // 1 - store the rows in the hash table (HashTableBuffer) |
| // 2 - store the result rows (sql_buffer) |
| // first determine the minimum size for the hash table buffers. |
| // a buffer has to store at least one (extended) group/row plus tupp_desc |
| |
| // get the default value for the buffer size |
| ULng32 bufferSize = (ULng32) getDefault(GEN_HGBY_BUFFER_SIZE); |
| |
| ULng32 hashBufferSize = extGroupRowLength + sizeof(tupp_descriptor); |
| bufferSize = MAXOF( hashBufferSize, bufferSize ); |
| |
| // minimum result buffer size |
| ULng32 resBufferSize = resultRowLength + sizeof(tupp_descriptor); |
| bufferSize = MAXOF( resBufferSize, bufferSize ); |
| |
| short scrthreshold = |
| (short) CmpCommon::getDefaultLong(SCRATCH_FREESPACE_THRESHOLD_PERCENT); |
| short hgbGrowthPercent = |
| RelExpr::bmoGrowthPercent(getEstRowsUsed(), getMaxCardEst()); |
| |
| ComTdbHashGrby * hashGrbyTdb = new(space) |
| ComTdbHashGrby(childTdb, |
| givenDesc, |
| returnedDesc, |
| hashExpr, |
| bitMuxExpr, |
| bitMuxAggrExpr, |
| hbMoveInExpr, |
| ofMoveInExpr, |
| resMoveInExpr, |
| hbAggrExpr, |
| ofAggrExpr, |
| resAggrExpr, |
| havingExpr, |
| moveOutExpr, |
| hbSearchExpr, |
| ofSearchExpr, |
| keyLength, |
| resultRowLength, |
| extGroupRowLength, |
| workCriDesc, |
| hbRowAtpIndex, |
| ofRowAtpIndex, |
| hashValueAtpIndex, |
| bitMuxAtpIndex, |
| bitMuxCountOffset, |
| resultRowAtpIndex, |
| returnedAtpIndex, |
| (unsigned short)getDefault(BMO_MEMORY_USAGE_PERCENT), |
| (short)getDefault(GEN_MEM_PRESSURE_THRESHOLD), |
| scrthreshold, |
| (queue_index)getDefault(GEN_HGBY_SIZE_DOWN), |
| (queue_index)getDefault(GEN_HGBY_SIZE_UP), |
| isPartialGroupBy, |
| expectedRows, |
| (Lng32)getDefault(GEN_HGBY_NUM_BUFFERS), |
| bufferSize, |
| getDefault(GEN_HGBY_PARTIAL_GROUP_FLUSH_THRESHOLD), |
| getDefault(GEN_HGBY_PARTIAL_GROUP_ROWS_PER_CLUSTER), |
| (ULng32)getDefault(EXE_HGB_INITIAL_HT_SIZE), |
| // 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 |
| (ULng32) getDefault(COMP_INT_67), // numInBatch |
| hgbGrowthPercent |
| ); |
| |
| generator->initTdbFields(hashGrbyTdb); |
| hashGrbyTdb->setOverflowMode(generator->getOverflowMode()); |
| if (CmpCommon::getDefault(EXE_DIAGNOSTIC_EVENTS) == DF_ON) |
| hashGrbyTdb->setLogDiagnostics(TRUE); |
| hashGrbyTdb->setBmoMinMemBeforePressureCheck((Int16)getDefault(EXE_BMO_MIN_SIZE_BEFORE_PRESSURE_CHECK_IN_MB)); |
| |
| if(generator->getOverflowMode() == ComTdb::OFM_SSD ) |
| hashGrbyTdb->setBMOMaxMemThresholdMB((UInt16)(ActiveSchemaDB()-> |
| getDefaults()). |
| getAsLong(SSD_BMO_MAX_MEM_THRESHOLD_IN_MB)); |
| else |
| hashGrbyTdb->setBMOMaxMemThresholdMB((UInt16)(ActiveSchemaDB()-> |
| getDefaults()). |
| getAsLong(EXE_MEMORY_AVAILABLE_IN_MB)); |
| |
| hashGrbyTdb->setScratchIOVectorSize((Int16)getDefault(SCRATCH_IO_VECTOR_SIZE_HASH)); |
| |
| double memQuota = 0; |
| Lng32 numStreams; |
| double memQuotaRatio; |
| double bmoMemoryUsagePerNode = generator->getEstMemPerNode(getKey() ,numStreams); |
| |
| if(isPartialGroupBy) { |
| // The Quota system does not apply to Partial GroupBy |
| UInt16 partialMem = |
| (UInt16)(ActiveSchemaDB()->getDefaults()). |
| getAsULong(EXE_MEMORY_FOR_PARTIALHGB_IN_MB); |
| hashGrbyTdb->setPartialGrbyMemoryMB(partialMem); |
| |
| // Test the PASS Thru mode. |
| if (CmpCommon::getDefault(COMP_BOOL_159) == DF_ON) |
| hashGrbyTdb->setPassPartialRows(TRUE); |
| |
| } else { |
| |
| // 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(); |
| |
| if (mmu != 0) { |
| hashGrbyTdb->setMemoryQuotaMB(mmu); |
| memQuota = 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 hjGyMemoryLowbound = defs.getAsLong(BMO_MEMORY_LIMIT_LOWER_BOUND_HASHGROUPBY); |
| Lng32 memoryUpperbound = defs.getAsLong(BMO_MEMORY_LIMIT_UPPER_BOUND); |
| if ( memQuota < hjGyMemoryLowbound ) { |
| memQuota = hjGyMemoryLowbound; |
| memQuotaRatio = BMOQuotaRatio::MIN_QUOTA; |
| } |
| else if (memQuota > memoryUpperbound) |
| memQuota = memoryUpperbound; |
| |
| hashGrbyTdb->setMemoryQuotaMB( UInt16(memQuota) ); |
| hashGrbyTdb->setBmoQuotaRatio(memQuotaRatio); |
| } |
| |
| } |
| |
| // For debugging overflow only (default is zero == not used). |
| hashGrbyTdb-> |
| setForceOverflowEvery((UInt16)(ActiveSchemaDB()->getDefaults()). |
| getAsULong(EXE_TEST_HASH_FORCE_OVERFLOW_EVERY)); |
| |
| double hashGBMemEst = generator->getEstMemPerInst(getKey()); |
| hashGrbyTdb->setEstimatedMemoryUsage(hashGBMemEst / 1024); |
| generator->addToTotalEstimatedMemory(hashGBMemEst); |
| |
| if ( generator->getRightSideOfFlow() ) |
| hashGrbyTdb->setPossibleMultipleCalls(TRUE); |
| |
| // For now use variable size records whenever Aligned format is |
| // used. |
| if (resizeCifRecord) {//tupleFormat == ExpTupleDesc::SQLMX_ALIGNED_FORMAT) { |
| hashGrbyTdb->setUseVariableLength(); |
| if (considerBufferDefrag) |
| { |
| hashGrbyTdb->setConsiderBufferDefrag(); |
| } |
| } |
| |
| hashGrbyTdb->setCIFON((tupleFormat == ExpTupleDesc::SQLMX_ALIGNED_FORMAT)); |
| hashGrbyTdb->setHgbMemEstInKBPerNode(bmoMemoryUsagePerNode / 1024 ); |
| if (!generator->explainDisabled()) { |
| generator->setExplainTuple( |
| addExplainInfo(hashGrbyTdb, childExplainTuple, 0, generator)); |
| |
| } |
| |
| // set the new up cri desc. |
| generator->setCriDesc(returnedDesc, Generator::UP); |
| |
| // restore the original down cri desc since this node changed it. |
| generator->setCriDesc(givenDesc, Generator::DOWN); |
| |
| generator->setGenObj(this, hashGrbyTdb); |
| |
| // 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; |
| } // HashGroupBy::codeGen() |
| |
| |
| ExpTupleDesc::TupleDataFormat HashGroupBy::determineInternalFormat( const ValueIdList & valIdList, |
| RelExpr * relExpr, |
| NABoolean & resizeCifRecord, |
| Generator * generator, |
| NABoolean bmo_affinity, |
| NABoolean & considerBufferDefrag) |
| { |
| |
| RelExpr::CifUseOptions bmo_cif = RelExpr::CIF_SYSTEM; |
| |
| |
| if (CmpCommon::getDefault(COMPRESSED_INTERNAL_FORMAT_BMO) == DF_OFF) |
| { |
| bmo_cif = RelExpr::CIF_OFF; |
| } |
| else |
| if (CmpCommon::getDefault(COMPRESSED_INTERNAL_FORMAT_BMO) == DF_ON) |
| { |
| bmo_cif = RelExpr::CIF_ON; |
| } |
| |
| //CIF_SYSTEM |
| |
| return generator->determineInternalFormat(valIdList, |
| relExpr, |
| resizeCifRecord, |
| bmo_cif, |
| bmo_affinity, |
| considerBufferDefrag); |
| |
| } |
| |
| CostScalar HashGroupBy::getEstimatedRunTimeMemoryUsage(Generator *generator, NABoolean perNode, Lng32 *numStreams) |
| { |
| GroupAttributes * childGroupAttr = child(0).getGroupAttr(); |
| const CostScalar childRecordSize = childGroupAttr->getCharacteristicOutputs().getRowLength(); |
| const CostScalar childRowCount = getEstRowsUsed(); // the number of |
| //TODO: Line below dumps core at times |
| //const CostScalar maxCard = childGroupAttr->getResultMaxCardinalityForEmptyInput(); |
| const CostScalar maxCard = 0; |
| |
| // distinct rows groupped |
| // 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; |
| // totalHashTableMemory is for all CPUs at this point of time. |
| CostScalar totalHashTableMemory = |
| childRowCount * (childRecordSize + memOverheadPerRecord); |
| Lng32 numOfStreams = 1; |
| const PhysicalProperty* const phyProp = getPhysicalProperty(); |
| if (phyProp) |
| { |
| PartitioningFunction * partFunc = phyProp -> getPartitioningFunction() ; |
| numOfStreams = partFunc->getCountOfPartitions(); |
| if (numOfStreams <= 0) |
| numOfStreams = 1; |
| } |
| if (numStreams != NULL) |
| *numStreams = numOfStreams; |
| estMemPerNode = totalHashTableMemory / MINOF(MAXOF(gpClusterInfo->getTotalNumberOfCPUs(), 1), numOfStreams); |
| estMemPerInst = totalHashTableMemory / numOfStreams; |
| NABoolean isPartialGroupBy = (isAPartialGroupByNonLeaf() || |
| isAPartialGroupByLeaf()); |
| if (isPartialGroupBy) |
| { |
| estMemPerNode = 1024; |
| estMemPerInst = 1024; |
| } |
| OperBMOQuota *operBMOQuota = new (generator->wHeap()) OperBMOQuota(getKey(), numOfStreams, |
| estMemPerNode, estMemPerInst, childRowCount, maxCard); |
| generator->getBMOQuotaMap()->insert(operBMOQuota); |
| if (perNode) |
| return estMemPerNode; |
| else |
| return estMemPerInst; |
| } |
| |
| ///////////////////////////////////////////////////////// |
| // |
| // GroupByAgg::codeGen() |
| // |
| // Used by SortGroupBy and ShortCutGroupBy |
| ///////////////////////////////////////////////////////// |
| |
| short GroupByAgg::codeGen(Generator * generator) { |
| |
| Space * space = generator->getSpace(); |
| |
| // create a map table for returned group by and aggregate values |
| MapTable * myMapTable = generator->appendAtEnd(); |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // |
| // Layout at this node: |
| // |
| // |-------------------------------------------------| |
| // | input data | Grouped data | child's data | |
| // | ( I tupps ) | ( 1 tupp ) | ( C tupps ) | |
| // |-------------------------------------------------| |
| // <-- returned row to parent ---> |
| // <------------ returned row from child ------------> |
| // |
| // input data: the atp input to this node by its parent. |
| // grouped data: tupp where the aggr/grouped result is moved |
| // child data: tupps appended by the left child |
| // |
| // Input to child: I + 1 tupps |
| // |
| // Tupps returned from child are only used to create the |
| // grouped data. They are not returned to parent. |
| // |
| //////////////////////////////////////////////////////////////////////////// |
| |
| ex_cri_desc * givenDesc |
| = generator->getCriDesc(Generator::DOWN); |
| |
| ex_cri_desc * returnedDesc |
| = new(space) ex_cri_desc(givenDesc->noTuples() + 1, space); |
| |
| generator->setCriDesc(returnedDesc, Generator::DOWN); |
| |
| ComTdb * childTdb = 0; |
| |
| ex_expr * aggrExpr = 0; |
| ex_expr * havingExpr = 0; |
| ex_expr * grbyExpr = 0; |
| ex_expr * moveExpr = 0; |
| |
| ExpTupleDesc * tupleDesc = 0; |
| |
| genAggrGrbyExpr(generator, |
| aggregateExpr(), |
| groupExpr(), |
| rollupGroupExprList(), |
| selectionPred(), |
| 1, |
| returnedDesc->noTuples() - 1, |
| (short) (returnedDesc->noTuples() - 1), |
| &aggrExpr, |
| &grbyExpr, |
| &moveExpr, |
| &havingExpr, |
| &childTdb, |
| &tupleDesc); |
| |
| |
| ExplainTuple *childExplainTuple = generator->getExplainTuple(); |
| |
| returnedDesc-> |
| setTupleDescriptor(returnedDesc->noTuples() - 1, tupleDesc); |
| |
| ComTdbSortGrby * sortGrbyTdb |
| = new(space) ComTdbSortGrby(aggrExpr, |
| grbyExpr, |
| moveExpr, |
| havingExpr, |
| tupleDesc->tupleDataLength(), |
| returnedDesc->noTuples()-1, |
| childTdb, |
| givenDesc, |
| returnedDesc, |
| (queue_index)getDefault(GEN_SGBY_SIZE_DOWN), |
| (queue_index)getDefault(GEN_SGBY_SIZE_UP), |
| (Cardinality) getGroupAttr()-> |
| getOutputLogPropList()[0]-> |
| getResultCardinality().value(), |
| getDefault(GEN_SGBY_NUM_BUFFERS), |
| getDefault(GEN_SGBY_BUFFER_SIZE), |
| generator->getTolerateNonFatalError()); |
| generator->initTdbFields(sortGrbyTdb); |
| |
| if (isRollup()) |
| { |
| sortGrbyTdb->setIsRollup(TRUE); |
| |
| sortGrbyTdb->setNumRollupGroups(rollupGroupExprList().entries()); |
| } |
| |
| if(!generator->explainDisabled()) { |
| generator->setExplainTuple( |
| addExplainInfo(sortGrbyTdb, childExplainTuple, 0, generator)); |
| } |
| |
| // set the new up cri desc. |
| generator->setCriDesc(returnedDesc, Generator::UP); |
| |
| // restore the original down cri desc since this node changed it. |
| generator->setCriDesc(givenDesc, Generator::DOWN); |
| |
| generator->setGenObj(this, sortGrbyTdb); |
| |
| return 0; |
| } // GroupByAgg::codeGen() |
| |
| |