blob: 64e224b298ce3486a8a651b09ac95fe39b3f2e1a [file] [log] [blame]
// **********************************************************************
// @@@ 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 @@@
// **********************************************************************
#include "MVCandidates.h"
#include "QRDescGenerator.h"
#include "Analyzer.h"
#include "NormWA.h"
#include "parser.h"
#include "RelJoin.h"
#include "ItemLog.h"
#include "RelUpdate.h"
#include "ItemOther.h"
#include "MVInfo.h"
#ifdef NA_DEBUG_GUI
void initializeGUIData(SqlcmpdbgExpFuncs* expFuncs);
#endif
// Prefix for correlation names used for backjoin tables.
const char* const MVCandidates::BACKJOIN_CORRNAME_PREFIX = "BACKJOIN@";
static ULng32 nodeIdHashFn(const CollIndex& i) { return i; }
static ULng32 bjScanHashFn(const NAString& s) { return NAString::hash(s); }
static OperatorTypeEnum determineAggForRollup(QRMVColumnPtr mvCol);
MVCandidates::MVCandidates(RelRoot* root,
QRQueryDescriptorPtr queryDesc,
QRDescGenerator& descGenerator)
: bindWA_(root->getRETDesc()->getBindWA()),
queryRoot_(root),
queryDesc_(queryDesc),
qdescGenerator_(descGenerator),
mvqrFavoriteCandidates_(NULL),
forbiddenMVs_(NULL),
bjScanHash_(bjScanHashFn, 17, TRUE, QueryAnalysis::Instance()->outHeap())
{}
Int32 MVCandidates::nodeCount(ExprNode* node)
{
// Recurse for each child, and add one for the current node.
Int32 nodes = 0;
for (Int32 i=0; i<node->getArity(); i++)
{
nodes += nodeCount(node->getChild(i));
}
return nodes + 1;
}
// extract the list of candidate MVs from the value of the MVQR_REWRITE_CANDIDATES CQD
void MVCandidates::buildFavoritesList (CollHeap* heap)
{
NAString MVsStr = "";
CmpCommon::getDefault(MVQR_REWRITE_CANDIDATES, MVsStr);
if (MVsStr.length() > 0 ) // have any
{
// initialize mvqr favorites list
if (mvqrFavoriteCandidates_ == NULL)
mvqrFavoriteCandidates_ = new (heap) LIST(NAString)(heap);
// prepare to extract the partitions/tokens from the default string
char *token;
const char* sep = " ,:" ;
token = strtok( const_cast<char*> (MVsStr.data()), sep );
while ( token != NULL )
{
mvqrFavoriteCandidates_->insert (token);
token = strtok( NULL , sep ); // get next token
} // end of while( token )
}
}
// root is not necessarily a RelRoot, just the root node of the subtree to be
// displayed.
ItemExpr* MVCandidates::parseExpression(QRExprPtr expr, const NAString& mvName, CollHeap* heap)
{
return expr->getExprRoot()->toItemExpr(mvName, heap, &bjScanHash_);
}
GroupByAgg* MVCandidates::getGroupByAggNode(RelExpr* childNode,
QRGroupByPtr groupBy,
QRCandidatePtr candidate,
CollHeap* heap)
{
GroupByAgg* groupByAgg = new(heap) GroupByAgg(childNode, REL_GROUPBY,
NULL, NULL, heap);
// Check for the Aggregate with no GroupBy case.
if (groupBy->isEmpty())
return groupByAgg;
const ElementPtrList& groupItems = groupBy->getPrimaryList()->getList();
for (CollIndex i=0; i<groupItems.entries(); i++)
{
QRElementPtr groupItem = groupItems[i];
ElementType elemType = groupItem->getElementType();
if (elemType == ET_MVColumn)
{
QRMVColumnPtr mvcol = groupItem->downCastToQRMVColumn();
groupByAgg->addGroupExprTree(
new(heap) ColReference(getColRefName(candidate, mvcol, heap)));
}
else if (elemType == ET_Column)
{
QRColumnPtr col = groupItem->getReferencedElement()->downCastToQRColumn();
groupByAgg->addGroupExprTree(
new(heap) ColReference(getColRefName(candidate, col, heap)));
}
else if (elemType == ET_Expr)
groupByAgg->addGroupExprTree(parseExpression
(groupItem->downCastToQRExpr(),
candidate->getMVName()->getMVName(),
heap));
else
assertLogAndThrow1(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
FALSE, MVCandidateException,
"Unexpected element type for grouping item -- %d",
elemType)
}
return groupByAgg;
}
void MVCandidates::getEqSetMembers(VEGPredicate* vegPred, ValueIdSet& vegMembers)
{
ValueId vegrefVid = vegPred->getVEG()->getVEGReference()->getValueId();
const NAList<EqualitySet*>& eqSets = qdescGenerator_.getAllEqualitySets();
NABoolean found = FALSE;
for (CollIndex i=0; !found && i<eqSets.entries(); i++)
{
if (eqSets[i]->getJoinPredId() == vegrefVid)
{
EqualitySet& eqSet = *eqSets[i];
for (CollIndex j=0; j<eqSet.entries(); j++)
vegMembers.insert(eqSet[j]->getValueId());
found = TRUE;
}
}
}
ItemExpr* MVCandidates::rewriteVegPredicate(const ElementPtrList& mvColumns,
VEGPredicate* vegPred,
QRCandidatePtr candidate,
CollHeap* heap)
{
ItemExpr* andBackbone = NULL;
ItemExpr* prevOperand = NULL;
ItemExpr* operand;
ItemExpr* eqPred;
QRMVColumnPtr mvCol;
//const ValueIdSet& vegMembers = vegPred->getVEG()->getAllValues();
ValueIdSet vegMembers;
getEqSetMembers(vegPred, vegMembers);
for (ValueId memVid=vegMembers.init();
vegMembers.next(memVid);
vegMembers.advance(memVid))
{
// memVid is the valueid of the current veg member
operand = NULL;
for (CollIndex i=0; i<mvColumns.entries() && !operand; i++)
{
switch (mvColumns[i]->getElementType())
{
case ET_MVColumn:
mvCol = mvColumns[i]->downCastToQRMVColumn();
if (mvCol->hasRefTo(memVid))
operand = new(heap) ColReference(
getColRefName(candidate, mvCol, heap));
break;
default:
assertLogAndThrow1(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
FALSE, MVCandidateException,
"No code for handling element of type %d in "
"vegpred yet", mvColumns[i]->getElementType());
}
}
// If we didn't find a replacement, just make a copy of the member's
// itemexpr.
if (!operand)
{
if (memVid.getItemExpr()->getOperatorType() == ITM_BASECOLUMN)
continue;
else
operand = memVid.getItemExpr()->copyTopNode(0, heap);
}
if (prevOperand)
{
eqPred = new(heap) BiRelat(ITM_EQUAL, prevOperand, operand);
if (andBackbone)
andBackbone = new(heap) BiRelat(ITM_AND, andBackbone, eqPred);
else
andBackbone = eqPred;
}
prevOperand = operand;
}
assertLogAndThrow(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
andBackbone, MVCandidateException,
"rewriteVegPredicate returning NULL");
return andBackbone;
} // rewriteVegPredicate
ItemExpr* MVCandidates::rewriteItemExpr(const ElementPtrList& mvColumns,
ItemExpr* itemExpr,
QRCandidatePtr candidate,
CollHeap* heap,
CollIndex tblId)
{
// Copy the ItemExpr as we rewrite it; the original is currently
// owned by a node in the jbbsubset being replaced, and we would have
// to figure out which one owns it to take ownership from it. Each node
// in the copy is marked as unbound.
ItemExpr* replacement = NULL;
QRMVColumnPtr mvCol;
QRColumnPtr col;
QRElementPtr colRefedElem;
Int32 numChildren = itemExpr->getArity();
if (numChildren == 0)
{
// This happens when there is an equality predicate on a column that is
// used in an equijoin, e.g., t1.a=t12.a and t1.a=6.
if (itemExpr->getOperatorType() == ITM_VEG_PREDICATE)
return rewriteVegPredicate(mvColumns,
static_cast<VEGPredicate*>(itemExpr),
candidate, heap);
// Check each col/mvcol to see if it replaces this leaf node.
for (CollIndex i=0; i<mvColumns.entries() && !replacement; i++)
{
switch (mvColumns[i]->getElementType())
{
case ET_MVColumn:
mvCol = mvColumns[i]->downCastToQRMVColumn();
if (itemExpr->containsTheGivenValue(mvCol->getRefNum()))
{
replacement = new(heap) ColReference(
getColRefName(candidate, mvCol, heap));
OperatorTypeEnum agg = NO_OPERATOR_TYPE;
if (isRollup(candidate))
agg = determineAggForRollup(mvCol);
if (agg != NO_OPERATOR_TYPE)
replacement = new(heap) Aggregate(agg, replacement);
}
break;
case ET_Column:
case ET_JoinPred:
colRefedElem = mvColumns[i]->getReferencedElement();
if (colRefedElem->getElementType() == ET_Column)
{
col = colRefedElem->downCastToQRColumn();
if (itemExpr->containsTheGivenValue(col->getIDNum()))
replacement=new(heap)ColReference(getColRefName(candidate,
col, heap));
}
else
{
QRJoinPredPtr jp = colRefedElem->downCastToQRJoinPred();
const ElementPtrList& eqList = jp->getEqualityList();
NABoolean foundOne = FALSE;
NABoolean foundRightOne = FALSE;
for (CollIndex i=0; !foundRightOne && i<eqList.entries(); i++)
{
if (eqList[i]->getElementType() == ET_Column)
{
colRefedElem = eqList[i]->getReferencedElement();
if (colRefedElem->getElementType() == ET_Column)
{
col = colRefedElem->downCastToQRColumn();
if (itemExpr->containsTheGivenValue(col->getIDNum()))
{
foundOne = TRUE;
if (tblId == 0 ||
tblId == col->getTableIDNum())
{
foundRightOne = TRUE;
replacement = new(heap)
ColReference(getColRefName(candidate,
col, heap));
}
}
}
}
}
// We used to pick the first one, now we only accept one from
// the correct node. This should still find a member for any
// case where it did before.
assertLogAndThrow(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
!foundOne || foundRightOne,
MVCandidateException,
"Found vegref member, but not one to "
"match target node.");
}
break;
case ET_Expr:
// A col may replace an expr, but not vice versa.
break;
default:
assertLogAndThrow1(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
FALSE, MVCandidateException,
"rewriteItemExpr() not ready for element of type %d",
mvColumns[i]->getElementType());
break;
}
}
// If no replacement col/mvcol found, just copy the node.
if (!replacement)
{
if (itemExpr->getOperatorType() == ITM_VEG_REFERENCE)
{
// copyTopNode() not defined for vegref, use one of its members.
// Use a constant if available. There are cases where a constatn-
// quated column is not available in the MV.
VEGReference* vegref = static_cast<VEGReference*>(itemExpr);
ValueId someMemberId = vegref->getVEG()->getAConstant(TRUE);
if (someMemberId == NULL_VALUE_ID)
{
const ValueIdSet& vegMembers = vegref->getVEG()->getAllValues();
someMemberId = vegMembers.init();
vegMembers.next(someMemberId);
}
replacement = someMemberId.getItemExpr()->copyTopNode();
}
else
replacement = itemExpr->copyTopNode();
}
}
else // >0 children
{
// See if an mv column matches the entire expression.
for (CollIndex i=0; i<mvColumns.entries() && !replacement; i++)
{
switch (mvColumns[i]->getElementType())
{
case ET_MVColumn:
mvCol = mvColumns[i]->downCastToQRMVColumn();
if ((NumericID)itemExpr->getValueId() == mvCol->getRefNum())
{
replacement = new(heap) ColReference(
getColRefName(candidate, mvCol, heap));
OperatorTypeEnum agg = NO_OPERATOR_TYPE;
if (isRollup(candidate))
agg = determineAggForRollup(mvCol);
if (agg != NO_OPERATOR_TYPE)
replacement = new(heap) Aggregate(agg, replacement);
}
break;
case ET_Expr:
if ((NumericID)itemExpr->getValueId() == mvColumns[i]->getRefNum())
replacement = parseExpression(mvColumns[i]->downCastToQRExpr(),
candidate->getMVName()->getMVName(),
heap);
break;
default:
break;
}
}
if (!replacement)
{
replacement = itemExpr->copyTopNode();
ItemExpr* newChild;
for (Int32 i=0; i<numChildren; i++)
{
newChild = rewriteItemExpr(mvColumns, itemExpr->child(i),
candidate, heap, tblId);
if (newChild)
replacement->setChild(i, newChild);
}
}
}
replacement->markAsUnBound();
return replacement;
}
ItemExpr* MVCandidates::rewriteItemExpr(QRElementPtr mvCol, // could be col or mvcol
ItemExpr* itemExpr,
QRCandidatePtr candidate,
CollHeap* heap)
{
ElementPtrList mvColList(heap);
mvColList.insert(mvCol);
return rewriteItemExpr(mvColList, itemExpr, candidate, heap);
}
static OperatorTypeEnum determineAggForRollup(QRMVColumnPtr mvCol)
{
OperatorTypeEnum result = mvCol->getAggForRewrite();
// If already initialized, return result.
if (result != ITM_NOT)
return result;
ItemExpr* itemExpr = ((ValueId)mvCol->getRefNum()).getItemExpr();
if (itemExpr->isAnAggregate())
{
switch (itemExpr->getOperatorType())
{
case ITM_MAX:
result = ITM_MAX;
break;
case ITM_MIN:
result = ITM_MIN;
break;
case ITM_SUM:
case ITM_COUNT:
case ITM_COUNT_NONULL:
result = ITM_SUM;
break;
default:
assertLogAndThrow1(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
FALSE, MVCandidateException,
"Can't rewrite this agg func for rollup: %d",
itemExpr->getOperatorType());
}
}
else
result = NO_OPERATOR_TYPE;
mvCol->setAggForRewrite(result);
return result;
}
void MVCandidates::rewriteRangePreds(QRCandidatePtr candidate,
RelExpr* scan,
RelExpr* groupBy,
CollHeap* heap)
{
ValueId rangePredValueId;
ItemExpr* rewrittenRangePredItemExpr;
QRElementPtr rangeItem;
QRRangePredListPtr rangePreds = candidate->getRangePredList();
if (!rangePreds)
return;
for (CollIndex rpInx=0; rpInx<rangePreds->entries(); rpInx++)
{
QRRangePredPtr rangePred = (*rangePreds)[rpInx];
switch (rangePred->getResult())
{
case QRElement::Outside:
case QRElement::Provided:
// nothing needs to be done
break;
case QRElement::NotProvided:
// Need to use the ItemExpression for the range predicate from the
// query, and substitute the value id of the MV column in place of
// the value id of the column the range is on in the query.
rangePredValueId = rangePred->getRefNum();
rewrittenRangePredItemExpr = NULL;
rangeItem = rangePred->getRangeItem();
if (rangeItem->getElementType() == ET_MVColumn)
{
QRMVColumnPtr mvCol = rangeItem->downCastToQRMVColumn();
if (candidate->isIndirectGroupBy())
determineAggForRollup(mvCol);
if (rangePred->getRefNum() == 0) // If no ref attribute.
{
// This is not a reference to a query range predicate, but
// rather one we need to construct here.
// We only do IS NOT NULL range preds for LOJ support.
NABoolean isNotNull = (rangePred->getOperatorList().entries() == 0);
assertLogAndThrow(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
isNotNull, MVCandidateException,
"Expecting only IS NOT NULL range predicates to have no ref attributes.");
ItemExpr* mvCol =
new(heap) ColReference(getColRefName(candidate, rangeItem, heap));
rewrittenRangePredItemExpr =
new(heap) UnLogic(ITM_IS_NOT_NULL, mvCol);
}
else
{
rewrittenRangePredItemExpr =
rewriteItemExpr(rangeItem,
rangePredValueId.getItemExpr(),
candidate,
heap);
}
}
else if (rangeItem->getElementType() == ET_Column)
{
// Range pred for a dimension table of an IGB query is handled
// in addPredsFromList(). For non-IGB query, a column here is
// for a NotProvided range predicate on a column of a backjoin
// table. Use the hash table to see which scan node to add the
// rewritten predicate to.
if (!candidate->isIndirectGroupBy())
{
QRColumnPtr col = rangeItem->getReferencedElement()
->downCastToQRColumn();
Scan* bjScan = bjScanHash_.getFirstValue(&col->getTableID());
assertLogAndThrow(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
bjScan, MVCandidateException,
"Could not find scan node for back join "
"table.");
// Don't assign the rewritten pred to rewrittenRangePredItemExpr,
// or it will be added to the MV node below.
bjScan->addSelPredTree(
rewriteItemExpr(rangeItem,
rangePredValueId.getItemExpr(),
candidate,
heap));
}
}
else if (rangeItem->getElementType() == ET_Expr)
{
rewrittenRangePredItemExpr =
rewriteItemExpr(rangeItem,
rangePredValueId.getItemExpr(),
candidate,
heap);
}
else
assertLogAndThrow1(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
FALSE, MVCandidateException,
"Not ready for range item with element type %d",
rangeItem->getElementType())
if (rewrittenRangePredItemExpr)
{
if (!rewrittenRangePredItemExpr->containsAnAggregate())
scan->addSelPredTree(rewrittenRangePredItemExpr);
else if (groupBy)
groupBy->addSelPredTree(rewrittenRangePredItemExpr);
else
QRLogger::log(CAT_SQL_COMP_MVCAND, LL_DEBUG,
"Rewritten range pred contains an aggregate, but "
"there is no Group By node -- pred not added.");
}
break;
default:
assertLogAndThrow1(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
FALSE, MVCandidateException,
"Unknown value for result attribute -- %d",
rangePred->getResult());
break;
}
}
}
// If back joins were added to get columns used in predicates, this function
// will determine the owning node for a predicate based on its inputs. If a
// single back join scan node is used, it is returned. If >1 back join node is
// used, or the MV node and 1 or more back join node, the root of the join
// tree that connects the MV and all the back join nodes is returned. If only
// the MV is involved, NULL is returned, and the predicate will be attached to
// the MV scan or Group By node as appropriate.
static RelExpr* getBackJoinNode(const ElementPtrList& inputList,
RelExpr* bjRoot,
NAHashDictionary<const NAString, Scan>& bjScanHash)
{
QRElementPtr elem;
RelExpr* bjOwningNode = NULL;
NABoolean mvUsed = FALSE;
Scan* bjScan;
QRColumnPtr col;
for (CollIndex i=0; i<inputList.entries(); i++)
{
elem = inputList[i];
if (elem->getElementType() == ET_MVColumn)
{
if (bjOwningNode)
return bjRoot; // multinode pred, put in highest join node
else
mvUsed = TRUE;
}
else
{
assertLogAndThrow1(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
elem->getElementType() == ET_Column,
MVCandidateException,
"getBackJoinNode() -- can't handle element of type %d",
elem->getElementType());
col = elem->getReferencedElement()->downCastToQRColumn();
bjScan = bjScanHash.getFirstValue(&col->getTableID());
assertLogAndThrow(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
bjScan, MVCandidateException,
"Could not find scan node for back join table.");
if (mvUsed)
return bjRoot; // multinode pred, put in highest join node
else if (bjOwningNode)
{
if (bjScan != bjOwningNode)
return bjRoot; // multinode pred, put in highest join node
}
else
bjOwningNode = bjScan;
}
}
return bjOwningNode;
}
void MVCandidates::rewriteResidPreds(QRCandidatePtr candidate,
RelExpr* scan,
RelExpr* groupBy,
RelExpr* bjRoot,
CollHeap* heap)
{
QRExprPtr residPred;
QRResidualPredListPtr residPreds = candidate->getResidualPredList();
if (!residPreds)
return;
// Set up the list of element types we want to look for when searching the
// tree for inputs.
NAList<ElementType> inputTypes(heap);
inputTypes.insert(ET_MVColumn);
inputTypes.insert(ET_Column); // obtained by back join
for (CollIndex rpInx=0; rpInx<residPreds->entries(); rpInx++)
{
residPred = (*residPreds)[rpInx];
switch (residPred->getResult())
{
case QRElement::Outside:
case QRElement::Provided:
// nothing needs to be done
break;
case QRElement::NotProvided:
{
ValueId residPredValueId = residPred->getRefNum();
ElementFinderVisitorPtr visitor =
new(heap) ElementFinderVisitor(inputTypes, FALSE,
ADD_MEMCHECK_ARGS(heap));
residPred->getExprRoot()->treeWalk(visitor);
const ElementPtrList& inputList = visitor->getElementsFound();
RelExpr* bjOwningNode = getBackJoinNode(inputList, bjRoot, bjScanHash_);
ItemExpr* rewrittenResidPredItemExpr =
rewriteItemExpr(inputList,
residPredValueId.getItemExpr(),
candidate, heap);
if (bjOwningNode)
bjOwningNode->addSelPredTree(rewrittenResidPredItemExpr);
else if (!rewrittenResidPredItemExpr->containsAnAggregate())
scan->addSelPredTree(rewrittenResidPredItemExpr);
else if (groupBy)
groupBy->addSelPredTree(rewrittenResidPredItemExpr);
else
QRLogger::log(CAT_SQL_COMP_MVCAND, LL_DEBUG,
"Rewritten residual pred contains an aggregate, "
"but there is no Group By node -- pred not added.");
deletePtr(visitor);
}
break;
default:
assertLogAndThrow1(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
FALSE, MVCandidateException,
"Unknown value for result attribute -- %d",
residPred->getResult());
break;
}
}
}
ColRefName* MVCandidates::getColRefName(QRCandidatePtr candidate,
QRElementPtr elem,
CollHeap* heap)
{
ColRefName* returnVal;
ElementType elemType = elem->getElementType();
if (elemType == ET_Output)
{
elem = static_cast<QROutputPtr>(elem)->getOutputItem();
elemType = elem->getElementType();
}
if (elemType == ET_Column)
{
QRColumnPtr colElem = elem->downCastToQRColumn();
const NAString& colName = colElem->getColumnName();
NAString fqTblName(colElem->getFullyQualifiedColumnName(), heap);
// Truncate last dot and col name to leave fully qualified table name.
fqTblName.resize(fqTblName.length() - colName.length() - 1);
QualifiedName tblQualName(fqTblName, 3, heap, bindWA_);
NAString corrNameStr;
// Add special correlation name if the column is from a backjoin table.
const NAString& tblID = colElem->getTableID();
if (bjScanHash_.getFirstValue(&tblID))
(corrNameStr = BACKJOIN_CORRNAME_PREFIX).append(tblID);
CorrName tblCorrName(tblQualName, heap, corrNameStr);
returnVal = new(heap) ColRefName(colName, tblCorrName, heap);
}
else if (elemType == ET_MVColumn)
{
QualifiedName mvQualName(candidate->getMVName()->getMVName(),
3, heap, bindWA_);
CorrName mvCorrName(mvQualName, heap);
returnVal = new(heap) ColRefName(elem->downCastToQRMVColumn()
->getMVColName(),
mvCorrName,
heap);
}
else
throw QRDescriptorException("getColRefName() cannot be called for element %s",
elem->getElementName());
return returnVal;
} // getColRefName()
static ItemExpr*
addToList(ItemExpr *list, ItemExpr *item, CollHeap* heap)
{
if (list == NULL)
list = item;
else
list = new(heap) ItemList(list, item);
return list;
}
void MVCandidates::buildOutputExprs(QRCandidatePtr candidate,
RelRoot* root,
ElementPtrList& topValElemList,
CollHeap* heap)
{
ItemExpr* compExpr;
ItemExpr* selectList = NULL;
QRElementPtr outputItem;
QRMVColumnPtr mvCol;
QRColumnPtr col;
QRExprPtr expr;
QROutputListPtr outputList = candidate->getOutputList();
// Output list could be null if all items in select list are part of a veg
// that includes a constant.
if (!outputList)
return;
for (CollIndex outInx=0; outInx<outputList->entries(); outInx++)
{
QROutputPtr output = (*outputList)[outInx];
switch (output->getResult())
{
case QRElement::Provided:
{
outputItem = output->getOutputItem();
assertLogAndThrow1(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
outputItem->getElementType() == ET_MVColumn,
MVCandidateException,
"Unexpected element type for output item with "
"result='Provided' -- %d",
outputItem->getElementType());
topValElemList.insert(output);
if (!candidate->isIndirectGroupBy())
{
mvCol = outputItem->downCastToQRMVColumn();
compExpr = new(heap) ColReference
(getColRefName(candidate, mvCol, heap));
selectList = addToList(selectList, compExpr, heap);
}
}
break;
case QRElement::NotProvided:
{
const ElementPtrList& outputItemList = output->getOutputItems();
if (outputItemList.entries() == 0)
{
// @ZX -- Output expression involving only constants. Maybe we
// should assert false here; I don't think such a thing
// will be included in the result descriptor.
}
else if (outputItemList.entries() > 1)
{
assertLogAndThrow(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
FALSE, MVCandidateException,
"Output item list with multiple entries "
"(partial matching) not handled yet")
}
else if (outputItemList[0]->getElementType() == ET_Column)
{
// Output expression is a column requiring a back-join.
// There must be a single output item, which is the column.
assertLogAndThrow1(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
outputItemList.entries() == 1,
MVCandidateException,
"Output item list for col ref should have "
"a single item, instead of %d",
outputItemList.entries());
topValElemList.insert(output);
if (!candidate->isIndirectGroupBy())
{
col = outputItemList[0]->downCastToQRColumn();
compExpr = new(heap) ColReference
(getColRefName(candidate, col, heap));
selectList = addToList(selectList, compExpr, heap);
}
}
else // The output item must be an <Expr>.
{
assertLogAndThrow1(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
outputItemList.entries() == 1 &&
outputItemList[0]->getElementType() == ET_Expr,
MVCandidateException,
"Element of type <Expr> expected in "
"this <Output> element instead of %d",
outputItemList[0]->getElementType());
// Don't include aggregates in the top value list for the
// value id map, they are added by a different method.
// Aggs are the only items added to the select list of
// the root for an IQB query.
expr = outputItemList[0]->downCastToQRExpr();
NABoolean isAggregate = expr->getExprRoot()->isAnAggregate();
NABoolean hasAggregate = expr->getExprRoot()->containsAnAggregate(heap);
if (!isAggregate)
topValElemList.insert(output);
if (!candidate->isIndirectGroupBy() || hasAggregate)
{
compExpr = parseExpression(expr, candidate->getMVName()->getMVName(), heap);
if (hasAggregate)
{
// Create a fake name for the agg that embeds the
// value id it maps to, so we can create the map
// vid entry for it when it has its own value id.
char colName[20];
sprintf(colName, "%.10s%d",
AGG_NAME_PREFIX, output->getRefNum());
compExpr = new(heap)
RenameCol(compExpr, new(heap) ColRefName(colName, heap));
}
selectList = addToList(selectList, compExpr, heap);
}
}
}
break;
case QRElement::Outside:
// No action; output item is outside this jbbsubset.
break;
default:
assertLogAndThrow1(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
FALSE, MVCandidateException,
"Unknown value for result attribute -- %d",
output->getResult());
break;
}
}
// Add the select list to the node. For an IGB without aggs (unlikely), it
// will be null.
if (selectList)
{
root->addCompExprTree(selectList);
}
} // buildOutputExprs()
void MVCandidates::analyzeCandidateMVs(QRJbbSubsetPtr qrJbbSubset)
{
// Get the actual (Analyzer) jbb subset corresponding to the tables in the
// QRJbbSubset, as well as the GroupBy node if there is one.
QRTableListPtr tables = qrJbbSubset->getTableList();
CANodeIdSet jbbNodeSet;
for (CollIndex i=0; i<tables->entries(); i++)
{
jbbNodeSet.addElement((*tables)[i]->getRefNum());
}
if (qrJbbSubset->hasGroupBy())
jbbNodeSet.addElement(qrJbbSubset->getRefNum());
JBBSubset* actualJbbSubset = jbbNodeSet.computeJBBSubset();
// No jbb subset for single-table queries
NABoolean handleRewriteOnSingleTable =
(CmpCommon::getDefault(MVQR_REWRITE_SINGLE_TABLE_QUERIES) == DF_ON);
if (actualJbbSubset->getJBBCs().entries() == 0 && !handleRewriteOnSingleTable)
{
QRLogger::log(CAT_SQL_COMP_MVCAND, LL_INFO,
"JBBSubset has no JBBCs; skipping candidate analysis");
delete actualJbbSubset;
return;
}
QRCandidateListPtr candidates = qrJbbSubset->getCandidateList();
QRCandidatePtr candidate;
QueryAnalysis* qa = QueryAnalysis::Instance();
CollHeap* heap = qa->outHeap();
// if we have candidates go ahead and build the favorites list
if ((mvqrFavoriteCandidates_ == NULL) && (candidates->entries()))
buildFavoritesList (heap);
NABoolean rewriteSingleNode;
for (CollIndex i=0; i<candidates->entries(); i++)
{
candidate = (*candidates)[i];
if (actualJbbSubset->getJBBCs().entries() == 0)
{
assertLogAndThrow(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
(handleRewriteOnSingleTable && (queryRoot_->child(0)->getOperator().match(REL_SCAN))),
MVCandidateException,
"No JBBCs in jbbsubset analysis");
rewriteSingleNode = TRUE;
}
else
rewriteSingleNode = FALSE;
try
{
analyzeCandidate(candidate,
(candidate->isIndirectGroupBy()
? const_cast<JBBSubset&>(actualJbbSubset->getJBB()->getMainJBBSubset())
: *actualJbbSubset),
rewriteSingleNode,
jbbNodeSet, heap);
}
catch (MVCandidateException&)
{
// Throwers of this exception should use one of the macros that logs the
// exception message with file and line info (e.g., assertLogAndThrow).
// Here, we just log the fact that the candidate was ignored.
QRLogger::log(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
"Skipping candidate MV %s due to exception",
candidate->getMVName()->getMVName().data());
}
}
delete actualJbbSubset;
}
ValueId MVCandidates::getRewriteVid(RETDesc* retDesc,
QRElementPtr elem,
QRCandidatePtr candidate,
CollHeap* heap)
{
const ColumnDescList* colDescList = retDesc->getColumnList();
CollIndex numCols = colDescList->entries();
ColRefName* colRefName = getColRefName(candidate, elem, heap);
for (CollIndex i=0; i<numCols; i++)
{
if (*colRefName == (*colDescList)[i]->getColRefNameObj())
return retDesc->getValueId(i);
}
assertLogAndThrow(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
FALSE, MVCandidateException,
"Column in <Output> element of result descriptor not found "
"in column descriptor list");
}
ValueId MVCandidates::getBaseColValueId(QRElementPtr referencingElem)
{
if (*referencingElem->getRef() == 'O')
referencingElem = static_cast<QROutputPtr>(referencingElem)->getOutputItem();
const NAString& ref = referencingElem->getRef();
char elemIdChar = *ref.data();
// If an expression, return null and no mapvid entry will be added.
if (elemIdChar == 'X' || elemIdChar == 'S' || elemIdChar == 0)
return NULL_VALUE_ID;
if (elemIdChar != 'J')
{
assertLogAndThrow1(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
elemIdChar == 'C',
MVCandidateException,
"Reference %s does not start with 'J' or 'C'",
ref.data());
return referencingElem->getRefNum();
}
ValueId vegrefVid = referencingElem->getRefNum();
ItemExpr* ie = vegrefVid.getItemExpr();
assertLogAndThrow1(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
ie->getOperatorType() == ITM_VEG_REFERENCE,
MVCandidateException,
"Reference %s is not to a vegref", ref.data());
const ValueIdSet &vegMembers =
(static_cast<VEGReference*>(ie))->getVEG()->getAllValues();
ValueId someMemberId = vegMembers.init();
if (vegMembers.next(someMemberId) &&
someMemberId.getItemExpr()->getOperatorType() == ITM_BASECOLUMN)
return someMemberId;
assertLogAndThrow1(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
FALSE, MVCandidateException,
"No base column found in vegref referenced by %s",
ref.data());
//else if (pExpr->getOperatorType() == ITM_INDEXCOLUMN)
// {
// cvid = static_cast<IndexColumn*>(pExpr)->getDefinition();
// pBC = static_cast<BaseColumn*>(cvid.getItemExpr());
// }
}
ValueId MVCandidates::getVegrefValueId(QRElementPtr referencingElem)
{
// If the reference is not to a column, it won't have a vegref in its
// query descriptor element. In these cases, the ref number should itself
// be that of the vegref.
const NAString& ref = referencingElem->getRef();
if (*ref.data() != 'C')
return referencingElem->getRefNum();
const QRElementHash& hash = qdescGenerator_.getColTblIdHash();
QRElementPtr referencedElem = hash.getFirstValue(&ref);
if (referencedElem && referencedElem->getElementType() == ET_Column)
{
ValueId vrid = static_cast<QRColumnPtr>(referencedElem)->getVegrefId();
return (vrid != NULL_VALUE_ID
? vrid
: (ValueId)referencingElem->getRefNum());
}
else
{
QRLogger::log(CAT_SQL_COMP_MVCAND, LL_DEBUG,
"No vegref found for element refed by id %s, using ref id", ref.toCharStar());
return referencingElem->getRefNum();
}
}
// For the method mapVidNode->pushdownCoveredExpr to work correctly, the lower values
// of the MVID have to be expressed as vegrefs hence the reason for this method.
void MVCandidates::populateOneVidMap(RelExpr* root,
ValueIdSet& vegRewriteVids,
QRElementPtr elem,
QRCandidatePtr candidate,
ValueIdMap& vidMap,
CollHeap* heap)
{
BaseColumn* baseCol;
Int32 colIndex;
TableDesc* tableDesc;
ItemExpr* ie;
ItemExpr* rewriteIE;
ValueId rewriteVid;
ValueId baseColVid;
rewriteVid = getRewriteVid(root->getRETDesc(),
elem,
candidate, heap);
rewriteIE = rewriteVid.getItemExpr();
if (rewriteIE->getOperatorType() == ITM_BASECOLUMN)
{
baseCol = static_cast<BaseColumn*>(rewriteIE);
colIndex = baseCol->getColNumber();
tableDesc = baseCol->getTableDesc();
if (tableDesc)
{
ValueIdList baseCols;
ValueIdList vegCols;
ValueId vegrefVid;
baseCols.insert(baseCol->getValueId());
tableDesc->getEquivVEGCols(baseCols, vegCols);
ie = vegCols[0].getItemExpr();
if (ie->getOperatorType() == ITM_VEG_REFERENCE)
{
// In a rollup case, sometimes QRMVColumn elements have no direct
// corresponding element in the query descriptor and therefore
// have no ref attribute. In these cases, we need to avoid adding
// a zero value id to the mapvid node.
vegrefVid = getVegrefValueId(elem);
if (vegrefVid == NULL_VALUE_ID)
return;
if (vidMap.getTopValues().contains(vegrefVid))
return;
vidMap.addMapEntry(vegrefVid, ie->getValueId());
}
else
assertLogAndThrow(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
FALSE, MVCandidateException,
"Unable to get vegref for map value id lower value")
baseColVid = getBaseColValueId(elem);
if (baseColVid != NULL_VALUE_ID)
vegRewriteVids.addElement(baseColVid);
}
}
else
{
assertLogAndThrow(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
FALSE, MVCandidateException,
"Expecting a base column while constructing map value id lower value")
}
}
void MVCandidates::populateVidMap(RelExpr* root,
ElementPtrList& vidMapTopElements,
ValueIdSet& vegRewriteVids,
QRCandidatePtr candidate,
ValueIdMap& vidMap,
CollHeap* heap)
{
QROutputPtr output;
QRElementPtr elem;
ElementType elemType;
QRExprPtr expr;
for (CollIndex i=0; i<vidMapTopElements.entries(); i++)
{
output = vidMapTopElements[i]->downCastToQROutput();
elem = output->getOutputItem();
elemType = elem->getElementType();
// ET_Columns will be present if there are back joins.
if (elemType == ET_MVColumn || elemType == ET_Column)
{
// Use id of Output elem, so it will work for LOJ as well
populateOneVidMap(root, vegRewriteVids, output, candidate, vidMap, heap);
}
else if (elemType == ET_Expr)
{
expr = elem->downCastToQRExpr();
if (expr->getExprRoot()->isAnAggregate())
{
// These have already been taken care of and shouldn't be in the
// top value list.
assertLogAndThrow(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
FALSE, MVCandidateException,
"Aggregate function found in top value list");
}
else
{
const ElementPtrList& colsUsed = expr->getInputColumns(heap, FALSE);
for (CollIndex colInx=0; colInx<colsUsed.entries(); colInx++)
{
elem = colsUsed[colInx];
populateOneVidMap(root, vegRewriteVids, elem, candidate, vidMap, heap);
}
}
}
else
assertLogAndThrow(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
FALSE, MVCandidateException,
"Output item is neither Column, MVColumn nor expression")
}
}
void MVCandidates::addBackJoins(QRCandidatePtr candidate,
QRTableListPtr bjTables,
RelExpr*& node,
CollHeap* heap)
{
QRTablePtr table;
CollIndex numJoinItems;
ItemExpr* backJoinPred = NULL;
ItemExpr* eq;
Scan* bjScan;
QRJoinPredListPtr joinPredList = candidate->getJoinPredList();
// Build the join tree. Use a correlation name for backjoin tables in case of
// self-join.
for (CollIndex bjInx=0; bjInx<bjTables->entries(); bjInx++)
{
table = (*bjTables)[bjInx];
QualifiedName tableQualName(table->getTableName(), 3, heap, bindWA_);
NAString corrNameStr(BACKJOIN_CORRNAME_PREFIX);
corrNameStr.append(table->getRef());
CorrName tableCorrName(tableQualName, heap, corrNameStr);
bjScan = new(heap) Scan(tableCorrName, NULL, REL_SCAN, heap);
node = new(heap) Join(node, bjScan, REL_JOIN);
bjScanHash_.insert(&table->getReferencedElement()->getID(), bjScan);
}
// Build the join predicate.
for (CollIndex jpInx=0; jpInx<joinPredList->entries(); jpInx++)
{
const ElementPtrList& joinItems =
(*joinPredList)[jpInx]->getEqualityList();
numJoinItems = joinItems.entries();
assertLogAndThrow(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
numJoinItems > 1, MVCandidateException,
"Must have at least 2 items in a JoinPred");
for (CollIndex jiInx=1; jiInx<numJoinItems; jiInx++)
{
eq = new(heap) BiRelat(
ITM_EQUAL,
new(heap) ColReference
(getColRefName(candidate, joinItems[jiInx-1],
heap)),
new(heap) ColReference
(getColRefName(candidate, joinItems[jiInx],
heap)));
if (!backJoinPred)
backJoinPred = eq;
else
backJoinPred = new(heap) BiLogic(ITM_AND, backJoinPred, eq);
}
}
// Attach the predicate to the highest join node.
static_cast<Join*>(node)->setJoinPredTree(backJoinPred);
} // addBackJoins()
// The result of this function indicates whether or not the passed element
// is owned by tblNode. We are ultimately looking for a column, but the
// passed element could reference a JoinPred the equality list of which we
// must extract the needed column.
static NABoolean igbColMatchesTableNode(QRElementPtr elem, CANodeId tblNode)
{
QRElementPtr refedElem = elem->getReferencedElement();
if (refedElem->getElementType() == ET_Column)
return (static_cast<QRColumnPtr>(refedElem))->getTableIDNum() == tblNode;
assertLogAndThrow1(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
refedElem->getElementType() == ET_JoinPred,
MVCandidateException,
"Column refs element of type %d",
refedElem->getElementType());
NABoolean found = FALSE;
const ElementPtrList& eqList =
(static_cast<QRJoinPredPtr>(refedElem))->getEqualityList();
for (CollIndex i=0; !found && i<eqList.entries(); i++)
{
if (eqList[i]->getElementType() == ET_Column)
{
refedElem = static_cast<QRColumnPtr>(eqList[i])->getReferencedElement();
// Not sure if it's possible for an item contained in a JoinPred to
// reference another JoinPred, but let's check for it since it isn't
// handled.
assertLogAndThrow(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
refedElem->getElementType() == ET_Column,
MVCandidateException,
"Column element of JoinPred refs a non-column");
found = static_cast<QRColumnPtr>(refedElem)->getTableIDNum() == tblNode;
}
}
return found;
}
// This function is used to assign range and residual predicates to the proper
// scan nodes in the rewrite of an IGB query.
template <class T>
void MVCandidates::addPredsFromList(QRCandidatePtr candidate,
Scan* scan,
CANodeId tableNodeId,
const NAPtrList<T>& list,
CollHeap* heap)
{
ItemExpr* itemExpr;
// For each range/residual (based on type argument T) pred from the query
// descriptor, see if the table id referenced is tableNodeId. If so, call
// rewriteItemExpr passing the list of columns occurring in the pred, and
// add the result to the sel pred list for node.
for (CollIndex i=0; i<list.entries(); i++)
{
QRElementPtr elem = list[i];
ElementFinderVisitorPtr visitor =
new(heap) ElementFinderVisitor(ET_Column, TRUE,
ADD_MEMCHECK_ARGS(heap));
elem->treeWalk(visitor);
const ElementPtrList& colList = visitor->getElementsFound();
if (igbColMatchesTableNode(colList[0], tableNodeId))
{
itemExpr = rewriteItemExpr(colList,
((ValueId)elem->getIDNum()).getItemExpr(),
candidate, heap, tableNodeId);
scan->addSelPredTree(itemExpr);
}
}
}
void MVCandidates::addIGBPreds(QRCandidatePtr candidate, Scan* scan,
CANodeId tableNodeId, QRJBBPtr jbb,
CollHeap* heap)
{
// Get first the JBB's range preds, then the residual preds, passing each to
// the function that will add to the passed scan node the ones that belong to it.
const NAPtrList<QRRangePredPtr>& rangePreds = jbb->getHub()->getRangePredList()->getList();
addPredsFromList(candidate, scan, tableNodeId, rangePreds, heap);
const NAPtrList<QRExprPtr>& residPreds = jbb->getHub()->getResidualPredList()->getList();
addPredsFromList(candidate, scan, tableNodeId, residPreds, heap);
}
// elem is a join predicate operand in an IGB query, for which we need to
// create an ItemExpr. This is done differently depending on whether the owning
// table is a fact table or dimension table. The latter do not underlie the
// MV candidate.
ItemExpr* MVCandidates::getIGBJoinCondOp(QRElementPtr elem,
QRCandidatePtr candidate,
CANodeIdSet& jbbSubsetNodes,
NABoolean& fromFactTable,
CollHeap* heap)
{
if (elem->getElementType() != ET_Column)
assertLogAndThrow1(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
FALSE, MVCandidateException,
"Can only handle column join condition operands, not %d",
elem->getElementType());
QRElementPtr outputItem;
QRColumnPtr col = elem->getReferencedElement()->downCastToQRColumn();
NumericID tableId = col->getTableIDNum();
CANodeId tableNodeId(tableId);
fromFactTable = jbbSubsetNodes.containsThisId(tableNodeId);
if (fromFactTable)
{
// Search output list from candidate in result descriptor for an MVColumn
// with a ref to the column's id#.
QRMVColumnPtr mvCol;
QROutputListPtr outputs = candidate->getOutputList();
if (outputs)
for (CollIndex i=0; i<outputs->entries(); i++)
{
outputItem = (*outputs)[i]->getOutputItem();
if (outputItem->getElementType() == ET_MVColumn)
{
mvCol = outputItem->downCastToQRMVColumn();
if (mvCol->getRefNum() == col->getIDNum())
return new(heap) ColReference(getColRefName(candidate,
mvCol, heap));
}
}
assertLogAndThrow(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
FALSE, MVCandidateException,
"Could not find join operand from fact table for "
"indirect Group By query");
}
else
// If the column is from a dimension table, just create a ColReference for it.
return new(heap) ColReference(getColRefName(candidate, col, heap));
}
void MVCandidates::addIGBDimJoins(QRCandidatePtr candidate,
CANodeIdSet& jbbSubsetNodes,
QRJBBPtr jbbElem,
RelExpr*& node,
NodeIdScanHash& dimScanHash,
CollHeap* heap)
{
QRElementPtr jbbcElem;
QRTablePtr table;
CollIndex numJoinItems;
ItemExpr* joinPredItem = NULL;
ItemExpr* eqItem;
NABoolean op1FromFactTable, op2FromFactTable;
ItemExpr *leftChild, *rightChild;
Scan* dimScanNode;
CollIndex* nodeIdHashKey;
// Loop through all the dimension tables in the JBB and join them. The fact
// tables are already in the tree that we start with. They can be distinguished
// from dimension tables as we look through the jbbc list by checking whether
// the CANodeId is in jbbSubsetNodes -- these are the ones that comprise the
// candidate MV, and are always the fact tables in the case of an IGB candidate.
QRJBBCListPtr jbbcElemList = jbbElem->getHub()->getJbbcList();
CANodeId tableNodeId;
for (CollIndex jbbcInx=0; jbbcInx<jbbcElemList->entries(); jbbcInx++)
{
jbbcElem = jbbcElemList->getElement(jbbcInx);
if (jbbcElem->getElementType() == ET_Table)
{
table = jbbcElem->downCastToQRTable();
tableNodeId = table->getIDNum();
if (!jbbSubsetNodes.containsThisId(tableNodeId))
{
QualifiedName tableName(table->getTableName(), 3, heap, bindWA_);
dimScanNode = new(heap) Scan(tableName, NULL, REL_SCAN, heap);
nodeIdHashKey = new(heap) CollIndex(table->getIDNum());
dimScanHash.insert(nodeIdHashKey, dimScanNode);
addIGBPreds(candidate, dimScanNode, table->getIDNum(), jbbElem, heap);
node = new(heap) Join(node, dimScanNode, REL_JOIN);
}
}
}
// Build the join predicates.
QRJoinPredListPtr joinPredList = jbbElem->getHub()->getJoinPredList();
for (CollIndex jpInx=0; jpInx<joinPredList->entries(); jpInx++)
{
const ElementPtrList& joinItems =
(*joinPredList)[jpInx]->getEqualityList();
numJoinItems = joinItems.entries();
assertLogAndThrow(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
numJoinItems > 1, MVCandidateException,
"Must have at least 2 items in a JoinPred");
for (CollIndex jiInx=1; jiInx<numJoinItems; jiInx++)
{
leftChild = getIGBJoinCondOp(joinItems[jiInx-1]->getReferencedElement(),
candidate, jbbSubsetNodes,
op1FromFactTable, heap);
rightChild = getIGBJoinCondOp(joinItems[jiInx]->getReferencedElement(),
candidate, jbbSubsetNodes,
op2FromFactTable, heap);
if (op1FromFactTable && op2FromFactTable)
continue; // this join already represented in MV scan node
eqItem = new(heap) BiRelat(ITM_EQUAL, leftChild, rightChild);
if (!joinPredItem)
joinPredItem = eqItem;
else
joinPredItem = new(heap) BiLogic(ITM_AND, joinPredItem, eqItem);
}
}
// Attach the join predicate to the top join node.
if (joinPredItem)
static_cast<Join*>(node)->setJoinPredTree(joinPredItem);
}
void MVCandidates::mapDimOutputs(QRJBBPtr jbbElem,
CANodeIdSet& jbbSubsetNodes,
NodeIdScanHash& dimScanHash,
ValueIdMap& vidMap,
CollHeap* heap)
{
QROutputListPtr outputList = jbbElem->getOutputList();
QROutputPtr output;
ElementPtrList elems(heap);
// Get the initial list of cols/exprs that are directly contained in the output
// elements. When this list is traversed, if an expression is encountered
// (typically an aggregate function), the columns referenced in the expression
// will be added to the end of the list, so that all columns used in the query's
// select list will be part of the map.
//
// Each output will have exactly one col/expr, except in the case of NotProvided
// output expression with partial matching (not yet supported).
for (CollIndex outInx=0; outInx<outputList->entries(); outInx++)
{
output = (*outputList)[outInx];
const ElementPtrList& outputItems = output->getOutputItems();
for (CollIndex outItemInx=0; outItemInx<outputItems.entries(); outItemInx++)
elems.insert(outputItems[outItemInx]);
}
QRColumnPtr col;
CANodeId tblNodeId;
TableDesc* tdesc;
ValueId colId;
CollIndex tblNodeIdNum;
Scan* dimScan;
NABoolean found;
QRElementPtr elem;
for (CollIndex elemInx=0; elemInx<elems.entries(); elemInx++)
{
elem = elems[elemInx]->getReferencedElement();
switch (elem->getElementType())
{
case ET_Column:
col = elem->downCastToQRColumn();
tblNodeId = tblNodeIdNum = col->getTableIDNum();
if (!jbbSubsetNodes.containsThisId(tblNodeId))
{
// A dimension table in the IGB query.
tdesc = tblNodeId.getNodeAnalysis()->getTableAnalysis()->getTableDesc();
const ValueIdList& qColList = tdesc->getColumnList();
colId = col->getIDNum();
found = FALSE;
for (CollIndex qColInx=0; qColInx<qColList.entries() && !found; qColInx++)
{
if (qColList[qColInx] == colId)
{
dimScan = dimScanHash.getFirstValue(&tblNodeIdNum);
assertLogAndThrow(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
dimScan, MVCandidateException,
"Dimension scan not found in hash table");
const ValueIdList& rColList = dimScan->getTableDesc()
->getColumnList();
vidMap.addMapEntry(colId, rColList[qColInx]);
found = TRUE;
}
}
assertLogAndThrow1(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
found, MVCandidateException,
"Column value id not found in col desc list: %ud",
colId.toUInt32());
}
break;
case ET_Expr:
{
// Find the columns in the expression and add them to the end of
// the list, so they will be processed on subsequent iterations
// of the loop.
QRExprPtr expr = elem->downCastToQRExpr();
ElementFinderVisitorPtr visitor =
new(heap) ElementFinderVisitor(ET_Column, TRUE,
ADD_MEMCHECK_ARGS(heap));
expr->treeWalk(visitor);
elems.insert(visitor->getElementsFound());
}
break;
case ET_JoinPred:
{
// Column in output list referenced a joinpred. Put the joinpred's
// equality list items in the list for processing on subsequent
// iterations of the loop.
QRElementPtr eqListElem;
ElementType eqListElemType;
QRJoinPredPtr jp = elem->downCastToQRJoinPred();
const ElementPtrList& eqList = jp->getEqualityList();
for (CollIndex i=0; i<eqList.entries(); i++)
{
eqListElem = eqList[i];
eqListElemType = eqListElem->getElementType();
if (eqListElemType == ET_Column || eqListElemType == ET_Expr)
elems.insert(eqListElem);
}
}
break;
default:
assertLogAndThrow1(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
FALSE, MVCandidateException,
"Unexpected element type in findDimOutputs: %d",
elem->getElementType());
}
}
}
// Look for agg fns that we entered in root node.
static void mapVidsForAggs(RETDesc* retDesc, ValueIdMap& vidMap, CollHeap* heap)
{
const ColumnDescList* colDescList = retDesc->getColumnList();
CollIndex numCols = colDescList->entries();
const char* name;
size_t prefixLen = strlen(AGG_NAME_PREFIX);
for (CollIndex i=0; i<numCols; i++)
{
name = (*colDescList)[i]->getColRefNameObj().getColName().data();
if (strlen(name) > prefixLen &&
!strncmp(name, AGG_NAME_PREFIX, prefixLen))
{
vidMap.addMapEntry(atoi(name + prefixLen),
retDesc->getValueId(i));
}
}
}
void MVCandidates::analyzeCandidate(QRCandidatePtr candidate,
JBBSubset& jbbSubsetToReplace,
NABoolean rewriteSingleNode,
CANodeIdSet& jbbSubsetNodes,
CollHeap* heap)
{
// If the MV is to be ignored - ignore it.
const NAString& mvName = candidate->getMVName()->getMVName();
if (isForbiddenMV(mvName))
{
QRLogger::log(CAT_SQL_COMP_MVCAND, LL_DEBUG,
"MV Candidate %s was ignored because this statement writes to it.",
mvName.data());
return;
}
// Some result descriptor elements with refs need to access the refed element,
// which must be the element object used in the query descriptor.
SetRefVisitorPtr visitor =
new(heap) SetRefVisitor(qdescGenerator_.getColTblIdHash(),
ADD_MEMCHECK_ARGS(heap));
candidate->treeWalk(visitor);
deletePtr(visitor);
MVMatchPtr match = new(heap) MVMatch(&jbbSubsetToReplace, heap);
match->setMvName(mvName);
// Create a scan node, with a parent groupby node if additional grouping
// of the MV is required. Attach this to a root, and then bind the tree
// fragment using the binder work area for the query tree.
QualifiedName mvQualName(match->getMvName(), 3, heap, bindWA_);
CorrName mvCorrName(mvQualName, heap);
// First check if the MV exist as it is possible that QMS returned an MV
// that was just dropped or altered. This should not be happening but
// sometimes if the publish takes longer it could happen.
Lng32 marker = CmpCommon::diags()->mark();
NATable *naTable = bindWA_->getNATable(mvCorrName);
if (bindWA_->errStatus())
{
QRLogger::log(CAT_SQL_COMP_MVCAND, LL_DEBUG,
"Skipping candidate MV %s since it does not exist",
candidate->getMVName()->getMVName().data());
for (Int32 i=1; i<=CmpCommon::diags()->getNumber(); i++)
{
const NAWchar *errorMsg = (*CmpCommon::diags())[i].getMessageText();
NAString* errorStr = unicodeToChar(errorMsg, NAWstrlen(errorMsg), CharInfo::ISO88591, NULL, TRUE);
QRLogger::log(CAT_SQL_COMP_MVCAND, LL_WARN, " condition %d: %s", i, errorStr->data());
delete errorStr;
}
CmpCommon::diags()->rewind(marker, TRUE);
bindWA_->resetErrStatus();
deletePtr(match);
return;
}
MVInfoForDML* mvInfo = naTable->getMVInfo(bindWA_);
if (mvInfo == NULL || !mvInfo->isInitialized())
{
QRLogger::log(CAT_SQL_COMP_MVCAND, LL_DEBUG,
"Skipping candidate MV %s since it is not initialized",
candidate->getMVName()->getMVName().data());
return;
}
GroupByAgg* gbNode = NULL;
Scan* scan = new(heap) Scan(mvCorrName, NULL, REL_SCAN, heap);
scan->setRewrittenMV();
QRGroupByPtr groupBy = candidate->getGroupBy();
// is this MV one of the favorite MVs included in the
// MVQR_REWRITE_CANDIDATES default
NABoolean favoriteMV = FALSE;
if (mvqrFavoriteCandidates_ != NULL)
favoriteMV = isAfavoriteMV(match->getMvName());
RelExpr* node = scan;
// Back-join MV to any of its underlying tables needed to pick up columns
// that aren't included in the MV. Make a single ItemExpr for all the back
// join conditions and attach to topmost join as created above.
bjScanHash_.clear(); // clear back joins from previous candidate
QRTableListPtr bjTables = candidate->getTableList();
NABoolean hasBackJoins = bjTables && bjTables->entries() > 0;
RelExpr* backJoinRoot = NULL;
if (hasBackJoins)
{
addBackJoins(candidate, bjTables, node, heap);
backJoinRoot = node;
}
// If the candidate replaces an IGB, we need to determine which JBB of the
// query descriptor it replaces (an IGB always constitutes an entire JBB).
QRJBBPtr jbbElem = NULL;
NodeIdScanHash dimScanHash(&nodeIdHashFn, 41, TRUE, heap);
if (candidate->isIndirectGroupBy())
{
CollIndex jbbId = jbbSubsetToReplace.getJBB()->getJBBId();
const NAPtrList<QRJBBPtr>& jbbList = queryDesc_->getJbbList();
for (CollIndex jbbInx=0; jbbInx<jbbList.entries() && !jbbElem; jbbInx++)
{
if (jbbList[jbbInx]->getIDNum() == jbbId)
jbbElem = jbbList[jbbInx];
}
assertLogAndThrow1(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
jbbElem, MVCandidateException,
"Could not find JBB with id = %d", jbbId);
addIGBDimJoins(candidate, jbbSubsetNodes, jbbElem, node, dimScanHash, heap);
}
if (groupBy && groupBy->getResult() == QRElement::NotProvided)
node = gbNode = getGroupByAggNode(node, groupBy, candidate, heap);
ElementPtrList vidMapTopElements(heap);
RelRoot* root =
new(heap) RelRoot(node, REL_ROOT, NULL, NULL, NULL, NULL, heap);
buildOutputExprs(candidate, root, vidMapTopElements, heap);
rewriteRangePreds(candidate, scan, gbNode, heap);
rewriteResidPreds(candidate, scan, gbNode, backJoinRoot, heap);
// Bind the replacement tree.
marker = CmpCommon::diags()->mark();
RelExpr* boundRoot = root->bindNode(bindWA_);
if (bindWA_->errStatus())
{
QRLogger::log(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
"Skipping candidate MV %s -- failed to bind root",
candidate->getMVName()->getMVName().data());
for (Int32 i=1; i<=CmpCommon::diags()->getNumber(); i++)
{
const NAWchar *errorMsg = (*CmpCommon::diags())[i].getMessageText();
NAString* errorStr = unicodeToChar(errorMsg, NAWstrlen(errorMsg), CharInfo::ISO88591, NULL, TRUE);
QRLogger::log(CAT_SQL_COMP_MVCAND, LL_WARN, " condition %d: %s", i, errorStr->data());
delete errorStr;
}
CmpCommon::diags()->rewind(marker, TRUE);
bindWA_->resetErrStatus();
delete root;
deletePtr(match);
return;
}
// queryRoot_->getStoiList().insert(scan->getOptStoi());
// Before transform removes the root node, get the mapvid entries for the
// aggregate functions we added to its select list. These have been given
// fake names that embed the value ids they map to.
ValueIdMap vidMap;
mapVidsForAggs(root->getRETDesc(), vidMap, heap);
// Transform and normalize the new tree fragment.
NormWA normWA(CmpCommon::context());
ExprGroupId eg(boundRoot);
normWA.allocateAndSetVEGRegion(IMPORT_AND_EXPORT, boundRoot);
boundRoot->transformNode(normWA, eg);
RelExpr* transformedRoot = eg.getPtr();
transformedRoot->rewriteNode(normWA);
normWA.setInMVQueryRewrite(TRUE);
RelExpr* normalizedRoot = transformedRoot->normalizeNode(normWA);
normWA.setInMVQueryRewrite(FALSE);
// Take the id mappings from the output list and add them to the ValueIdMap,
// and for an IGB, add mappings for columns of the dimension tables that are
// used in the query's select list.
ValueIdSet vegRewriteVids;
populateVidMap(normalizedRoot, vidMapTopElements, vegRewriteVids, candidate,
vidMap, heap);
if (candidate->isIndirectGroupBy())
mapDimOutputs(jbbElem, jbbSubsetNodes, dimScanHash, vidMap, heap);
MapValueIds* mapVidNode = new(heap) MapValueIds(normalizedRoot, vidMap, heap);
mapVidNode->addValuesForVEGRewrite(vegRewriteVids);
mapVidNode->synthLogProp(&normWA);
normalizedRoot->finishSynthEstLogProp();
mapVidNode->setIncludesFavoriteMV(favoriteMV);
mapVidNode->primeGroupAttributes();
//match->setMvRelExprTree(mapVidNode);
// until the MVID for IGB uses vegref in the upper and lower values this transformation
// cannot be used
if (!candidate->isIndirectGroupBy())
{
// Make sure that the output for the operator under the MVID is minimized.
// In addition to fixing this issue, this fix also ensures minimal message
// sizes between this operator and its ancestor(s). When the mapvid inputs
// (bottom values) contains a vegref that includes a constant, it may get
// removed as a covered expression. To safeguard against this, we copy the
// map, and restore it if it has shrunk as a result of the call to
// pushdownCoveredExpr. See Solution 10-100707-1647.
ValueIdMap mapCopy(mapVidNode->getMap());
mapVidNode->pushdownCoveredExpr(mapVidNode->getGroupAttr()->getCharacteristicOutputs(),
mapVidNode->getGroupAttr()->getCharacteristicInputs(),
mapVidNode->selectionPred()
);
const ValueIdList& tops = mapCopy.getTopValues();
const ValueIdList& bottoms = mapCopy.getBottomValues();
if (tops.entries() > mapVidNode->getMap().getTopValues().entries())
{
mapVidNode->clear();
for (CollIndex i=0; i<tops.entries(); i++)
mapVidNode->addMapEntry(tops[i], bottoms[i]);
}
}
// Save some info about the original query analysis before analyzing the
// candidate tree. This info helps determine if we can test the rewrite by
// trading out the entire tree.
ULng32 maxJbbSize = QueryAnalysis::Instance()->getSizeOfLargestJBB();
NABoolean multiJBBs = QueryAnalysis::Instance()->getJBBs().entries();
RelExpr* analyzedExpr = QueryAnalysis::Instance()->analyzeThis(mapVidNode, TRUE);
match->setMvRelExprTree(analyzedExpr);
if (!rewriteSingleNode)
// Insert preferred matches at the front of the list, others at back.
jbbSubsetToReplace.getJBBSubsetAnalysis()->addMVMatch(match, candidate->isPreferredMatch());
else
{
// this is a defensive check, since this same check was done in MVCandidates::analyzeCandidateMVs
// but just to make sure nothing went wrong in between
assertLogAndThrow(CAT_SQL_COMP_MVCAND, LL_MVQR_FAIL,
queryRoot_->child(0)->getOperator().match(REL_SCAN),
MVCandidateException,
"Expecting a SCAN node under the Query Root node while rewriting "
"a single Table Query");
((Scan *)(queryRoot_->child(0).getPtr()))->addMVMatch(match, candidate->isPreferredMatch());
}
} // analyzeCandidate()
void MVCandidates::analyzeJbbSubsets(QRJbbResultPtr jbb)
{
const NAPtrList<QRJbbSubsetPtr>& jbbSubsetList = jbb->getJbbSubsets();
for (CollIndex i=0; i<jbbSubsetList.entries(); i++)
{
analyzeCandidateMVs(jbbSubsetList[i]);
}
}
// User maintained MVs are typically initialized by using an insert-select,
// that is a perfect match to the MV query. If we rewrite this insert-select,
// it will initialize the MV from its empty self.
// To make things a bit more generic, any statement doing an Insert, Update
// or Delete to an MV, should not be rewritten to use that MV.
// Here we collect the MV names that this statement writes to.
void MVCandidates::findForbiddenMVs()
{
// Does the query tree have any Insert/UPdate/Delete nodes?
if (queryRoot_->seenIUD() == FALSE)
return;
QueryAnalysis* qa = QueryAnalysis::Instance();
CollHeap* heap = qa->outHeap();
collectMVs(queryRoot_, heap);
}
// A recursive method to walk the tree and look for IUD nodes on MVs.
void MVCandidates::collectMVs(RelExpr* topNode, CollHeap* heap)
{
if (topNode->getOperator().match(REL_ANY_GEN_UPDATE))
{
GenericUpdate* gu = (GenericUpdate*)topNode;
if (gu->getTableDesc()->getNATable()->isAnMV())
{
NAString mvName = gu->getTableName().getQualifiedNameAsString();
if (forbiddenMVs_ == NULL)
forbiddenMVs_ = new(heap) LIST(NAString)(heap);
forbiddenMVs_->insert(mvName);
}
}
else // No need to look below GU nodes for more GU nodes.
{
for (Int32 i=0; i<topNode->getArity(); i++)
collectMVs(topNode->child(i), heap);
}
}
// Is the candidate MV name mvName apear in the forbidden list?
NABoolean MVCandidates::isForbiddenMV(const NAString& mvName)
{
if (forbiddenMVs_ == NULL)
return FALSE;
for (CollIndex i=0; i<forbiddenMVs_->entries(); i++)
if ((*forbiddenMVs_)[i] == mvName)
return TRUE;
return FALSE;
}
void MVCandidates::analyzeResultDescriptor(QRResultDescriptorPtr rd)
{
findForbiddenMVs();
const NAPtrList<QRJbbResultPtr>& jbbResultList = rd->getJbbResults();
for (CollIndex i=0; i<jbbResultList.entries(); i++)
{
analyzeJbbSubsets(jbbResultList[i]);
}
// clean up the list of candidates MVs, they are no longer needed
if (mvqrFavoriteCandidates_ != NULL)
{
mvqrFavoriteCandidates_->clear();
delete mvqrFavoriteCandidates_;
}
}