blob: 2f256b1269608666301a3d88aa612310f0900203 [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 @@@
**********************************************************************/
/* -*-C++-*-
*****************************************************************************
*
* File: ColStatDesc.cpp
* Description: Column Statistics Descriptor
* Created: June 7, 1995
* Language: C++
*
*
*
*****************************************************************************
*/
// -----------------------------------------------------------------------
#define SQLPARSERGLOBALS_FLAGS // must precede all #include's
#include "ColStatDesc.h"
#include "Sqlcomp.h"
#include "ItemColRef.h"
#include "ItemOther.h"
#include "ItemFunc.h"
#include "Cost.h" /* for lookups in defaults table */
#include "Analyzer.h"
#include "../exp/exp_ovfl_ptal.h" //check for overflow & underflow
#include "CompException.h"
#include "ItemLog.h" // for like predicates
#include "hs_globals.h" // for ustat automation setting
#include "hs_cli.h" // ustat automation, insert empty histograms
#include "hs_log.h" // ustat log
#include "SqlParserGlobals.h"
#include "CmpDescribe.h"
#ifdef DEBUG
#define HISTWARNING(x) fprintf(stdout, "Histogram optimizer warning: %s\n", x);
#else
#define HISTWARNING(x)
#endif
//#define MONITOR_SAMEROWCOUNT
// This is an arbitrary constant; perhaps someday it will end up in the
// defaults table.
//
// HIST_MAX_IN_LIST_MEMBERS: the largest number of IN-list members for
// which we attempt to do exact histogram-manipulation; for IN-lists of
// cardinality greater than this constant, we do some massive
// simplifications (see CSDL::estimateCardinality)
const Int32 HIST_MAX_IN_LIST_MEMBERS = 40 ;
// -----------------------------------------------------------------------
// Methods on ColStatDesc class
// -----------------------------------------------------------------------
ColStatDesc::ColStatDesc (const ColStatsSharedPtr& stats,
const ValueIdList& columnList,
NAMemory * h) :
nonVegEquals_(h)
{
// Set up a reference to the ColStats structure
colStats_ = stats;
// ---------------------------------------------------------------------
// Now, generate the valueidlist for this set of column statistics.
// Also, generate the initial mergeState_ for this stats' column.
// Initially, the state is that only the current column has been merged.
// ---------------------------------------------------------------------
const ValueId & id = columnList[stats->getStatColumns()[0]->getPosition()];
column_ = id ;
VEGcolumn_ = column_;
mergeState_.insert(column_);
fromInnerTable_ = FALSE;
// use default constructor for nonVegEquals, and appliedPreds_
// underlying histogram is not modified yet
modified_ = FALSE;
inputCard_ = 1.0;
}
// Construct a ColStatDesc given a single ValueId and a pointer
// to a ColStats object. This constructor does not assume that
// the ColStats object contains a NAColumnList. Used when constructing
// ColStats for generated columns that are not based on BASECOLS,
// such as with TRANSPOSE.
//
ColStatDesc::ColStatDesc(const ColStatsSharedPtr& stats,
const ValueId & column, NAMemory * h)
: nonVegEquals_(h)
{
// Set up a reference to the ColStats structure
colStats_ = stats;
// ---------------------------------------------------------------------
// Set up the column, VEGColumn and MergeState.
// ---------------------------------------------------------------------
column_ = column ;
VEGcolumn_ = column;
mergeState_.insert(column) ;
fromInnerTable_ = FALSE;
// underlying histogram is not modified yet
modified_ = FALSE;
inputCard_ = 1.0;
}
void
ColStatDesc::copy (const ColStatDesc& other)
{
column_ = other.column_;
VEGcolumn_ = other.VEGcolumn_;
mergeState_ = other.mergeState_;
nonVegEquals_ = other.nonVegEquals_;
fromInnerTable_ = other.fromInnerTable_;
appliedPreds_ = other.appliedPreds_;
colStats_ = other.colStats_;
inputCard_ = other.inputCard_;
modified_ = FALSE;
}
void
ColStatDesc::mapUpAndCopy (const ColStatDesc& other, ValueIdMap &map)
{
copy(other);
map.mapValueIdUp(column_, other.column_);
map.mapValueIdUp(VEGcolumn_, other.VEGcolumn_);
// if the map only maps the column or VEGcolumn but not both, then
// map both to the same value id
if (column_ == other.column_ && VEGcolumn_ != other.VEGcolumn_)
column_ = VEGcolumn_;
if (VEGcolumn_ == other.VEGcolumn_ && column_ != other.column_)
VEGcolumn_ = column_;
// rewrite applied predicates
if (!appliedPreds_.isEmpty() &&
(column_ != other.column_ || VEGcolumn_ != other.VEGcolumn_))
{
appliedPreds_.clear();
map.rewriteValueIdSetUp(appliedPreds_, other.appliedPreds_);
}
}
void
ColStatDesc::deallocate()
{
colStats_ = NULL;
}
// -----------------------------------------------------------------------
// ColStatDesc::getColStatsToModify
//
// If this is the first time that the underlying colstats is being
// modified, then make a copy first. Otherwise, just return the reference
// to colStats.
// -----------------------------------------------------------------------
ColStatsSharedPtr
ColStatDesc::getColStatsToModify()
{
if (NOT isModified())
{
colStats_ = ColStatsSharedPtr(new (HISTHEAP)
ColStats (*colStats_, HISTHEAP));
modified_ = TRUE;
}
return colStats_;
}
// argh! pow(x,0.0) returns not-a-number!
//
// This version of pow has been written specially to do the right thing
// for its call in the following function ; use of it by any other method
// is probably not a good idea.
double mypow (double base, double exp)
{
if ( exp == 0 )
return 1 ; // duh!
else if ( base <= 0 )
return 0 ; // this isn't obvious ... hmmmm ...
else
return pow(base,exp) ;
}
// -----------------------------------------------------------------------
// Historically we've reduced UEC values by the same amount as rowcounts
// when a predicate is applied; but this is extremely foolish, as UEC
// values generally do not reduce very rapidly as rowcounts go down (e.g.,
// a "downsizing" of 50% of employees (reduction of .5) is not likely to
// reduce the UEC of the genders of employees by the same .5!) In general,
// we expect UEC to go down only when rowcount goes down a lot.
//
// Making this a member function so that anyone else who wants to use it
// will be able to "find" it easier -- and so it doesn't pollute the
// global namespace.
// -----------------------------------------------------------------------
CostScalar
ColStatDesc::calculateCorrectResultUec( const CostScalar & baseRows,
const CostScalar & newRows,
const CostScalar & baseUec)
{
// Sanity checks.
CCMPASSERT( baseRows.isGreaterOrEqualThanZero() );
CCMPASSERT( newRows.isGreaterOrEqualThanZero() );
CCMPASSERT( baseUec.isGreaterOrEqualThanZero() );
// we start with a simple sanity check
if ( baseRows.isZero() || newRows.isZero() || baseUec.isZero() )
return csZero; // rows(0) ==> uec(0)
// now let's get started ... here's a good zeroth approximation
CostScalar firstApprox = baseUec ;
// as a first approximation, we limit uec to be (at most) the new rowcount
if ( firstApprox > newRows )
firstApprox = newRows ; // reduce to the new rowcount
// be careful to not reduce uec if the rowcount is increasing (or
// remaining constant)!
if ( newRows >= baseRows )
{
return firstApprox ; // this is equal to MIN (baseUec, newRows)
}
// ------------------------------------------------------------------------
// The UEC reduction formula is derived as follow
//
// u boxes
// N balls
//
// Balls are distributed among the u boxes.
//
// The distribution was performed randomly except for that the occupancy
// of any box should be greater than 0. This was done by putting 1 ball
// in each of the u boxes and distributing the remaining N-u balls
// randomly (with equal probabilities for each box).
//
// Now apply an orthogonal selection with reduction factor R; i.e., remove
// (1-R)*N balls randomly (each ball has same chance of being removed,
// i.e., (1-R))
//
// The probability that a box is empty is
// = (Prob. of that first ball removed)
// * (Prob. all other balls in the box were removed)
//
// The first term is simply (1-R)
// The second term can be shown to be (1-R/u)^(N-u)
// which can be approximated to exp(-R(N-u)/u) for N-u >> 1
//
// Consequently the result UEC (or # non empty boxes) is
// UEC = u*(1-(1-R)*(1-R/u)^(N-u))
// = u*(1-(1-R)*exp(-R(N-u)/u)) N-u >> 1 ----(1)
//
// As for the linear count estimate it does not take into account
// the requirement that box initial occupancy > 0. Consequently
// It performs poorly when the values of N and u are close, which
// is a very common case.
// -----------------------------------------------------------------------
//NB: if we reach here, baseRows >= newRows
double R = ((CostScalar) newRows / baseRows).value() ;
double N = baseRows.value() ;
double u = baseUec.value() ;
CostScalar secondApprox ;
// we fudge >> to mean greater than 10; this should be fine
if ( N-u > 10 )
secondApprox = u * (1-(1-R) * exp(-R*(N-u)/u)) ;
else
secondApprox = u * (1-(1-R) * mypow(1-R/u,N-u)) ;
// sanity check
// if ( secondApprox.getValue() <= csZero.getValue() || secondApprox > firstApprox )
if ( NOT secondApprox.isGreaterThanZero() OR secondApprox > firstApprox )
secondApprox = firstApprox ;
return secondApprox ;
}
// -----------------------------------------------------------------------
// ColStatDesc::applySel
//
// Apply the provided selectivity to the underlying column statistics
// (ColStats structure).
// -----------------------------------------------------------------------
void
ColStatDesc::applySel( const CostScalar & selectivity )
{
if ( selectivity == csOne )
return ; // nothing to do
ColStatsSharedPtr colStats = getColStatsToModify() ;
// --------------------------------------------------------------------
// when we "scale up" a histogram, it loses its uniqueness
if ( selectivity.isGreaterThanOne() /* > 1 */)
{
colStats->setUnique( FALSE );
}
// --------------------------------------------------------------------
const CostScalar & baseUec = colStats->getTotalUec();
const CostScalar & baseRowcount = colStats->getRowcount();
const CostScalar newRowcount = MIN_ONE_CS(baseRowcount * selectivity);
const CostScalar & originalBaseRowCount = colStats->getBaseRowCount();
CostScalar baseRowCountForUec = baseRowcount;
if ((originalBaseRowCount < baseRowcount) &&
(originalBaseRowCount != csMinusOne))
{
baseRowCountForUec = originalBaseRowCount;
colStats->setBaseRowCount(-1);
}
CostScalar newTotalUec, uecSelectivity;
if ( baseUec.isZero() ) // avoid div-by-zero!
{
newTotalUec = csZero;
uecSelectivity = csZero;
}
else if ( colStats->isUnique() )
{
// If this is a UNIQUE column, UEC == rowcount
newTotalUec = newRowcount;
uecSelectivity = selectivity;
}
else
{
newTotalUec = calculateCorrectResultUec( baseRowCountForUec,
newRowcount,
baseUec);
uecSelectivity = newTotalUec / baseUec;
}
// If row count is anywhere >= one, then uplift the UEC to atleast one.
if ((newRowcount >= csOne) && (newTotalUec < csOne))
newTotalUec = csOne;
colStats->setRedFactor ( colStats->getRedFactor() * selectivity );
colStats->setUecRedFactor( colStats->getUecRedFactor() * uecSelectivity );
colStats->setRowsAndUec ( newRowcount, newTotalUec );
}
// ----------------------------------------------------------------------
// ColStatDesc::applySelIfSpecifiedViaHint
//
// This helper method is called by various cardinality estimation
// methods to adjust cardinality based on user-specified selectivity.
// ----------------------------------------------------------------------
void
ColStatDesc::applySelIfSpecifiedViaHint(ItemExpr * pred, const CostScalar & oldRowcount)
{
// If user specified selectivity for this predicate, we need to make
// adjustment in reduction to reflect that.
if(pred->isSelectivitySetUsingHint())
{
ColStatsSharedPtr colStats = getColStats();
CostScalar selAdjustment(oldRowcount * pred->getSelectivityFactor()/getColStats()->getRowcount());
if(selAdjustment > csZero)
{
applySel(selAdjustment);
colStats->setSelectivitySetUsingHint();
}
}
}
// ----------------------------------------------------------------------
// ColStatDesc::synchronizeStats
//
// Change the rowcount of THIS to match the specified newRowcount;
// Apply the change to histograms by changing the associated RedFactor.
// Change the UEC of THIS in proportion to the change in UEC characterized
// by baseUec and newUec. Again, apply this to histograms by changing
// the associated uecRedFactor. Do not allow the resulting UEC to exceed
// the resulting rowcount.
//
// The third parameter is optional; it is used by some functions which
// are certain that under no circumstances should the UEC be allowed to
// be changed. Otherwise, UEC is reduced as per the function above.
//
// NOTE how similar this function is to CSD::applySel() above. In most
// cases, when both the old and new rowcounts are known (e.g., in cases
// when we're not just applying some selectivity), this function should
// be used --> except, of course, in cases when we need to make sure to
// reduce the uec to be at most 1.
// ----------------------------------------------------------------------
void
ColStatDesc::synchronizeStats( const CostScalar & baseRowcount,
const CostScalar & newRowcount,
SynchSpecialFlag specialFlag )
{
CCMPASSERT( baseRowcount.isGreaterOrEqualThanZero() );
CCMPASSERT( newRowcount.isGreaterOrEqualThanZero() );
if ( getColStats()->getRowcount() == newRowcount )
return ; // nothing to do
// First check to do is to see if RowCount can be uplifted to one, if
// it is not zero and less than one. This will solve the problem of
// UECs less than 1, which can reach 0 and cause overflow - RV
CostScalar newRowCountCorrected;
newRowCountCorrected = MIN_ONE_CS(newRowcount);
// now use this adjusted newRowCount for all further calculations
// it's really not clear what the rowred should be when the baseRowcount
// starts out as zero ...
// avoid divided by zero
CostScalar rowSelectivity =
(baseRowcount.isZero()) ? csOne : newRowCountCorrected / baseRowcount;
ColStatsSharedPtr colStats = getColStatsToModify();
// get base row count of the histogram, to see if that can be used to
// calculate the Uec reduction RV
const CostScalar &originalBaseRowCount = colStats->getBaseRowCount();
// to get the maximum reduction, use the lower row count
CostScalar baseRowCountForUec = baseRowcount;
if ((originalBaseRowCount < baseRowcount) &&
(originalBaseRowCount != csMinusOne))
{
baseRowCountForUec = originalBaseRowCount;
colStats->setBaseRowCount(-1);
}
if ( colStats->getRowcount() != baseRowcount )
{
CostScalar cardDiff = colStats->getRowcount() - baseRowcount;
if (_ABSOLUTE_VALUE_(cardDiff.value()) > 1.0)
{
// unusual case: but if it does happen, fudge the synchronization
// results based upon how far our numbers are from what is expected.
// The way we do round our cardinalities, difference of one row shows
// up, which could be unintentional. Hence we would fudge the
// synchronization results only if the difference is more than one.
// Else we shall go by the reduction of newRowcount / oldRowcount
// because that is what it should be
// There is an extra check of != to avoid computing of ABSOLUTE_VALUE
// in most cases.
rowSelectivity =
(baseRowcount.isZero()) ? csOne : colStats->getRowcount() / baseRowcount;
}
}
// --------------------------------------------------------------------
// when we "scale up" a histogram, it loses its uniqueness
if ( rowSelectivity > csOne )
{
colStats->setUnique( FALSE );
}
// --------------------------------------------------------------------
const CostScalar & baseUec = colStats->getTotalUec();
CostScalar newTotalUec, uecSelectivity;
NABoolean rowCountIsOne = FALSE;
if ( baseUec.isZero() ) // avoid div-by-zero
{
newTotalUec = csZero;
uecSelectivity = csZero;
}
else if ( specialFlag == DO_NOT_REDUCE_UEC )
{
// Sometimes the calling function knows that the UEC should not change.
// Want to calculate uecSelectivity right.
newTotalUec = MINOF( baseUec, newRowCountCorrected );
uecSelectivity = newTotalUec / baseUec; // usually 1
}
else if ( colStats->isUnique() )
{
// If it's a UNIQUE column, then UEC == ROWCOUNT.
if ( specialFlag == SET_UEC_TO_ONE )
{
// In this case, both uec & rowcount should be 1.
rowCountIsOne = TRUE;
rowSelectivity =
(baseRowcount.isZero()) ? csOne : csOne / baseRowcount;
}
newTotalUec = rowCountIsOne ? csOne : newRowCountCorrected ;
uecSelectivity = rowSelectivity ;
}
else if ( specialFlag == SET_UEC_TO_ONE )
{
// Sometimes the calling function knows that
// resulting UEC should be at most 1.
newTotalUec = MINOF( baseUec, csOne );
// Want to calculate uecSelectivity right.
newTotalUec = MINOF( newTotalUec, newRowCountCorrected );
uecSelectivity = newTotalUec / baseUec;
}
else // the usual case
{
newTotalUec = calculateCorrectResultUec( baseRowCountForUec,
newRowCountCorrected,
baseUec);
uecSelectivity = newTotalUec / baseUec;
}
// If row count is one, then uplift the UEC to one also.
if ((newRowCountCorrected >= csOne) && (newTotalUec < csOne))
newTotalUec = csOne;
colStats->setRedFactor ( colStats->getRedFactor() * rowSelectivity );
colStats->setUecRedFactor( colStats->getUecRedFactor() * uecSelectivity );
colStats->setRowsAndUec ( ( rowCountIsOne ? csOne : newRowCountCorrected ),
newTotalUec );
}
// -----------------------------------------------------------------------
// ColStatDesc::modifyStats
//
// Given a supported predicate, apply the effect of the predicate on the
// ColStats structure, as well as its corresponding histogram. This
// method expects the unary predicates
// IS [NOT] NULL
// IS [NOT] UNKNOWN
// and binary predicates of the form
// COLUMN <op> <constant>
// <constant> <op> COLUMN
// This routine also checks for doable, simple, special, cases like
// column_a <op> column_a
//
// Calls these functions to do the work of predicate application:
//
// ColStats::modifyStats
// ColStats::simplestPreds
//
// return value: semantic =~= "everything's OK"
// -------------
// FALSE if one of the following:
// 1. none of the above predicate cases applies
// 2. the identified column does not appear in the column list
// 3. the CSD's histogram is NULL
//
// TRUE if none of the three conditions for FALSE are met,
// or if the predicate has already been applied
// -----------------------------------------------------------------------
NABoolean
ColStatDesc::modifyStats( ItemExpr *pred, CostScalar & newRowcount,
CostScalar *maxSelectivity )
{
const ValueId & predValueId = pred->getValueId();
OperatorTypeEnum op = pred->getOperatorType();
ItemExpr * lhs = pred->child(0);
ItemExpr * rhs = NULL;
ConstValue * constant = NULL;
NABoolean negate = FALSE;
if ( pred->getArity() > 1 )
{
rhs = pred->child(1);
constant = rhs->castToConstValue( negate );
if(constant == NULL)
{
if (rhs->getOperatorType() == ITM_VEG_REFERENCE)
{
const VEG * veg = ((VEGReference *)rhs)->getVEG();
ValueId constId = veg->getAConstant();
if(constId != NULL_VALUE_ID)
constant = constId.getItemExpr()->castToConstValue( negate );
}
else
{
if ((op == ITM_EQUAL) &&
(rhs->getOperatorType() == ITM_CACHE_PARAM) )
{
ItemExpr * constantExpr = ((ConstantParameter *)rhs)->getConstVal();
if (constantExpr == NULL)
return FALSE;
constant = constantExpr->castToConstValue(negate); }// cache_param
} // not aveg_reference
}// constant !=null
} //arity > 1
// get writable copies of the ColStats structure
ColStatsSharedPtr colStats = getColStatsToModify();
newRowcount = colStats->getRowcount();
// stand alone column should always be on lhs
// if the predicate being applied is IS NULL then do not skip
// instantiate_null expression from lhs.
NABoolean digIntoInstantiateNull = TRUE;
if ((op == ITM_IS_NULL) || (op == ITM_IS_NOT_NULL))
digIntoInstantiateNull = FALSE;
if ( (lhs->getOperatorType() != ITM_VEG_REFERENCE) &&
(constant || (op == ITM_EQUAL) || (op == ITM_NOT_EQUAL) ||
(op == ITM_IS_NULL) || (op == ITM_IS_NOT_NULL)))
{
lhs = lhs->getLeafValueIfUseStats(digIntoInstantiateNull);
}
ValueId vegCol = getVEGColumn();
if (lhs->getValueId() != vegCol )
{
// if the valueIDs match, then we have found the histograms for the
// child, proceed with applying predicate
// If the valueIds are not equal, then see if it is a VEG region
// (defined by VEG_REFERENCE). If it is, dig into the region to
// get VEG references. This is required especially for full outer joins,
// where the join expression forms a VEG region of its own with
// VEG references from the two children. However, the histogram for the join
// is still identified by the VEG reference of the left child
if (lhs->getOperatorType() == ITM_VEG_REFERENCE)
{
ValueIdSet cols;
lhs->findAll(ITM_VEG_REFERENCE, cols, TRUE, TRUE);
if (!cols.contains(vegCol))
return FALSE;
}
else
return FALSE;
}
// three kinds of preds that we can handle:
// 1. constant is not NULL (i.e., of form <constant> op <col>)
// 2. unary predicate
// 3. binary predicate on same column
// ==> if none of these is TRUE, then we can't evaluate this predicate
if ( NOT ( constant != NULL
OR pred->getArity() == 1
OR ( pred->getArity() == 2
AND lhs->getValueId() == rhs->getValueId()
)
)
)
{
return FALSE; // Correct column, but can't evaluate predicate....
}
// OK, all's well, now we evaluate the predicate
CostScalar rowcount = csZero;
CostScalar uec = csZero;
const CostScalar & origRowcount = colStats->getRowcount();
const CostScalar & origUec = colStats->getTotalUec();
// These will be used later to check for the boundary condition for
// range predicates
const EncodedValue maxValue = colStats->getMaxValue();
const EncodedValue minValue = colStats->getMinValue();
// get the encoded value of the constant if any
EncodedValue val (UNINIT_ENCODEDVALUE) ;
if (constant != NULL)
// get the encoded format for the constant
val = EncodedValue (constant, negate);
// check: if we've already applied this predicate, don't do it again!
if ( isPredicateApplied( predValueId ) )
{
newRowcount = origRowcount;
return TRUE;
}
// extrapolate histogram, if the value being looked for lies outside the histogram boundaries
// no need to extrapolate if the predicate being applied is less than or less than equal to
if (colStats->getStatColumns()[0]->getType()->getTypeQualifier() == NA_DATETIME_TYPE
&& val >= maxValue
&& !colStats->isOrigFakeHist()
&& (op == ITM_EQUAL || op == ITM_GREATER || op == ITM_GREATER_EQ)
&& maxSelectivity == NULL
&& constant != NULL)
colStats->adjustRowcountforRollingColumns(constant);
// OK, predicate has NOT already been applied
// remember whether or not a histogram is 'fake' prior to applying the
// given predicate.
NABoolean isaFakeHistogram = colStats->isFakeHistogram() ;
if ( (pred->getArity() == 2) &&
(lhs->getValueId() == rhs->getValueId()) )
{
if (maxSelectivity == NULL)
colStats->simplestPreds( pred );
}
else
{
colStats->modifyStats( pred, maxSelectivity );
}
// maxSelectivity computation is done
if (maxSelectivity) {
newRowcount = colStats->getRowcount();
return TRUE;
}
rowcount = colStats->getRowcount();
uec = colStats->getTotalUec();
// ------------------------------------------------------------------
// Lastly, if this predicate was applied to a 'fake' histogram
// AND the predicate did not eliminate all of the rows,
// AND no 'similar' predicate has already been applied.
// ==> Alter this predicate's impact on the total RowCount & UEC to
// match that of a 'default' predicate. In cases of multiple
// applications of 'similar' predicates, any shape-changing impact that
// occurs above will continue to have its effect, but no actual
// reduction in rowcount will occur.
//
// All changes are accomplished via alterations to appropriate reduction
// factors.
// ------------------------------------------------------------------
// ------------------------------------------------------------------
// When we're certain that we should end up with zero rows, we set the
// histogram accordingly :
// 1. max(min)-set-by-pred are TRUE; and
// 2. histogram has one interval with bounds UNINIT_ENCODEDVALUE
// 3. max/min bounds are set to UNINIT_ENCODEDVALUE
// Unless these conditions are met, then if we ever end up with zero
// rows, we want to undo what we did and apply default selectivity
// (instead of the overly-selective predicate that we just applied).
// ------------------------------------------------------------------
HistogramSharedPtr hist = colStats->getHistogramToModify() ;
if ( rowcount.isZero()
AND NOT ( hist->entries() == 1
AND colStats->isMaxSetByPred()
AND colStats->isMinSetByPred()
AND colStats->getMaxValue() == UNINIT_ENCODEDVALUE
AND colStats->getMinValue() == UNINIT_ENCODEDVALUE
)
)
{
// At this point we're not going to preserve any multiple-interval
// shape of the histogram, because we're grasping at straws in our
// attempt to prevent a zero-overall rowcount. This is a kludge to
// prevent undesired zero rowcounts (we operate under the assumption
// that *all* zero rowcounts are unwanted). The easiest/best way to
// proceed is to populate a single-interval histogram with the
// original rows & uec, declare this a fake histogram, and then
// apply default selectivity (see next if-clause below) to come up with
// a more reasonable reduction.
hist->condenseToSingleInterval();
colStats->setIsCompressed(TRUE);
// first, set the aggregate values
colStats->setRowsAndUec ( origRowcount, origUec );
colStats->setRedFactor ( csOne );
colStats->setUecRedFactor( csOne );
// Set first interval's rowcount and uec.
hist->getFirstInterval().setRowsAndUec( origRowcount, origUec );
// from this point forward, we're going to consider this a fake histogram
colStats->setFakeHistogram();
isaFakeHistogram = TRUE;
// finally, reset the rowcount/uec values
rowcount = colStats->getRowcount();
uec = colStats->getTotalUec();
}
// after we have applied the predicate on the histogram (modified the
// interval boundaries to reflect the predicate), and taken care of
// special conditions, we check to see if the predicate applied
// is a derivative of LIKE, or a similar predicate has been applied
// to that histogram. If it is, then we don't want to apply the
// reduction twice on that histogram. We shall bring the
// rowcount and the UEC to what it was before the predicate was
// applied. Hence, at the end of it, we shall have the boundaries
// of the histogram intervals reflecting the two predicates,
// and the rows and UEC reflecting one predicate. For LIKE predicate
// this will ensure that the final selectivity after applying both range
// predicates is equal to the default selectivity of LIKE predicates
//
if (derivOfLikeAndSimilarPredApp(pred) )
{
synchronizeStats( rowcount, origRowcount, DO_NOTHING_SPECIAL );
}
else
{
if (isaFakeHistogram
AND NOT ( hist->entries() == 1
AND colStats->isMaxSetByPred()
AND colStats->isMinSetByPred()
AND colStats->getMaxValue() == UNINIT_ENCODEDVALUE
AND colStats->getMinValue() == UNINIT_ENCODEDVALUE
)
)
{
CostScalar defaultRowcount = origRowcount;
if (!isSimilarPredicateApplied(pred->getOperatorType()))
{
// if it is a fake histogram, then for equality predicate, set the rowcount as
// the sqrt of the original rowcount
if (op == ITM_EQUAL)
{
if (colStats->isUnique())
defaultRowcount = 1;
else
defaultRowcount = ceil(sqrt(origRowcount.getValue()));
}
else
if (op == ITM_NOT_EQUAL)
{
defaultRowcount.minCsOne();
if (colStats->isUnique())
defaultRowcount -= 1;
else
defaultRowcount = origRowcount - ceil(sqrt(origRowcount.getValue()));
}
else
defaultRowcount = origRowcount * pred->defaultSel();
}
SynchSpecialFlag specialFlag = DO_NOTHING_SPECIAL;
// handle the operators which set UEC to one
if ( op == ITM_IS_NULL ||
op == ITM_IS_UNKNOWN ||
op == ITM_EQUAL )
{
// the resulting UEC after this operation should be at most one
specialFlag = SET_UEC_TO_ONE;
}
synchronizeStats( rowcount, defaultRowcount, specialFlag );
}
}
addToAppliedPreds( predValueId ); // in this case: add when done
// Now do the lower bound check for range predicates
if ( !isaFakeHistogram && (val != UNINIT_ENCODEDVALUE) )
{
if ( op == ITM_LESS || op == ITM_LESS_EQ ||
op == ITM_GREATER || op == ITM_GREATER_EQ)
{
double baseRowcount = colStats->getBaseRowCount().getValue();
double baseUec = (colStats->getUecBeforePreds().getValue());
if (baseUec < 1.0) baseUec = 1.0;
double minRowcount = baseRowcount/baseUec;
if (colStats->getRowcount() < minRowcount)
synchronizeStats( rowcount, minRowcount );
}
}
newRowcount = colStats->getRowcount();
return TRUE;
} // ColStatDesc::modifyStats
// This method merges the two histograms from the same table
NABoolean
ColStatDesc::mergeColStatDescOfSameTable(ColStatDescSharedPtr &rightColStats,
OperatorTypeEnum opType)
{
NABoolean checkForMergeFromScan = FALSE;
if ((opType == REL_SCAN) || (opType == ITM_OR) || (opType == ITM_AND) )
checkForMergeFromScan = TRUE;
if ((!checkForMergeFromScan) ||
(CmpCommon::getDefault(COMP_BOOL_74) == DF_OFF) )
return FALSE;
ColStatsSharedPtr rootColStats = getColStatsToModify();
CostScalar leftUec = rootColStats->getTotalUec();
CostScalar rightUec = rightColStats->getColStats()->getTotalUec();
// In exceptional cases where one of the columns has been reduced by another predicate
// example, upper(col1) = upper(col2) and col1 = 'B', we should to take the lower rowcount
// Without the upper expression, we will get a VEG here, and that would be handled at
// the method applyVEGPred method and the control will not come here
CostScalar newRowcount = MINOF(rootColStats->getRowcount(), rightColStats->getColStats()->getRowcount());
CostScalar minUec = MINOF(leftUec, rightUec);
// use HIST_NO_STATS_UEC to compute selectivity for equality prdicates
// from same table (T1.a = T1.b)
double selectivityForPredEqual = (1.0/(CURRSTMT_OPTDEFAULTS->defNoStatsUec()) );
newRowcount = (newRowcount * selectivityForPredEqual).minCsOne();
CostScalar newUec = MINOF(minUec, newRowcount);
HistogramSharedPtr hist = rootColStats->getHistogramToModify() ;
hist->condenseToSingleInterval();
rootColStats->setIsCompressed(TRUE);
// Set first interval's rowcount and uec.
hist->getFirstInterval().setRowsAndUec( newRowcount, newUec );
// first, set the aggregate values
rootColStats->setRowsAndUec ( newRowcount, newUec );
if(opType == REL_SCAN)
rootColStats->setBaseUec(newUec);
rootColStats->setRedFactor ( csOne );
rootColStats->setUecRedFactor( csOne );
rootColStats->setRecentJoin(TRUE);
appliedPreds().insert( rightColStats->getAppliedPreds() );
mergeState().insert(rightColStats->getMergeState() );
// from this point forward, we're going to consider this a fake histogram
rootColStats->setFakeHistogram();
return TRUE;
}
// -----------------------------------------------------------------------
// ColStatDesc::mergeColStatDesc
//
// merge two ColStatDesc's
//
// forceMerge overrides logic that prevents equijoins of the form
// <col_1> = <col_1>
//
// Calls
//
// ColStats::mergeColStats
//
// to do the low-level merge work
// -----------------------------------------------------------------------
void
ColStatDesc::mergeColStatDesc (ColStatDescSharedPtr& mergedStatDesc,
MergeType mergeMethod,
NABoolean forceMerge,
OperatorTypeEnum exprOpCode,
NABoolean mergeFVs)
{
ColStatsSharedPtr rootColStats = getColStatsToModify();
ColStatsSharedPtr mergedColStats = mergedStatDesc->getColStats();
CostScalar minCard = csOne;
// before we start manipulating histograms for join
// let collect the minimum cardinality, that will be used
// to do the sanity check later. Do this only if the
// parent is a Join
if ((CmpCommon::getDefault(COMP_BOOL_45) == DF_ON ) &&
(exprOpCode == REL_JOIN) &&
(mergeMethod == INNER_JOIN_MERGE) &&
!rootColStats->isVirtualColForHist() &&
!mergedColStats->isVirtualColForHist() )
{
if (CmpCommon::getDefault(COMP_BOOL_46) == DF_OFF)
{
CostScalar leftFreq = rootColStats->getMaxFreq();
CostScalar rightFreq = mergedColStats->getMaxFreq();
minCard = MAXOF(leftFreq, rightFreq).minCsOne();
}
} // exprOpCode == REL_JOIN
if ( NOT ( mergeMethod == UNION_MERGE ||
mergeMethod == OR_MERGE ||
mergeMethod == LEFT_JOIN_OR_MERGE )
/* not really a join */
)
{
// --------------------------------------------------------------------
// We do the usual case first :
// merges that are *NOT* (the unusual) UNIONs, ORs, or LEFT_JOIN_ORs
// --------------------------------------------------------------------
// For join-related merges, add any/all entries in the nonVegEquals_
// ColStatDesc LIST of the mergedStatDesc to the nonVegEquals_ in
// THIS.
//
// Unfortunately, this can't be a simple set insert, because members
// of the set(s) are pointers, and we may have two different pointers
// pointing to the 'same' ColStatDesc.
// --------------------------------------------------------------------
for ( CollIndex j = 0; j < mergedStatDesc->nonVegEquals_.entries(); j++ )
{
ColStatDescSharedPtr tmpDescJ = mergedStatDesc->nonVegEquals_[j];
NABoolean foundFlag = FALSE;
for ( CollIndex i = 0; i < nonVegEquals_.entries(); i++ )
{
ColStatDescSharedPtr tmpDescI = nonVegEquals_[i];
if ( tmpDescI->VEGcolumn_ == tmpDescJ->VEGcolumn_ )
{
foundFlag = TRUE;
break ;
}
} // for i
if ( foundFlag == FALSE )
nonVegEquals_.insert( tmpDescJ );
} // for j
NABoolean skipJoin = FALSE;
// --------------------------------------------------------------------
// For any type of Join-related merge, Test for column statistics
// with identical merge states, and don't perform that join.
// --------------------------------------------------------------------
if ( mergedStatDesc->getMergeState() == mergeState_ &&
forceMerge == FALSE )
{
// if the root contains more rows than the to-be-merged version,
// overwrite the root with the other version.
// else do nothing: rootColStats is o.k.
if ( rootColStats->getRowcount() > mergedColStats->getRowcount() )
rootColStats->overwrite( *mergedColStats );
// update the applied predicates
appliedPreds().insert( mergedStatDesc->getAppliedPreds() );
skipJoin = TRUE;
} // forceMerge == FALSE, the two merge states are same
// Test for rootState being a subset of the mergedState.
else if ( mergedStatDesc->getMergeState().contains( mergeState() ) &&
forceMerge == FALSE )
{
// the to-be-merged colStats shows the effects of a merge with
// the rootColStats; overwrite the root copy
rootColStats->overwrite( *mergedColStats );
// update the mergeState_ of the root copy.
mergeState().clear();
mergeState().insert( mergedStatDesc->getMergeState() );
// and its applied predicates
appliedPreds().insert( mergedStatDesc->getAppliedPreds() );
skipJoin = TRUE;
} // root state is a subset of merged state
// left contains right -- join's already been done, and we're not forced
else if ( mergeState().contains( mergedStatDesc->getMergeState() ) &&
forceMerge == FALSE )
{
return ; // nothing left to merge
}
else // join on unique columns
if ( forceMerge == FALSE &&
( (mergeMethod == INNER_JOIN_MERGE) ||
(mergeMethod == SEMI_JOIN_MERGE) ) )
{
// Before starting with the checks, if the statistics exist
// for the columns being joined. Skip the following logic, if
// 1. no statistics exists for either of the columns
// 2. join is being performed on the histograms with virtual column
if ((CmpCommon::getDefault(COMP_BOOL_48) == DF_ON) ||
(!rootColStats->isOrigFakeHist() &&
!mergedColStats->isOrigFakeHist() &&
!rootColStats->isVirtualColForHist() &&
!mergedColStats->isVirtualColForHist() ))
{
NABoolean scaleFreq = TRUE;
if (mergeMethod == SEMI_JOIN_MERGE)
scaleFreq = FALSE;
// merge freq values of the two sides, if the number of values in the freq value list for both
// histograms is less than the threshold value and in case of tuple list, a frequent value list
// has been created
//
NABoolean mergeFreqValues = FALSE;
CostScalar uecCushion ((ActiveSchemaDB()->getDefaults()).getAsDouble(COMP_FLOAT_4));
// check if it is a primary_key - foreign_key join.If yes, and
// No merge is required
ValueIdSet colSetLeft = mergeState();
NABoolean leftJoinUnique = FALSE;
ValueIdSet colSetRight = (ValueIdSet)(mergedStatDesc->getMergeState());
NABoolean rightJoinUnique = FALSE;
NABoolean joinWithTupleList = FALSE;
// For semi_joins, only right side being unique matters
if ( (colSetLeft.entries() == 1) &&
(mergeMethod != SEMI_JOIN_MERGE) )
{
ValueId colIdLeft;
colSetLeft.getFirst(colIdLeft);
// Check to see if it is a join with a tuple list
// In this case the column type should be ITM_NATYPE
if ((CmpCommon::getDefault(COMP_BOOL_48) == DF_ON) &&
(colIdLeft.getItemExpr()->getOperatorType() == ITM_NATYPE) )
{
joinWithTupleList = TRUE;
// in case of a tuple list, do not do a regular merge of frequent values if
// there was no frequent value list created. That will be true if the
// number of elemenst in the IN list for which the tuple list was created
// had elements less than or equal to CQD HIST_TUPLE_FREQVAL_LIST_THRESHOLD
// Please refer method addColStatDescForVirtualCol for use of this CQD
if (rootColStats->getFrequentValues().entries() > 0)
mergeFreqValues = TRUE;
}
// join with a tuple list will always be unique as the
// UEC = rowcount
if (joinWithTupleList || rootColStats->isAlmostUnique())
leftJoinUnique = TRUE;
else
{
BaseColumn * colExprLeft = colIdLeft.castToBaseColumn();
if (colExprLeft != NULL)
{
TableDesc * tableDescForLeftCol = colExprLeft->getTableDesc();
leftJoinUnique = colSetLeft.doColumnsConstituteUniqueIndex(tableDescForLeftCol);
}
}
}
if (colSetRight.entries() == 1)
{
ValueId colIdRight;
colSetRight.getFirst(colIdRight);
// Check to see if it is a join with a tuple list
// In this case the column type should be ITM_NATYPE
if ((CmpCommon::getDefault(COMP_BOOL_48) == DF_ON) &&
(colIdRight.getItemExpr()->getOperatorType() == ITM_NATYPE) )
{
joinWithTupleList = TRUE;
// in case of a tuple list, do not do a regular merge of frequent values if
// there was no frequent value list created. That will be true if the
// number of elemenst in the IN list for which the tuple list was created
// had elements less than or equal to CQD HIST_TUPLE_FREQVAL_LIST_THRESHOLD
// Please refer method addColStatDescForVirtualCol for use of this CQD
if (mergedColStats->getFrequentValues().entries() > 0)
mergeFreqValues = TRUE;
}
// join with a tuple list will always be unique as the
// UEC = rowcount
if(joinWithTupleList || mergedColStats->isAlmostUnique() )
rightJoinUnique = TRUE;
else
{
BaseColumn * colExprRight = colIdRight.castToBaseColumn();
if (colExprRight != NULL)
{
TableDesc * tableDescForRightCol = colExprRight->getTableDesc();
rightJoinUnique = colSetRight.doColumnsConstituteUniqueIndex(tableDescForRightCol);
}
}
}
CostScalar leftUec;
CostScalar rightUec;
// Fix for Sol:10-070222-2759. The join cardinalities were highly
// underestimated. This is because of the assumption the optimizer makes
// regarding the relationship between the joining columns. The joining
// columns can be either orthogonal or contained within each other.
// Containment determines if the joining column of the one table
// should take into account the reduction on the column from the other side
// The way the reduction should be computed is controlled by a CQD
// By default we assume that the columns are orthogonal. That is reduction
// on one side of a table, should not impact the other side.
// CQD used for this is HIST_ASSUME_INDEPENDENT_REDUCTION,
// and is ON by default
if (CURRSTMT_OPTDEFAULTS->histAssumeIndependentReduction())
{
leftUec = rootColStats->getBaseUec();
rightUec = mergedColStats->getBaseUec();
UInt32 upliftCardCond = CURRSTMT_OPTDEFAULTS->histOptimisticCardOpt();
if ((CmpCommon::getDefault(COMP_BOOL_45) == DF_ON) &&
( (upliftCardCond == 1) ||
(upliftCardCond == 2) ) )
{
ValueIdSet joinedCols = this->getColumn();
joinedCols.insert(mergedStatDesc->getColumn());
// first get the joining column with minimum UEC
// CostScalar minOriginalUec = joinedCols.getMinOrigUecOfJoiningCols();
CostScalar minOriginalUec = MINOF(leftUec, rightUec);
// next get the UEC of the left and the right joining columns
CostScalar leftTotalUec = rootColStats->getTotalUec();
CostScalar rightTotalUec = mergedColStats->getTotalUec();
// max of left and right UEC to compute join cardinality
// uses single interval concept and the containment assumption
//leftUec = MAXOF(minOriginalUec, leftTotalUec);
//rightUec = MAXOF(minOriginalUec, rightTotalUec);
leftUec = MAXOF(leftTotalUec, minOriginalUec);
rightUec = MAXOF(rightTotalUec, minOriginalUec);
} // upliftCardCond = 1 OR 2
} // histAssumeIndependentReduction
else
{
leftUec = rootColStats->getTotalUec();
rightUec = mergedColStats->getTotalUec();
}
if (leftJoinUnique && (joinWithTupleList ||
((rootColStats->getBaseUec() * uecCushion) > mergedColStats->getBaseUec() ) ) )
{
// result is the otherColStats
CostScalar reduction = csOne;
if (joinWithTupleList)
reduction = 1 / MAXOF(rightUec, leftUec).getValue();
else
reduction = 1/leftUec.value();
CostScalar minUec = MINOF(rootColStats->getTotalUec(), mergedColStats->getTotalUec()) ;
// make a copy of original rootColStats so we can merge frequent values properly later
// This will be required if we mergeFreqValues flag is TRUE that is we need to do detailed
// merge of frequent values
ColStatsSharedPtr rootColStatsCopy;
if (mergeFreqValues)
rootColStatsCopy = ColStats::deepCopy(*(rootColStats),HISTHEAP);
// Over write the rootColStats with the right histogram, which is the resultant
// histogram now
rootColStats->overwrite( *mergedColStats );
// Overwrite function does not copy frequent value list. I tried t make a copy
// but that resulted in cardinality changes for outer joins, till we
// figure out how to handle frequent values for outer joins, we will copy frequent
// values separately, so that the behaviour of left joins does not change.
// Also did not want to change the behavior of short cut join for join on columns,
// hence do that only for tuple lists.
if (joinWithTupleList &&
(rootColStats->getFrequentValues().entries() == 0) &&
(mergedColStats->getFrequentValues().entries() > 0))
{
FrequentValueList * resultantFreqValList = new (STMTHEAP)
FrequentValueList(mergedColStats->getFrequentValues(), STMTHEAP);
rootColStats->setFrequentValue(*resultantFreqValList);
}
CostScalar uecReduction = minUec / rootColStats->getTotalUec();
// do a detailed merge of joins, if the tuple list has frequent values attached to it
// else simply scale the frequency and the probability of the frequent value list
// by the row and uec reduction computed
if (mergeFreqValues)
// rootColStats is now the rigthColStats, hence merge frequentvalues of right and
// original left root colstats
NABoolean dummy = rootColStats->mergeFrequentValues(rootColStatsCopy, scaleFreq);
else
{
FrequentValueList & rootFrequentValueList = rootColStats->getModifableFrequentValues();
rootFrequentValueList.scaleFreqAndProbOfFrequentValues(reduction, uecReduction);
}
// later scale the histogram keeping the frequent values unchanged, as they have already been
// scaled
rootColStats->scaleHistogram(reduction, uecReduction, FALSE);
// update the applied predicates
appliedPreds().insert( mergedStatDesc->getAppliedPreds() );
mergeState().insert(mergedStatDesc->getMergeState() );
rootColStats->setRecentJoin(TRUE);
rootColStats->setModified (TRUE) ;
skipJoin = TRUE;
}
else
{
if (rightJoinUnique && (joinWithTupleList ||
(( mergedColStats->getBaseUec() * uecCushion) > rootColStats->getBaseUec() ) ))
{
// final result is this colStats, hence not much to do
// except scale this colstats to take care of cross product
// that had taken place earlier
// For semi-joins, the left and the rigth histograms are not scaled
// at the time of merge, hence, no scaling is required here too
CostScalar reduction = csOne;
// if it is join with tuple list, reduction is equal to the
// larger of the two bases UECs
if (joinWithTupleList)
reduction = 1 / MAXOF(rightUec, leftUec).getValue();
else
{
if (mergeMethod != SEMI_JOIN_MERGE)
reduction = 1/rightUec.value();
else
reduction = mergedColStats->getTotalUec()/mergedColStats->getBaseUec().value();
}
CostScalar minUec = MINOF(rootColStats->getTotalUec(), mergedColStats->getTotalUec()) ;
CostScalar uecReduction = minUec / rootColStats->getTotalUec();
// merge the frequent values of the two sides if the heuristic used above is TRUE else
// just scale the probability of the frequent values, keeping frequency unchanged
// This is similar to applying direct reduction to histogram intervals
if (mergeFreqValues)
NABoolean dummy = rootColStats->mergeFrequentValues(mergedColStats, scaleFreq);
else
{
FrequentValueList & rootFrequentValueList = rootColStats->getModifableFrequentValues();
rootFrequentValueList.scaleFreqAndProbOfFrequentValues(reduction, uecReduction);
}
// later scale the histogram keeping the frequent values unchanged, as they have already been
// scaled
rootColStats->scaleHistogram(reduction, uecReduction, FALSE);
appliedPreds().insert( mergedStatDesc->getAppliedPreds() );
mergeState().insert(mergedStatDesc->getMergeState() );
rootColStats->setRecentJoin(TRUE);
rootColStats->setModified (TRUE) ;
skipJoin = TRUE;
} // rightJoinUnique
} // end else leftJoinUnique
} // histogram is !fake and !histForVirtualCol
} // forceMerge = FALSE && mergeMethod == INNER_JOIN or SEMI_JOIN
if (forceMerge == TRUE || skipJoin == FALSE)
{
// ----------------------------------------------------------------
// merge the two column statistics
// ----------------------------------------------------------------
NABoolean isNumeric = FALSE; //variable used to indicate if datatype is numeric
if(CURRSTMT_OPTDEFAULTS->reduceIntermediateHistograms())
if(getVEGColumn()){
isNumeric = getVEGColumn().getType().isNumeric();
}
else if (getColumn())
{
isNumeric = getColumn().getType().isNumeric();
};
rootColStats->mergeColStats( mergedColStats, mergeMethod, isNumeric,
exprOpCode, mergeFVs);
// update the mergeState_ of the root copy.
mergeState().insert( mergedStatDesc->getMergeState() );
// and its applied predicates
appliedPreds().insert( mergedStatDesc->getAppliedPreds() );
} // if (forceMerge == TRUE || skipJoin == FALSE)
} // not UNION or left join
else // It's a UNION, OR or LEFT_JOIN_OR.
{
// --------------------------------------------------------------------
// We do the unusual case second :
// merges that are UNIONs, ORs, or LEFT_JOIN_ORs
// (i.e., "non-join" merges)
// --------------------------------------------------------------------
// For non-join merges, an entry should remain in the nonVegEquals_
// ColStatDesc list of the result only if it appears in both of the
// nonVegEquals_ involved in the merge.
// --------------------------------------------------------------------
CollIndex j = 0;
while ( j < nonVegEquals_.entries() )
{
ColStatDescSharedPtr tmpDescJ = nonVegEquals_[j];
NABoolean foundFlag = FALSE;
for ( CollIndex i = 0;
i < mergedStatDesc->nonVegEquals_.entries() && !foundFlag;
i++ )
{
ColStatDescSharedPtr tmpDescI = mergedStatDesc->nonVegEquals_[i];
if ( tmpDescI->VEGcolumn_ == tmpDescJ->VEGcolumn_ )
foundFlag = TRUE;
} // for i
if ( foundFlag == FALSE )
{
nonVegEquals_.removeAt( j );
}
else
{
j++;
}
} // while j
// ----------------------------------------------------------------
// merge the two column statistics
// ----------------------------------------------------------------
NABoolean isNumeric = FALSE; //variable used to indicate if datatype is numeric
if(CURRSTMT_OPTDEFAULTS->reduceIntermediateHistograms())
if(getVEGColumn()){
isNumeric = getVEGColumn().getType().isNumeric();
}
else if (getColumn())
{
isNumeric = getColumn().getType().isNumeric();
};
rootColStats->mergeColStats( mergedColStats, mergeMethod, isNumeric,
exprOpCode, mergeFVs);
// And update the mergeState_ and applied predicates of the root copy.
if ( mergeMethod == UNION_MERGE ||
mergeMethod == OR_MERGE )
{
// -------------------------------------------------------------
// An OR's or a UNION's result, should only indicate that a
// predicate has been applied when it has been applied to both
// sides of the OR or the UNION.
//
// This restriction impacts both the result's mergeState_ and
// its Applied Predicate set.
// -------------------------------------------------------------
mergeState().intersectSet( mergedStatDesc->getMergeState() );
appliedPreds().intersectSet( mergedStatDesc->getAppliedPreds() );
}
else
{
mergeState().insert( mergedStatDesc->getMergeState() );
appliedPreds().insert( mergedStatDesc->getAppliedPreds() );
}
}
if ((exprOpCode == REL_JOIN) && (rootColStats->getRowcount() < minCard) )
rootColStats->setRowsAndUec(minCard, rootColStats->getTotalUec());
} // ColStatDesc::mergeColStatDesc
void
ColStatDescList::setScaleFactor(CostScalar val)
{
if (CmpCommon::getDefault(COMP_BOOL_42) == DF_OFF)
return;
for ( CollIndex i = 0; i < entries(); i++ ) {
(*this)[i]->getColStatsToModify()->setScaleFactor(val);
}
}
// ------------------------------------------------------------------------
// ColStatDesc::derivOfLikeAndSimilarPredApp
//
// This method is used in modifyStats (during applyPred) and
// applyDefaultPred.
//
// It is used for range predicates derived from LIKE predicate. This method
// returns TRUE if the range predicate is a derivative of LIKE predicate
// and the first range predicate derived from LIKE has already been
// applied to this column's histogram
// ------------------------------------------------------------------------
NABoolean
ColStatDesc::derivOfLikeAndSimilarPredApp(const ItemExpr * predApplied )
{
// get the operator for the predicate
OperatorTypeEnum op = predApplied->getOperatorType();
// check to see if this is a derivative of LIKE predicate
NABoolean predDerivOfLike = FALSE;
if ( (op == ITM_GREATER_EQ) OR
(op == ITM_GREATER) OR
(op == ITM_LESS) OR
(op == ITM_LESS_EQ))
{
BiRelat *br = (BiRelat *) predApplied;
predDerivOfLike = br->derivativeOfLike();
if (! predDerivOfLike)
return FALSE;
}
for ( ValueId id = appliedPreds_.init();
appliedPreds_.next( id );
appliedPreds_.advance( id ) )
{
const ItemExpr *pred = id.getItemExpr();
OperatorTypeEnum appliedOp = pred->getOperatorType();
if ( (appliedOp == ITM_GREATER_EQ) OR
(appliedOp == ITM_GREATER) OR
(appliedOp == ITM_LESS) OR
(appliedOp == ITM_LESS_EQ) )
{
BiRelat *br = (BiRelat *) pred;
if (predDerivOfLike && br->derivativeOfLike())
{
getColStatsToModify()->setFakeHistogram();
return TRUE;
}
}
}
return FALSE;
}
// ------------------------------------------------------------------------
// ColStatDesc::isSimilarPredicateApplied
//
// Used to avoid redundant predicate application against 'fake' ColStats,
// this routine determines whether or not THIS's appliedPreds_ contains a
// predicate 'similar' to its input OperatorTypeEnum op.
//
// Currently, the only non-identical 'similar' predicates are the >, >=
// and <, <= predicate pairs.
// ------------------------------------------------------------------------
NABoolean
ColStatDesc::isSimilarPredicateApplied (const OperatorTypeEnum op) const
{
for ( ValueId id = appliedPreds_.init();
appliedPreds_.next( id );
appliedPreds_.advance( id ) )
{
const ItemExpr *pred = id.getItemExpr();
OperatorTypeEnum appliedOp = pred->getOperatorType();
switch ( op )
{
case ITM_LESS:
case ITM_LESS_EQ:
if ( appliedOp == ITM_LESS || appliedOp == ITM_LESS_EQ )
return TRUE;
else
break ;
case ITM_GREATER:
case ITM_GREATER_EQ:
if ( appliedOp == ITM_GREATER || appliedOp == ITM_GREATER_EQ )
return TRUE;
else
break;
default:
if ( appliedOp == op )
return TRUE;
}
}
return FALSE;
}
// ------------------------------------------------------------------------
// ColStatDesc::selForRelativeRange
//
// this routine determines whether or not THIS's appliedPreds_ contains a
// predicate opposite in range to its input OperatorTypeEnum op.
//
// Currently, the only opposite range predicates are the >, >=
// and <, <= predicate pairs.
//
// This returns the value of the constant (adjusted for >= and <=) over
// high - low + 1
//
// A return of 1 indicates that this routine could not calculate the selectivity
// ------------------------------------------------------------------------
CostScalar
ColStatDesc::selForRelativeRange (const OperatorTypeEnum op,
const ValueId & column,
ItemExpr *newPred) const
{
CostScalar sel = csOne;
OperatorTypeEnum appliedOp = NO_OPERATOR_TYPE;
ItemExpr *savePred = NULL;
// Return the selctivity as csOne if it is a fake histograms. Then the
// default selectivity will be applied.
if ( getColStats()->isFakeHistogram() )
return csOne;
// This algorithm only works for numeric and datetime types.
if ( NOT (( column.getType().getTypeQualifier() == NA_NUMERIC_TYPE ) ||
( column.getType().getTypeQualifier() == NA_DATETIME_TYPE)))
return csOne;
// Go through all the predicates we have already applied, looking for
// the second part of a between type operation.
// One of the predicates must be (>, or >=) and the other (<, or <=)
for ( ValueId id = appliedPreds_.init();
appliedPreds_.next (id);
appliedPreds_.advance (id) )
{
ItemExpr *pred = id.getItemExpr();
appliedOp = pred->getOperatorType();
switch (op)
{
case ITM_LESS:
case ITM_LESS_EQ:
if ( appliedOp == ITM_GREATER || appliedOp == ITM_GREATER_EQ )
savePred=pred;
else
break ;
case ITM_GREATER:
case ITM_GREATER_EQ:
if ( appliedOp == ITM_LESS || appliedOp == ITM_LESS_EQ )
savePred=pred;
else
break;
default:
if ( appliedOp == op )
break;
}
}
if ( savePred ) // if we have found a between type of operation - do more
{
// look for common host variables and constant with +/- with a constant
ItemExpr * saveChildPred = savePred->child(1);
//Use the actual node hiding behind the cast or the notCovered node.
if ((saveChildPred->getOperatorType() == ITM_CAST) ||
(saveChildPred->getOperatorType() == ITM_NOTCOVERED) )
saveChildPred = saveChildPred->child(0);
CollIndex newPredArity = newPred->getArity();
CollIndex savePredArity = saveChildPred->getArity();
OperatorTypeEnum newOp = newPred->getOperatorType();
OperatorTypeEnum saveOp = saveChildPred->getOperatorType();
// We need to have one predicate compare the column to a stand alone
// host variable and the other predicate to compare the column to a
// host variable +/- a constant.
if( NOT ( ( newPredArity == 0
AND ( saveOp == ITM_PLUS OR saveOp == ITM_MINUS )
)
OR ( savePredArity == 0
AND ( newOp == ITM_PLUS OR newOp == ITM_MINUS )
)
)
)
{
return csOne;
}
// Now normalize to the two predicate side and the one predicate side
ItemExpr * onePred;
ItemExpr * twoPred;
ItemExpr * hostVar = NULL;
ConstValue * constant = NULL;
double cn;
OperatorTypeEnum twoOp;
OperatorTypeEnum origTwoOp;
NABoolean def = FALSE;
if ( newPredArity == 0 )
{
onePred = newPred;
twoPred = saveChildPred;
twoOp = saveOp;
origTwoOp = appliedOp;
}
else
{
onePred = saveChildPred;
twoPred = newPred;
twoOp = newOp;
origTwoOp = op;
}
// Identify which is the constant and which the host variable
// If this is a more complicated expression, (has more than 1 operator)
// we will not find either the constant or host variable.
for ( Int32 arity = 0; arity < twoPred->getArity(); arity++ )
{
ItemExpr * operand = (*twoPred)[arity].getPtr();
//Use the actual node hidding behind the cast / not covered node.
if ( (operand->getOperatorType() == ITM_CAST) ||
(operand->getOperatorType() == ITM_NOTCOVERED) )
operand = operand->child(0);
//Check for all operators types that stay constant
//for a particular execution of a statement
if ( ( operand->getOperatorType() == ITM_HOSTVAR )
OR ( operand->getOperatorType() == ITM_DYN_PARAM )
OR (operand->getOperatorType() == ITM_CACHE_PARAM)
OR (operand->getOperatorType() == ITM_CURRENT_USER)
OR (operand->getOperatorType() == ITM_CURRENT_TIMESTAMP)
OR (operand->getOperatorType() == ITM_UNIQUE_ID)
OR (operand->getOperatorType() == ITM_UNIX_TIMESTAMP)
OR (operand->getOperatorType() == ITM_SESSION_USER)
OR (operand->getOperatorType() == ITM_GET_TRIGGERS_STATUS)
OR (operand->getOperatorType() == ITM_UNIQUE_EXECUTE_ID))
hostVar = operand;
//changed from
/*
if ( operand->getOperatorType() == ITM_CONSTANT )
constant = operand->castToConstValue( def );
*/
//changed to
//the two are almost equivalent,
//castToConstValue is virtual method
//on ItemExpr and class derived from it
//the base implementation always returns
//NULL
//The above lines of code can only cover
//constant literals e.g. 2. But the following
//lines of code also cover simple expressions
//like 1+3, 2-2. That is because castToConstValue
//is overloaded for BiArithmetic expressions.
//A non-null return value from castToConstValue
//indicates a constant or simple constant expression, and
//therefore we use the return value from castToConstValue
//to check for constants.
ConstValue * isAConstant = operand->castToConstValue(def);
if(isAConstant)
constant = isAConstant;
}
// COLUMN <op> constant predicate?
// if so, does column match the leading prefix of histogram?
if ( constant != NULL )
{
// get the encoded format for the constant
//use double cn instead.
EncodedValue encodedConst = EncodedValue( constant, def );
cn = encodedConst.getDblValue();
// we want to convert Interval into seconds from what its present
// type.
const NAType * constantType = constant->getType();
if (constantType->getTypeQualifier() == NA_INTERVAL_TYPE)
cn = ((SQLInterval *)constantType)->getValueInSeconds(cn);
}
else
return csOne;
if ( onePred != hostVar) // if both predicates don't refer to same hv.
return csOne;
// If the range is impossible - we have zero selectivity
if ( ( twoOp == ITM_PLUS
AND ( origTwoOp== ITM_GREATER_EQ OR origTwoOp == ITM_GREATER )
)
OR ( twoOp == ITM_MINUS
AND ( origTwoOp == ITM_LESS_EQ OR origTwoOp == ITM_LESS )
)
)
return csZero;
EncodedValue minVal = getColStats()->getMinValue();
EncodedValue maxVal = getColStats()->getMaxValue();
double hi = maxVal.getDblValue();
double lo = minVal.getDblValue();
// calculate the multiplier we will use for scaling some constants we want to add
// to the hi/lo/cn.
double scaleMult = 1;
Lng32 scale = column.getType().getScale();
for ( Lng32 i = 0; i < scale; i++ )
{
if ( scale > 0 )
scaleMult = scaleMult / 10.0;
else
scaleMult = scaleMult * 10.0;
}
// Since the ration function always calculates (cn-lo)/(hi-lo).
// it doesn't add one to hi-lo to get the correct range.
// Ensure that the calculation is accurate for small ranges - avoiding overflow.
if ( lo > 0 )
lo = lo - scaleMult;
else if ( hi < ( scaleMult * 1000000 ) )
hi = hi + scaleMult;
minVal = EncodedValue( lo );
maxVal = EncodedValue( hi );
// Adjust the constant value for >= and <=, each of which add 1 to range
double rangeInc = -1; //(-1) since we will add lo+cn, subtract out extra value.
if ( op == ITM_GREATER_EQ || op == ITM_LESS_EQ )
rangeInc += 1;
if ( appliedOp == ITM_GREATER_EQ || appliedOp == ITM_LESS_EQ )
rangeInc += 1;
rangeInc = rangeInc * scaleMult;
if ( lo + cn + rangeInc > hi )
return csOne;
// the ratio function uses (val-lo) to get the numerator
// and (hi-lo) for the denominator while avoiding overflow
EncodedValue testVal = EncodedValue( lo + cn + rangeInc );
sel = testVal.ratio( minVal, maxVal );
if ( sel.getValue() < 0.0 || sel.getValue() > csOne.getValue() )
return csOne;
CostScalar defaultSel =
CURRSTMT_OPTDEFAULTS->defSelForRangePred();
// For window predicates like col between ? and ?+const,
// (it gets converted as col >= ? and col <= ?+const).
// We come to this point for the second predicate. i.e for col <= ?+const.
// The selectivity for the first predicate col >= ? is already applied,
// which is the default selectivity.
// So here make sure the selectivity will not go beyond defaultSel.
// The caller of this method (applyDefaultPred) will call applySel to apply
// the selectivity. Which in a way multiplies the selectivity for the
// predicates. In this particular case, since it is a window predicate and
// the selectivity should be the selectivity of the window we just
// calculated, we should not multiply the selectivities.
// Since the multiplication happens unconditionally in the caller, divide
// it by the defaultSel.
// If the selectivity is equal to defaultSel return 0.999999 (if we
// return 1 (csOne) then it is considered as fakeHistogram and again
// the defalutSel will be applied.
if (sel >= defaultSel)
return 0.999999;
else
sel = sel / defaultSel;
// Sanity check to make sure the cardinality did not go below average rowcount.
CostScalar selForAvgRowcount = 1/getColStats()->getUecBeforePreds().getValue();
if(sel < selForAvgRowcount)
sel = selForAvgRowcount;
}
return sel;
}
void
ColStatDesc::print (FILE *ofd,
const char * prefix,
const char * suffix,
CollHeap *c, char *buf,
NABoolean hideDetail) const
{
Space * space = (Space *)c;
char mybuf[1000];
if (!hideDetail)
{
snprintf(mybuf, sizeof(mybuf), "%scolumn:", prefix);
PRINTIT(ofd, c, space, buf, mybuf);
ValueIdList columns;
columns.insert(column_);
columns.print(ofd, prefix, suffix, c, buf);
}
snprintf(mybuf, sizeof(mybuf), "%sVEGcolumn:", prefix) ;
PRINTIT(ofd, c, space, buf, mybuf);
ValueIdList VEGcolumns;
VEGcolumns.insert(VEGcolumn_);
VEGcolumns.print(ofd, prefix, suffix, c, buf) ;
snprintf(mybuf, sizeof(mybuf), "%sMerge state:", prefix) ;
PRINTIT(ofd, c, space, buf, mybuf);
mergeState_.print(ofd, prefix, suffix, c, buf) ;
snprintf(mybuf, sizeof(mybuf), "%sApplied preds:", prefix) ;
PRINTIT(ofd, c, space, buf, mybuf);
appliedPreds_.print(ofd, prefix, suffix, c, buf) ;
if (getColStats() == NULL)
{
snprintf(mybuf, sizeof(mybuf), "NULL colStats_!\n");
PRINTIT(ofd, c, space, buf, mybuf);
}
else
{
getColStats()->print(ofd,prefix,suffix, c, buf, hideDetail);
}
}
void
ColStatDesc::display() const
{
print();
}
// ------------------------------------------------------------------------
// Methods for ColStatDescList Class
// ------------------------------------------------------------------------
// some users of the CSDL class want to be able to destroy the temporary
// objects they create
void ColStatDescList::destroy()
{
while ( entries() > 0 )
removeDeepCopyAt( 0 );
}
// Returns TRUE if the given ColStatDesc is contained in this
// ColStatDescList. The ColStatDesc could have also been merged
// with the other colStatDesc
NABoolean
ColStatDescList::contains(const ValueId & column) const
{
for ( CollIndex i = 0; i < entries(); i++ )
{
// The "merge state" of a ColStatDesc indicates all the
// columns that have been merged into this ColStatDesc.
// Initially, the merge state consists of the original
// base table column, therefore, this will work even for
// ColStatDesc's that have not been merged
const ValueIdSet & msSet = (*this)[i]->getMergeState();
if ( msSet.contains( column ) )
return TRUE;
}
return FALSE;
}
// Returns TRUE if the given ColStatDesc is contained in this
// ColStatDescList. The ColStatDesc could have also been merged
// with the other colStatDesc
NABoolean
ColStatDescList::contains(const ValueIdList & colList) const
{
ColStatsSharedPtr colStats;
for(CollIndex i =0; i<colList.entries(); i++)
{
colStats = this->getColStatsPtrForColumn(colList[i]);
// get the histogram pointer for the column in list
if (colStats == NULL)
{
// if the histogram is missing for this column
return FALSE;
}
}
return TRUE;
}
NABoolean
ColStatDescList::containsAtLeastOneFake() const
{
// -----------------------------------------------------------------------
// Go through the list of ColStatDesc and if you find
// a fake ColStats set the valid flag to FALSE and return:
// -----------------------------------------------------------------------
NABoolean thereIsAFake = FALSE;
for ( CollIndex i = 0; i < entries(); i++ )
{
if ( (*this)[i]->getColStats()->isFakeHistogram() )
{
thereIsAFake = TRUE;
break;
}
}
return thereIsAFake;
} // ColStatDescList::containsAtLeastOneFake() const
NABoolean
ColStatDescList::selectivityHintApplied() const
{
for ( CollIndex i = 0; i < entries(); i++ )
{
if ( (*this)[i]->getColStats()->isSelectivitySetUsingHint() )
return TRUE;
}
return FALSE;
}
// ---------------------------------------------------------------------
// Methods for doing Deep Copies of the ColStatDescs whose pointers are
// inserted into a ColStatDescList.
// In the various routines,
// 'firstN' specifies that only the first N entries in the source are to
// be inserted.
// 'scale' specifies the factor by which the RowCounts (not UECs) should
// be multiplied.
// 'shapeChangedMask' is AND'd with the current setting of the shape-
// changed flag, allowing it to be either left alone (the default) or
// cleared.
// ---------------------------------------------------------------------
// Macro to make a CSD copy and set it up correctly.
#define ALLOCATE_COL_STAT_DESC_AND_SET_IT_UP( x ) \
ColStatDescSharedPtr tmpColStatDescPtr(new (HISTHEAP) ColStatDesc( x ), HISTHEAP); \
ColStatsSharedPtr tmpColStatsPtr = tmpColStatDescPtr->getColStatsToModify(); \
tmpColStatsPtr->copyAndScaleHistogram( scale ); \
tmpColStatsPtr->setShapeChanged( \
shapeChangedMask && tmpColStatsPtr->isShapeChanged() );
void
ColStatDescList::makeDeepCopy (const ColStatDescList & source,
const CostScalar & scale,
const NABoolean shapeChangedMask)
{
for ( CollIndex i = 0; i < source.entries(); i++ )
{
ALLOCATE_COL_STAT_DESC_AND_SET_IT_UP( *source[i] );
insertAt( i, tmpColStatDescPtr );
}
setUecList( source.getUecList() );
} // makeDeepCopy
void
ColStatDescList::appendDeepCopy (const ColStatDescList & source,
const CollIndex firstN,
const CostScalar & scale,
const NABoolean shapeChangedMask)
{
CollIndex thisEntries = entries();
for ( CollIndex i = 0; i < source.entries() && i < firstN; i++ )
{
ALLOCATE_COL_STAT_DESC_AND_SET_IT_UP( *source[i] );
insertAt( thisEntries, tmpColStatDescPtr );
thisEntries++;
}
// add the other CSDL's uec list to this one
insertIntoUecList( source.getUecList() );
} // appendDeepCopy
void
ColStatDescList::prependDeepCopy (const ColStatDescList & source,
const CollIndex firstN,
const CostScalar & scale,
const NABoolean shapeChangedMask)
{
for ( CollIndex i = 0; i < source.entries() && i < firstN; i++ )
{
ALLOCATE_COL_STAT_DESC_AND_SET_IT_UP( *source[i] );
insertAt( i, tmpColStatDescPtr );
}
// add the other CSDL's uec list to this one
insertIntoUecList( source.getUecList() );
} // prependDeepCopy
void
ColStatDescList::insertDeepCopy (const ColStatDescSharedPtr& source,
const CostScalar & scale,
const NABoolean shapeChangedMask)
{
ALLOCATE_COL_STAT_DESC_AND_SET_IT_UP( *source );
insert( tmpColStatDescPtr );
} // insertDeepCopy
void
ColStatDescList::insertDeepCopyAt (const CollIndex entry,
const ColStatDescSharedPtr& source,
const CostScalar & scale,
const NABoolean shapeChangedMask)
{
ALLOCATE_COL_STAT_DESC_AND_SET_IT_UP( *source );
insertAt( entry, tmpColStatDescPtr );
} // insertDeepCopyAt
void
ColStatDescList::makeMappedDeepCopy (const ColStatDescList & source,
ValueIdMap &map,
NABoolean includeUnmappedColumns)
{
// similar to makeDeepCopy, but this method maps all the ValueIds in
// the source, using the provided map, in the "up" direction
for ( CollIndex i = 0; i < source.entries(); i++ )
{
ValueId topVid;
ValueId bottomVid(source[i]->getVEGColumn());
map.mapValueIdUp(topVid, bottomVid);
if (includeUnmappedColumns || topVid != bottomVid)
{
ColStatDescSharedPtr tmpColStatDescPtr(
new (HISTHEAP) ColStatDesc(), HISTHEAP);
tmpColStatDescPtr->mapUpAndCopy(*source[i], map);
ColStatsSharedPtr tmpColStatsPtr = tmpColStatDescPtr->getColStatsToModify();
tmpColStatsPtr->copyAndScaleHistogram(1.0);
tmpColStatsPtr->setShapeChanged(tmpColStatsPtr->isShapeChanged());
insert(tmpColStatDescPtr);
}
}
// map the multi-column UECs
MultiColumnUecList * mappedMultiColUECs = new (CmpCommon::statementHeap())
MultiColumnUecList();
mappedMultiColUECs->insertMappedList(source.getUecList(), map);
setUecList(mappedMultiColUECs);
} // makeMappedDeepCopy
void
ColStatDescList::removeDeepCopyAt (const CollIndex entry)
{
removeAt( entry );
} // removeDeepCopyAt
// -------------------------------------------------------------------
// copyAndScaleHistogram
// This method goes thru each colStatDesc in the list and scales the
// underlying histogram based on the scale and the row reduction factor
// and uec reduction factors specified in the colStat header.
// --------------------------------------------------------------------
void
ColStatDescList::copyAndScaleHistograms(CostScalar scale)
{
for (CollIndex i = 0; i < entries(); i++)
{
ColStatDescSharedPtr columnStatDesc = (*this)[i];
ColStatsSharedPtr columnStats = columnStatDesc->getColStatsToModify();
columnStats->scaleHistogram(scale);
columnStats->computeMaxFreqOfCol(!(CmpCommon::getDefault(COMP_BOOL_42)));
}
}
void
ColStatDescList::setBaseUecToTotalUec()
{
for (CollIndex i = 0; i < entries(); i++)
{
ColStatDescSharedPtr columnStatDesc = (*this)[i];
ColStatsSharedPtr columnStats = columnStatDesc->getColStatsToModify();
columnStats->setBaseUec(columnStats->getTotalUec());
}
}
void
ColStatDescList::computeMaxFreq(NABoolean forced)
{
for (CollIndex i = 0; i < entries(); i++)
{
ColStatDescSharedPtr columnStatDesc = (*this)[i];
ColStatsSharedPtr columnStats = columnStatDesc->getColStatsToModify();
columnStats->computeMaxFreqOfCol(forced);
}
}
// add colStatDesc for a constant in this colStatDescList.
// This will be used for cases like inserts, transpose or rowsets.
// Since there are no columns associated with these histograms
// they are treated in a special manner. These are distinguished
// from regular histograms by means of a flag virtualColForHist_
void ColStatDescList::addColStatDescForVirtualCol(const CostScalar & uec,
const CostScalar & rowCount,
const ValueId colId,
const ValueId vegCol,
const ValueId mergeState,
const RelExpr * expr,
NABoolean defineVirtual)
{
// create a frequent value list
FrequentValueList * frequentValueList = new (STMTHEAP) FrequentValueList(STMTHEAP);
NABoolean fakeHist = TRUE;
EncodedValue minValue = UNINIT_ENCODEDVALUE;
EncodedValue maxValue = UNINIT_ENCODEDVALUE;
// If the histogram is being created for a tuple expr create a frequent value list
// with tuple expressions.
if ((defineVirtual == FALSE) &&
expr &&
(expr->getOperatorType() == REL_TUPLE_LIST) )
{
fakeHist = FALSE;
ItemExprList tList(((Tuple *) expr)->tupleExprTree(), STMTHEAP);
if (tList.entries() <= CURRSTMT_OPTDEFAULTS->histTupleFreqValListThreshold())
{
for (CollIndex i = 0; (i < (CollIndex) tList.entries());i++)
{
ItemExpr * tupleVal =
((ItemExpr *) tList[i])->castToItemExpr();
if (tupleVal == NULL)
continue;
ValueIdSet leafs;
tupleVal->getLeafValueIds(leafs);
if (leafs.entries() == 0)
continue;
for (ValueId val = leafs.init();
leafs.next(val);
leafs.advance(val))
{
if (val == NULL_VALUE_ID)
continue;
ItemExpr * leafVal = val.getItemExpr();
if (leafVal->getOperatorType() == ITM_CONSTANT)
{
// add the constant to the frequent value list
NABoolean neg = FALSE;
ConstValue * cv = leafVal->castToConstValue(neg);
if (cv == NULL) continue;
EncodedValue ev(cv, neg);
UInt32 hashValue = 0;
if (cv->getType()->useHashRepresentation() &&
cv->getType()->useHashInFrequentValue())
hashValue = cv->computeHashValue(*(cv->getType()));
FrequentValue newV(hashValue, csOne, csOne, ev);
frequentValueList->insertFrequentValue(newV);
} // if leafVal is a constant
} // for all leaf values.
} // for all elements in the IN list
} // if number of elements in IN list is less than the threshold
} // if the histogram is being created for tuple_list
// finally get the in and the max values from the frequent value list
// these will be used to set the min and the max values of the histograms
// if frequentValueList is empty, maxValue = number of values in the tuple list
if (frequentValueList->entries() > 0)
{
minValue = (*frequentValueList)[0].getEncodedValue();
maxValue = (*frequentValueList)[frequentValueList->entries() - 1].getEncodedValue();
}
else
{
maxValue = uec.getValue();
}
HistogramSharedPtr emptyHist(new (HISTHEAP) Histogram(HISTHEAP));
HistInt newFirstHistInt(minValue, FALSE);
newFirstHistInt.setCardAndUec(0, 0);
HistInt newSecondHistInt(maxValue, FALSE);
newSecondHistInt.setCardAndUec(rowCount, uec);
emptyHist->insert(newFirstHistInt);
emptyHist->insert(newSecondHistInt);
ComUID id(ColStats::nextFakeHistogramID());
ColStatsSharedPtr fakeColStats(
new (HISTHEAP) ColStats(id,
uec,
rowCount,
rowCount,
FALSE,
FALSE,
emptyHist,
FALSE,
1.0,
1.0,
-1, // default avg VarChar size
HISTHEAP));
// This histogram is not an actual histogram
fakeColStats->setFakeHistogram( fakeHist);
fakeColStats->setFrequentValue(*frequentValueList);
// Setting this flag will ensure that the compiler does not start
// to look for this column name in the NAColumn. As there does not
// exist a column for a constant
fakeColStats->setVirtualColForHist ( defineVirtual );
fakeColStats->setIsCompressed (TRUE);
fakeColStats->setRowsAndUec(rowCount, uec);
if (rowCount == uec)
{
fakeColStats->setAlmostUnique(TRUE);
fakeColStats->setUnique(TRUE);
}
ColStatDescSharedPtr fakeStatDesc(new (HISTHEAP)
ColStatDesc (fakeColStats,colId), HISTHEAP);
fakeStatDesc->VEGColumn() = vegCol;
fakeStatDesc->mergeState().clear() ;
fakeStatDesc->mergeState().insert(mergeState);
this->insert(fakeStatDesc);
} // ColStatDescList::addColStatDescForVirtualCol
// ---------------------------------------------------------------------
// isPredTransformedInList
//
// subroutine of CSDL::estimateCardinality()
//
// NB: It is extremely important that this routine be tail-recursive;
// otherwise it is sure to overflow the stack for large IN-lists, which is
// the motivation for writing this code in the first place!
// ---------------------------------------------------------------------
NABoolean isPredTransformedInList( const ValueId & predId,
const ValueId & column,
Int32 & leaves )
{
const ItemExpr * pred = predId.getItemExpr();
OperatorTypeEnum op = pred->getOperatorType();
if ( op != ITM_OR )
return FALSE;
// first make sure the right child conforms
const ValueId & Rid = pred->child(1)->getValueId();
const ItemExpr * Rpred = Rid.getItemExpr();
const OperatorTypeEnum Rop = Rpred->getOperatorType();
if ( Rop != ITM_EQUAL )
return FALSE;
const ValueId & RidLeftChild = Rpred->child(0)->getValueId();
if ( RidLeftChild != column )
return FALSE;
// now make sure the left child conforms
const ValueId & Lid = pred->child(0)->getValueId();
const ItemExpr * Lpred = Lid.getItemExpr();
const OperatorTypeEnum Lop = Lpred->getOperatorType();
// base case : an ITM_OR between two ITM_EQUALs
if ( Lop == ITM_EQUAL )
{
const ValueId & LidLeftChild = Lpred->child(0)->getValueId();
leaves += 2;
if ( LidLeftChild == column )
return TRUE;
else
return FALSE;
}
else if ( Lop == ITM_OR ) // recursive case -- right child OK, now recurse down left child
{
leaves++;
return isPredTransformedInList( Lid, column, leaves );
}
else
return FALSE;
}
ULng32 ValueIdHashFn (const ValueId & key) { return (CollIndex) key ; }
// ------------------------------------------------------------------------
// $$$$ NOTE:
// The method ColStatDescList::estimateCardinality() can, as a side effect,
// update the unique entry count in the supplied ColStatsDescList.
//
// Also, use of ColStatDescList::applyVEGPred, or ColStatDescList::applyPred
// can cause the number of column stats for outer references to be decreased,
// altering the in/out parameter numOuterColStats.
// ------------------------------------------------------------------------
CostScalar
ColStatDescList::estimateCardinality (const CostScalar & initialRowCount,
const ValueIdSet & setOfPredicates,
const ValueIdSet & outerReferences,
const Join * expr,
const SelectivityHint * selHint,
const CardinalityHint * cardHint,
CollIndex & numOuterColStats,
ValueIdSet & unresolvedPreds,
MergeType mergeMethod,
OperatorTypeEnum exprOpCode,
CostScalar *maxSelectivity)
{
// -----------------------------------------------------------------------
// 1) estimateCardinality is computing expected cardinality by
// implicitly computing the composite selectivity of a conjunct of
// predicate terms:
// p1 AND p2 AND ... pn
// as the product of the individual selectivities:
// selectivity(p1 AND p2 AND ... pn) =
// selectivity(p1) * selectivity(p2) * ... selectivity(pn)
// 2) this computation is diffused across multiple methods which
// routinely modify histograms and/or sometimes directly manipulate
// a rowcount argument.
// 3) estimateCardinality assumes it is computing expected cardinality
// and expected cardinality alone.
// Therefore, to avoid problems such as those reported in genesis cases
// 10-080530-0291, 10-080530-0305, solution 10-080530-3538, we have to:
// a) logically separate estimateMaxSelectivity from estimateCardinality,
// b) have them work on separate copies of the same ColStatDescList.
// But, to minimize code duplication and maintenance, we have to physically
// keep only one method. So, estimateCardinality has both the expected
// cardinality and the max selectivity code.
//
// For computing maximum cardinality estimate, we need max selectivity to
// be computed as:
// maxSelectivity(p1 AND p2 and ... pn) =
// MIN(maxSelectivity(p1), maxSelectivity(p2), ... maxSelectivity(pn))
//
// The predicate conjuncts are spread across various predicate categories
// which are evaluated at different points in the code. Therefore, this MIN
// will often be seen in the code as
// maxSelectivity = MINOF( <someSelectivityExpr>, maxSelectivity )
// -----------------------------------------------------------------------
// NB: we are here to compute either:
// 1) expected cardinality, or
// 2) max selectivity
// When we are doing 1) we should avoid doing 2) and vice versa.
// We are doing 1) when maxSelectivity==NULL.
// We are doing 2) when maxSelectivity!=NULL.
// -----------------------------------------------------------------------
// If there is any cardinality hint is given return that, even
// if there are no predicates to be applied. This would give more
// control on the user to play around with the base cardinality
// indirectly
CollIndex i;
CostScalar newRowCount = MIN_ONE_CS( initialRowCount );
ValueIdSet * predsWithNoHints = new (STMTHEAP) ValueIdSet(setOfPredicates);
// Note: Can apply Default selectivity, even if no column list....
if (maxSelectivity == NULL) {
if ( setOfPredicates.entries() == 0 OR this->entries() == 0 )
{
// This is the rowcount without using hints. Save it
setScanRowCountWithoutHint(initialRowCount);
// Normally if there is no cardinalityHint given, there is nothing much
// we can do in the absence of predicates or the histograms. So we return
// from there. But if there is a hint given, then we can apply the hint
// on the initial row count and return. This would be useful for cases
// where we want to uplift the base cardinality of the histograms
if (cardHint == NULL)
return initialRowCount;
else
{
newRowCount = cardHint->getScanCardinality();
if((newRowCount.getValue() - floor(newRowCount.getValue())) > 0.00001)
newRowCount = MIN_ONE_CS(ceil(newRowCount.getValue()) );
return newRowCount;
}
}
} // maxSelectivity == NULL
// Now apply all the predicates on the histograms
CostScalar tempRowcount = MIN_ONE_CS( initialRowCount );
// all CSD's should have the same rowcount!
if (maxSelectivity == NULL)
{
// (this code in its own scope so it can easily be cut-and-pasted anywhere)
CollIndex limit = ( ( mergeMethod == SEMI_JOIN_MERGE ||
mergeMethod == ANTI_SEMI_JOIN_MERGE ) ?
numOuterColStats : entries() );
// first, check from 0..limit-1
enforceInternalConsistency( 0, limit );
// next, check from limit..entries()
enforceInternalConsistency( limit, entries() );
const CostScalar & matchRowcount =
(*this)[0]->getColStats()->getRowcount();
// try this out : if the current rowcount isn't what it's "supposed" to be,
// set it as requested and see how things go
if ( matchRowcount != newRowCount )
synchronizeStats( matchRowcount, newRowCount, limit );
} // maxSelectivity == NULL
ValueIdSet EqLocalPreds, OtherLocalPreds, EqNonLocalPreds,
OtherNonLocalPreds, BiLogicPreds, DefaultPreds;
setOfPredicates.categorizePredicates (outerReferences, EqLocalPreds,
OtherLocalPreds, EqNonLocalPreds,
OtherNonLocalPreds, BiLogicPreds,
DefaultPreds);
NAHashDictionary<ValueId, CostScalar> biLogicPredReductions // <ValueId, rowred> pairs
(&(ValueIdHashFn), 11, TRUE, HISTHEAP) ;
// hash fn, initsize, uniq, heap
ValueIdSet subDefPreds;
if (CmpCommon::getDefault(RANGESPEC_TRANSFORMATION) == DF_ON )
{
for( ValueId subVid = DefaultPreds.init();
DefaultPreds.next( subVid );
DefaultPreds.advance( subVid ) )
{
ValueIdSet vs;
ItemExpr * pred = subVid.getItemExpr();
if ( pred->getOperatorType() == ITM_RANGE_SPEC_FUNC )
{
((RangeSpecRef *)pred)->getValueIdSetForReconsItemExpr(vs);
vs.categorizePredicates (outerReferences, EqLocalPreds,
OtherLocalPreds, EqNonLocalPreds,
OtherNonLocalPreds, BiLogicPreds,
subDefPreds);
DefaultPreds.remove(subVid);
if (subDefPreds.entries() > 0)
{
DefaultPreds += subDefPreds;
subDefPreds.clear();
}
}
} // for
} //if
// -----------------------------------------------------------------------
// Apply AND / OR logical predicates first
// -----------------------------------------------------------------------
newRowCount = applyBiLogicPred(
tempRowcount,
BiLogicPreds,
outerReferences,
expr,
selHint,
cardHint,
numOuterColStats,
unresolvedPreds,
mergeMethod,
biLogicPredReductions,
exprOpCode,
maxSelectivity
);
// If the histograms have been modified while applying OR / AND predicates,
// then they should be scaled, else we end up computing selectivity based on
// the old rowcount and uec. This would happen only when there were some
// OR / AND predicates in the query since these predicates synchronize
// all histograms to have the same rowcount but do not modify the histogram
// intervals. Rest all other predicates do not synchronize histograms till
// all the predicates have been applied, hence their aggregate rowcount is
// equal to the total rowcount from all histogram intervals. TPCH Q07 is a
// good example, where if the histograms are not scaled, it results in high
// join cardinalities
if (maxSelectivity == NULL) {
for (i = 0; i < entries(); i++)
{
if ((*this)[i]->getColStats()->getRedFactor() != csOne)
{
ColStatsSharedPtr thisColStats = (*this)[i]->getColStatsToModify();
thisColStats->scaleHistogram(1);
}
}
} // maxSelectivity == NULL
ValueId id;
ValueIdSet nonVEGEqualPredSet;
// do all non-VEG preds together
OtherLocalPreds.insert( OtherNonLocalPreds );
for( id = OtherLocalPreds.init();
OtherLocalPreds.next( id );
OtherLocalPreds.advance( id ) )
{
ItemExpr * pred = id.getItemExpr();
if(pred->getOperatorType() == ITM_EQUAL)
{
nonVEGEqualPredSet.insert(id);
OtherLocalPreds.remove(id);
}
}
// predicate application : we do it "all at once" (previously, we used
// to re-synchronize histograms after every predicate application)
CostScalar rowcountBeforePreds = newRowCount;
// in the case of a [ANTI_]SEMI_JOIN, the outer reference columns have a
// different rowcount-before-preds :
CostScalar semiJoinRowcountBeforePreds = csZero;
if ( ( mergeMethod == SEMI_JOIN_MERGE ||
mergeMethod == ANTI_SEMI_JOIN_MERGE )
AND
( numOuterColStats < this->entries() )
AND maxSelectivity == NULL
)
{
semiJoinRowcountBeforePreds =
(*this)[numOuterColStats]->getColStats()->getRowcount();
}
// -----------------------------------------------------------------------
// Apply VEG predicates: Local and non-local
// Column stats for outer references are included in THIS.
// -----------------------------------------------------------------------
// do all VEG preds together
EqLocalPreds.insert( EqNonLocalPreds );
for( id = EqLocalPreds.init();
EqLocalPreds.next( id );
EqLocalPreds.advance( id ) )
{
tempRowcount = rowcountBeforePreds;
ItemExpr * pred = id.getItemExpr();
if ( !applyVEGPred( pred, tempRowcount, numOuterColStats,
mergeMethod, exprOpCode, maxSelectivity ) )
DefaultPreds.insert( id );
if (maxSelectivity == NULL)
newRowCount = tempRowcount;
}
// -----------------------------------------------------------------------
// Apply non-VEG equality predicates: Local and non-local
// -----------------------------------------------------------------------
for( id = nonVEGEqualPredSet.init();
nonVEGEqualPredSet.next( id );
nonVEGEqualPredSet.advance( id ) )
{
ItemExpr * pred = id.getItemExpr();
tempRowcount = rowcountBeforePreds;
if ( !applyPred( pred,
tempRowcount,
numOuterColStats,
mergeMethod,
exprOpCode,
maxSelectivity
)
)
{
DefaultPreds.insert( id );
}
else if (maxSelectivity == NULL)
newRowCount = tempRowcount;
}
// predCountSC is used to collect the number of histograms reduced after applying predicates on single
// column histograms
CollIndex predCountSC = 0;
if(exprOpCode == REL_SCAN && !maxSelectivity && (CmpCommon::getDefault(COMP_BOOL_67) == DF_ON))
predCountSC += biLogicPredReductions.entries();
// add another variable to count the number of columns reduced that should be considered for MC adjustment
CollIndex predCountMC = predCountSC;
CostScalar rowRedProduct = csOne;
CostScalar rowRedFromEquiPreds = csOne;
CostScalar rowRedAfterMCUECAdj = csOne;
CollIndex loopLimit = ( ( mergeMethod == SEMI_JOIN_MERGE ||
mergeMethod == ANTI_SEMI_JOIN_MERGE ) ?
numOuterColStats : entries() );
// ---------------------------------------------------------------------
// now we see whether we can apply multi-column uec info, in the case
// of a multi-column join between two tables
// ---------------------------------------------------------------------
// we can if:
// 1. multiple joins have been performed (isResultOfJoin() is true for >1)
// 2. we have multicolumn-uec information that matches those columns
// --> this is all handled by the subroutine
// CSDL::useMultiUecIfMultipleJoins()
//
// The following call to useMultiUecIfMultipleJoins() will adjust the
// value of newRowCount _iff_ there has been a multi-column join and
// the necessary multi-column uec information exists.
//
// NB: we may need to undo some of the adjustments done in the call to
// useMultiUecIfCorrelatedPreds() above.
// ---------------------------------------------------------------------
CollIndexList joinHistograms(STMTHEAP); // the CSDL-indices of the join histograms
if (maxSelectivity == NULL)
{
computeRowRedFactor(mergeMethod, numOuterColStats, rowcountBeforePreds, predCountSC, predCountMC, rowRedProduct);
// ---------------------------------------------------------------------
// how to calculate the resulting rowcount ...?
// ---------------------------------------------------------------------
// first a first approximation, we use the simplest formula, ignoring
// multi-column uec info
// ---------------------------------------------------------------------
rowRedFromEquiPreds = rowRedProduct;
newRowCount = rowRedProduct * rowcountBeforePreds;
this->addRecentlyJoinedCols(0, loopLimit);
// Use comp_int_40 to compute the reduction beyond which mc adjustment should be done
// Default value for the CQD is 10, which means we use multi-columns for cardinality
// adjustment, only if the reduction from single column histograms is more than 90%
NABoolean doMCAdjust;
double mcThreshold = (ActiveSchemaDB()->getDefaults()).getAsLong(COMP_INT_40);
if (mcThreshold >= 1 && mcThreshold <= 100)
{
double adjustment = 1.0 - (mcThreshold/100);
if (newRowCount.getValue() < (rowcountBeforePreds.getValue()* adjustment))
doMCAdjust = TRUE;
else
doMCAdjust = FALSE;
}
else
{
if (newRowCount < rowcountBeforePreds)
doMCAdjust = TRUE;
else
doMCAdjust = FALSE;
}
// For semi_joins, the newRowCount cannot be greater than the left child's
// row count, and if we have already reached that row count, then we don't
// need to uplift it further using multi_column stats
if ( ( (mergeMethod != SEMI_JOIN_MERGE) &&
(mergeMethod != ANTI_SEMI_JOIN_MERGE) ) ||
doMCAdjust )
{
this->useMultiUecIfMultipleJoins(
newRowCount, /* in/out */
rowcountBeforePreds, /* in */
0, /* in: start idx */
loopLimit, /* in: end idx+1 */
joinHistograms, /* out */
expr,
mergeMethod
);
// ---------------------------------------------------------------------
// now we see whether we can apply multi-column uec info, in the case of
// predicates on 2+ columns in the table
// ---------------------------------------------------------------------
// idea: if those columns are correlated exactly or almost exactly, then
// the resulting reduction should be less than the product of the 2+
// reductions -- that is, by multiplying these two reductions together
// we'll end up removing more rows in our estimate than we should.
//
// The underlying assumption here is that the two predicates being applied
// to the two correlated columns are somehow "redundant" -- that is, both
// predicates remove some of the "same rows".
// ---------------------------------------------------------------------
// At this point we need to take a look at the predicates that have been
// applied to the various table columns. If we have applied two
// predicates to the same table, and those two predicates are correlated,
// then we need to adjust the overall reduction.
//
// The following call to useMultiUecIfCorrelatedPreds() will
// adjust the value of newRowCount _iff_ this is appropriate, as
// outlined above.
// ---------------------------------------------------------------------
this->useMultiUecIfCorrelatedPreds(
newRowCount, /* in/out */
rowcountBeforePreds, /* in */
predCountMC, /* in: # preds */
joinHistograms, /* in: hists in join */
0, /* in: start idx */
loopLimit, /* in: end idx+1 */
biLogicPredReductions
);
}
rowRedAfterMCUECAdj = newRowCount/rowcountBeforePreds;
} // maxSelectivity == NULL
// -----------------------------------------------------------------------
// Apply other supported predicates: Local and non-local
// -----------------------------------------------------------------------
for( id = OtherLocalPreds.init();
OtherLocalPreds.next( id );
OtherLocalPreds.advance( id ) )
{
ItemExpr * pred = id.getItemExpr();
tempRowcount = rowcountBeforePreds;
if ( !applyPred( pred,
tempRowcount,
numOuterColStats,
mergeMethod,
exprOpCode,
maxSelectivity
)
)
{
DefaultPreds.insert( id );
}
else if (maxSelectivity == NULL)
newRowCount = tempRowcount;
}
// -----------------------------------------------------------------------
// Apply predicates with default selectivity
// -----------------------------------------------------------------------
CostScalar allPredsGlobalReduction = csOne;
for( id = DefaultPreds.init();
DefaultPreds.next( id );
DefaultPreds.advance( id ) )
{
tempRowcount = rowcountBeforePreds;
ItemExpr * pred = id.getItemExpr();
// don't repeat the application of any default predicate.
if ( NOT unresolvedPreds.contains( id ) )
{
CostScalar thisPredGlobalReduction = csOne; // initialize it just in case
applyDefaultPred( pred, thisPredGlobalReduction, exprOpCode,
maxSelectivity );
if (maxSelectivity == NULL) {
if ( thisPredGlobalReduction.getValue() > 0.0)
{
if (CostScalar(DBL_MIN)/thisPredGlobalReduction < allPredsGlobalReduction)
allPredsGlobalReduction *= thisPredGlobalReduction;
}
else
allPredsGlobalReduction = csZero;
// keep note of all predicates for which we've applied default sel
unresolvedPreds.insert( id );
} // maxSelectivity == NULL
}
}
// maxSelectivity computation is done
if (maxSelectivity) return newRowCount;
// -----------------------------------------------------------------------
// Have now applied all predicates; now figure out the correct resulting
// rowcount and normalize all of the histograms to have that rowcount.
// -----------------------------------------------------------------------
rowRedProduct = csOne;
computeRowRedFactor(mergeMethod, numOuterColStats, rowcountBeforePreds, predCountSC, predCountMC, rowRedProduct);
// -----------------------------------------------------------------------
// Reduction from equality predicates; Avoid applying reduction from equality
// predicates twice and also account for MC UEC adjustment
// -----------------------------------------------------------------------
rowRedProduct *= (rowRedAfterMCUECAdj/rowRedFromEquiPreds);
// -----------------------------------------------------------------------
// don't forget to apply the effects of global (default) predicates!
// -----------------------------------------------------------------------
rowRedProduct *= allPredsGlobalReduction;
// ---------------------------------------------------------------------
// how to calculate the resulting rowcount ...?
// ---------------------------------------------------------------------
// first a first approximation, we use the simplest formula, ignoring
// multi-column uec info
// ---------------------------------------------------------------------
newRowCount = rowRedProduct * rowcountBeforePreds;
//ensure that we include the largest int.
if((newRowCount.getValue() - floor(newRowCount.getValue())) > 0.00001)
newRowCount = ceil(newRowCount.getValue());
newRowCount = MIN_ONE_CS(newRowCount);
// after applying MC stats, ensure that the newRowCount has not exceeded
// the left rowcount, which is the oldRowCount for [anti_]semi_join
if (mergeMethod == SEMI_JOIN_MERGE ||
mergeMethod == ANTI_SEMI_JOIN_MERGE)
{
// if after applying MC stats, the new rowcount exceeded left row count
// then max it out to left row count
if (newRowCount > rowcountBeforePreds)
newRowCount = rowcountBeforePreds;
}
// ---------------------------------------------------------------------
// We've now computed the resulting rowcount; now normalize all
// histograms to have that rowcount.
// ---------------------------------------------------------------------
for( i = 0; i < loopLimit; i++ )
{
// if this histogram already has a reduction factor applied to it,
// apply only the remaining delta (not the entire reduction again)
const CostScalar & oldCount = (*this)[i]->getColStats()->getRowcount();
if ( oldCount != rowcountBeforePreds ) // only apply the delta
(*this)[i]->synchronizeStats( oldCount, newRowCount );
else
(*this)[i]->synchronizeStats( rowcountBeforePreds, newRowCount );
}
tempRowcount = newRowCount;
// -----------------------------------------------------------------------
//
// ALMOST DONE!
//
// we now need to handle the other columns in the [ANTI_]SEMI_JOIN case
//
// "initial rowcount" == semiJoinRowcountBeforePreds
//
// -----------------------------------------------------------------------
// All the following code should be done only if we have a [anti_]semi_join
CostScalar rowred = 0;
if (mergeMethod == SEMI_JOIN_MERGE ||
mergeMethod == ANTI_SEMI_JOIN_MERGE)
{
rowRedProduct = csOne;
for( i = loopLimit; i < entries(); i++ )
{
if ( semiJoinRowcountBeforePreds.isGreaterThanZero() /* > csZero */)
rowred =
(*this)[i]->getColStats()->getRowcount() / semiJoinRowcountBeforePreds;
else
rowred = csZero;
rowRedProduct *= rowred;
}
// don't forget to apply the effects of global (default) predicates!
rowRedProduct *= allPredsGlobalReduction;
// ---------------------------------------------------------------------
// how to calculate the resulting rowcount ...?
// ---------------------------------------------------------------------
// 1. first, we use the simplest formula, ignoring multi-column uec info;
// 2. then, we take the results of that formula, and see if we can apply
// mc-uec info
// ---------------------------------------------------------------------
CostScalar newSemiJoinRowCount = rowRedProduct * semiJoinRowcountBeforePreds;
CollIndexList joinHists(STMTHEAP); //the CSDL-indices of the join histograms
this->addRecentlyJoinedCols(loopLimit, entries());
this->useMultiUecIfMultipleJoins(
newSemiJoinRowCount, /* in/out */
semiJoinRowcountBeforePreds, /* in */
loopLimit, /* in: start idx */
entries(), /* in: end idx+1 */
joinHists, /* out */
expr,
mergeMethod
);
// Similarly for columns from the outer, after applying all changes,
// we do not want the new row count to exceed the
// initial row count, which for semi_joins is actually the row count
// of the left child.
if (newSemiJoinRowCount > rowcountBeforePreds)
newSemiJoinRowCount = rowcountBeforePreds;
for( i = loopLimit; i < entries(); i++ )
{
const CostScalar & oldCount = (*this)[i]->getColStats()->getRowcount();
if ( oldCount != semiJoinRowcountBeforePreds ) // only apply the delta
(*this)[i]->synchronizeStats( oldCount, newSemiJoinRowCount );
else
(*this)[i]->synchronizeStats( semiJoinRowcountBeforePreds,
newSemiJoinRowCount );
}
#ifdef _DEBUG
// ---------------------------------------------------------------------
// After all this predicate application, the CSD's should all still have
// the same rowcount
// ---------------------------------------------------------------------
{
// (this code in its own scope so it can easily be cut-and-pasted anywhere)
CollIndex limit = ( ( mergeMethod == SEMI_JOIN_MERGE ||
mergeMethod == ANTI_SEMI_JOIN_MERGE ) ?
numOuterColStats : entries() );
// verifyInternalConsistency is quite redundant, as we do not do anything
// in case of inconsistency between the CSDs. We should be much better of
// enforcingInternalConsistency. replace verifyInternalConsistency
// with enforceInternalConsistency
// first, check from 0..limit-1
enforceInternalConsistency( 0, limit );
// next, check from limit..entries()
enforceInternalConsistency( limit, entries() );
if ( limit > 0 )
{
const CostScalar & matchRowcount =
(*this)[0]->getColStats()->getRowcount();
// try this out : if the current rowcount isn't what it's "supposed" to be
// set it as requested and see how things go
if ( matchRowcount != newRowCount )
synchronizeStats( matchRowcount, newRowCount, limit );
}
}
#endif
}
// done with predicate application
// --------------------------------------------------------------------
// Lastly, IF we're *NOT* the immediate child of a boolean _AND_
// operator, undo any of the ColStatDescList length reduction resulting
// from application of non-VEG-equality preds appearing in the
// expression tree.
// --------------------------------------------------------------------
if ( exprOpCode != ITM_AND )
{
NABoolean redo = FALSE;
CollIndex i = 0;
while( i < entries() )
{
CollIndex moveI = 0;
if( (*this)[i]->nonVegEquals().entries() != 0 )
{
ColStatsSharedPtr rootColStats = (*this)[i]->getColStatsToModify();
while( (*this)[i]->nonVegEquals().entries() != 0 )
{
// ----------------------------------------------------------
// This ColStatDesc has been equated to other
// ColStatDescs by a non-VEG Equality predicate.
//
// Replace the previously-removed ColStatDesc entry.
//
// One minor complication results from the possible
// presence of an Outer Join, which as can be seen in
// the above logic, doesn't remove entries from the
// inner table's ColStatDesc List.
// ----------------------------------------------------------
ColStatDescSharedPtr tmpDesc = (*this)[i]->nonVegEquals()[0];
((*this)[i]->nonVegEquals()).removeAt( 0 );
ValueId column = tmpDesc->getColumn();
NABoolean found = FALSE;
// -----------------------------------------------------------
// Don't restore a ColStatDesc that already has an existing
// entry.
// -----------------------------------------------------------
for ( CollIndex j = 0; j < entries(); j++ )
{
if ( NOT found AND column == (*this)[j]->getColumn() )
found = TRUE;
}
if ( NOT found && NOT ( tmpDesc->isFromInnerTable() &&
mergeMethod == OUTER_JOIN_MERGE ) )
{
// update the copy of the removed entry, and re-insert
ColStatsSharedPtr upColStats = tmpDesc->getColStatsToModify();
upColStats->overwrite( *rootColStats );
if( tmpDesc->isFromInnerTable() )
{
insertDeepCopyAt( numOuterColStats, tmpDesc );
if ( i > numOuterColStats )
moveI++;
}
else
{
insertDeepCopyAt( 0, tmpDesc );
numOuterColStats++;
moveI++;
}
// Gen Sol:10-090218-9369: Make sure any ColStatDescs that
// are in nonVegEquals of 'tmpDesc' are correctly put back.
if(tmpDesc->nonVegEquals().entries() != 0 )
redo = TRUE;
}
else // it's from the inner table or it's an OUTER_JOIN
{
// ----------------------------------------------------------
// In BETA when the line below wasn't commented out,
// we got a memory violation
//
// NB: if we don't remove it, optimization will use
// more memory overall, but there isn't a memory
// leak per se.
//
// Basically, it's safest to comment out the line
// below (this fixed Genesis case 10-980121-2284).
// ----------------------------------------------------------
//delete tmpDesc;
}
}
if ( moveI == 0 )
moveI++;
}
else
moveI++;
i += moveI;
if(redo)
{
i = 0;
redo = FALSE;
}
}
}
// Update MC Skew values for joins
if(expr)
{
ValueIdList joinColsGroup;
const MCSkewedValueList * joinedMCSkewedValueList = ((Join *)expr)->getMCSkewedValueListForJoinPreds(joinColsGroup);
if(joinedMCSkewedValueList)
{
if(!mcSkewedValueLists_)
mcSkewedValueLists_ = new (HISTHEAP) MultiColumnSkewedValueLists();
ValueIdList * key = new (HISTHEAP) ValueIdList(joinColsGroup);
MCSkewedValueList * value = new (HISTHEAP) MCSkewedValueList (*joinedMCSkewedValueList, HISTHEAP);
mcSkewedValueLists_->insert( key, value );
}
else if(mcSkewedValueLists_)
{
mcSkewedValueLists_->clear(TRUE);
mcSkewedValueLists_ = NULL;
}
}
// The estimated cardinality should never go below zero
if (newRowCount.getValue() < 0)
{
CCMPASSERT( newRowCount.isGreaterOrEqualThanZero() );
newRowCount.minCsZero();
}
// This is the rowcount without using hints. Save it
setScanRowCountWithoutHint(newRowCount);
// Now after applying all the predicates, make any adjustments based on the
// hints provided by the user. see if the expression is of the
// index, and that if we can partially apply cardinality or selectivity hint
if (cardHint || selHint)
newRowCount = adjustRowcountWithHint(cardHint, selHint,
setOfPredicates,
newRowCount,
initialRowCount);
return newRowCount;
} // ColStatDescList::estimateCardinality()
// Adjust the rowcount based on the cardinality / selectivity / count(*) hint
CostScalar
ColStatDescList::adjustRowcountWithHint(const CardinalityHint * cardHint,
const SelectivityHint * selHint,
const ValueIdSet & setOfPredicates,
CostScalar & newRowCount,
const CostScalar & initialRowCount)
{
double selectivityHint = -1.0;
ValueIdSet localPredsFromHint;
double baseSelectivity = 1.0;
CostScalar selWithoutHint = newRowCount / initialRowCount;
if (cardHint!= NULL)
{
selectivityHint = cardHint->getScanSelectivity().getValue();
localPredsFromHint = cardHint->localPreds();
baseSelectivity = cardHint->getBaseScanSelectivityFactor();
}
else
{
selectivityHint = selHint->getScanSelectivityFactor();
localPredsFromHint = selHint->localPreds();
baseSelectivity = selHint->getBaseScanSelectivityFactor();
}
if (localPredsFromHint.entries() > 0)
{
ValueIdSet copySetOfPreds(setOfPredicates);
// Intersect the localPreds with localPredsFromHint to see
// if all predicate for which the Hint was calculated are covered
// More localPreds can come because of join predicates that showup
// as selection predicates. Less number of local predicates
// can be due to indexes. For lesser number of predicates, adjust
// selectivity from Hint accordingly
// get common predicates
copySetOfPreds.intersect(localPredsFromHint);
if (copySetOfPreds.entries() < localPredsFromHint.entries())
{
// The control will come here only for indexes, and by that time
// the selectivityHint would have been set, either from selectivityHint
// directly or from CardinalityHint
selectivityHint = pow(selWithoutHint.getValue(), baseSelectivity);
}
if (cardHint && (selectivityHint < 0) )
{
// this is the case when estimateCardinality has come from synthLogProp
// and is still trying to compute the selectivityHint from cardinalityHint
newRowCount = cardHint->getScanCardinality();
}
else
{
if (selectivityHint > 0.0)
{
// compute newRowCount from SelectivityHint
newRowCount = initialRowCount * selectivityHint;
}
else
newRowCount = csOne;
}
if((newRowCount.getValue() - floor(newRowCount.getValue())) > 0.00001)
newRowCount = ceil(newRowCount.getValue());
newRowCount = MIN_ONE_CS(newRowCount);
// Now, normalize all histograms to the same resultant rowcount.
for( CollIndex i = 0; i < entries(); i++ )
{
const CostScalar & oldCount =
(*this)[i]->getColStats()->getRowcount();
if( oldCount != newRowCount ) // && oldCount != 0
(*this)[i]->synchronizeStats( oldCount, newRowCount );
}
}
return newRowCount;
}
// -------------------------------------------------------------------
// ColStatDescList::getCardOfBusiestStream
// method returns the cardinality of the busiest stream for the given
// partitioning key
// -------------------------------------------------------------------
CostScalar
ColStatDescList::getCardOfBusiestStream(const PartitioningFunction* partFunc,
Lng32 numOfParts,
GroupAttributes * groupAttr,
Lng32 countOfCPUs)
{
// get the partitioning key and number of partitions
ValueIdSet partKey = partFunc->getPartitioningKey();
// get the total rows in the histogram
CostScalar rowCount = (*this)[0]->getColStats()->getRowcount();
// if number of partitions is 1, return rowcount
if ( ( numOfParts == 1) ||
( partFunc->isASinglePartitionPartitioningFunction() ) )
{
return (rowCount).minCsOne();
}
// The cardinality is based on the number of CPUs or the number of
// partitions (whichever is fewer) for a few situations:
// 1) if partitioning key is empty
// 2) the round robin partitioning scheme is used.
// 3) the skew buster partitioning scheme is used.
if ( (partKey.isEmpty()) ||
(partFunc->isASkewedDataPartitioningFunction()) ||
(partFunc->isARoundRobinPartitioningFunction()) )
{
Lng32 availableCpus = MINOF( numOfParts , countOfCPUs );
return (rowCount / availableCpus).minCsOne();
}
// In the following loop, get the min UEC from amongst the partitioning
// key to compute the number of streams. In the same loop also compute
// the accumulated frequency of the partitioning key to compute cardinality
// per stream for hash partitions
// min UEC for partitioning key
CostScalar uecForPartKey = csOne;
// accumulated freq of the partitioning key
CostScalar accFreq = csOne;
for (ValueId partKeyElement = partKey.init();
partKey.next(partKeyElement);
partKey.advance(partKeyElement) )
{
// extract all base columns from the partitioning key column before
// looking for statistics
ValueIdSet baseColSet;
// this is because findAllReferencedBaseCols, is defined on ValueIdSet
ValueIdSet partKeySet(partKeyElement);
partKeySet.findAllReferencedBaseCols(baseColSet);
if (groupAttr)
{
GroupAnalysis * grpAnalysis = groupAttr->getGroupAnalysis();
if (grpAnalysis)
{
CANodeIdSet treeSet = grpAnalysis->getAllSubtreeTables();
ValueIdSet myColumns;
if (NOT treeSet.isEmpty() )
myColumns = treeSet.getUsedCols();
// from all the base columns for this partitioning key, get the
// one that belong to me. In case of joins, base column set would
// also contain the column I am joining to
if (NOT myColumns.isEmpty())
baseColSet.intersectSet(myColumns);
}
}
// get minimum UEC from the given column set
CostScalar colUec = csMinusOne;
CostScalar freq = csOne;
if (CmpCommon::getDefault(COMP_BOOL_47) == DF_OFF)
{
colUec = getMinUec(baseColSet);
// get the max frequency of any column from the given column set
freq = getMaxOfMaxFreqOfCol(baseColSet);
}
else
{
ItemExpr * partKeyExpr = partKeyElement.getItemExpr();
// if partitioning key is a Case expression, then use all leaf values
// including the constants to compute max frequencies. For all
// other expressions use just the base column set
if (partKeyExpr->getOperatorType() == ITM_CASE)
{
ValueIdSet partKeyLeafValueSet;
partKeyExpr->getLeafValueIdsForCaseExpr(partKeyLeafValueSet);
colUec = getMaxUecForCaseExpr(partKeyLeafValueSet);
freq = getMaxFreqForCaseExpr(partKeyLeafValueSet);
}
else
{
colUec = getMaxUec(baseColSet);
freq = getMinOfMaxFreqOfCol(baseColSet);
}
}
if (colUec == csMinusOne)
uecForPartKey = rowCount;
else
uecForPartKey *= colUec;
accFreq *= (freq / rowCount);
} // for all partitioning key columns
// uec cannot be greater than the row count
uecForPartKey = MINOF(uecForPartKey, rowCount);
// compute the number of streams
CostScalar noOfStreams = MINOF((CostScalar)numOfParts, uecForPartKey);
// If partitioning key column is a Random number, and activeStreams_ is less
// than the current value of noOfStreams, then noOfStreams = activeStreams_;
CostScalar activeStreams = partFunc->getActiveStreams();
long randomFix = ActiveSchemaDB()->getDefaults().getAsLong(COMP_INT_26);
if ( (partKey.entries() == 1) AND (randomFix != 0) AND (activeStreams != 0) )
{
// Get first key column.
ValueId myPartKeyCol;
partKey.getFirst(myPartKeyCol);
// is it a random number?
if (myPartKeyCol.getItemExpr()->getOperatorType() == ITM_RANDOMNUM)
{
noOfStreams = MINOF(activeStreams, noOfStreams);
}
}
// based on the partitioning type, compute the cardinality of the stream
// For all but hash type partitions, cardinality = number of rows / # of streams
// such that # of streams = MINOF(# of partitions, UEC of partitioning key)
if (partFunc->isAHashPartitioningFunction() ||
partFunc->isATableHashPartitioningFunction() )
{
// CostScalar cardOfFreqValue = (rowCount * accFreq).minCsOne();
CostScalar cardOfFreqValue = (rowCount * accFreq);
CostScalar maxCardPerStream = (((rowCount - cardOfFreqValue)/noOfStreams) + cardOfFreqValue).round();
// cardinality per stream cannot be greater than the total row count
maxCardPerStream = MINOF(maxCardPerStream, rowCount);
// some plans are over-penalized by incorporate_skew_in_costing.
// provide a cqd HIST_SKEW_COST_ADJUSTMENT to soften effect
// of incorporate_skew_in_costing.
// take weighted average of uniform distribution and skewed data
// based on the CQD.
// 0 -> get RC as if uniformly distributed
// 1-> get RC as if skewed
// anything in between is the linear average
CostScalar uniformDistRowCountPerStream = rowCount / noOfStreams;
CostScalar histSkewAdjustment =
(ActiveSchemaDB()->getDefaults()).getAsDouble(HIST_SKEW_COST_ADJUSTMENT);
maxCardPerStream =
(maxCardPerStream * histSkewAdjustment) +
(uniformDistRowCountPerStream * (csOne - histSkewAdjustment));
return maxCardPerStream.minCsOne();
}
else
{
// for all other partitions
// return rows per stream
return (rowCount / noOfStreams).minCsOne();
} // for non hash partitioning functions
} // ColStatDescList::getCardOfBusiestStream
// ColStatDescList::identifyMergeCandidates
//
// A utility routine to identify which of the entries in the given
// ColStatDescList (containing statistics associated with columns that
// are accessible in the current operator) are related to the given
// predicate.
// -----------------------------------------------------------------------
NABoolean
ColStatDescList::identifyMergeCandidates (ItemExpr *VEGpred,
CollIndex & rootStatIndex,
CollIndexList & statsToMerge) const
{
NABoolean foundRoot = FALSE;
// First, get all members of the VEG group
const VEG * predVEG = ((VEGPredicate *)VEGpred)->getVEG();
const ValueIdSet & VEGGroup = predVEG->getAllValues();
for ( CollIndex i = 0; i < entries(); i++ )
{
// 1. See if this histogram references the VEGReference id.
// 2. Keep track of all histograms that need to be merged.
// 3. Among those to merge, remember the first as the 'root' histogram.
//
// The contents of (*this)[i]->getVEGColumn() may be a VEGReference
// or an instantiate_null expression.
const ItemExpr * columnsExpr = (*this)[i]->getVEGColumn().getItemExpr();
OperatorTypeEnum colType = columnsExpr->getOperatorType() ;
// QSTUFF
// In addition to checking for the following colTypes, we shall also
// see if the histogram for this column is a for a virtual column
// This could be for cases where a virtual column was created for a
// constant expression. This is something that is expected. If there are
// other columns in this colStatDescList, then we might be able to
// find the root. If not, then the method will return FALSE, which is
// acceptable.
if ( NOT ( colType == ITM_VEG_REFERENCE
OR colType == ITM_INSTANTIATE_NULL
OR colType == ITM_VALUEIDUNION
OR colType == ITM_UNPACKCOL
OR colType == ITM_ROWSETARRAY_SCAN
OR (*this)[i]->getColStats()->isVirtualColForHist()
)
)
{
HISTWARNING("Unexpected column type");
}
// QSTUFF
// There are 4 conditions where the current histogram is of interest:
// (a) its identifying column's VEG matches the current predicate
// or is contained by a VEG_REFERENCE that is contained by
// the current predicate.
// (b) its identifying column's VEG is an instantiate_null, the
// current predicate also contains an instantiate_null, and
// the valueIds of those instantiate_nulls are identical;
// (c) its identifying column's VEG is a VALUEIDUNION
// (d) its identifying column's VEG is a ITM_ROWSETARRAY_SCAN implying
// it is a histogram created for a rowset
NABoolean foundCandidate = FALSE;
// case (a) : exact match
if ( colType == ITM_VEG_REFERENCE )
{
if ( VEGpred->containsTheGivenValue( columnsExpr->getValueId() ) )
{
foundCandidate = TRUE;
}
}
else if ( colType == ITM_INSTANTIATE_NULL )
{
for ( ValueId id = VEGGroup.init();
VEGGroup.next( id );
VEGGroup.advance( id ) )
{
// case (b) : instantiate_null
if ( id.getItemExpr()->getOperatorType() == ITM_INSTANTIATE_NULL
AND colType == ITM_INSTANTIATE_NULL )
{
const ValueId & nulledVid = columnsExpr->getValueId();
if ( nulledVid == id )
{
foundCandidate = TRUE;
break;
}
}
else
{
// ITM_INDEXCOLUMN (on insert, select, drop table)
// ITM_INDEXCOLUMN, ITM_DYN_PARAM (on create table, drop table)
// ITM_BASECOLUMN (during init_sql)
// ITM_CONSTANT (on select, drop table)
continue;
}
}
}
else if ( colType == ITM_VALUEIDUNION || colType == ITM_UNPACKCOL ||
colType == ITM_ROWSETARRAY_SCAN )
{
if ( VEGpred->containsTheGivenValue( columnsExpr->getValueId() ) )
{
foundCandidate = TRUE;
}
}
if ( foundCandidate )
{
statsToMerge.insert( i );
if ( NOT foundRoot )
{
foundRoot = TRUE;
rootStatIndex = i;
}
}
}
return foundRoot;
} // ColStatDescList::identifyMergeCandidates (#1)
// -----------------------------------------------------------------------
// ColStatDescList::identifyMergeCandidates
//
// A variant routine to identify which of the entries in the given
// ColStatDescList match the given operand, which appears in a non-VEG
// equality predicate.
//
// NOTE : the return type from this function (for some reason) is always FALSE.
// -----------------------------------------------------------------------
NABoolean
ColStatDescList::identifyMergeCandidates (ItemExpr * operand,
CollIndexList & statsToMerge) const
{
OperatorTypeEnum exprType = operand->getOperatorType();
ValueId nulledVId;
ValueId id;
// walk the ColStatDescList
for ( CollIndex i = 0; i < entries(); i++ )
{
NABoolean foundCandidate = FALSE;
CollIndex nonVEGCount = ((*this)[i]->getNonVegEquals()).entries();
ItemExpr * columnsExpr = (*this)[i]->getVEGColumn().getItemExpr();
OperatorTypeEnum colType = columnsExpr->getOperatorType();
do
{
// Case 1: a VegReference that might contain an instantiate null
if ( colType == ITM_INSTANTIATE_NULL &&
exprType == ITM_VEG_REFERENCE )
{
nulledVId = columnsExpr->getValueId();
const VEG * exprVEG = ((VEGReference *)operand)->getVEG();
const ValueIdSet & VEGGroup = exprVEG->getAllValues();
for ( id = VEGGroup.init();
VEGGroup.next( id );
VEGGroup.advance( id ) )
{
if ( id.getItemExpr()->getOperatorType() == ITM_INSTANTIATE_NULL &&
nulledVId == id.getItemExpr()->getValueId() )
{
foundCandidate = TRUE;
break; // jump to outer do-while
}
}
}
// Case 2: inverse of case 1
else if ( colType == ITM_VEG_REFERENCE &&
exprType == ITM_INSTANTIATE_NULL )
{
nulledVId = operand->getValueId();
const VEG * statVEG = ((VEGReference *)columnsExpr)->getVEG();
const ValueIdSet & VEGGroup = statVEG->getAllValues();
for ( id = VEGGroup.init();
VEGGroup.next( id );
VEGGroup.advance( id ) )
{
if ( id.getItemExpr()->getOperatorType() == ITM_INSTANTIATE_NULL &&
nulledVId == id.getItemExpr()->getValueId() )
{
foundCandidate = TRUE;
break; // jump to outer do-while
}
}
}
// Case 3: two VEG References
else if ( colType == ITM_VEG_REFERENCE &&
exprType == ITM_VEG_REFERENCE )
{
const VEG * statVEG = ((VEGReference *)columnsExpr)->getVEG();
const VEG * exprVEG = ((VEGReference *)operand)->getVEG();
if ( statVEG == exprVEG )
{
foundCandidate = TRUE;
}
else // not a direct match
{
// First, get all members of the various VEG groups
const ValueIdSet & VEGGroup = exprVEG->getAllValues();
const ValueIdSet & VEGGroup2 = statVEG->getAllValues();
// look for column's valueId in a VEGREF in the operand's
// valueId set.
for ( id = VEGGroup.init();
VEGGroup.next( id );
VEGGroup.advance( id ) )
{
if ( id.getItemExpr()->getOperatorType() == ITM_VEG_REFERENCE )
{
const VEG* nestedVEG =
((VEGReference *)(id.getItemExpr()))->getVEG();
if ( statVEG == nestedVEG )
{
foundCandidate = TRUE;
break; // jump to outer do-while
}
}
}
if ( !foundCandidate )
{
// look for the operand's valueId in a VEGREF in the
// column's valueId set.
for ( id = VEGGroup2.init();
VEGGroup2.next( id );
VEGGroup2.advance( id ) )
{
if ( id.getItemExpr()->getOperatorType() == ITM_VEG_REFERENCE )
{
const VEG* nestedVEG =
((VEGReference *)(id.getItemExpr()))->getVEG();
if ( exprVEG == nestedVEG )
{
foundCandidate = TRUE;
break; // jump to outer do-while
}
}
}
}
}
}
else if (
// Case 4: two Instantiate NULLs.
// (could look inside their operands???
( colType == ITM_INSTANTIATE_NULL &&
exprType == ITM_INSTANTIATE_NULL )
// Case 5 : ValueIdUnions
OR ( colType == ITM_VALUEIDUNION &&
exprType == ITM_VALUEIDUNION )
// Case 6 : special case for packed col
OR ( colType == ITM_UNPACKCOL &&
exprType == ITM_UNPACKCOL )
// Case 7 : VEG_PREDs
OR ( colType == ITM_VEG_PREDICATE &&
exprType == ITM_VEG_PREDICATE )
OR ( ( CmpCommon::getDefault(COMP_BOOL_48) == DF_ON) &&
( colType == ITM_VEG_REFERENCE &&
exprType == ITM_NATYPE) ) )
{
const ValueId & statVid = columnsExpr->getValueId();
const ValueId & exprVid = operand->getValueId();
if ( statVid == exprVid )
{
foundCandidate = TRUE;
}
}
else // VEG_REF + VEG_PRED, others?
{
// $$$ this is safe, but probably not complete!
if ( columnsExpr->getValueId() == operand->getValueId() )
{
foundCandidate = TRUE;
}
}
if ( foundCandidate )
{
statsToMerge.insert( i );
}
// look for a transitive equality relationship between this column
// and any other columns, where that equality relationship is not
// represented in a VEGPred.
if ( nonVEGCount > 0 )
{
ColStatDescSharedPtr tmpColStatDesc =
((*this)[i]->getNonVegEquals())[--nonVEGCount];
columnsExpr = tmpColStatDesc->getVEGColumn().getItemExpr();
colType = columnsExpr->getOperatorType();
}
else
columnsExpr = NULL;
}
while ( columnsExpr != NULL AND foundCandidate == FALSE );
} // for
return FALSE;
} // ColStatDescList::identifyMergeCandidates (#2)
// -----------------------------------------------------------------------
// ColStatDescList::applyVEGPred
//
// A CSDL::estimateCardinality() subroutine
//
// Given a VEG Predicate, merge all histograms that belong to the same
// equivalence group.
//
// EX 1: VEG = (T1.A, T1.B, T1.C)
// synthesize the effect of T1.A = T1.B = T1.C,
// resulting in one consolidated histogram for the VEG
//
// EX 2: VEG = (T1.A, T1.B, 10)
// synthesize the effect of T1.A = T1.B = 10,
// resulting in a degenerate histogram with a single
// interval representing the value 10.
//
// There is an assumption that all outer histograms in the ColStatDescList
// show the same number of rows and that all inner histograms in the list
// show the same number of rows. But it is not guaranteed that the number
// of rows in the outer 'table' equals the number of rows in the inner
// 'table.'
//
// ColStatDescList::mergeStats has a return value to tell the caller
// whether or not Default Selectivity should be applied (e.g., because
// mergeStats couldn't process the predicate).
// The test for setting the return value is non-obvious:
// - If there is a predicate and it can be completely applied, then don't
// tell the caller to apply default;
// - If there is a predicate and it can't be completely applied, then do
// apply default.
// - If there is no predicate that can be evaluated at run time by this
// node, then don't apply default.
//
// This is the new version of this function which facilitates applying all
// predicates at once.
// -----------------------------------------------------------------------
NABoolean
ColStatDescList::applyVEGPred (ItemExpr *VEGpred,
CostScalar & rowcount,
CollIndex & numOuterColStats,
MergeType mergeMethod,
OperatorTypeEnum opType,
CostScalar *maxSelectivity)
{
VEG * predVEG = NULL;
if ( VEGpred->getOperatorType() == ITM_VEG_PREDICATE )
predVEG = ((VEGPredicate *)VEGpred)->getVEG();
else
return FALSE ; // we did not apply the predicate
NABoolean selHintSpecified = VEGpred->isSelectivitySetUsingHint();
const ValueId & predValueId = VEGpred->getValueId();
// Get all members of the VEG group
const ValueIdSet & VEGGroup = predVEG->getAllValues();
// This is used by selectivity adjustment code.
ValueIdSet mergeStatePriorToJoin;
// retain initial guess-timated rowcount and uec, in case we can't
// improve on this...
CostScalar newRowcount = rowcount; // guesstimate of outer rows
CostScalar newUec = csOne;
CostScalar saveRowcount = rowcount;
CostScalar saveUec = csOne;
CostScalar oldRowcountForSelAdj = rowcount;
CollIndexList statsToMerge(CmpCommon::statementHeap());
CollIndex i;
CollIndex rootStatIndex = NULL_COLL_INDEX ;
ColStatDescSharedPtr rootStatDesc;
ColStatsSharedPtr rootColStats;
HistogramSharedPtr hist;
NABoolean appliedPredicateFlag = FALSE; // return flag: Success Indicator
// See if the VEG group contains a constant.
// NOTE: A VEG group should not contain more than 1 constant; but if
// that should happen, referencesAConstValue will return the first
// constant found.
ItemExpr *constant = NULL;
NABoolean containsConstant = VEGGroup.referencesAConstValue( &constant );
ItemExpr* constExprPtr = NULL;
NABoolean containsConstExpr = VEGGroup.referencesAConstExpr(&constExprPtr);
// We want the selectivity param to contribute in the same way as its
// substituted literal, in the context of apply VEG predicate. Note
// simply treating the param as a host variable does not work well
// because the selectivity that it contributes is computed from the
// interval of the base histogram. Here the ::applyXXXPred() methods
// can be called on behalf of a set of computed histograms. In that case,
// contributing "original" selectivity info can lead to incorrect
// result as demonstrated by Query 02 failure in opt/optdml03 test. Here
// the cardinality estimate for REGION table in the left part of the join
// tree (the right part is for the subquery) is way too small.
NABoolean containsSelectivityParam = (containsConstExpr AND
constExprPtr AND
constExprPtr->getOperatorType() == ITM_CACHE_PARAM AND
constExprPtr->castToSelParameter()
);
// Turn on "containsConstant" when a selectivity param is found in the
// predicate, so that the param can be used in place of the literal.
if ( containsSelectivityParam )
containsConstant = TRUE;
NABoolean containsHostvar = !containsConstant && containsConstExpr;
// Explanation of the above: there is currently no "contains a hostvar
// or dynamic parameter" function. So, we use what's available.
//
// referencesAConstValue() returns TRUE if the VEGGroup contains a constant
//
// referencesAConstExpr() returns TRUE if the VEGGroup contains a constant
// or a hostvar/dynamic parameter
//
// Thus, if referencesAConstExpr returns TRUE, but referencesAConstValue
// returns FALSE, then there's a hostvar, not a constant.
// In the case of a join, the left child's histograms are at the
// beginning of the input ColStatDescList, and that fact is used
// when mergeColStats is dealing with a [anti-]semi-Join.
//
// Also note: in the case of a [anti-]semi-join, this logic depends upon
// there being no outer-table-only VEGPreds remaining in the
// input predicate (they should have been dealt with in the
// scan of the outer table).
//
// locate the entries in this ColStatDescList that are associated with
// the current VEG predicate.
NABoolean foundRoot =
identifyMergeCandidates( VEGpred, rootStatIndex, statsToMerge );
CollIndex candidateHistograms = statsToMerge.entries();
// There is no merge to perform if we don't find a root histogram.
// BUT, failure to find a root doesn't mean that there is nothing to
// do. There is only nothing to do if we didn't find any histogram.
if ( candidateHistograms == 0 )
return FALSE; // we did not apply the predicate
// If there's a hostvar, we want to apply some selectivity! So return
// FALSE, and later on, in applyDefaultPred, apply the selectivity we
// want.
if ( foundRoot && candidateHistograms == 1 && containsHostvar )
return FALSE; // we didn't apply the predicate
if ( foundRoot)
{
// Get modifiable copy of ColStats
rootStatDesc = (*this)[rootStatIndex];
rootColStats = rootStatDesc->getColStatsToModify();
if(VEGpred->isSelectivitySetUsingHint())
mergeStatePriorToJoin = rootStatDesc->getMergeState();
if (maxSelectivity == NULL) {
// Get a modifiable copy of Histogram
hist = rootColStats->getHistogramToModify();
if ( containsConstant || statsToMerge.entries() > 1 )
{
// Initialize resultant rowcount and uec to the actual
// (non-estimated) initial values.
newRowcount = rootColStats->getRowcount();
newUec = rootColStats->getTotalUec();
// save the initial aggregate information for later processing.
saveRowcount = newRowcount;
saveUec = newUec;
// Indicate that we have applied this predicate. Of course we
// haven't already applied this predicate, but we expect to,
// soon.
//
// Note that unlike for other predicates, the appliedPreds set
// is not used to prevent duplicate application of a VEG
// predicate.
//
// Different VEG predicates look identical!
rootStatDesc->addToAppliedPreds( predValueId );
}
} // maxSelectivity == NULL
} // foundRoot
// Temp fix for solution 10-100607-0915, off by default.
NABoolean tupleVirtColFix = FALSE;
if ( foundRoot && ((rootColStats->getStatColumns()).entries() == 0) &&
(CmpCommon::getDefault(COMP_BOOL_165) == DF_ON) )
tupleVirtColFix = TRUE;
// check first for " = constant " case
if ( foundRoot && containsConstant && (tupleVirtColFix == FALSE) )
{
// If reference a constant or a selectivity param, then first reduce the histogram
if ( containsSelectivityParam == TRUE )
constant = ((SelParameter*)constExprPtr)->getConstVal();
EncodedValue normValue( constant, FALSE );
NABoolean neg = FALSE;
ConstValue* constExpr = constant->castToConstValue(neg);
if (maxSelectivity == NULL)
rootColStats->setToSingleValue( normValue, constExpr, &rowcount);
else
rootColStats->adjustMaxSelectivity(normValue, constExpr, &rowcount, maxSelectivity);
appliedPredicateFlag = TRUE;
if (maxSelectivity == NULL)
{
if (rootColStats->isOrigFakeHist())
{
// set the final rowcount as the square root of the baserowcount
CostScalar newRC = csOne;
if (!rootColStats->isUnique())
newRC = ceil(sqrt(rowcount.getValue()));
rootStatDesc->synchronizeStats(rootColStats->getRowcount(), newRC);
}
// update the uec and rowcount
newRowcount = rootColStats->getRowcount();
newUec = rootColStats->getTotalUec();
// $$$ WE NO LONGER WANT TO NORMALIZE HISTOGRAMS AFTER EVERY
// $$$ PREDICATE!
//
// Instead, we will normalize all of the histograms' rowcounts after
// we have applied all of the predicates.
// update the 'saved' information
saveRowcount = newRowcount;
saveUec = newUec;
} // maxSelectivity == NULL
}
// Next, check for non-root histograms to merge.
if ( foundRoot && statsToMerge.entries() > 1 ) // make sure there is work
{
// Typically, this means: For each matching histogram interval,
// # rows = (#rows A) * (#rows B) * (1/MAX(uecA, uecB))
// # uec = MIN (uecA, uec B)
// where A represents one histogram and B the other histogram
//
// However, there are non-typical cases to worry about.
// If the predicate being done is t1.a=t1.a, then the result of
// that merge is the histogram that has the smaller rowcount.
// If the predicate is one where the result of the merge t1.a=
// t1.b is now again being merged with t1.a, then the result
// is the result of the earlier merge of t1.a=t1.b
//
// In response to a possible protest that the above can't happen,
// you might be correct. But what is it that would prevent it??
// NOTE: this code assumes that all predicates that can be pushed
// down to 'identical' columns are pushed down to all copies
// of those columns.
for ( i = 0; i < statsToMerge.entries(); i++ )
{
if ( statsToMerge[i] != rootStatIndex )
{
appliedPredicateFlag = TRUE;
// If the statistics to be merged are from opposite sides of
// the numOuterColStats boundary, then we are doing a
// left_table_column = right_table_column
// merge that should be done as the caller requested.
// Otherwise, use HIST_NO_STATS_UEC to compute selectivity
MergeType localMergeMethod = mergeMethod;
NABoolean joinOnOneTable = FALSE;
if ( NOT ( rootStatIndex < numOuterColStats &&
statsToMerge[i] >= numOuterColStats ) )
{
if(!containsConstant && maxSelectivity == NULL)
{
// even though the mergeMethod may never be used, but we are
// initializing this for cases when COMP_BOOL_74 is OFF
localMergeMethod = INNER_JOIN_MERGE;
joinOnOneTable = rootStatDesc->mergeColStatDescOfSameTable((*this)[statsToMerge[i]], opType);
}
else if (containsConstant)
{
// This is the case of query with selection predicates (A = 1 and B = 1)
// part of fix for genesis cases 10-080530-0291, 10-080530-0305
// this block of code is needed to correctly compute
// maxcardinality(a=1 and b=1)
// Otherwise, it is incorrectly computed as
// maxcardinality(a=b and b=1)
joinOnOneTable = TRUE;
}
}
if (containsConstant)
{
// Do not apply the predicate if it has already been applied
ValueIdSet appliedPreds = (*this)[statsToMerge[i]]->getAppliedPreds();
if (!appliedPreds.contains(predValueId))
{
EncodedValue normValue( constant, FALSE );
NABoolean neg = FALSE;
ConstValue* constExpr = constant->castToConstValue(neg);
if (maxSelectivity == NULL)
{
if(!selHintSpecified)
(*this)[statsToMerge[i]]->getColStatsToModify()->setToSingleValue
(normValue, constExpr, &rowcount);
}
else
(*this)[statsToMerge[i]]->getColStatsToModify()->
adjustMaxSelectivity (normValue, constExpr, &rowcount, maxSelectivity );
}
}
if (maxSelectivity == NULL)
{
// for outer joins, if the join VEG pred contains constant, then
// that has already been applied, so do not do any merge with the right side now
// Example for queries like T1 left jon T2 on T1.a = T2.a and T1.a = 1
// the VEG pred (T2.a = 1 = VegRef(T1.a)) will appear both at the Scan (T2.a) and
// left join node. The predicate has already been applied on rootStatDesc (join result)
// by the time the control comes here, so skip the merge
if (((mergeMethod == OUTER_JOIN_MERGE) && containsConstant)
|| joinOnOneTable)
{
// The merge has already been done, so skip mergeColStatDesc
}
else
{
// else merge the left and the right histograms
rootStatDesc->mergeColStatDesc ((*this)[statsToMerge[i]],
localMergeMethod,
FALSE, // don't force merge
opType
);
}
// get the aggregate results following the latest merge
newRowcount = rootColStats->getRowcount();
newUec = rootColStats->getTotalUec();
// $$$ WE NO LONGER WANT TO NORMALIZE HISTOGRAMS AFTER EVERY
// $$$ PREDICATE!
//
// Instead, we will normalize all of the histograms' rowcounts after
// we have applied all of the predicates.
// update the 'saved' information
saveRowcount = newRowcount;
saveUec = newUec;
} // maxSelectivity == NULL
}
}
}
if (maxSelectivity == NULL) {
// For histograms that belong to the same equivalence class:
// IF this is a not an Outer Join keep only the root
// IF this is an Outer Join, the original histograms need to be
// replaced by copies of the root; they will be null-augmented.
if ( !containsConstant && statsToMerge.entries() > 1 )
{
// Walk the list backwards to avoid invalidating entries in the list
// statsToMerge
//NB: i is unsigned, so this loop as-is should never terminate
for ( i = statsToMerge.entries() - 1; i >= 0; i-- )
{
if (statsToMerge[i] != rootStatIndex)
{
if ( mergeMethod == OUTER_JOIN_MERGE &&
statsToMerge[i] >= numOuterColStats )
{
ColStatsSharedPtr updateColStats =
(*this)[statsToMerge[i]]->getColStatsToModify();
updateColStats->overwrite( *rootColStats );
}
else
{
this->remove( (*this)[statsToMerge[i]] );
// maintain the numOuterColStats value when removing
// column statistics from the outer table's list.
if ( statsToMerge[i] < numOuterColStats )
numOuterColStats--;
}
}
// Since i is unsigned, it'll never become negative, so the loop
// above (should) never terminate! Note that for many months, the
// loop above DID terminate just fine ... dunno why, though
if ( i == 0 )
break ;
} // for
} // if
// return the [altered] rowcount
rowcount = newRowcount;
// If no merge could be performed (due to unavailable columns: NOTE
// we cannot distinguish between run-time unavailability of columns
// and optimizer unavailability of column statistics), instruct the
// caller to not apply default selectivity.
//
// If the number of candidate histograms is greater than the number
// actually merged (which should never happen, because we removed
// multi-column histograms ... oh well), then tell the caller to apply
// default selectivity ==> even if we *did* do some merging.
//
// ** special case: a VEG containing a constant: we'll do enough
// ** reduction here that default selectivity shouldn't also be applied
if ( candidateHistograms <= 1 )
appliedPredicateFlag = TRUE;
else if ( candidateHistograms > statsToMerge.entries() &&
!containsConstant )
appliedPredicateFlag = FALSE;
} // maxSelectivity == NULL
// If user specified selectivity for this predicate, we need to make
// adjustment in reduction to reflect that.
if ((containsConstant || statsToMerge.entries() > 1)
&& selHintSpecified)
{
ItemExpr * tempPred = NULL;
ItemExpr * selPred = NULL;
NABoolean neg = FALSE;
ValueIdSet baseCols;
// If VEG predicate already has selectivity specified, use it directly.
if(VEGpred->getSelectivityFactor() != -1)
selPred = VEGpred;
ValueIdSet mergeState = rootStatDesc->getMergeState();
ValueIdSet predSet = ((VEGPredicate *)VEGpred)->getPredsWithSelectivities();
for ( ValueId predId = predSet.init();
predSet.next( predId );
predSet.advance( predId ) )
{
if(selPred)
break;
tempPred = predId.getItemExpr();
if(containsConstant)
{
// This is for scenario where we have just applied a local predicate
// (eg. 't1.a = 5') and have a hint on that predicate.
if((mergeState == ValueIdSet(tempPred->child(0))) &&
(tempPred->child(1)->castToConstValue(neg)))
selPred = tempPred;
else
continue;
}
else
{
baseCols.clear();
tempPred->findAll(ITM_BASECOLUMN, baseCols, TRUE, TRUE);
// This is the scenario where we are dealing with the first join
if(mergeState == baseCols)
selPred = tempPred;
else if(mergeState.contains(baseCols))
{
// If the query has multiple tables joined on the same column resulting in VEG.
// Eg, t1.a = t2.b and t1.a = t3.c and t1.a = t4.d => VEGPred(t1.a, t2.b, t3.c,t4.d)
// We need to get the correct predicate from the set of predicates in the VEG predicate.
ValueIdSet newColumnMerged = mergeState;
newColumnMerged.subtractSet(mergeStatePriorToJoin);
if(baseCols.contains(newColumnMerged))
{
ValueIdSet secondColumnInTheMerge = baseCols;
secondColumnInTheMerge.subtractSet(newColumnMerged);
if(mergeStatePriorToJoin.contains(secondColumnInTheMerge))
selPred = tempPred;
}
else
continue;
}
else
continue;
}
}
if(selPred)
rootStatDesc->applySelIfSpecifiedViaHint(selPred, oldRowcountForSelAdj);
}
return appliedPredicateFlag;
} // applyVEGPred
// -----------------------------------------------------------------------
// ColStatDescList::applyBiLogicPred
//
// A CSDL::estimateCardinality() subroutine
//
// -----------------------------------------------------------------------
CostScalar
ColStatDescList::applyBiLogicPred(CostScalar & tempRowcount,
ValueIdSet & BiLogicPreds,
const ValueIdSet & outerReferences,
const Join * expr,
const SelectivityHint * selHint,
const CardinalityHint * cardHint,
CollIndex & numOuterColStats,
ValueIdSet & unresolvedPreds,
MergeType mergeMethod,
NAHashDictionary<ValueId, CostScalar> & biLogicPredReductions, // in/mod
OperatorTypeEnum exprOpCode,
CostScalar *maxSelectivity)
{
ValueId id;
if (BiLogicPreds.entries() == 0) return tempRowcount;
CostScalar newRowCount = MIN_ONE_CS( tempRowcount );
// maxSelectivity(p1 AND p2) == MIN(maxSelectivity(p1), maxSelectivity(p2))
// maxSelectivity(p1 OR p2) == MIN(1, maxSelectivity(p1)+maxSelectivity(p2))
CostScalar origRowCount = tempRowcount;
for( id = BiLogicPreds.init();
BiLogicPreds.next( id );
BiLogicPreds.advance( id ) )
{
ItemExpr * pred = id.getItemExpr();
ValueIdSet leftChildSet ( pred->child(0)->getValueId() );
ValueIdSet rightChildSet( pred->child(1)->getValueId() );
OperatorTypeEnum op = pred->getOperatorType();
NABoolean isLike = ((BiLogic*)pred)->isLike();
if(isLike && maxSelectivity)
{
*maxSelectivity = 1.0;
break;
}
NABoolean doEstCard = (maxSelectivity==NULL);
origRowCount = tempRowcount;
if ( op == ITM_AND )
{
CostScalar maxSel1 = 1.0, maxSel2 = 1.0;
// Apply both left and right predicates to the given ColStatDescList
newRowCount = estimateCardinality(tempRowcount,
leftChildSet,
outerReferences,
expr,
selHint,
cardHint,
numOuterColStats,
unresolvedPreds,
mergeMethod,
ITM_AND,
doEstCard ? NULL : &maxSel1
);
tempRowcount = newRowCount;
newRowCount = estimateCardinality(tempRowcount,
rightChildSet,
outerReferences,
expr,
selHint,
cardHint,
numOuterColStats,
unresolvedPreds,
mergeMethod,
ITM_AND,
doEstCard ? NULL : &maxSel2
);
tempRowcount = newRowCount;
// maxSelectivity(p1 AND p2)==MIN(maxSelectivity(p1),maxSelectivity(p2))
if (!doEstCard)
{
*maxSelectivity = MINOF(*maxSelectivity, MINOF(maxSel1, maxSel2));
}
}
else if ( op == ITM_OR )
{
// special case: we'd like to detect OR-trees which result from
// large IN-lists
//
// we will try to detect such an IN-list below; if this predicate is
// such an IN-list, then we'll handle the histogram-transformation
// a little simpler than what we usually do ...
NABoolean large_in_list = FALSE;
const ValueId & Lid = pred->child(0)->getValueId();
const ItemExpr * Lpred = Lid.getItemExpr();
const OperatorTypeEnum Lop = Lpred->getOperatorType();
const ValueId & Rid = pred->child(1)->getValueId();
const ItemExpr * Rpred = Rid.getItemExpr();
const OperatorTypeEnum Rop = Rpred->getOperatorType();
ValueId RidLeft;
NABoolean inListCase = TRUE;
if ( Rop == ITM_EQUAL) // don't look at child if not correct operator!
{
RidLeft = Rpred->child(0)->getValueId();
}
else
{
inListCase = FALSE;
}
Int32 in_list_members = 1; // count the first right child (above)
if ( (
Lop == ITM_OR
AND Rop == ITM_EQUAL
AND
// at this point we're reasonably sure that we're inside an OR-tree
// that was transformed from a (large) IN-list; we now call a
// function to check this carefully ...
isPredTransformedInList ( Lid, RidLeft, in_list_members )
AND
// $$$ NB: we ARBITRARILY set the number of IN-list members
// $$$ for which we try to carefully perform *EXACT*
// $$$ histogram manipulation
in_list_members > HIST_MAX_IN_LIST_MEMBERS
AND
// just in case the constant/default has a dumb value
in_list_members > 5
) ||
((Lop == ITM_OR )
AND
(inListCase == FALSE)
AND
(((BiLogic*)pred)->getNumLeaves() > HIST_MAX_IN_LIST_MEMBERS))
)
{
large_in_list = TRUE;
if (maxSelectivity == NULL)
{
// now do the necessary histogram-manipulation ...
//
// first we find the histogram which matches the IN-list;
// then we do some manipulation
if (RidLeft == NULL_VALUE_ID)
return newRowCount;
ItemExpr * RidLeftExpr = RidLeft.getItemExpr();
OperatorTypeEnum RidLeftOp = RidLeftExpr->getOperatorType();
while ((RidLeftOp != ITM_INDEXCOLUMN) &&
(RidLeftOp != ITM_INSTANTIATE_NULL) &&
(RidLeftOp != ITM_BASECOLUMN) &&
(RidLeftOp != ITM_VEG_REFERENCE) )
{
// Do not assume that the left child of EQUAL will always be a column
// it can be a SUBSTRING too, with the column CASTed.
if ( (RidLeftExpr->child(0)) &&
(RidLeftExpr->child(0)->getOperatorType() != ITM_CONSTANT) )
RidLeft = RidLeftExpr->child(0)->getValueId();
else
{
// for TRIM like functions, the left child is a constant while
// the right child is a column.
if (RidLeftExpr->child(1) )
RidLeft = RidLeftExpr->child(1)->getValueId();
else
{
// There is no column to apply predicate, hence return newRowCount
return newRowCount;
}
}
RidLeftExpr = RidLeft.getItemExpr();
RidLeftOp = RidLeftExpr->getOperatorType();
}
if (RidLeft == NULL_VALUE_ID)
return newRowCount;
CollIndex histIdx;
if ( NOT this->getColStatDescIndexForColumn( histIdx,RidLeft ) )
{
//10/15/2004
//
//Initially we used to assert here, the following if was missing
//The assertion was because even though RidLeft was covered
//the node to which this ColStatDescList was attached, the node
//did not produce any column in the RidLeft VEG (i.e. if RidLeft
//represented a VEGREF). This is possible if the VEG contains a
//constant, in such a case the VEGREG is covered by any node, since
//any node can produce a constant. To cover such a VEG the node
//does not need to produce any columns in the VEG.
//This assertion was being hit for a valid query
//Consider the scenario
//create table t1(x integer);
//create table t2(y integer);
//create table t3(z integer);
//display
//select *
//from t3, t2, t1
//where t1.x in (1, 2, 3, 4, 5, 6, 7, 8, 10 ,
// 11 ,12 ,13, 14, 15 ,16, 17 ,18, 19, 20,
// 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
// 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
// 41, 42)
// and
// t1.x = t2.y
// and
//t2.y = 0;
//
//The code we are in is called when we have a large in
//list (>42 items)
//
//this could happen if we get a VEGREF (i.e. RidLeft is a VEGREF)
//that contains a constant. In such a case, the VEG will be considered
//covered as it contains a constant and could be pushed down to a
//scan that does not produce any columns in the VEG.
// Keep a check, this ASSERTION could be not relevant now. In that case
// remove the following code.
//if RidLeft is a VEGREF
if(RidLeft.getItemExpr()->getOperatorType() == ITM_VEG_REFERENCE)
{
//Get the VEG referred by RidLeft
VEG * RidLeftVEG = ((VEGReference*) RidLeft.getItemExpr())\
->getVEG();
//Get the set of valueIds in RidLeftVEG
ValueIdSet RidLeftSet;
RidLeftVEG->getAndExpandAllValues(RidLeftSet);
//check if the VEG contains a constant
NABoolean containsConstant = RidLeftSet.referencesAConstExpr();
//If VEG contains a constant, then RidLeft is covered
//by this node (i.e. node to which this ColStatDescList is attached)
//even if it does not produce any of the columns in the VEG.
if(containsConstant)
return newRowCount;
}
//This should not happen and so assert in a
//debug build
// could not find a matching histogram ... huh?!
// CCMPASSERT( FALSE ); // this should not happen!
//CCMPASSERT is only compiled for debug builds
//Therefore exit gracefully for a release build
//Since this is a non-fatal error
return newRowCount;
}
else
{
ColStatDescSharedPtr predColumn = (*this)[histIdx];
ColStatsSharedPtr predStats = predColumn->getColStatsToModify();
if (inListCase && (predStats->getTotalUec() <= in_list_members ) )
{
// do nothing : assume no uec/rows are removed by
// application of this large in-list
}
else
{
CostScalar redFactor = csOne;
if (inListCase)
{
// Set the histogram's new totalUec to be the
// number of in-list members, adjusting the
// rowcount by the same proportion
redFactor = (CostScalar( in_list_members ) / predStats->getTotalUec()).maxCsOne();
}
else
{
// it is a large OR predicate, but not derived from IN
// list. Instead it could be a set of AND and ORs
redFactor = (CostScalar (((BiLogic*)pred)->getNumLeaves()) / predStats->getTotalUec()).maxCsOne();
}
newRowCount =
predStats->getRowcount() * redFactor;
predStats->scaleHistogram(redFactor, redFactor);
predStats->setBaseUec(predStats->getTotalUec());
// after this approximation, we no longer completely "trust"
// this histogram ...
predStats->setFakeHistogram( TRUE );
// Now, normalize all histograms to the same resultant rowcount.
for( CollIndex i = 0; i < entries(); i++ )
{
const CostScalar & oldCount =
(*this)[i]->getColStats()->getRowcount();
if( oldCount != newRowCount ) // && oldCount != 0
(*this)[i]->synchronizeStats( oldCount, newRowCount );
}
// update tempRowcount which is baing used later as the starting
// cardinality for the remaining bi-logic predicate
tempRowcount = newRowCount;
}
}
} // maxSelectivity == NULL
} // large_in_list case handled
// ----------------------------------------------------------------------
// If this predicate is simply a large OR-tree that resulted
// from an IN-list transformation, then we've already handled
// that case above, so we're done applying this predicate
// ----------------------------------------------------------------------
if ( NOT large_in_list )
{
// the following two lists, one of ValueId's, one of char *'s, are used
// to keep track of the TRUE shape-changed flag's of this OR-node's
// parents -- assuming the parent is not also an OR
//
// this is needed because when we look at the children of the OR,
// we set the shape-changed flags for all columns to be FALSE
//
// if we're to do the right thing, we will need to know later on
// whether a column was shape-changed before it was passed to this OR node
ValueIdList shapeChangedItemExprs;
NAList<ComUID> shapeChangedHistIds(CmpCommon::statementHeap());
CollIndex dictSize = MIN_ONE(this->entries());
NAHashDictionary<ValueId, ValueIdSet> priorAppliedPredsSet (
&(ValueIdHashFn), dictSize, TRUE, HISTHEAP );
NAHashDictionary<ValueId, ValueIdSet> priorMergeStateSet (
&(ValueIdHashFn), dictSize, TRUE, HISTHEAP );
// Make a copy of the current input ColStatDescList THIS;
// Apply the left predicate to THIS;
// Apply the right predicate to the manufactured copy;
// OR the two lists together, into THIS...
// carefully, because the two lists no longer have to be the
// same length;
// Discard the copy (it'll go away since it's a stack variable)
ColStatDescList copyStatsList( HISTHEAP );
CollIndex i;
copyStatsList.makeDeepCopy (
*this,
1, // scale remains unchanged
FALSE // clear shapeChanged flag
);
// for OR's, clear THIS's shapeChanged_ flag (copyStatsList cleared above)
if (maxSelectivity == NULL) {
for ( i = 0; i < entries(); i++ )
{
ColStatDescSharedPtr copyStatDesc = (*this)[i];
ColStatsSharedPtr copyStats = copyStatDesc->getColStatsToModify();
// need to store these shape-changed flags in order to keep
// track of all the information that our parent gave us!
// --> we don't do this if our parent is an OR
if ( exprOpCode != ITM_OR )
{
const ValueId & saveExpr = copyStatDesc->getVEGColumn().getItemExpr()->getValueId();
if ( copyStats->isShapeChanged() )
{
shapeChangedItemExprs.insert( saveExpr );
shapeChangedHistIds.insert( copyStats->getHistogramId() );
}
if (!copyStatDesc->getAppliedPreds().isEmpty())
{
ValueId * key = new (HISTHEAP) ValueId( saveExpr );
ValueIdSet * keyValue = new (HISTHEAP) ValueIdSet( copyStatDesc->getAppliedPreds() );
priorAppliedPredsSet.insert(key, keyValue);
}
if (copyStatDesc->getMergeState() != copyStatDesc->getColumn())
{
ValueId * key = new (HISTHEAP) ValueId( saveExpr );
ValueIdSet * keyValue = new (HISTHEAP) ValueIdSet( copyStatDesc->getMergeState() );
priorMergeStateSet.insert(key, keyValue);
}
} // now we've saved the ItemExpr / Histogram Id for every
// column that was shape-changed
copyStats->setShapeChanged( FALSE ); // for OR'ing, init clear.
}
} // maxSelectivity == NULL
// track information for probabilistic newRowCount determination
CostScalar saveTempRowCount = tempRowcount;
tempRowcount = MIN_ONE_CS( tempRowcount ); // prevent division by Zero!
// Note, if we use numOuterColStats to the two
// estimateCardinality() function calls below, it may
// get changed to a value greater than the number of entries
// in copyStatsList after the first function call.
// When that happens and we pass it to the second function call
// below, it will cause an assertion failure.
// Copies of numOuterColStats to be applied below.
CollIndex leftNumOuterColStats = numOuterColStats;
CollIndex rightNumOuterColStats = numOuterColStats;
ValueIdSet originalUnResolved = unresolvedPreds;
CostScalar maxSel1 = 1.0, maxSel2 = 1.0;
CostScalar leftRowCount = estimateCardinality(
tempRowcount,
leftChildSet,
outerReferences,
expr,
selHint,
cardHint,
leftNumOuterColStats,
unresolvedPreds,
mergeMethod,
ITM_OR,
doEstCard ? NULL :
&maxSel1
);
CostScalar prob1, prob2;
prob1 = leftRowCount / tempRowcount;
CostScalar rightRowCount = copyStatsList.estimateCardinality(
tempRowcount,
rightChildSet,
outerReferences,
expr,
selHint,
cardHint,
rightNumOuterColStats,
originalUnResolved,
mergeMethod,
ITM_OR,
doEstCard ? NULL :
&maxSel2
);
prob2 = rightRowCount / tempRowcount;
// maxSelectivity(p1 OR p2)==MIN(1,maxSelectivity(p1)+maxSelectivity(p2))
if (!doEstCard)
{
*maxSelectivity = MINOF(*maxSelectivity, MINOF(maxSel1+maxSel2, 1.0));
}
if (maxSelectivity == NULL)
{
// since both left and right colStats have been initialized by the
// same list of histograms, these should have equal number of entries.
// if not, something when wrong, so ignore this predicate
// at the customer.
if (leftNumOuterColStats != rightNumOuterColStats )
{
CCMPASSERT( leftNumOuterColStats == rightNumOuterColStats );
return newRowCount;
}
numOuterColStats = leftNumOuterColStats;
// Under unusual conditions, prob1 and prob2 can be greater than
// one! (i.e., if we have a long in-list predicate applied to a
// histogram that only has a few rows). So, in other words, the
// application of some predicates can result in an increase in
// estimated rowcount!
//
// However, the calculation for newRowCount below (involving
// p1+p2-p1*p2) will not produce a reasonable result if we're
// not using probabilities! It makes absolutely no sense to
// talk about a "probability" that's >1!
//
// So, in the case where prob1 or prob2 is >1, we need to
// scale down the corresponding CSDL to have the original
// rowcount, then set prob1/prob2 to be 1. We don't want
// predicate application to ever increase rowcounts!
if ( prob1 > csOne )
{
// scale down the histograms
for ( CollIndex i = 0; i < this->entries(); i++ )
(*this)[i]->synchronizeStats( leftRowCount, tempRowcount );
// Don't forget to reset these two values!
leftRowCount = tempRowcount;
prob1 = csOne;
}
if ( prob2 > csOne )
{
for ( CollIndex i = 0; i < copyStatsList.entries(); i++ )
(copyStatsList)[i]->synchronizeStats( rightRowCount, tempRowcount );
rightRowCount = tempRowcount;
prob2 = csOne;
}
double prob1prob2 = prob1.value() * prob2.value();
// estimate for case where histogram merges are not possible.
newRowCount = saveTempRowCount *
( prob1 + prob2 - CostScalar(prob1prob2) );
// overLappedRowcount first was saveTempRowCount;
// then, it was leftRowCount + rightRowCount
// But, the real maximum amount of overlap between two histograms
// is determined by the smaller side's rowcount.
CostScalar overLappedRowcount = MINOF( leftRowCount, rightRowCount );
// predicates are only completely un-resolved when they are
// un-resolved on Both sides of the OR.
unresolvedPreds.intersectSet( originalUnResolved );
// ---------------------------------------------------------------
// walk over every entry in the updated THIS.
// Find its match in the corresponding list.
// An entry matches when the first columns match and the histogram ID
// matches.
// -----------------------------------------------------------------
// Did they change consistently?
NABoolean bothChangedWheneverOneChanged = TRUE;
// Did at least one change?
NABoolean atLeastOneChanged = FALSE;
ValueIdSet matchedRight;
CollIndex currentL, currentR;
if( this->entries() != copyStatsList.entries() )
{
// copyStatsList is a copy of THIS statsList, during
// the process of applying predicates to THIS and
// the copy, we should not have dropped any columns.
Int32 stophere = 0;
}
for( currentL = 0; currentL < entries(); currentL++ )
{
const ItemExpr * leftItem =
(*this)[currentL]->getVEGColumn().getItemExpr();
ComUID leftHistId =
(*this)[currentL]->getColStats()->getHistogramId();
NABoolean found = FALSE;
currentR = 0;
while( NOT found && currentR < copyStatsList.entries() )
{
const ItemExpr * rightItem =
(copyStatsList)[currentR]->getVEGColumn().getItemExpr();
ComUID rightHistId =
(copyStatsList)[currentR]->getColStats()->getHistogramId();
if( leftItem->getValueId() == rightItem->getValueId() &&
(leftHistId == rightHistId) )
found = TRUE;
else
currentR++;
}
if (found == FALSE)
{
// if could not find the histogram to be merged
// return for release mode. Side effect could be
// increased cardinality
CCMPASSERT( found == TRUE );
return newRowCount;
}
ColStatsSharedPtr leftColStats =
(*this)[currentL]->getColStatsToModify();
ColStatsSharedPtr rightColStats =
(copyStatsList)[currentR]->getColStatsToModify();
// If either side has changed, perform an OR merge.
// If both sides have shape changed, find how much rowcount
// was overlapped between the two.
// Save the smallest amount of overlap and use it to
// subtract from the sum of the two rowcounts when all
// shape changes come in pairs.
// neither changed
if( NOT leftColStats->isShapeChanged() &&
NOT rightColStats->isShapeChanged() )
{
copyStatsList.removeAt( currentR );
}
else // at least one side shape-changed.
{
atLeastOneChanged = TRUE;
CostScalar saveRowcount =
leftColStats->getRowcount() + rightColStats->getRowcount();
(*this)[currentL]->mergeColStatDesc(
(copyStatsList)[currentR],
OR_MERGE,
FALSE,
exprOpCode
);
// for debugging informational value, track the
// histograms impacted by an OR.
(*this)[currentL]->appliedPreds().insert( id );
if(rightColStats->isSelectivitySetUsingHint())
leftColStats->setSelectivitySetUsingHint();
// both sides changed?
if ( leftColStats->isShapeChanged() &&
rightColStats->isShapeChanged() )
{
CostScalar curDif = saveRowcount -
(*this)[currentL]->getColStats()->getRowcount();
if (curDif < overLappedRowcount)
overLappedRowcount = curDif;
}
else // only one side changed!
{
bothChangedWheneverOneChanged = FALSE;
}
copyStatsList.removeAt( currentR );
}
} // for (currentL)
if( copyStatsList.entries() != 0 )
{
CCMPASSERT (copyStatsList.entries() == 0);
// The OR merge between THIS and the copy did not go properly
return newRowCount;
}
// Determine the resultant rowcount. If not all columns overlapped,
// use previously determined rowcount
// (i.e. rowcount * (prob1 + prob2) - (prob1 * prob2))
// otherwise just use leftRows + rightRows - overlappedRows.
if( atLeastOneChanged && bothChangedWheneverOneChanged )
newRowCount = leftRowCount + rightRowCount - overLappedRowcount;
newRowCount = MINOF(newRowCount, origRowCount);
// If user specified selectivity for this predicate, we need to make
// adjustment in reduction to reflect that.
if((exprOpCode == REL_SCAN) &&
(pred->isSelectivitySetUsingHint()))
{
ColStatDescSharedPtr firstColStatDesc = (*this)[0];
firstColStatDesc->applySelIfSpecifiedViaHint(pred, origRowCount);
newRowCount = firstColStatDesc->getColStats()->getRowcount();
}
tempRowcount = newRowCount;
// Now, normalize all histograms to the same resultant rowcount.
for( i = 0; i < entries(); i++ )
{
const CostScalar & oldCount =
(*this)[i]->getColStats()->getRowcount();
if( oldCount != newRowCount ) // && oldCount != 0
(*this)[i]->synchronizeStats( oldCount, newRowCount );
}
if( exprOpCode != ITM_OR )
// now, restore our parents' TRUE shape-changed flags after we come out
// of the OR -- we lost this information when we started the OR and
// set all shape-changed flags to be FALSE
// ** this has to be so complicated because the number of columns
// ** in THIS may have been reduced by the right child
// ** --> or does this not have to be as cautious as it is?
{
for( i = 0; i < shapeChangedItemExprs.entries(); i++ )
{
NABoolean found = FALSE;
for( CollIndex j = 0; j < entries(); j++ )
{
const ValueId & checkExpr =
(*this)[j]->getVEGColumn().getItemExpr()->getValueId();
ComUID checkHistId =
(*this)[j]->getColStats()->getHistogramId();
NABoolean equalExprs = ( shapeChangedItemExprs[i] == checkExpr );
NABoolean equalHistIds = ( shapeChangedHistIds[i] == checkHistId );
if ( equalExprs && equalHistIds )
{
found = TRUE;
(*this)[j]->getColStatsToModify()->setShapeChanged(TRUE);
break;
}
}
CCMPASSERT( found == TRUE );
}
// Restore the original appliedPred and mergeState sets which were removed
// before applying OR-related join predicates
for( CollIndex j = 0; j < entries(); j++ )
{
(*this)[j]->appliedPreds().clear();
(*this)[j]->mergeState().clear();
const ValueId & checkExpr = (*this)[j]->getVEGColumn().getItemExpr()->getValueId();
if(priorAppliedPredsSet.contains(&checkExpr))
(*this)[j]->appliedPreds().insert(*(priorAppliedPredsSet.getFirstValue(&checkExpr)));
if(priorMergeStateSet.contains(&checkExpr))
{
// At Join-level, we will have histograms from both sides with the same VEG column.
// We need to make sure that the valueId of the column is part of saved mergeState
// before copying the mergeState.
ValueIdSet mergeState = *(priorMergeStateSet.getFirstValue(&checkExpr));
if(mergeState.contains((*this)[j]->getColumn()))
(*this)[j]->mergeState().insert(mergeState);
else
(*this)[j]->mergeState().insert((*this)[j]->getColumn());
}
else
(*this)[j]->mergeState().insert((*this)[j]->getColumn());
}
}
} // maxSelectivity == NULL
} // NOT large_in_list
} // ITM_OR
else // error: It isn't ITM_AND or ITM_OR ... oops
{
CCMPASSERT("Unsupported binary logic operator" );
return newRowCount;
}
if(exprOpCode == REL_SCAN && !maxSelectivity && (CmpCommon::getDefault(COMP_BOOL_67) == DF_ON))
{
ValueIdSet predSet;
pred->getLeafPredicates(predSet);
NABoolean skip = FALSE;
ItemExpr * column = NULL;
ItemExpr *lhs = NULL;
for( ValueId tempId = predSet.init();
predSet.next( tempId );
predSet.advance( tempId ) )
{
ItemExpr *tempExpr = tempId.getItemExpr();
if (tempExpr->getArity() > 0)
lhs = tempExpr->child(0);
else
lhs = NULL;
if (!tempExpr->equatesToAConstant() ||
(column && (lhs != column)) ||
(!lhs))
{
skip = TRUE;
break;
}
else if(!column)
column = lhs;
}
if(skip)
continue;
CollIndex i;
column = column->getLeafValueIfUseStats();
ValueId colId = column->getValueId();
const BaseColumn * baseCol = colId.castToBaseColumn();
if(!baseCol)
continue;
else
colId = baseCol->getValueId();
if(!getColStatDescIndexForColumn(i, colId))
continue;
//calculate the reduction from this predicate
CostScalar rowCnt = (*this)[i]->getColStats()->getRowcount();
if ( rowCnt < origRowCount )
{
ValueId * key = new (HISTHEAP) ValueId(colId);
CostScalar * value = new (HISTHEAP) CostScalar( rowCnt / origRowCount );
ValueId * result = biLogicPredReductions.insert( key, value );
}
}
}
return newRowCount;
} // ColStatDescList::applyBiLogicPred
// -----------------------------------------------------------------------
// ColStatDescList::applyPred
//
// A CSDL::estimateCardinality() subroutine
//
// This method applies the effect of a predicate on the set of column
// statistics. This method supports the unary predicates IS [NOT] NULL
// and IS [NOT] UNKNOWN, as well as binary predicates involving the
// operators {>,>=,<,<=,=,<>}, where one of the operands is a literal
// constant.
//
// NOTE: Support for nonVEG <col A> <eqOp> <col B> is provided in this
// routine; ColStatDesc::modifyStats takes effect only for inequality
// predicates involving constants. Support of <col A> <ineqOp> <col B>
// will (eventually) need to be added.
//
// NOTE: The code is written as if the same predicate can apply to more
// than one ColStatDesc in the given ColStatDescList. This may never
// happen, but it doesn't hurt to code it this way.
//
// This is the new version of this function which facilitates applying all
// predicates at once.
// -----------------------------------------------------------------------
NABoolean
ColStatDescList::applyPred (ItemExpr *pred,
CostScalar & newRowcount,
CollIndex & numOuterColStats,
MergeType mergeMethod,
OperatorTypeEnum exprOpCode,
CostScalar *maxSelectivity)
{
// sanity check: operator understood by synthesis?
if ( NOT pred->synthSupportedOp() ) return FALSE;
CostScalar rowCountBeforePreds = newRowcount;
CostScalar tempRowcount = csZero;
CostScalar newUec;
CollIndex i;
NABoolean appliedPredicateFlag = FALSE;
NABoolean negate = FALSE;
// Check to see if the predicate is a derivative from Like predicate
// If it is then it can only be either >= or <
NABoolean derivedFromLike = FALSE;
if ( (pred->getOperatorType() == ITM_GREATER_EQ) OR
(pred->getOperatorType() == ITM_LESS) )
{
BiRelat *br = (BiRelat *) pred;
derivedFromLike = br->derivativeOfLike();
}
// ---------------------------------------------------------------------
// first check for ITM_IS_NULL, ITM_IS_NOT_NULL, ITM_IS_UNKNOWN, and
// ITM_IS_NOT_UNKNOWN or the more complex case where the predicate
// is NOT an equality predicate which does NOT involve a <constant>.
// ---------------------------------------------------------------------
if ( ( pred->getOperatorType() == ITM_IS_NULL
OR pred->getOperatorType() == ITM_IS_NOT_NULL
OR pred->getOperatorType() == ITM_IS_UNKNOWN
OR pred->getOperatorType() == ITM_IS_NOT_UNKNOWN
)
OR NOT ( pred->getOperatorType() == ITM_EQUAL
AND pred->child(0)->castToConstValue( negate ) == NULL
AND pred->child(1)->castToConstValue( negate ) == NULL
)
OR (pred->equatesToAConstant())
)
{
CostScalar oldRowcountForSelAdj;
// Simple Case: look for <op> <col>,
// <col> <op> <constant> or
// <constant> <op> <col> predicates
for ( i = numOuterColStats /*fix to ALM5128*/; i < entries(); i++ )
{
ColStatDescSharedPtr thisColStatDesc = (*this)[i];
ColStatsSharedPtr colStats = thisColStatDesc->getColStats();
if ((colStats->isVirtualColForHist() ||
colStats->isMCforHbasePartitioning ())) // ignore MC stats intervals for now, only used by the splitting logic
continue;
if(pred->isSelectivitySetUsingHint())
oldRowcountForSelAdj = colStats->getRowcount();
CostScalar totalUecBeforePreds = colStats->getTotalUec();
CostScalar baseUecBeforePreds = colStats->getBaseUec();
CostScalar predMaxSel = 1.0;
if ( thisColStatDesc->modifyStats
( pred, tempRowcount, maxSelectivity ? &predMaxSel : NULL) )
{
appliedPredicateFlag = TRUE;
if(derivedFromLike)
{
// for LIKE predicates, maxSelectivity will stay 1.0
// This range predicate is a derivative of Like predicate.
// Hence we cannot use the usual selectivity obtained after
// applying range predicates. Here we use a portion of the
// default selectivity for like predicates to obtain the
// resultant rowcount. We still continue to use the histograms
// obtained after applying range predicates the usual way
// The selectivity would be applied to the initial rowcount
// before any predicates were applied.
if (maxSelectivity == NULL) {
BiRelat *br = (BiRelat *) pred;
br->adjustRowcountAndUecForLike((*this)[i],
rowCountBeforePreds,
totalUecBeforePreds,
baseUecBeforePreds);
} // maxSelectivity == NULL
}
else // !derivedFromLike
{
// modifyStats has just computed selectivity(p) by side effecting
// the histogram. For cases where maxSelectivity(p)=selectivity(p)
// we want to reflect that in maxSelectivity.
if (pred->maxSelectivitySameAsSelectivity() && maxSelectivity
&& newRowcount > csZero)
{
predMaxSel = MINOF(predMaxSel, tempRowcount/newRowcount);
*maxSelectivity = MINOF(predMaxSel, *maxSelectivity);
}
}
// If user specified selectivity for this predicate, we need to make
// adjustment in reduction to reflect that.
thisColStatDesc->applySelIfSpecifiedViaHint(pred, oldRowcountForSelAdj);
}
}
// $$$ WE NO LONGER WANT TO NORMALIZE HISTOGRAMS AFTER EVERY
// $$$ PREDICATE!
//
// Instead, we will normalize all of the histograms' rowcounts after
// we have applied all of the predicates.
return appliedPredicateFlag;
} // simple case
// maxSelectivity computation is done
if (maxSelectivity) return appliedPredicateFlag;
// ---------------------------------------------------------------------
// Complex Case: The input is an Equality Predicate not of the form
// <col> = <constant>.
// Equality predicates can only be performed amongst:
//
// ITM_INSTANTIATE_NULL
// ITM_VALUEIDUNION
// ITM_VEG_REFERENCE
// ITM_UNPACKCOL
//
// So, look for that case. Actions performed depend upon the type of the
// join, and the positions of the usable entries in the ColStatDescList.
//
// But before checking anything further, make sure that this predicate
// is not a no-op of the form <expr_1> = <expr_1>.
// ---------------------------------------------------------------------
ItemExpr * lhs = pred->child(0);
ItemExpr * rhs = pred->child(1);
OperatorTypeEnum predExprType = pred->getOperatorType();
if ( (*lhs) == (*rhs) )
return TRUE; // indicate that this no-op predicate was applied
if (predExprType == ITM_EQUAL)
{
lhs = lhs->getLeafValueIfUseStats();
rhs = rhs->getLeafValueIfUseStats();
}
OperatorTypeEnum leftExprType = lhs->getOperatorType();
OperatorTypeEnum rightExprType = rhs->getOperatorType();
// Special handling for predicates of the following form:
// T1.A = max(T2.B);
//
// If there were no local predicates on T2 prior to join,
// then we can replace max(T2.B) with the histogram maximum
// boundary value of T2.B and apply that predicate instead.
// This is because boundary values are no longer reliable if
// any local predicates have been applied to the same table.
if ((CmpCommon::getDefault(HIST_MIN_MAX_OPTIMIZATION) == DF_ON) &&
(predExprType == ITM_EQUAL) &&
(leftExprType == ITM_VEG_REFERENCE) &&
(rightExprType == ITM_MIN || rightExprType == ITM_MAX))
{
// Sometimes, aggregate is transformed to max(max(col)),
// need to do the following to extract column.
while(rhs->getOperatorType() == ITM_MIN || rhs->getOperatorType() == ITM_MAX)
rhs = rhs->child(0);
CollIndex i = 0;
// check that the right hand histogram exists
if ( getColStatDescIndexForColumn(i, rhs->getValueId()) )
{
ColStatDescSharedPtr rhsStatDesc = (*this)[i];
ColStatsSharedPtr rhsColStats = rhsStatDesc->getColStats();
ValueId colId = rhsStatDesc->getColumn();
BaseColumn * colExpr = colId.castToBaseColumn();
// Need to make sure there is no local preds on the right hand side
// (where aggregate exists).
if( colExpr && colExpr->getTableDesc()->getLocalPreds().entries() == 0)
{
// check that the left hand histogram exists
if (getColStatDescIndexForColumn(i, lhs->getValueId()))
{
ColStatDescSharedPtr lhsStatDesc = (*this)[i];
ColStatsSharedPtr currLhsColStats = lhsStatDesc->getColStats();
CostScalar currLhsScaleFactor = currLhsColStats->getScaleFactor();
CostScalar currLhsRC = (currLhsColStats->getRowcount()/currLhsScaleFactor);
// Need to get original full histogram
const NAColumnArray &columnList = currLhsColStats->getStatColumns();
NATable * lhsTable = ((NATable *)columnList[0]->getNATable());
StatsList &lhsStats = lhsTable->getStatistics();
Lng32 colPosition = columnList[0]->getPosition();
ColStatsSharedPtr origLhsColStats = lhsStats.getSingleColumnColStats(colPosition);
// Make a deep copy
ColStatsSharedPtr copyOfOrigLeftColStats =
ColStats::deepCopy(*origLhsColStats, HISTHEAP);
CostScalar origLhsRC = origLhsColStats->getRowcount();
//Apply the reduction to the orig histogram if any from local preds
CostScalar localPredReduction = currLhsRC/origLhsRC;
if(localPredReduction != csOne)
copyOfOrigLeftColStats->scaleHistogram(localPredReduction);
// Before we can perform the optimization, we need to make sure we have
// a good hash value to use to search via MIN/MAX value for an interval.
// See ColStats::setToSingleValue(const EncodedValue&, ConstValue*, ...)
// in the block of code where the constant is in MFV.
// Here since it is not easy to construct a ConstValue object out of the MIN/MAX
// value of EncodedValue, we will restrict ourselves to MIN/MAX value ==
// one of the frequent value from the rhs.
const FrequentValueList& fvList = rhsColStats->getFrequentValues();
if ( fvList.entries() > 0 ) {
EncodedValue normValue;
FrequentValue fv;
if(rightExprType == ITM_MIN) {
normValue = rhsColStats->getMinValue();
fv = fvList[0];
} else {
normValue = rhsColStats->getMaxValue();
fv = fvList[fvList.entries()-1];
}
if ( fv.getEncodedValue() == normValue ) {
// Reduce to single interval via a key. The key value is
// normValue and the hash stored in fv. The interface is
// a little bit of awkward. But we will live with it for now.
copyOfOrigLeftColStats->setToSingleValue(normValue, NULL, NULL, &fv);
// Need to scale it back by original scaling factor
copyOfOrigLeftColStats->scaleHistogram(currLhsScaleFactor);
// Finally overlay the original histogram onto existing ColStatDesc
lhsStatDesc->setColStats(copyOfOrigLeftColStats);
return TRUE;
}
}
}
}
}
}
// For 'Sample' inserts 'notCovered' over the expression, in order to avoid
// its pushing down. But this should not have any effect on the selectivity
// hence we shall remove any not_covered from the expressions. And estimate
// cardinality like a regular predicate.
NABoolean notCovered = FALSE;
if (leftExprType == ITM_NOTCOVERED)
{
lhs = lhs->child(0);
if (predExprType == ITM_EQUAL)
notCovered = TRUE;
}
if (rightExprType == ITM_NOTCOVERED)
{
rhs = rhs->child(0);
if (predExprType == ITM_EQUAL)
notCovered = TRUE;
}
if (CmpCommon::getDefault(COMP_BOOL_48) == DF_ON)
{
if ( (rightExprType == ITM_NATYPE ) AND
(leftExprType == ITM_VEG_REFERENCE ) )
notCovered = TRUE;
}
if (!notCovered)
{
if ( (rightExprType == ITM_ROWSETARRAY_SCAN ) OR
(leftExprType == ITM_ROWSETARRAY_SCAN ) )
{
}
else
{
if ( NOT ( ( leftExprType == rightExprType )
AND ( leftExprType == ITM_INSTANTIATE_NULL
OR leftExprType == ITM_VALUEIDUNION
OR leftExprType == ITM_VEG_REFERENCE
OR leftExprType == ITM_UNPACKCOL
)
)
)
{
return FALSE;
}
}
}
// ---------------------------------------------------------------------
// IF the referenced ColStatDesc's are provided, this equality predicate
// *can* be performed.
//
// Note that Left and Right are relative to the operands of the equality
// predicate. Later they need to be oriented to the inner/outer sides
// of the input ColStatDescList.
// ---------------------------------------------------------------------
CollIndexList leftStatsToMerge(STMTHEAP);
CollIndexList rightStatsToMerge(STMTHEAP);
identifyMergeCandidates( lhs, leftStatsToMerge );
if ( leftStatsToMerge.entries() == 0 )
return FALSE;
identifyMergeCandidates( rhs, rightStatsToMerge );
if ( rightStatsToMerge.entries() == 0 )
return FALSE;
// Take care of the possibility that one, or more, of the given column
// references referenced more than one column in the ColStatDescList.
// If this happens : perform an equijoin amongst the multiple columns.
CollIndex leftRootIndex = leftStatsToMerge[0];
ColStatDescSharedPtr leftRootDesc = (*this)[leftRootIndex];
CollIndex rightRootIndex = rightStatsToMerge[0];
ColStatDescSharedPtr rightRootDesc = (*this)[rightRootIndex];
CollIndex rootIndex;
ColStatDescSharedPtr rootStatDesc;
if ( leftRootIndex < rightRootIndex )
{
rootIndex = leftRootIndex;
rootStatDesc = leftRootDesc;
}
else
{
rootIndex = rightRootIndex;
rootStatDesc = rightRootDesc;
}
ColStatsSharedPtr rootColStats = rootStatDesc->getColStatsToModify();
ColStatsSharedPtr leftColStats = leftRootDesc->getColStatsToModify();
CostScalar leftRowcount = leftColStats->getRowcount();
ColStatsSharedPtr rightColStats = rightRootDesc->getColStatsToModify();
CostScalar rightRowcount = rightColStats->getRowcount();
// Finally, given a 'root' ColStatDesc,
// determine whether or not we have applied this predicate
const ValueId & predValueId = pred->getValueId();
if ( rootStatDesc->isPredicateApplied( predValueId ) || // Already applied?
leftRootIndex == rightRootIndex ) // or no-op?
return TRUE;
rootStatDesc->addToAppliedPreds( predValueId );
CostScalar saveRowcount = rootColStats->getRowcount();
CostScalar saveUec = rootColStats->getTotalUec();
//Under a OR we do not want to handle vegRefs because
// 1. for predicates a=b and ( c =b or c = d)
// if we do perform a=b when we are computing c =b we will
// applying the same predicates twice as a=b later gets applied
// also.
// 2. Application of vegref under or gives rise to an unexpected
// ColStats as we unpack the histograms to get all the merged
// ColStats back
if( exprOpCode != ITM_OR )
{
mergeSpecifiedStatDescs (
leftStatsToMerge, leftRootIndex,
mergeMethod, numOuterColStats,
saveRowcount, saveUec,
FALSE,
exprOpCode); // not for VEGPred
// repeat for the right equivalence group.
mergeSpecifiedStatDescs (
rightStatsToMerge, rightRootIndex,
mergeMethod, numOuterColStats,
saveRowcount, saveUec,
FALSE,
exprOpCode); // not for VEGPred
}
// Gen Sol:10-090728-3382:
// In some cases, the same column is participating in more than one join
// predicates. When subsequent predicates (second onwards) are applied,
// the effect of the first predicate application is lost. We will capture
// the existing reduction here before additional predicates are applied.
CostScalar leftReduction = csOne, rightReduction = csOne, scaleFactor = csMinusOne;
NABoolean isASemiJoin = ((mergeMethod == SEMI_JOIN_MERGE) || (mergeMethod == ANTI_SEMI_JOIN_MERGE));
if(!isASemiJoin && leftRowcount < rowCountBeforePreds)
{
// Need to set the original scale factor before the prior predicates were applied
if(leftRootDesc->getVEGColumn() == lhs->getValueId())
scaleFactor = rowCountBeforePreds/leftColStats->getBaseRowCount();
else
{
CollIndex nonVegEqualEntries = leftRootDesc->nonVegEquals().entries();
for (i = 0; i < nonVegEqualEntries; i++)
{
ColStatDescSharedPtr tmpDesc = leftRootDesc->nonVegEquals()[i];
if(tmpDesc->getVEGColumn() == lhs->getValueId())
{
scaleFactor = rowCountBeforePreds / tmpDesc->getColStats()->getBaseRowCount();
break;
}
}
}
if(scaleFactor != csMinusOne)
{
leftReduction = leftRowcount / rowCountBeforePreds;
leftColStats->scaleHistogram(csOne/leftReduction);
leftColStats->setScaleFactor(scaleFactor);
}
}
scaleFactor = csMinusOne;
if(!isASemiJoin && rightRowcount < rowCountBeforePreds)
{
// Need to set the original scale factor before the prior predicates were applied
if(rightRootDesc->getVEGColumn() == rhs->getValueId())
scaleFactor = rowCountBeforePreds/rightColStats->getBaseRowCount();
else
{
CollIndex nonVegEqualEntries = rightRootDesc->nonVegEquals().entries();
for (i = 0; i < nonVegEqualEntries; i++)
{
ColStatDescSharedPtr tmpDesc = rightRootDesc->nonVegEquals()[i];
if(tmpDesc->getVEGColumn() == rhs->getValueId())
{
scaleFactor = rowCountBeforePreds / tmpDesc->getColStats()->getBaseRowCount();
break;
}
}
}
if(scaleFactor != csMinusOne)
{
rightReduction = rightRowcount / rowCountBeforePreds;
rightColStats->scaleHistogram(csOne/rightReduction);
rightColStats->setScaleFactor(scaleFactor);
}
}
// --------------------------------------------------------------------
// Now, safely past that unlikely situation, perform the join between
// the left and right sides of the equality predicate.
// For merging columns from the same table,
// selectivity is HIST_NO_STATS_UEC.
// --------------------------------------------------------------------
MergeType localMergeMethod = mergeMethod;
NABoolean joinFromSameTable = FALSE;
if ( leftRootIndex < rightRootIndex )
{
ColStatDescSharedPtr tmpDesc (new (HISTHEAP)
ColStatDesc( *rightRootDesc ), HISTHEAP);
tmpDesc->setFromInnerTable( rightRootIndex >= numOuterColStats ?
TRUE : FALSE );
if ( NOT ( leftRootIndex < numOuterColStats &&
rightRootIndex >= numOuterColStats ) )
{
// even though the mergeMethod may never be used, but we are
// initializing this for cases when COMP_BOOL_74 is OFF
localMergeMethod = INNER_JOIN_MERGE;
joinFromSameTable = leftRootDesc->mergeColStatDescOfSameTable(tmpDesc, exprOpCode);
}
if (!joinFromSameTable)
{
leftRootDesc->mergeColStatDesc(
tmpDesc,
localMergeMethod,
FALSE, // don't force merge
exprOpCode
);
}
leftRootDesc->nonVegEquals().insert( tmpDesc );
}
else
{
ColStatDescSharedPtr tmpDesc(new (HISTHEAP)
ColStatDesc( *leftRootDesc ), HISTHEAP);
tmpDesc->setFromInnerTable( leftRootIndex >= numOuterColStats ?
TRUE : FALSE );
if ( NOT ( rightRootIndex < numOuterColStats &&
leftRootIndex >= numOuterColStats ) )
{
// even though the mergeMethod may never be used, but we are
// initializing this for cases when COMP_BOOL_74 is OFF
localMergeMethod = INNER_JOIN_MERGE;
joinFromSameTable = rightRootDesc->mergeColStatDescOfSameTable(tmpDesc, exprOpCode);
}
if(!joinFromSameTable)
{
rightRootDesc->mergeColStatDesc(
tmpDesc,
localMergeMethod,
FALSE, // don't force merge
exprOpCode
);
}
rightRootDesc->nonVegEquals().insert( tmpDesc );
}
if(leftReduction != csOne)
rootColStats->scaleHistogram(leftReduction);
if(rightReduction != csOne)
rootColStats->scaleHistogram(rightReduction);
// get the aggregate results following the latest merge
newRowcount = rootColStats->getRowcount();
newUec = rootColStats->getTotalUec();
// If user specified selectivity for this predicate, we need to make
// adjustment in reduction to reflect that.
rootStatDesc->applySelIfSpecifiedViaHint(pred, rowCountBeforePreds);
// --------------------------------------------------------------------
// For histograms that belong to the same equivalence class:
// IF this is NOT an Outer Join keep only the root
// IF this is an Outer Join, the original histograms need to be
// replaced by copies of the root; they will be null-augmented.
//
// This process is complicated by our prior separation of left-right.
// --------------------------------------------------------------------
//If under a or we did not merge so should not remove.
CollIndex rightCount = 0;
CollIndex leftCount = 0;
if ( exprOpCode != ITM_OR )
{
rightCount = rightStatsToMerge.entries();
leftCount = leftStatsToMerge.entries();
}
CollIndex nextToMerge;
// Walk the list backwards to avoid invalidating entries in the
// statsToMerge sets
while ( rightCount > 0 || leftCount > 0 )
{
if ( rightCount > 0 && leftCount > 0 )
{
if ( rightStatsToMerge[rightCount-1] > leftStatsToMerge[leftCount-1] )
{
nextToMerge = rightStatsToMerge[rightCount-1];
rightCount--;
}
else
{
nextToMerge = leftStatsToMerge[leftCount-1];
leftCount--;
}
}
else if ( rightCount > 0 )
{
nextToMerge = rightStatsToMerge[rightCount-1];
rightCount--;
}
else // leftCount > 0
{
nextToMerge = leftStatsToMerge[leftCount-1];
leftCount--;
}
if ( nextToMerge != rootIndex )
{
if ( mergeMethod == OUTER_JOIN_MERGE &&
nextToMerge >= numOuterColStats )
{
ColStatsSharedPtr updateColStats =
(*this)[nextToMerge]->getColStatsToModify();
updateColStats->overwrite( *rootColStats );
// Avoid having duplicate entries in the nonVegEquals_ of
// the root and the updated ColStatDesc.
if ( (*this)[nextToMerge]->nonVegEquals().entries() > 0 )
{
(*this)[nextToMerge]->nonVegEquals().clear();
}
}
else
{
removeAt( nextToMerge );
// maintain the numOuterColStats value when removing
// column statistics from the outer table's list.
if ( nextToMerge < numOuterColStats )
numOuterColStats--;
}
}
} // while
// $$$ WE NO LONGER WANT TO NORMALIZE HISTOGRAMS AFTER EVERY
// $$$ PREDICATE!
//
// Instead, we will normalize all of the histograms' rowcounts after
// we have applied all of the predicates.
return TRUE;
} // ColStatDescList::applyPred
// -----------------------------------------------------------------------
// ColStatDescList::applyDefaultPred
// A CSDL::estimateCardinality() subroutine
//
// This routine deals with all those predicates the two earlier routines
// don't know how to deal with.
//
// NOTE: It is Presumed that this routine is only called with predicates
// for which a default selectivity should be applied.
//
// NOTE: as elsewhere, it is assumed that if a predicate can be pushed
// down to one instance of a column's histogram it has been pushed down to
// all 'identical' instances of that column's histograms.
//
// I.e., if default selectivity was applied to 1 instance of some
// histogram it has been applied to all same-group instances of that
// histogram to which it can be applied.
//
// NOTE: One oddity of this routine is that if the histogram impacted by a
// default predicate is 'real', default selectivity is applied once for
// each distinct predicate. However, if the histogram is 'fake', default
// selectivity is only applied once for each Type of predicate.
//
// FUTURE WORK: Eventually, it ought to be possible to associate different
// default selectivity with different joins. Right now, all default joins
// apply the same default selectivity.
//
// This is the new version of this function which facilitates applying all
// predicates at once.
// -----------------------------------------------------------------------
// $$$ difference between this routine and the original :
//
// 1. When the predicate in question has not been previously applied, only
// apply it to the histogram it affects, and then DO NOT normalize all
// histograms to have that resulting rowcount
//
// 2. In the case of a global predicate (one that does not apply to any
// particular histogram), we use the 'globalPredicateReduction' out
// parameter to communicate with the calling routine that we need to apply
// a global reduction to all histograms --> but LATER
void
ColStatDescList::applyDefaultPred (ItemExpr *pred,
CostScalar & globalPredicateReduction,
OperatorTypeEnum exprOpCode,
CostScalar *maxSelectivity)
{
// -------------------------------------------------------------------
// If any operand of this default predicate is an ITM_HOSTVAR *and* if
// that ITM_HOSTVAR isSystemGenerated(), or it is a selectivity param,
// then this predicate is to be treated (for purposes of estimating
// selectivity) as a no-op.
//
// Note Selectivity Params are processed in
// ColStatDescList::applyVEGPred() and ColStatDescList::applyPred().
//
// In ColStatDescList::applyPred(), predicates containing selectivity
// params are identified through the virtual method
// SelParameter::castToConstValue().
// -------------------------------------------------------------------
for ( Int32 arity = 0; arity < pred->getArity(); arity++ )
{
const ItemExpr * operand = (*pred)[arity].getPtr();
if ( (operand->getOperatorType() == ITM_HOSTVAR) &&
((HostVar *) operand)->isSystemGenerated() ||
(
operand->getOperatorType() == ITM_CACHE_PARAM &&
operand->castToSelParameter()
)
)
{
return; // no further processing needed.
}
}
const ValueId & origPredValueId = pred->getValueId();
CostScalar defaultSel = csOne; // starting assumptions
NABoolean alreadyApplied = FALSE;
NABoolean globalPredicate = TRUE;
NABoolean statsExist = FALSE;
OperatorTypeEnum op = pred->getOperatorType();
if (op == ITM_ONE_ROW || op == ITM_ONE_TRUE || op == ITM_ANY_TRUE)
{
pred = pred->child(0);
op = pred->getOperatorType();
}
NABoolean isAFalsePred = FALSE;
if(op == ITM_CONSTANT)
{
const ConstValue * constPred = (ConstValue *)pred->castToItemExpr();
NAString constPredStr(constPred->getRawText()->data());
constPredStr = constPredStr.strip(NAString::both);
if(constPredStr == "0.")
isAFalsePred = TRUE;
}
// fix soln 10-080605-3680: maxSelectivity(false) should be 0
if (maxSelectivity && (op == ITM_RETURN_FALSE || isAFalsePred))
{
*maxSelectivity = MINOF(csZero, *maxSelectivity);
}
if (CmpCommon::getDefault(COMP_BOOL_107) == DF_ON)
{
if ( pred->synthSupportedOp() )
alreadyApplied = pred->applyDefaultPred
(*this, exprOpCode, origPredValueId, globalPredicate,
maxSelectivity);
else
{
// no need to pass maxSelectivity here because
// maxSel(unsupported default predicate) == 1.0
alreadyApplied = pred->applyUnSuppDefaultPred
(*this, origPredValueId, globalPredicate);
}
}
else
{
//
// CASE 1 : predicate supports synthesis
//
if ( pred->synthSupportedOp() ) // does the operator support synthesis??
{
// get the histogram on which the predicate is to be applied
ValueId column;
// leftColIndex contains the position of the left histogram whose statistics
// will be used for computing selectivity.
// In case the left child contains more than one columns,
// it would be the position of histogram with max UEC amongst left child
CollIndex leftColIndex;
// following two sets contain the leaf values of the respective children
ValueIdSet leftLeafValues;
ValueIdSet rightLeafValues;
// This boolean will be set to TRUE if it is an equality predicate with more
// than one column involved and COMP_BOOL_40 is ON.
// When counting the number of columns, it takes a combined set of the
// columns from the left and the right children
NABoolean equiJoinWithExpr = FALSE;
if (op == ITM_VEG_PREDICATE)
{
// could be a VEG predicate with no children
CollIndexList statsToMerge(STMTHEAP);
// locate entries in this ColStatDescList that are associated
// with the current VEG predicate.
statsExist =
identifyMergeCandidates( pred, leftColIndex, statsToMerge );
}
else
{
if ( (op == ITM_EQUAL) &&
(CmpCommon::getDefault(COMP_BOOL_40) == DF_ON))
{
// if COMP_BOOL_40 == ON; for equality expressions involving
// more than one columns, we pick the column with Max UEC
ItemExpr *rightChild = pred->child(1);
ItemExpr *leftChild = pred->child(0);
rightChild->findAll(ITM_VEG_REFERENCE, rightLeafValues, TRUE, TRUE);
leftChild->findAll(ITM_VEG_REFERENCE, leftLeafValues, TRUE, TRUE);
// get leftColIndex for column with Max UEC. In case of predicates
// like a + 4 = 10, it will be the index for col 'a'
if ( ((rightLeafValues.entries() + leftLeafValues.entries()) > 1) &&
(rightLeafValues != leftLeafValues))
{
statsExist = getColStatDescIndexForColWithMaxUec(leftColIndex, leftLeafValues);
// if there are more than one columns involved in the expression, consider
// histogram with MaxUEC to compute selectivities. This would include cases
// where leftChild has two columns while right child is a constant.
// Example (col1 + col2) = 10; and col1 = col2 + col3; and Fn(col1) = Fn(col2).
// For predicates like col1 = 10 or col1 + 4 = 10
// use Max frequency as the selectivity
if ( (statsExist) &&
( (exprOpCode != REL_SCAN) ||
(CmpCommon::getDefault(COMP_BOOL_74) == DF_OFF) ) )
equiJoinWithExpr = TRUE;
} // equi-join
else
{
// Is a local predicate with column only on the left child
leftChild = leftChild->getLeafValueIfUseStats();
leftLeafValues.clear();
leftChild->findAll(ITM_BASECOLUMN, leftLeafValues, TRUE, TRUE);
column = leftChild->getValueId();
if (getColStatDescIndexForColumn(leftColIndex, column) )
statsExist = TRUE;
}
} // equal predicate and comp_bool_40 == DF_ON
else
{
// for all other predicates with arity > 0
if (pred->getArity() > 0)
{
ItemExpr * leftChild = pred->child(0);
leftChild = leftChild->getLeafValueIfUseStats();
column = leftChild->getValueId();
leftChild->findAll(ITM_BASECOLUMN, leftLeafValues, TRUE, TRUE);
if (getColStatDescIndexForColumn(leftColIndex, column) )
statsExist = TRUE;
} // end if predArity > 0
} // if non-equality-predicates or COMP_BOOL_40 == OFF
} // !ITM_VEG_PREDICATE
if (statsExist)
{
// Get the first column for which the histogram is found.
// multi-column histograms no longer are running around the system, but
// there are still predicates that reach here
//
// e.g., predicates involving aggregates (count(*) = 10)
// e.g., predicates on host variables (?p = 10)
//
// ... probably many more
// CASE 1a : VEG pred
if ( op == ITM_VEG_PREDICATE ) // Transitive closure predicate
{
// ----------------------------------------------------------
// This logic replicates a portion to the logic used in
// ColStatDescList::applyVEGPred to determine the parts of this
// VEGPred for which 'real' histogram manipulation was NOT done.
//
// Default selectivity should be applied *once* for *each* valid
// join that couldn't be executed because of its multi-column
// nature, but which would have otherwise been a valid join.
// (So long as an involved column hasn't already appeared in a
// default EQ-join.)
// ----------------------------------------------------------
// $$$ FCS_ONLY kludge -- this code should be reviewed and
// $$$ reconsidered post-FCS
//
// We reach this point when we're applying a predicate that looks
// like <col> = hostvar.
//
// We may reach this point in other instances, too. For now, we're
// not going to worry about that.
//
// For this situation, we set the rowcount to be rc/uec, and set
// uec to be 1. This reduction is exactly what you'd expect
// when applying an eqpred with a hostvar.
//
ColStatDescSharedPtr rootStatDesc = (*this)[leftColIndex];
ColStatsSharedPtr rootColStats = rootStatDesc->getColStatsToModify();
CostScalar oldUec = rootColStats->getTotalUec();
CostScalar oldRows = rootColStats->getRowcount();
CostScalar newRows = oldRows / oldUec;
// compute maxSelectivity before histograms get modified
if (maxSelectivity)
{
VEG* veg = ((VEGPredicate*)pred)->getVEG();
const ValueIdSet & values = veg->getAllValues();
// we must distinguish between "X=?" and "X=Y".
ItemExpr *cExpr = NULL;
if (!values.referencesAConstExpr(&cExpr))
{ // veg is an "X=Y" predicate
// maxSelectivity("X=Y") == 1.0
}
else // veg is an "X=?" predicate
{
// maxFreq = maxFrequency(X) for VEGPred "X=?"
// NB: "maxFreq = histograms.getMaxFreq(v);" may look
// attractive here. But, beware: it causes many maxCard
// tests to fail in compGeneral/test015 -- they get 0.
CostScalar maxFreq = csMinusOne;
for (ValueId v = values.init();
values.next(v);
values.advance(v))
{
if (v.getItemExpr()->getOperatorType() ==
ITM_BASECOLUMN)
{
maxFreq = getMaxFreq(v);
if (maxFreq > csMinusOne)
break;
}
}
// maxSelectivity("X=?") == max frequency(X) / total rows
if (maxFreq > csMinusOne &&
rootColStats->getRowcount() > csZero)
*maxSelectivity =
MINOF(maxFreq / rootColStats->getRowcount(),
*maxSelectivity);
} // veg is an "X=?" predicate
} // maxSelectivity != NULL
rootStatDesc->synchronizeStats(
oldRows,
newRows,
ColStatDesc::SET_UEC_TO_ONE
);
alreadyApplied = TRUE;
} // 1a: veg pred
// CASE 1b : equal, not equal
else if ( ( op == ITM_EQUAL ) || ( op == ITM_NOT_EQUAL ) )
{
ColStatDescSharedPtr leftStatDesc = (*this)[leftColIndex];
if (equiJoinWithExpr)
{
// Control comes here for equality predicates with more
// than one column involved. The columns could be from
// both children or from only left child
globalPredicate = FALSE; // not a global predicate
if (NOT(leftStatDesc->isPredicateApplied(origPredValueId) ) )
{
CostScalar fudgeFactorForAggFn ((ActiveSchemaDB()->getDefaults()).getAsDouble(COMP_FLOAT_6));
// rightLeafValues > 0 and leaftLeafValues > 0 or <col1> = <col2>
// col1 = Fn(col2, col3) OR Fn(col1, col2) = col3
// selectivity is equal to 1/MAXUEC of the columns
// participating in the query
// get histograms from right child. In case there are
// more than one columns in the right child, get the histogram
// with max UEC
ColStatsSharedPtr rightColStat;
CollIndex rightColIndex;
ColStatsSharedPtr leftColStat = leftStatDesc->getColStats();
NABoolean leftColStatReal = !leftColStat->isOrigFakeHist();
NABoolean rightColStatReal = FALSE;
// UEC of the children child and maxUEC
CostScalar rightUec = csMinusOne;
CostScalar leftUec = leftColStat->getTotalUec();;
// If we reached here, it is guaranteed that we have histograms for the
// left child. But, we cannot say anything for sure for the right child.
// Hence check if there exists any column in the right side of the equality
// predicates.
// if rightside has any column, get the histograms for the right child
if ((rightLeafValues.entries() > 0) &&
(getColStatDescIndexForColWithMaxUec(rightColIndex, rightLeafValues)) )
{
// out of all these columns, pick the one with Max UEC. While doing
// that it also makes sure that it is comparing the default UEC to
// default UEC and actual UEC to actual one of both children
rightColStat = (*this)[rightColIndex]->getColStats();
rightUec = rightColStat->getTotalUec();
rightColStatReal = !rightColStat->isOrigFakeHist();
} // if getColIndex for rightLeafValues
// check for aggregate function for both children
// and adjust UECs accordingly
// We would have ideally like to compute selectivity for
// aggregate functions by considering UEC for only those
// columns which are a children of Aggregate functions.
// Because of time constraint and not being able to find
// an inexpensive way to handle that, we are now taking the
// cardinality of the column with MAX UEC from the child
// and in case of an aggregate, multiply with a fudge factor
// Hence there could be cases where for predicates like
// "a + max(b) = c", we might pickup col 'a' as that has the
// highest UEC and multiple cardinality of that by the fudge
// factor. If columns 'a' and 'b' belog to the same table,
// or have already been joined, it would not matter which
// column we pickup, but in some cases it might poor estimates
if (pred->child(0)->containsAnAggregate())
{
leftUec = (leftUec * fudgeFactorForAggFn).minCsOne();
}
if (rightColStat && pred->child(1)->containsAnAggregate())
{
rightUec = (rightUec * fudgeFactorForAggFn).minCsOne();
}
// To get the maxUec for selectivity, we don't want to compare
// real UEC with the fake one. Hence, also check for fakeness
// before comparing
if (leftColStatReal && !rightColStatReal)
defaultSel = csOne/leftUec;
else
if (rightColStatReal && !leftColStatReal)
defaultSel = csOne/rightUec;
else
defaultSel = csOne/MAXOF(leftUec, rightUec);
if( (pred->child(0)->getOperatorType() == ITM_DAYOFWEEK) ||
(pred->child(1)->getOperatorType() == ITM_DAYOFWEEK) )
defaultSel = csOne/7;
// Since the histograms have not been merged, we don't know which
// one will be finally picked up later for parent node, based on
// the characteristics output. Hence set the aggregate information
// of all correctly. Rowcount and UEC for all will be done automatically
// during synchronizeStats. appliedPreds is the only that needs to be
// correctly set
ValueIdSet columnWithPreds(leftLeafValues);
columnWithPreds.insert(rightLeafValues);
addToAppliedPredsOfAllCSDs( columnWithPreds, origPredValueId );
// This will modify the rowcount, which should be done for only
// one histogram. Remaining histograms will be synchronized later
leftStatDesc->applySel( defaultSel );
} // NOT(isPerdicateApplied)
else
{
// predicate has already been applied
alreadyApplied = TRUE;
}
} // equiJoinWithExpr
else
{
// go the usual way. Control comes here for equality predicates, if
// COMP_BOOL_40 is OFF or for cases where we have a local predicate
// wih one column in the left child.
////////////////////////////////////////////////////////////////////
// do not need to worry about selectivity constant parameters here
// because this branch is for predicates with ORs: a=? OR b=?. Each
// predicate factor such as a=? is processed once by this function.
// Since OR predicate is cacheable but not parameterizable, it is
// impossible to have a selectivity constant parameter situation in
// the item expression tree. That is, there is no need to search
// for a selectivity constant parameter and to use its selectivity
// value instead.
////////////////////////////////////////////////////////////////////
if (leftLeafValues.entries() == 1)
{
// First Question: is the predicate of the form
// "<col> <op> <expression>" or "<expression> <op> <col>"?
//
// Earlier logic places stand-alone columns on the left, so look
// for a histogram associated with the left-hand valueId.
//
// Of course, the trick here is that the following works even if
// the left-hand ValueId isn't for a column.
globalPredicate = FALSE; // not a global predicate
if ( NOT ( leftStatDesc->isPredicateApplied( origPredValueId ) ) )
{ // first time for this histogram
rightLeafValues.clear();
ItemExpr * rhs = pred->child(1);
rhs = rhs->getLeafValueIfUseStats();
rhs->findAll( ITM_BASECOLUMN, rightLeafValues, TRUE, TRUE );
// There is only one column in the predicate or there is one
// column on the right hand side of the predicate and
// this column is same as the left side column, use Uec
// of the column instead of using default statistics
if (( rightLeafValues.isEmpty() ) ||
( (rightLeafValues.entries() == 1) && (leftLeafValues == rightLeafValues ) ) )
{
// <col> <op> <constant> or <col> <op> <col>,
// where the left and the right col are same
// If this is a 'fake' histogram, the selectivity
// assumed is the default selectivity associated
// with the current predicate. (But, don't apply
// the same type of predicate multiple times.)
if ( leftStatDesc->getColStats()->isFakeHistogram() )
{
defaultSel =
(leftStatDesc->isSimilarPredicateApplied( op ) ?
csOne : pred->defaultSel() );
}
else
{ // not a 'fake histogram'
// Determine the rowcount of the non-NULL value
// with the greatest/least rowcount.
ColStatsSharedPtr colStatsP = leftStatDesc->getColStats();
HistogramSharedPtr histP = colStatsP->getHistogram();
const CostScalar & rowRedF = colStatsP->getRedFactor();
const CostScalar & rowCountBeforePred = (colStatsP->getRowcount()).minCsOne();
CostScalar maxRowCount = csOne;
CostScalar minRowCount = rowCountBeforePred;
CostScalar tmpRowCount;
CostScalar uec;
Interval iter = histP->getFirstInterval();
while ( iter.isValid() && !iter.isNull() )
{
// uec must be at least 1 for these
// calculations since we don't want to get
// a huge blowup in rowcount
if ( iter.getUec().isZero() )
{
iter.next();
continue; // avoid divide-by-zero!
}
CostScalar iRows = rowRedF * iter.getRowcount();
uec = (MINOF(iRows, iter.getUec())).minCsOne();
tmpRowCount =
iRows / uec;
if ( tmpRowCount > maxRowCount )
maxRowCount = tmpRowCount;
if ( tmpRowCount < minRowCount )
minRowCount = tmpRowCount;
iter.next();
} // end while iter() is valid
if ( op == ITM_EQUAL )
{
defaultSel = maxRowCount / rowCountBeforePred;
if (maxSelectivity)
{
// maxSelectivity(x=?) == max frequency / total rows
*maxSelectivity =
MINOF(defaultSel,
*maxSelectivity);
} // maxSelectivity != NULL
}
else
{
// With ITM_NOT_EQUAL, and no Histogram, avoid
// setting defaultSel to zero:
CostScalar numer;
CostScalar denom;
if ( minRowCount == rowCountBeforePred )
{
numer = MAXOF( rowCountBeforePred, csTwo ) - csOne;
denom = MAXOF( rowCountBeforePred, csTwo );
defaultSel = numer / denom;
}
else
{
numer = minRowCount;
denom = rowCountBeforePred;
defaultSel = csOne - ( numer / denom );
// maxSelectivity(x<>?) == 1.0
// which means do nothing here because 1.0 has
// already been set as the default maxSelectivity
// just before the estimateCardinality() call.
}
} // op == ITM_NOT_EQUAL
} // not a 'fake histogram'
} // The Operand is a constant expression.
else // i.e., NOT leafValues.isEmpty()
{
// <col1> <op> <col2>, or <col1 + col2> <op> <col3>
// or <col1> <op> <col2 + col3>
// The operand involves more than one column, which
// makes this an equality join that we are not now
// able to evaluate.
// Note that the current predicate-based defaultSel
// routine is not used in this case.....
if ( op == ITM_EQUAL )
{
if (leftStatDesc->isSimilarPredicateApplied( op ) )
defaultSel = csOne;
else
{
if ( (rightLeafValues.entries() == 1) &&
(exprOpCode != REL_SCAN) )
{
// <col1> = <col2>
// we already know that left side has one column. This is the case
// col1 Join col2
CollIndex rightColIndex;
if (getColStatDescIndexForColumn(rightColIndex, rhs->getValueId()) )
{
ColStatDescSharedPtr rightStatDesc = (*this)[rightColIndex];
ColStatsSharedPtr leftColStats = leftStatDesc->getColStats();
ColStatsSharedPtr rightColStats = rightStatDesc->getColStats();
CostScalar maxUec = (MAXOF(leftColStats->getTotalUec(),
rightColStats->getTotalUec())).minCsOne();
defaultSel = csOne/maxUec;
} // colStat for column found
else
{
// histogram does not exist use default selectivity for Join equal
defaultSel = CostPrimitives::getBasicCostFactor( HIST_DEFAULT_SEL_FOR_JOIN_EQUAL );
}
} // rightLeafValueEntries = 1
else
{
// right side has more than one columns, or it is a scan.
// Use default join equal selectivity for <col1> = <col2, col3>
// and hist_no_stats_uec for scan
if (exprOpCode == REL_SCAN)
defaultSel = (1.0/CURRSTMT_OPTDEFAULTS->defNoStatsUec());
else
defaultSel = CostPrimitives::getBasicCostFactor( HIST_DEFAULT_SEL_FOR_JOIN_EQUAL );
}
}
}
else
{ // op == ITM_NOT_EQUAL. Apply default selectivity
if (leftStatDesc->isSimilarPredicateApplied( op ) )
defaultSel = csOne;
else
{
if (exprOpCode == REL_SCAN)
defaultSel = 1 - (1.0/CURRSTMT_OPTDEFAULTS->defNoStatsUec() );
else
defaultSel = (1 - CostPrimitives::getBasicCostFactor( HIST_DEFAULT_SEL_FOR_JOIN_EQUAL ) );
}
}
} // NOT leafValue.isEmpty()
leftStatDesc->addToAppliedPreds( origPredValueId );
leftStatDesc->applySel( defaultSel );
}
else
{ // predicate has already been applied
alreadyApplied = TRUE;
}
} // column is leading prefix of histogram
} // 1b: equal, not equal
} // !equiJoinWithExpr
// CASE 1c : less, less_eq, greater, greater_eq
else if ( op == ITM_LESS
OR op == ITM_LESS_EQ
OR op == ITM_GREATER
OR op == ITM_GREATER_EQ
)
{
ItemExpr * rhs = pred->child(1);
rhs = rhs->getLeafValueIfUseStats();
// First Question: is the predicate of the form
// "<col> <op> <expression>" or "<expression> <op> <col>"?
globalPredicate = FALSE; // not a 'global' predicate
ColStatDescSharedPtr statDesc = (*this)[leftColIndex];
if ( NOT ( statDesc->isPredicateApplied( origPredValueId ) ) )
{ // first time for this histogram
defaultSel = statDesc->selForRelativeRange (op, column, rhs);
// defaultSel is one for range predicates on char or varchar
// column types.
// if a similar predicate has already been applied to this
// histogram, then we don't want to reduce the rowcount and
// uec further. Therefore we return the selectivity equal to
// one. Following two predicates are said to be similar:
// 1. < and <=
// 2. > and >=
// 3. If the two range predicates are derived from LIKE predicate
rightLeafValues.clear();
rhs->findAll( ITM_BASECOLUMN, rightLeafValues, TRUE, TRUE );
// There is only one column in the predicate or there is one
// column on the right hand side of the predicate and
// this column is same as the left side column, use Uec
// of the column instead of using default statistics
if (( rightLeafValues.isEmpty() ) ||
( (rightLeafValues.entries() == 1) && (leftLeafValues == rightLeafValues ) ) )
{
// pred <col> operatort <expression>
if ( defaultSel == csOne )
{
if (statDesc->derivOfLikeAndSimilarPredApp(pred) ||
statDesc->isSimilarPredicateApplied( op ) )
defaultSel = csOne;
else
{
BiRelat *br = (BiRelat *) pred;
if (br->derivativeOfLike())
defaultSel = br->getLikeSelectivity();
else
defaultSel = pred->defaultSel();
} // else statDesc->similarPredApplied
} // if defaultSel == csOne
} // if rightLeafValues.entries > 1 or leftLeafValues != rightLeafValues
else
{
// not a leaf value. Predicate is <col> operator <col>
defaultSel =
( statDesc->isSimilarPredicateApplied( op ) ?
csOne : CostPrimitives::getBasicCostFactor( HIST_DEFAULT_SEL_FOR_JOIN_RANGE ) );
} // end of join range
statDesc->addToAppliedPreds( origPredValueId );
statDesc->applySel( defaultSel );
}
else
{
alreadyApplied = TRUE;
}
} // 1c: less, less_eq, greater, greater_eq
// CASE 1d : null, not null, unknown, not unknown
else if ( op == ITM_IS_NULL
OR op == ITM_IS_NOT_NULL
OR op == ITM_IS_UNKNOWN
OR op == ITM_IS_NOT_UNKNOWN
)
{
globalPredicate = FALSE; // not a 'global' predicate
ColStatDescSharedPtr statDesc = (*this)[leftColIndex];
if ( NOT ( statDesc->isPredicateApplied( origPredValueId ) ) )
{ // first time for this histogram
defaultSel = ( statDesc->isSimilarPredicateApplied( op ) ?
csOne : pred->defaultSel() );
statDesc->addToAppliedPreds( origPredValueId );
statDesc->applySel( defaultSel );
}
else
{
alreadyApplied = TRUE;
}
} // 1d: null, not null, unknown, not unknown
// CASE 1e : or, and
else if ( ( op == ITM_OR) || ( op == ITM_AND ) )
{
// Don't do anything with this predicate right here, right now.
alreadyApplied = TRUE;
} // op is AND, or OR
// CASE 1f : should never occur!
else
{
DCMPASSERT( FALSE ); // unexpected condition!
alreadyApplied = TRUE;
}
} // if !alreadyApplied
} // if statsExist
//
// CASE 2 : predicate does not support synthesis
//
else
{
if ( pred->getArity() > 0 )
{
NABoolean isOpTypeNot = FALSE;
ItemExpr * tempPred;
OperatorTypeEnum tempOp;
ValueIdSet leftLeafValues;
ItemExpr * leftChild = pred->child(0);
if(pred->getOperatorType() == ITM_NOT)
{
isOpTypeNot = TRUE;
tempOp = leftChild->getOperatorType();
tempPred = leftChild;
leftChild = leftChild->child(0);
}
else
{
tempOp = op;
tempPred = pred;
}
leftChild = leftChild->getLeafValueIfUseStats();
leftChild->findAll(ITM_BASECOLUMN, leftLeafValues, TRUE, TRUE);
if (!leftLeafValues.isEmpty())
{
// First Question: is the predicate of the form
// "<col> <op> <expression>" or "<expression> <op> <col>"?
// Earlier logic places stand-alone columns on the left, so look
// for a histogram associated with the left-hand valueId.
//
CollIndex leftColIndex;
if (getColStatDescIndexForColumn(leftColIndex, leftChild->getValueId()) )
{
globalPredicate = FALSE; // not a 'global' predicate
ColStatDescSharedPtr statDesc = (*this)[leftColIndex];
if ( NOT ( statDesc->isPredicateApplied( origPredValueId ) ) )
{
// first time for this histogram
// if the predicate is a LIKE predicate with no wild cards
// in the pattern. And for some reason could not be transformed
// into an equality predicate, then set its selectivity equal to
// 1/UEC, else go the usual way
if ( (tempOp == ITM_LIKE) &&
((Like *)tempPred)->isPatternAStringLiteral())
{
ColStatsSharedPtr colStat = statDesc->getColStats();
if (colStat->isFakeHistogram())
defaultSel = 1.0/(CURRSTMT_OPTDEFAULTS->defNoStatsUec()) ;
else
{
CostScalar tempUec = colStat->getTotalUec();
//To guard against div-by-zero assertion
if(tempUec == csZero)
tempUec = 1;
defaultSel = 1.0/tempUec.value();
defaultSel.maxCsOne();
}
if(isOpTypeNot)
defaultSel = 1 - defaultSel.value();
}
else
defaultSel = pred->defaultSel();
statDesc->addToAppliedPreds( origPredValueId );
statDesc->applySel( defaultSel );
} // NOT isPredicateApplied
else
{
alreadyApplied = TRUE;
}
} // column is leading prefix of histogram
}
} // if ( pred->getArity() > 0 )
else
{
defaultSel = pred->defaultSel();
}
}
}
// maxSelectivity computation is done
if (maxSelectivity) return;
//
// we do the following whether the predicate supports synthesis or not
//
if ( globalPredicate )
{
if(pred->isSelectivitySetUsingHint())
{
defaultSel = pred->getSelectivityFactor();
(*this)[0]->getColStats()->setSelectivitySetUsingHint();
}
else
// not a local predicate, yet still a default predicate
// ==> should mean: No statistics.
defaultSel = pred->defaultSel();
// *******************************************************************
// $$$ this code should go away sooner or later (when the normalizer /
// constant-folding do everything they should ...)
//
// until then, handle a few simple braindead cases here that should
// already have been taken care of
if ( pred->getArity() == 2 )
{
NABoolean negate = FALSE;
ConstValue * lhs = pred->child(0)->castToConstValue( negate );
ConstValue * rhs = pred->child(1)->castToConstValue( negate );
if ( lhs != NULL && rhs != NULL )
{
EncodedValue left ( lhs, negate );
EncodedValue right( rhs, negate );
if ( left == right )
{
OperatorTypeEnum op = pred->getOperatorType();
switch ( op )
{
case ITM_EQUAL: // 1 == 1
case ITM_LESS_EQ: // 1 <= 1
case ITM_GREATER_EQ: // 1 >= 1
return; // pred has no effect on selectivity!
case ITM_NOT_EQUAL: // 1 <> 1
case ITM_GREATER: // 1 > 1
case ITM_LESS: // 1 < 1
defaultSel = csZero ; // pred removes all rows!
break;
default:
break;
}
}
}
} // $$$ end of stuff that should someday be removed ...
// *******************************************************************
} // globalPredicate
// $$$ WE NO LONGER WANT TO NORMALIZE HISTOGRAMS AFTER EVERY
// $$$ PREDICATE!
//
// Instead, we will normalize all of the histograms' rowcounts after
// we have applied all of the predicates.
//
// However, in the case of a global predicate (read: one that does not
// apply to any particular histogram or histograms), we apply its
// selectivity to all ColStatDesc's.
if ( NOT alreadyApplied && globalPredicate ) // don't redo what's already been done.
{
if(isAFalsePred)
defaultSel = csZero;
// Don't apply the selectivity here! Instead, we return it to the
// calling function (via the out-parameter globalPredicateReduction) ;
// later on, we will go through the histograms and apply it to all
// of them, at the same time that we apply all of the predicate
// selectivities to all of them (end of estimateCardinality).
//
// NOTE: if we apply this selectivity here, to all histograms, the
// end result is that it appears that each histogram had a separate
// _x_ reduction applied to it, so the total result of the predicate
// will be _x_^n, where n is the number of histograms!
globalPredicateReduction = defaultSel;
}
else
{
globalPredicateReduction = csOne;
}
} //ColStatDescList::applyDefaultPred
// -----------------------------------------------------------------------
// ColStatDescList::getMaxFreq
// get maximum frequency for the given col
//
// -----------------------------------------------------------------------
CostScalar
ColStatDescList::getMaxFreq(ValueId col)
{
CollIndex index;
NABoolean found = getColStatDescIndexForColumn( index, col );
// histogram not found, return
if ( found == FALSE )
return -1.0;
ColStatsSharedPtr colStats = (*this)[index]->getColStats();
CostScalar freq = colStats->getMaxFreq();
if (freq <= csZero)
{
ColStatsSharedPtr colStatsModifiable = (*this)[index]->getColStatsToModify();
colStatsModifiable->computeMaxFreqOfCol(TRUE);
freq = colStatsModifiable->getMaxFreq();
}
return freq;
}
CostScalar ColStatDescList::getUEC(ValueId col)
{
CollIndex index;
NABoolean found = getColStatDescIndexForColumn( index, col );
// histogram not found, return 1
if ( found == FALSE )
return 1.0;
ColStatsSharedPtr colStats = (*this)[index]->getColStats();
CostScalar uec = colStats->getTotalUec();
if (uec < 1.0)
uec = 1.0;
return uec;
}
// -----------------------------------------------------------------------
// ColStatDescList::getMaxOfMaxFreqOfCol
// get maximum frequency for the given column set
//
// -----------------------------------------------------------------------
CostScalar ColStatDescList::getMaxOfMaxFreqOfCol(const ValueIdSet & baseColSet)
{
CostScalar maxFreq = csZero;
CostScalar freq = csZero;
for (ValueId col = baseColSet.init();
baseColSet.next(col);
baseColSet.advance(col))
{
if (col == NULL_VALUE_ID)
continue;
ColStatsSharedPtr colStat = this->getColStatsPtrForColumn (col);
ColAnalysis * colAnalysis = col.colAnalysis();
// for higher way joins, if the base column does not appear as
// characteristic output, its histogram will not be cached. In that
// case we shall use the total frequency when that histogram last appeared
// in the list
if (colStat == NULL)
{
if (colAnalysis &&
(colAnalysis->getMaxFreq() != csMinusOne) )
{
freq = colAnalysis->getMaxFreq();
if (freq > maxFreq)
maxFreq = freq;
}
else
{
// if there is no way we can get the UEC for partitioning
// column, set it equal to row count, and don't bother to
// look at other columns
maxFreq = csZero;
break;
}
}
else
{
if (colStat->isOrigFakeHist())
{
maxFreq = csZero;
break;
}
freq = this->getMaxFreq(col);
if (freq > maxFreq)
maxFreq = freq;
}
}
return maxFreq;
}
// -----------------------------------------------------------------------
// ColStatDescList::getMinOfMaxFreqOfCol
// get maximum frequency for the given column set
//
// -----------------------------------------------------------------------
CostScalar ColStatDescList::getMinOfMaxFreqOfCol(const ValueIdSet & baseColSet)
{
CostScalar minFreq = COSTSCALAR_MAX;
CostScalar freq = csOne;
if (baseColSet.entries() == 0)
return csZero;
for (ValueId col = baseColSet.init();
baseColSet.next(col);
baseColSet.advance(col))
{
if (col == NULL_VALUE_ID)
continue;
ColStatsSharedPtr colStat = this->getColStatsPtrForColumn (col);
ColAnalysis * colAnalysis = col.colAnalysis();
// for higher way joins, if the base column does not appear as
// characteristic output, its histogram will not be cached. In that
// case we shall use the total frequency when that histogram last appeared
// in the list
if (colStat == NULL)
{
if (colAnalysis &&
(colAnalysis->getMaxFreq() > csZero) )
{
freq = colAnalysis->getMaxFreq();
if (freq < minFreq)
minFreq = freq;
}
else
{
// if there is no way we can get the UEC for partitioning
// column, set it equal to row count, and don't bother to
// look at other columns
minFreq = csZero;
break;
}
}
else
{
if (colStat->isOrigFakeHist())
{
minFreq = csZero;
break;
}
freq = this->getMaxFreq(col);
if (freq < minFreq)
minFreq = freq;
}
}
return minFreq;
}
// Max frequencies are computed slightly differently for Case expressions
// here if one of the leaves happens to be a constant, then we shall
// assume the frequency to be one. A leaf value is the result of if-then-else
// Example: if <condition> then <leaf1> else <leaf 2>
CostScalar
ColStatDescList::getMaxFreqForCaseExpr(const ValueIdSet & leafValues)
{
if(leafValues.entries() == 0)
{
CCMPASSERT ( leafValues.entries() > 0 );
// In absence of leaf values, it is not possible to calculate max freq.
// Return zero to indicate uniform distribution.
return csZero;
}
CostScalar maxFreq = csZero;
CostScalar freq = csZero;
for (ValueId id = leafValues.init(); leafValues.next(id); leafValues.advance(id))
{
if (id.getItemExpr()->doesExprEvaluateToConstant(FALSE))
{
maxFreq = csOne;
break;
}
else
{
freq = getMaxOfMaxFreqOfCol(id);
if (freq > maxFreq)
maxFreq = freq;
}
}
// Max out the number of leaves to 5, to avoid getting very low frequencies.
CostScalar maxFreqOfCaseExpr = csOne;
double numOfLeaves = (double)(MINOF(leafValues.entries(), 5));
// To avoid div-by-zero exception
numOfLeaves = MIN_ONE(numOfLeaves);
maxFreqOfCaseExpr = maxFreq * (1/numOfLeaves);
return maxFreqOfCaseExpr;
}
// -----------------------------------------------------------------------
// ColStatDescList::getMinUec
//
// Returns the minimum UEC from the given column set
// -----------------------------------------------------------------------
CostScalar ColStatDescList::getMinUec(const ValueIdSet & baseColSet) const
{
CostScalar uec = csMinusOne;
CostScalar minUec = COSTSCALAR_MAX;
// This is an error condition, which would later assume uniform distribution
if (baseColSet.entries() == 0)
return csMinusOne;
for (ValueId col = baseColSet.init();
baseColSet.next(col);
baseColSet.advance(col))
{
ColStatsSharedPtr colStat = this->getColStatsPtrForColumn (col);
// for higher way joins, if the base column does not appear as
// characteristic output, its histogram will not be cached. In that
// case we shall use the total UEC when that histogram last appeared
// in the list
if (colStat == NULL)
{
ColAnalysis * colAnalysis = col.colAnalysis();
if (colAnalysis &&
(colAnalysis->getFinalUec() != csZero) )
{
uec = colAnalysis->getFinalUec();
if (uec < minUec)
minUec = uec;
}
else
{
// if there is no way we can get the UEC for partitioning
// column, set it equal to row count, and don't bother to
// look at other columns
minUec = csMinusOne;
break;
}
}
else
{
uec = colStat->getTotalUec();
if (uec < minUec)
{
minUec = uec;
}
}
}
return minUec;
}
// -----------------------------------------------------------------------
// ColStatDescList::getMaxUec
//
// Returns the maximum UEC from the given column set. If histogram does
// not exist for any column, then return -1, as error condition.
// -----------------------------------------------------------------------
CostScalar ColStatDescList::getMaxUec(const ValueIdSet & baseColSet) const
{
CostScalar uec = csMinusOne;
CostScalar maxUec = csMinusOne;
// This is an error condition, which would later assume uniform distribution
if (baseColSet.entries() == 0)
return csMinusOne;
for (ValueId col = baseColSet.init();
baseColSet.next(col);
baseColSet.advance(col))
{
ColStatsSharedPtr colStat = this->getColStatsPtrForColumn (col);
// for higher way joins, if the base column does not appear as
// characteristic output, its histogram will not be cached. In that
// case we shall use the total UEC when that histogram last appeared
// in the list
if (colStat == NULL)
{
ColAnalysis * colAnalysis = col.colAnalysis();
if (colAnalysis &&
(colAnalysis->getFinalUec() > csZero) )
{
uec = colAnalysis->getFinalUec();
if (uec > maxUec)
maxUec = uec;
}
else
{
// if there is no way we can get the UEC for partitioning
// column, set it equal to row count, and don't bother to
// look at other columns. This would mean that if the partition
// function contains a constant, we will have maxUec = rowcount
maxUec = csMinusOne;
break;
}
}
else
{
if (colStat->isOrigFakeHist())
{
maxUec = csMinusOne;
break;
}
uec = colStat->getTotalUec();
if (uec > maxUec)
{
maxUec = uec;
}
}
}
return maxUec;
}
// -----------------------------------------------------------------------
// ColStatDescList::getMaxUecForCaseExpr
//
// Compute Max UEC based on the number of leaf expressions. Max
// UEC will be equal to max of ((number of leaf expressions)
// and (the max UEC of any columns in the leaf expressions)).
// -----------------------------------------------------------------------
CostScalar ColStatDescList::getMaxUecForCaseExpr(const ValueIdSet & leafValueSet) const
{
CostScalar maxUec = getMaxUec(leafValueSet);
// limit the maximum number of constants as a leaf in the expression to 5
maxUec = MAXOF(maxUec.getValue(), leafValueSet.entries());
return maxUec;
}
// -----------------------------------------------------------------------
// ColStatDescList::getAggregateUec
//
// Hopefully a useful method for various users of histograms, both
// internal and external.
// -----------------------------------------------------------------------
CostScalar ColStatDescList::getAggregateUec (const ValueIdSet & columns) const
{
CostScalar retval = csMinusOne;
// We first see if there's any multi-column information that exactly
// matches the column-list in question.
if ( getUecList() != NULL )
{
// The lookup method returns -1 if there is no multi-column uec
// information for the ValueIdSet parameter; otherwise, it returns
// those columns' multi-column uec.
retval = getUecList()->lookup( columns );
if ( retval.isGreaterThanZero() /*> csZero*/ )
{
// We know how many aggregate uec these columns had initially at
// the scan nodes, before any predicates were applied. Now we
// need to take those predicates into account.
CollIndex index;
CostScalar origUec, currUec;
for ( ValueId column = columns.init();
columns.next( column );
columns.advance( column ) )
{
origUec = getUecList()->lookup( column ); // get original single-column uec
NABoolean found = getColStatDescIndexForColumn( index, column );
// either orig or current single-column uec not found
if ( found == FALSE || origUec.isLessThanZero() /* <= csZero*/ )
continue; // we continue looping
currUec = (*this)[index]->getColStats()->getTotalUec();
if ( currUec > origUec ) // sanity check: should not increase the UEC!
continue;
retval *= currUec / origUec; // apply the reduction
}
return retval;
}
}
// If we reach this point in the code, then we weren't able to use any
// "true" multi-column information to answer the question. So we simply
// multiply the single-columns together to get a fudged aggregate
// multi-column uec number.
retval = csOne;
CollIndex index;
for ( ValueId column = columns.init();
columns.next( column );
columns.advance( column ) )
{
NABoolean found = getColStatDescIndexForColumn( index, column );
// if any of the columns can't be found, abort
if ( found == FALSE ) return csMinusOne;
// multiply the totaluec of the histogram that matches each column
retval *= (*this)[index]->getColStats()->getTotalUec();
}
return retval;
}
// ----------------------------------------------------------------------
// getColStatDescIndexForColWithMaxUec(leftColIndex, leftLeafValues)
// From the given ValueIdSet, the method returns the index of the histogram
// with max UEC
// ----------------------------------------------------------------------
NABoolean ColStatDescList::getColStatDescIndexForColWithMaxUec(
CollIndex & indexWithMaxUec, /* out */
const ValueIdSet & inputColumns /* in */
) const
{
if (inputColumns.entries() == 1)
{
ValueId vid;
inputColumns.getFirst(vid);
return getColStatDescIndexForColumn(indexWithMaxUec, vid);
}
// This flag is used to indicate that histogram exists for at least
// one of the columns on which the predicate is being applied
// It does not differentiate between the real and the default
// histogram
NABoolean statsExist = FALSE;
// This flag is set to TRUE if even one of the histogram for the
// column on which the predicate is being applied has real stats
NABoolean atleastOneRealHist = FALSE;
// This CostScalar will contain maximum UEC amongst all fake histograms
// if atleastOneRealHist flag is FALSE, or amongst all real histograms
// if the flag atleastOneRealHist flag is TRUE
CostScalar maxUec = csOne;
for (ValueId id = inputColumns.init();
inputColumns.next(id);
inputColumns.advance(id))
{
CollIndex index;
if (!(getColStatDescIndexForColumn(index, id) ) )
continue;
statsExist = TRUE;
ColStatsSharedPtr colStats = (*this)[index]->getColStats();
// UECs of fake histograms are not real,
// We have already found at least one histogram for which actual stats
// exist, hence skip the fake histograms for UEC comparison
if (colStats->isOrigFakeHist() && atleastOneRealHist)
continue;
// Does real stats exist for this histogram? If this is the first
// real histogram being encountered, then flush out whatever maxUec
// has been computed so far.
if (!atleastOneRealHist && !colStats->isOrigFakeHist())
{
maxUec = csOne;
atleastOneRealHist = TRUE;
}
// This point onwards, we are comparing
// either all default UECs or all real UECs
CostScalar uec = colStats->getTotalUec();
if (uec >= maxUec)
{
maxUec = uec;
indexWithMaxUec = index;
}
}
// if stats exist for even one column, return TRUE;
return statsExist;
}
// ------------------------------------------------------------------
// addToAppliedPredsOfAllCSDs
// Update appliedPred attribute for all histograms whose column
// information is passed in the ValueIdSet
// -------------------------------------------------------------------
void
ColStatDescList::addToAppliedPredsOfAllCSDs(const ValueIdSet & inputColumns,
const ValueId & newPredicate)
{
for (ValueId id = inputColumns.init();
inputColumns.next(id);
inputColumns.advance(id))
{
CollIndex index;
if (!(getColStatDescIndexForColumn(index, id) ) )
continue;
ColStatDescSharedPtr colStatDesc = (*this)[index];
colStatDesc->addToAppliedPreds(newPredicate);
// also set the shape changed flag to TRUE
colStatDesc->getColStats()->setShapeChanged(TRUE);
}
}
// -----------------------------------------------------------------------
// ColStatDescList::useMultiUecIfCorrelatedPreds
//
// A CSDL::estimateCardinality() subroutine
//
// Use multi-column uec to find the resulting rowcount if we are
// applying multiple predicates to the same table on columns which
// are highly correlated (i.e., functional dependencies, ...).
//
// helper fn : ValueIdHashFn(), used to create an associated list
// for <ValueId, CostScalar>, which proves very useful.
// -----------------------------------------------------------------------
void
ColStatDescList::useMultiUecIfCorrelatedPreds (
CostScalar & newRowcount, // in/out
const CostScalar & oldRowcount, // in
CollIndex predCount, // in : quick check : proceed if >=2
const CollIndexList &joinHistograms, // in : histograms used in MC Join
CollIndex startIndex, // in : 1st idx of CSDL to look at
CollIndex stopIndex, // in : idx of CSDL+1 to look at
NAHashDictionary<ValueId, CostScalar> & biLogicPredReductions)
{
if ( getUecList() == NULL )
return ;
if ( startIndex >= stopIndex ) // misuse of function, oh well
return ;
if ( predCount < 2 ) // fewer than 2 histograms changed, nothing to do
return ;
NABoolean largeTableNeedsStats = FALSE;
CostScalar adjRCBeforePreds = floor(oldRowcount.getValue());
CollIndexList predList(STMTHEAP); // the CSDL-indices of the predicate-applied histograms
CollIndex i;
for ( i = startIndex; i < stopIndex; i++ )
{
ColStatsSharedPtr thisColStats = (*this)[i]->getColStats();
// use ceil for the row count after preds on single column histograms
// and floor of the original rowcount
// to take care of costscalar rounding issues before doing the comparison
// to see if MCs should be used to uplift the cardinalities.
// use MC adjustment only if the adjusted rowcount from histogram after applying
// predicates is less than the adjusted row count before applying predicates
CostScalar adjHistRC = ceil(thisColStats->getRowcount().getValue());
if ( NOT joinHistograms.contains( i )
AND (adjHistRC < adjRCBeforePreds))
{
// Skip any histograms created for constants.
if ((NOT largeTableNeedsStats) AND
(thisColStats->isUpStatsNeeded() ) AND
!(thisColStats->isVirtualColForHist() ) )
largeTableNeedsStats = TRUE;
predList.insert( i ); // store the index, not the histogram
}
}
// should have already checked for this, but just to be sure ...
if ( (predList.entries() + biLogicPredReductions.entries()) < 2 )
return ;
NAHashDictionary<ValueId, CostScalar> predReductions // <ValueId, rowred> pairs
(&(ValueIdHashFn), 11, TRUE, HISTHEAP) ;
CostScalar highestReductionFromPreds = csOne;
for ( i = 0; i < predList.entries(); i++ )
{
ColStatDescSharedPtr tmpCSD = (*this)[ predList[i] ] ;
CostScalar scCard = tmpCSD->getColStats()->getRowcount();
const ValueIdSet & preds = tmpCSD->getMergeState();
// There could be cases when the null-instantiated column has participated
// in the join. This could be a case of left joins or in Union. In those
// cases, the mergeState is updated by the nulledExpression (don't know why?).
// But this should not have an impact on adjusting cardinalities for Scans
// So, in case of null_instantiated column, retrieve its child and use
// that to compute multi-col uec adjustment. Sol: 10-060609-7077 and 10-060607-7010
ValueIdSet baseColSet;
preds.findAllReferencedBaseCols(baseColSet);
const CostScalar rowRed = scCard / oldRowcount;
if(rowRed < highestReductionFromPreds)
highestReductionFromPreds = rowRed;
for ( ValueId id = baseColSet.init(); baseColSet.next( id ); baseColSet.advance( id ) )
{
ValueId * key = new (HISTHEAP) ValueId( id );
CostScalar * value = new (HISTHEAP) CostScalar( rowRed );
ValueId * result = predReductions.insert( key, value );
// all inserts should be successful; should not have the same
// ValueId in multiple histograms
//
// --> NOT TRUE! Outer joins can produce CSDL's which have 2
// histograms for a given column; however, in this case, it's
// generally (always?) the case that the 2nd instance of a
// histogram is the null-instantiated one, so we should be able
// to ignore it without any bad effects.
//
}
}
CostScalar highestReductionFromEqPreds = highestReductionFromPreds;
// Append the columns and the respective reductions from the bilogic
// predicates to the overall list of columns and reductions.
NAHashDictionaryIterator<ValueId, CostScalar> biLogicPredIter( biLogicPredReductions );
ValueId * biLogicPredColumn = NULL;
CostScalar * reduction = NULL;
CostScalar reductionFromBiLogicPreds = csOne;
for ( biLogicPredIter.getNext( biLogicPredColumn, reduction );
biLogicPredColumn != NULL && reduction != NULL;
biLogicPredIter.getNext( biLogicPredColumn, reduction ) )
{
reductionFromBiLogicPreds *= *(reduction);
// If the column already exists, multiply the bilogic reduction
// to the overall reduction of the column
if(predReductions.contains(biLogicPredColumn))
{
*(reduction) *= *(predReductions.getFirstValue(biLogicPredColumn));
predReductions.remove(biLogicPredColumn);
}
if(*(reduction) < highestReductionFromPreds)
highestReductionFromPreds = *(reduction);
ValueId * key = new (HISTHEAP) ValueId( *biLogicPredColumn );
CostScalar * value = new (HISTHEAP) CostScalar( *reduction );
ValueId * result = predReductions.insert( key, value );
}
// The following is the row count of the histogram encapsulating the most
// reducing predicate. This serves as the upper bound for the uplifted rowcount.
CostScalar minSingleColPredRC = csOne;
// updatedOldRowcount stores row count before any of the predicates
// being considered for correlation were applied.
CostScalar updatedOldRowcount = oldRowcount;
if(reductionFromBiLogicPreds != csZero)
{
updatedOldRowcount /= reductionFromBiLogicPreds;
minSingleColPredRC = highestReductionFromPreds * updatedOldRowcount;
}
else
minSingleColPredRC = highestReductionFromEqPreds * oldRowcount;
// Now we've got an association list of
// <columns we've applied predicates to, associated rowcount reduction>
// pairs.
//
// The question now becomes : do we have sufficient multi-column uec
// information to determine an adjustment to the rowcount (due to multiple
// predicates on highly-correlated columns)? If so, then the value
// of 'reductionAdjustment' should be applied to newRowcount; otherwise,
// we're done.
// When the newRowcount is zero, we give up the promotion
if(newRowcount.isExactlyZero())
return;
CostScalar reductionAdjustment;
NABoolean sufficientInformation =
uecList()->useMCUecForCorrPreds (predReductions, /* in/mod */
predCount, /* in : #param */
updatedOldRowcount, /* in */
newRowcount, /* in */
largeTableNeedsStats,
*this, /* in. Pass list of histograms to be used for displayMissingStatsWarnings */
reductionAdjustment); /* out */
// if we don't have sufficient MC-info, or there aren't any
// "highly-correlated" columns, then never mind
if(NOT sufficientInformation)
return;
CostScalar result = newRowcount * reductionAdjustment;
// sanity check : if our calculated result actually *reduced* the
// resulting rowcount (we are trying to increase it!), or if our
// calculated result is more than the rowcount before we applied these
// predicates (applying predicates should not increase rowcount!), then
// we screwed up somewhere : ergo, ignore these results and return.
if ( result < newRowcount)
return;
// High bound sanity check. The cardinality cannot be higher than, minimum
// single column predicate.
result = MINOF(result, minSingleColPredRC);
newRowcount = result; // #retval
}
// -----------------------------------------------------------------------
// ColStatDescList::addRecentlyJoinedCols
// Traverse the histogram list and collect the recently joined columns
// -----------------------------------------------------------------------
void
ColStatDescList::addRecentlyJoinedCols(CollIndex startIndex,
CollIndex stopIndex)
{
for (CollIndex i = startIndex ; i < stopIndex ; i++ )
{
ColStatsSharedPtr thisColStats = (*this)[i]->getColStats();
// skip any histograms created for constants
if ( !(thisColStats->isVirtualColForHist() ) &&
thisColStats->isRecentJoin() )
{
// first Join set the joined cols in joinStatDescList, which will be
// used later to set the min cardinality for join
// for left joins and Unions, the columns in the merged state could be
// hidden by another expression. Hence extract the base column from it
// Statistics is not affected by the extra expression.
ValueIdSet mergedState = (*this)[i]->getMergeState();
ValueIdSet baseColSet;
mergedState.findAllReferencedBaseCols(baseColSet);
this->addToJoinedCols(baseColSet);
}
}
}
// -----------------------------------------------------------------------
// ColStatDescList::useMultiUecIfMultipleJoins
//
// A CSDL::estimateCardinality() subroutine
//
// Use multi-column uec to find the resulting rowcount from a
// multi-column join between two tables, if possible.
//
// We need the "oldRowcount" parameter in order to determine the
// row reduction for each join Histogram. The rowRedFactor_ data
// member is not set until we do synchronizeStats() later on.
//
// E.g., for "join T1 and T2 on T1.a=T2.b and T1.c=T2.d", if we have
// multi-column uec information on (T1.a,T1.c) and on (T2.b,T2.d), then we
// can improve our rowcount estimate for this join.
// -----------------------------------------------------------------------
void
ColStatDescList::useMultiUecIfMultipleJoins (
CostScalar & newRowcount, /* in/out */
const CostScalar & oldRowcount, /* in */
CollIndex startIndex, /* in : first index of CSDL */
CollIndex stopIndex, /* in : last index of CSDL+1 */
CollIndexList & joinHistograms, /* out */
const Join * expr,
MergeType mergeMethod)
{
// compute reduction from single column histograms uptill this point
CostScalar redFromSC = newRowcount / oldRowcount;
// We start by saying that are no joins represented by this ColStatDescList
// a very typical case of cross product. As we see any joining column, we
// will set this to TRUE. If we see more than joining column, we will
// set it back to FALSE
joinOnSingleCol_ = FALSE;
// this should probably never happen ... but if it does: when we don't
// have any multi-column information whatsoever,
// don't cause a memory exception!
if ( getUecList() == NULL )
return;
if ( startIndex >= stopIndex ) // misuse of function, oh well
return;
// first, we need to determine if more than one join was performed -- and
// reset all of the "isRecentJoin" flags
CollIndex i, j;
for ( i = startIndex ; i < stopIndex ; i++ )
{
ColStatsSharedPtr thisColStats = (*this)[i]->getColStats();
// skip any histograms created for constants
if ( !(thisColStats->isVirtualColForHist() ) &&
thisColStats->isRecentJoin() )
{
joinOnSingleCol_ = TRUE;
(*this)[i]->getColStatsToModify()->setRecentJoin( FALSE ); // unset flag
joinHistograms.insert( i ); // store the index, not the histogram
// first Join set the joined cols in joinStatDescList, which will be
// used later to set the min cardinality for join
// for left joins and Unions, the columns in the merged state could be
// hidden by another expression. Hence extract the base column from it
// Statistics is not affected by the extra expression.
ValueIdSet mergedState = (*this)[i]->getMergeState();
ValueIdSet baseColSet;
mergedState.findAllReferencedBaseCols(baseColSet);
this->addToJoinedCols(baseColSet);
}
}
// if fewer than 2 joins were performed, never mind
if ( joinHistograms.entries() < 2 )
return;
// Join is on more than one column, so set the flag to FALSE
joinOnSingleCol_ = FALSE;
LIST(ValueIdList) joinValueIdPairs(STMTHEAP); // the ValueId's pairwise (per join)
// look inside the mergeStates of each join histogram, and grab the
// ValueIds associated with each join
for ( i = 0 ; i < joinHistograms.entries() ; i++ )
{
ValueIdList tmp =
(*this)[ joinHistograms[i] ]->getMergeState(); // set->list
ValueIdList * tmp2 = new STMTHEAP ValueIdList();
CollIndex indx = 0;
// There could be cases when the null-instantiated column has participated
// in the join. This could be a case of left joins or in Union. In those
// cases, the mergeState is updated by the nulledExpression (don't know why?).
// But this should not have an impact on adjusting cardinalities for join
// So, in case of null_instantiated column, retrieve its child and use
// that to compute multi-col uec adjustment. Sol: 10-060609-7077 and 10-060607-7010
for (CollIndex k = 0; k < tmp.entries(); k++)
{
if (tmp[k].getItemExpr()->getOperatorType() != ITM_BASECOLUMN)
{
ValueIdSet result;
tmp[k].getItemExpr()->findAll(ITM_BASECOLUMN, result, TRUE, TRUE);
tmp2->insertSet(result);
}
else
tmp2->insert(tmp[k]);
}
// although we have not considered the MC-UEC for the columns which have been reduced
// by non-equality predicates, we still need to take into their selectivity from
// join on single columns but by assuming that these are not correlated to other
// columns
joinValueIdPairs.insert( *tmp2 );
}
// now see how many of these match our multicolumn-uec information
// the ValueId's for the columns of the two tables involved in the
// multi-column join
CostScalar prodInitUec = csMinusOne, // #docvars
multiColUec = csMinusOne, // #retvals
leftMCUec = csOne;
LIST(ValueIdList) joinValueIdPairsRemaining = joinValueIdPairs; // #retval
const NABoolean largeTableNeedsStats = // should stats exist for this table?
(*this)[0]->getColStats()->isUpStatsNeeded();
NABoolean checkForLowBound = FALSE;
// baseRCForMaxMCUEC contains the baseRowCount of the table which
// has maximum multi-column UEC for the joining columns
CostScalar baseRCForMCUEC = csOne;
NABoolean joinOnUnique = FALSE;
// If the join is between unique/non-unique sides, then the output parameter
// 'multiColUec' will return row count of non-unique side.
NABoolean sufficientInformation =
uecList()->getUecForMCJoin (joinValueIdPairsRemaining, /* in/out */
largeTableNeedsStats, /* in */
expr,
prodInitUec, /* out */
multiColUec, /* out */
baseRCForMCUEC,
leftMCUec,
checkForLowBound, /* out */
joinOnUnique,
*this,
redFromSC);
// minimum cardinality from join should be the minimum cardinality of group
// for empty input logical properties.
if (checkForLowBound)
setCapForLowBound();
if ( NOT sufficientInformation )
return;
// error! value not set, or simply avoiding div-by-zero! ignore MC-info in this case
if ( multiColUec.isLessThanZero() )
return;
// Since we've reached this point, we know that the required
// multi-column uec information exists to improve on the single-column
// selectivity rowcount estimate!
// -------------------------------------------------------------------------
// What to do if joinValueIdPairsRemaining still has entries in it?
//
// If there are any joins that haven't been accounted for, that should
// be alright (I think?), since their reductions have already been
// applied to produce the single-column selectivity rowcount estimate.
//
// However, we don't want those entries in joinValueIdPairsRemaining to
// go through the loop below. So, remove these joins from
// joinHistograms.
// -------------------------------------------------------------------------
for ( i = 0; i < joinValueIdPairsRemaining.entries(); i++ )
{
const ValueIdSet ithPair =
joinValueIdPairsRemaining[i]; // convenience: list->set
for ( j = 0; j < joinHistograms.entries(); j++ )
{
const ValueIdSet & mergeStateSet =
(*this)[ joinHistograms[j] ]->getMergeState();
// for left joins and Unions, the columns in the merged state could be
// hidden by another expression. Hence extract the base column from it
// Statistics is not affected by the extra expression.
ValueIdSet mergedState = (*this)[i]->getMergeState();
ValueIdSet baseColSet;
mergedState.findAllReferencedBaseCols(baseColSet);
// the code in getUecForMCJoin sometimes removes a table
// reference from an entry in joinValueIdPairsRemaining --> so
// we can't use simple equality for the comparison below
if ( baseColSet.contains( ithPair ) )
{
joinHistograms.removeAt( j );
break ;
}
}
}
// the following lines contain variables defined/used in the MC-document
const CostScalar SC_cardinality = newRowcount; // #docvar ("single-col value")
CostScalar MC_cardinality = csOne; // #docvar ("multi-col value")
CostScalar minSingleColJoinRC = COSTSCALAR_MAX;
// first, apply the sumOfMaxUec values
for ( i = 0; i < joinHistograms.entries(); i++ )
{
CostScalar singleColJoinRC;
singleColJoinRC = (*this)[ joinHistograms[i] ]->getColStats()->getRowcount();
if (singleColJoinRC < minSingleColJoinRC)
minSingleColJoinRC = singleColJoinRC;
}
NABoolean aSemiJoin = (expr && (expr->isSemiJoin() || expr->isAntiSemiJoin())) ? TRUE : FALSE;
if (!expr && mergeMethod == SEMI_JOIN_MERGE)
aSemiJoin = TRUE;
if ( joinOnUnique && !aSemiJoin)
{
CostScalar leftRC = ((Join *)expr)->child(0).outputLogProp((*GLOBAL_EMPTY_INPUT_LOGPROP))->getResultCardinality();
CostScalar rightRC = ((Join *)expr)->child(1).outputLogProp((*GLOBAL_EMPTY_INPUT_LOGPROP))->getResultCardinality();
CostScalar baseRowcount = leftRC * rightRC;
// If joining columns are unique, rowcount from equality
// predicates is equal to that of non unique side
//
// multiColUec is a misnomer here as it will be storing row count
// of non-unique side instead of MC UEC.
MC_cardinality = multiColUec * oldRowcount / baseRowcount;
}
else
{
// for non-unique joining column set, compute reduction as follows
if (CmpCommon::getDefault(COMP_BOOL_145) == DF_OFF)
MC_cardinality = SC_cardinality * prodInitUec/multiColUec;
else
{
CostScalar sel = MAXOF((SC_cardinality / oldRowcount), COSTSCALAR_EPSILON);
if (baseRCForMCUEC < multiColUec)
multiColUec = baseRCForMCUEC;
if (!aSemiJoin)
{
CostScalar selAdj = sel * prodInitUec / multiColUec;
CostScalar adj = (csOne - sel * prodInitUec) / baseRCForMCUEC;
selAdj = selAdj + adj;
MC_cardinality = selAdj * oldRowcount;
}
else
{
MC_cardinality = oldRowcount * multiColUec / leftMCUec;
}
}
}
// Low bound sanity check for MC UEC. The cardinality should not go below
// MINOF (join from single column histograms, 1/max multi col UEC)
// The second factor (1/max MCUEC), takes into account the anti-correlation
// where the join from single column histograms might be over estimated
// For semi and anti semi join, we set the low bound as MIN of SC_cardinality
// as oldRowcount is equal to leftrowcount for semi_joins which if outer is
// unique results as lowBoundFromMCUec = 1
CostScalar lowBoundFromMCUec = SC_cardinality;
if (!aSemiJoin)
lowBoundFromMCUec = MINOF(lowBoundFromMCUec, oldRowcount/multiColUec);
newRowcount = MAXOF(lowBoundFromMCUec, MC_cardinality);
// High bound sanity check. The cardinality cannot be higher than, minimum
// single column join.
newRowcount = MINOF(newRowcount, minSingleColJoinRC);
}
// ----------------------------------------------------------------------
// this method will set the inputCard to all the colStats in this list.
// This input cardinality is what comes from outer. This is used to
// compute UECs in CalculateCorrectResultUec
// -----------------------------------------------------------------------
void ColStatDescList::setInputCard(CostScalar rows)
{
for ( CollIndex i = 0; i < entries(); i++ )
(*this)[i]->setInputCard(rows);
}
// ----------------------------------------------------------------------
// ColStatDescList::synchronizeStats
// A CSDL utility routine used to call ColStatDesc::synchronizeStats()
// ----------------------------------------------------------------------
void
ColStatDescList::synchronizeStats (const CostScalar & baseRowcount,
const CostScalar & newRowcount,
CollIndex loopLimit)
{
for ( CollIndex i = 0; i < loopLimit; i++ )
{
const CostScalar & oldCount = (*this)[i]->getColStats()->getRowcount();
if ( oldCount != newRowcount )
(*this)[i]->synchronizeStats( baseRowcount, newRowcount );
}
}
void
ColStatDescList::synchronizeStats (const CostScalar & newRowcount,
CollIndex loopLimit)
{
for ( CollIndex i = 0; i < loopLimit; i++ )
{
const CostScalar & oldCount = (*this)[i]->getColStats()->getRowcount();
(*this)[i]->synchronizeStats( oldCount, newRowcount );
}
} // ColStatDescList::synchronizeStats
// ---------------------------------------------------------------------
// ColStatDescList::mergeListPairwise
//
// A routine used solely (today) for performing the implicit inner-equi-
// join between columns appearing as outer references in both children of
// the current join.
//
// It presumes a certain structure to the given THIS ColStatDescList, and
// also presumes what type of join is to be done. (e.g., we assume that
// there is an even number of ColStatDescSharedPtr's in the list.)
// ---------------------------------------------------------------------
CostScalar
ColStatDescList::mergeListPairwise ()
{
CollIndex i = 0;
CostScalar newRowcount = csZero;
CostScalar newUec = csZero;
ColStatDescSharedPtr rootStatDesc = (*this)[i];
ColStatsSharedPtr rootColStats = rootStatDesc->getColStatsToModify();
if ( rootColStats->getRowcount().isZero() )
{
// can't do much here....
while ( i < this->entries() )
{
removeAt( i+1 );
i++;
}
return csZero;
}
// sanity check
if ((entries() % 2) != 0)
{
CCMPASSERT( entries() % 2 == 0 ); // should be an even number!
//if not return without merging. Don't want to land up with an
// unreferenced object in the collections class
return newRowcount;
}
// NB: we avoid having the resulting rowcount blow up by making sure
// we don't divide by something that's less than 1
const CostScalar saveRowcount = rootColStats->getRowcount();
CostScalar totalReduct = csOne;
CostScalar maxReduct = csOne;
while ( i < this->entries() )
{
rootStatDesc = (*this)[i];
rootColStats = rootStatDesc->getColStatsToModify();
if( NOT rootColStats->isShapeChanged() &&
NOT (*this)[i+1]->getColStats()->isShapeChanged() )
{
rootStatDesc->mergeColStatDesc(
(*this)[i+1],
AND_MERGE,
TRUE // force merge
);
// get the aggregate results following the latest merge
newRowcount = rootColStats->getRowcount();
newUec = rootColStats->getTotalUec();
CostScalar reduct = ( saveRowcount < csOne ?
csOne : newRowcount / saveRowcount );
totalReduct *= reduct;
// remove the i+1'th entry.
removeAt( i+1 );
}
else
if ( rootColStats->isShapeChanged() )
removeAt( i+1 );
else
removeAt( i );
i++; // skip over just merged ColStats to next pair
}
newRowcount = saveRowcount * totalReduct;
// Ensure that all histograms report the same new rowcount.
synchronizeStats( newRowcount, entries() );
return newRowcount;
} // ColStatDescList::mergeListPairwise
// ----------------------------------------------------------------------
// ColStatDescList::divideHistogramAtPartitionBoundaries
// An external routine used to take a CDSL and identify, via the rows for
// one of the range-partitioning table columns, which partitions are active
// (i.e., > 0 rows returned) in a query.
// ----------------------------------------------------------------------
NABoolean ColStatDescList::divideHistogramAtPartitionBoundaries
(const ValueIdList & listOfPartKeys, /*in*/
const ValueIdList & listOfPartKeyOrders, /*in*/
const LIST(EncodedValueList *) & listOfPartBounds, /*in*/
ValueId & keyCorrespondingToOutputRows, /*out*/
NABoolean & isKeyAscending, /*out*/
ColStats & outputRows, /*out*/
CollIndexList & outputFactors) const /*out*/
{
// $$$ first effort, we assume that the first partititioning key is the
// $$$ one we want
//
// $$$ this next stmt should be replaced by code that looks
// $$$ and makes sure the first column doesn't have only one uec!!!
CollIndex partKeyIndex = 0; // $$$ change this later !!!
// let the caller know the ValueId of the partitiong key we'll be using
keyCorrespondingToOutputRows = listOfPartKeys[partKeyIndex];
const ValueId & keyOrder = listOfPartKeyOrders[partKeyIndex];
const ItemExpr * ie = keyOrder.getItemExpr();
OperatorTypeEnum ote = ie->getOperatorType();
if ( keyOrder.getItemExpr()->getOperatorType() == ITM_INVERSE )
isKeyAscending = FALSE; // #retval
else
isKeyAscending = TRUE;
// now build the Histogram corresponding to this part. key; we will use
// this to create the outputRows array that we return from this function
HistogramSharedPtr targetHist(new Histogram(HISTHEAP));
CollIndex i, numBounds = listOfPartBounds.entries();
if ( isKeyAscending ) // insert the boundary values in their current order
{
for ( i = 0; i < numBounds; i++ )
targetHist->insertAt(
i,
HistInt( listOfPartBounds[i]->at(partKeyIndex) )
);
}
else // insert the intervals in the reverse of their current order
{
for ( i = 0; i < numBounds; i++ )
targetHist->insertAt(
i,
HistInt(listOfPartBounds[numBounds-i-1]->at(partKeyIndex))
);
}
// make sure the "MAX-valued" HistInt doesn't contain "NULL" -- recall
// (see EncodedValue.[cpp h], NULL is encoded as MAX_DBL, which is what
// the last HistInt in targetHist will have as a value if the partitioning
// key is over a key of type double.
if ( (*targetHist)[numBounds-1].getBoundary().isNullValue() )
{
(*targetHist)[numBounds-1].setBoundary(
EncodedValue( _ENCODEDVALUE_CLOSE_TO_NULL_ )
);
}
// Note that since we don't have any boundary-inclusiveness information,
// we'll just pretend that none exists; arbitrarily, we decide to make
// the flag be FALSE.
// The effect of this :
//
// < < < <
// | | | |
// | | | |
// 1 2 3 4
//
// This histogram represents a table with 3 partitions. The SQL
// partitions are specified at values 2 and 3. By having the flags be
// NOT-BOUNDARY-INCLUSIVE (FALSE), we are specifying that the first
// partition has values from 1 (including 1) to 2 (not including 2); the
// second partition has values from 2 (including 2) to 3 (not including
// 3); similarly, the the third partition goes from 3 (including 3) to 4
// (not including 4).
//
// If this interpretation of the SQL semantics is not correct,
// please let me know!
// now get the Histogram for the CSD matching the part. key we're using
ColStatsSharedPtr sourceColStats =
getColStatsPtrForColumn( keyCorrespondingToOutputRows );
if ( sourceColStats == NULL ||
sourceColStats->getHistogram()->entries() == 0 )
return FALSE; // couldn't find it! quit!
// at this point, we've found the colstats corresponding to the
// partitioning key that we'll be using; and we have the corresponding
// list of boundary values for that key; now we can generate the desired
// information
// ----------------------------------------------------------------------
// Before we proceed, however, we need to make sure that there aren't
// any contiguous partition boundaries' values which are equal.
// Histogram semantics does not allow for this, so we have to fudge this
// (possible, legitimate partition boundary value) case with a second
// variable, which keeps track of the number of partitions per histogram
// interval. Before we code this, here's an example which should help
// explain what we're trying to accomplish:
//
// Consider the following partition boundary intervals
//
// | | | | | | | | | | |
// M 3 3 3 3 5 5 7 7 7 M
// I A
// N X
//
// We want to compress this to :
//
// 0 1 4 2 3
// - - - - -
// | | | | |
// M 3 5 7 M
// I A
// N X
//
// (where the number across the top indicate the # of partitions that
// have values for that interval)
//
// Interpreting the condensed, internal histogram:
//
// interval 1: [MIN,3) : 1 partition
// interval 2: [3,5) : 4 partitions
// interval 3: [5,7) : 2 partitions
// interval 4: [7,MAX) : 3 partitions
//
// To create the mapping (the numbers indicating how many partitions
// have a particular boundary value), we simply count all of the
// HistInts which have a particular boundary value, then add a "0" to
// the beginning of our list of integers. Note that we don't count for
// the last partition boundary value (MAX).
//
// For the example above, our list-of-ints looks like :
//
// step1: 1 4 2 3
// step2: 0 1 4 2 3
//
outputFactors.clear();
CollIndex countDuplicates = 0;
// we're counting the # of intervals that have the same boundary value
for ( i = 0; i < targetHist->entries()-1; /* no automatic increment */ )
{
if ( (*targetHist)[i].getBoundary() == (*targetHist)[i+1].getBoundary() )
{
countDuplicates++;
targetHist->removeAt( i+1 ); // in-list removal : no increment of 'i'
}
else
{
outputFactors.insertAt( i, 1 + countDuplicates );
countDuplicates = 0; // reset
i++; // go to next interval
}
}
outputFactors.insertAt( 0, 0 ); // first HistInt always contains garbage info
// Are we done? Have we inserted the last number? This is needed to
// indicate the number of partitions which have the 2nd-to-last
// partition boundary value (we don't count for MAX)
if ( outputFactors.entries() < targetHist->entries() )
outputFactors.insertAt( targetHist->entries()-1, 1 ); // last boundary should be "MAX"
if (outputFactors.entries() != targetHist->entries() )
{
CCMPASSERT( outputFactors.entries() == targetHist->entries() ); // sanity check
// histogram-to-partition-boundary-list mapping failed. Partition is unusable
return FALSE;
}
if (outputFactors.entries() <= 0 )
{
CCMPASSERT( outputFactors.entries() > 0 );
// histogram-to-partition-boundary-list mapping failed. Partition is unusable
return FALSE;
}
if ( targetHist->entries() == 1 )
{
// unusual, but possible (is it?!) case : all partition boundary values are equal, ugh
// --> if this happens, then make it a 2-HistInt histogram so we can at least
// call the routines below
const EncodedValue & bound = (*targetHist)[0].getBoundary();
targetHist->clear();
targetHist->insertZeroInterval( bound, bound, TRUE );
outputFactors.insert( countDuplicates + 1 );
}
// ----------------------------------------------------------------------
// ----------------------------------------------------------------------
// less work for this special case: zero rows in sourceColStats
if ( sourceColStats->getRowcount().isZero() )
{
outputRows.setHistogram( new HISTHEAP Histogram( *targetHist, HISTHEAP ) );
outputRows.setMaxMinValuesFromHistogram();
outputRows.setRowsAndUec( csZero, csZero );
return TRUE ;
}
// ----------------------------------------------------------------------
// this histogram is the source of the rows that we'll be placing in targetHist
HistogramSharedPtr sourceHist = sourceColStats->getHistogram();
// create the template histogram, then populate it
HistogramSharedPtr templateHist =
sourceHist->createMergeTemplate( targetHist, FALSE );
ColStats templateStats( templateHist, HISTHEAP );
CollIndex templateEntries = templateHist->entries();
templateStats.populateTemplate( sourceColStats );
if ( templateHist->entries() != templateEntries )
return FALSE; // something went very wrong!
// now, "squeeze" the resulting histogram so that it only keeps the
// interval boundaries from the targetHistogram
NABoolean result =
templateHist->condenseToPartitionBoundaries( targetHist );
if ( result != TRUE )
return result; // something went wrong!
// now, return the resulting Histogram in a format that we can use
outputRows.setHistogram( templateHist );
outputRows.setMaxMinValuesFromHistogram();
outputRows.setRowsAndUecFromHistogram();
return TRUE;
// that's all folks!
}
// compress all histograms in the list to a single interval histogram
void
ColStatDescList::compressColStatsToSingleInt()
{
for (CollIndex i = 0; i < entries(); i++)
{
ColStatsSharedPtr colStat = (*this)[i]->getColStatsToModify();
if (!colStat->isVirtualColForHist() && !colStat->isOrigFakeHist())
colStat->compressToSingleInt();
}
}
// ----------------------------------------------------------------------
// ColStatDescList::mergeSpecifiedStatDescs
// A utility routine used to merge specific ColStatDesc's together.
// ----------------------------------------------------------------------
void
ColStatDescList::mergeSpecifiedStatDescs (const CollIndexList & statsToMerge,
CollIndex rootIndex,
MergeType mergeMethod,
CollIndex numOuterColStats,
CostScalar & newRowcount,
CostScalar & newUec,
NABoolean forVEGPred,
OperatorTypeEnum opType)
{
ColStatDescSharedPtr rootDesc = (*this)[rootIndex];
CostScalar saveRowcount = newRowcount;
CostScalar saveUec = newUec;
for ( CollIndex i = 0; i < statsToMerge.entries(); i++ )
{
if ( statsToMerge[i] != rootIndex )
{
// If the statistics to be merged are from opposite sides of the
// numOuterColStats boundary, then we are doing a
// left_table_column = right_table_column
// merge that should be done as the caller requested.
// Otherwise, we're merging columns from the same table, and
// selectivity for that is HIST_NO_STATS_UEC.
MergeType localMergeMethod = mergeMethod;
NABoolean joinOnOneTable = FALSE;
ColStatDescSharedPtr tmpDesc(new (HISTHEAP)
ColStatDesc( *((*this)[statsToMerge[i]]) ), HISTHEAP);
if ( NOT ( rootIndex < numOuterColStats &&
statsToMerge[i] >= numOuterColStats ) )
{
// even though the mergeMethod may never be used, but we are
// initializing this for cases when COMP_BOOL_74 is OFF
localMergeMethod = INNER_JOIN_MERGE;
joinOnOneTable = rootDesc->mergeColStatDescOfSameTable(tmpDesc, opType);
}
if (!joinOnOneTable)
{
rootDesc->mergeColStatDesc(tmpDesc,
localMergeMethod,
FALSE, // don't force merge
opType
);
}
// get the aggregate results following the latest merge
ColStatsSharedPtr colStats = rootDesc->getColStats();
newRowcount = colStats->getRowcount();
newUec = colStats->getTotalUec();
// update the 'saved' information
saveRowcount = newRowcount;
saveUec = newUec;
// if this isn't a VEG, then it's an equality predicate.
// update the info used to support transitive closure for
// non-VEG equality predicates.
if ( NOT forVEGPred )
{
tmpDesc->setFromInnerTable(
statsToMerge[i] >= numOuterColStats ? TRUE : FALSE
);
rootDesc->nonVegEquals().insert( tmpDesc );
}
}
}
} // ColStatDescList::mergeSpecifiedStatDescs
// -----------------------------------------------------------------------
// Input: inputColumn, the column for which we need the ColStats
// output: TRUE if there was a ColStatDesc for "inputColumn" in this
// ColStatDescList, in which case its index is returned
// in "index". FALSE otherwise.
// -----------------------------------------------------------------------
//
// Logic:
//
// A ColStatDescList contains a list of ColStatDesc in no particular
// order. Every ColStatDesc represents a facade to a ColStats, i.e. to a
// histogram. Sometimes, the histogram is fake but the ColStatDesc
// encapsulates this fact. Every ColStatDesc has important fields that
// describe to which column it applies to:
//
// mergeState_: a ValueIdSet, describing which histograms have been merged
// with it. Each element in the set contains the base column of the
// histogram that has been merged with the current one.
//
// -----------------------------------------------------------------------
NABoolean
ColStatDescList::getColStatDescIndexForColumn (
CollIndex& index, /* out */
const ValueId& inputColumn /* in */
) const
{
// ----------------------------------------------------------------
// Find the base column for the input column:
// ----------------------------------------------------------------
const ItemExpr *inputColumnIEPtr = inputColumn.getItemExpr();
ValueId baseColumnForInputColumn;
ValueId vegColumnForInputColumn;
switch ( inputColumnIEPtr->getOperatorType() )
{
case ITM_VEG_REFERENCE:
{
// -------------------------------------------------
// The inputColumn is a VEG reference
// Loop through the columns until you find it:
// -------------------------------------------------
const VEG * exprVEG = ((VEGReference *)inputColumnIEPtr)->getVEG();
const ValueIdSet & VEGGroup = exprVEG->getAllValues();
if(exprVEG->seenBefore())
return FALSE;
else
{
exprVEG->markAsSeenBefore();
for ( ValueId id = VEGGroup.init();
VEGGroup.next( id );
VEGGroup.advance( id ) )
{
if ( getColStatDescIndexForColumn( index, id ) )
{
exprVEG->markAsNotSeenBefore();
return TRUE;
}
}
// If we are here, then we did not find the column
exprVEG->markAsNotSeenBefore();
return FALSE;
}
}
break;
case ITM_INSTANTIATE_NULL:
case ITM_BASECOLUMN:
// -------------------------------------------------
// The inputColumn is a base column.
// -------------------------------------------------
baseColumnForInputColumn = inputColumn;
break;
case ITM_INDEXCOLUMN:
// -------------------------------------------------
// Get the base column for the index column:
// -------------------------------------------------
{
const BaseColumn *bcIEPtrForIndexColumn =
(BaseColumn *) ((IndexColumn *) inputColumnIEPtr)->
getDefinition().getItemExpr();
baseColumnForInputColumn = bcIEPtrForIndexColumn->getValueId();
break;
}
default:
// give a last shot to see if a histogram exists for the expression
// that we might be looking. Example we could have an aggregate
// in a VEG, and we may also have a histogram for that. Sol:10-090110-9758
vegColumnForInputColumn = inputColumn;
} // switch on type of input column
// -----------------------------------------------------------------------
// Now, traverse this list and check whether there is a ColStatDesc
// for the given inputColumn, if so save its index in "index"
// -----------------------------------------------------------------------
NABoolean foundInBT = FALSE; // assume no ColStatDesc for this column
for ( CollIndex i = 0; i < entries(); i++ )
{
// Obtain the info for the current ColStatDesc:
ColStatDescSharedPtr currentColStatDesc = (*this)[i];
// The "merge state" of a ColStatDesc indicates all the
// columns that have been merged into this ColStatDesc.
// Initially, the merge state consists of the original
// base table column, therefore, this will work even for
// ColStatDesc's that have not been merged
const ValueIdSet & msSet = currentColStatDesc->getMergeState();
if ( ( msSet.contains( baseColumnForInputColumn ) ) ||
( currentColStatDesc->VEGColumn() == vegColumnForInputColumn) ||
( currentColStatDesc->getColumn() == baseColumnForInputColumn ) )
{ // found!
index = i;
foundInBT = TRUE;
break;
}
}
return foundInBT;
} // getColStatDescIndexForColumn(CollIndex & index, const ValueId column) const
// -----------------------------------------------------------------------
// Input: inputColumn, the column for which we need the ColStats
// partKeyColArray, columns for which we need the ColStats
// output: TRUE if there was a ColStatDesc for "partKeyColArray or inputColumn"
// in this ColStatDescList, in which case its index is returned
// in "index". FALSE otherwise.
// -----------------------------------------------------------------------
NABoolean
ColStatDescList::getColStatDescIndexForColumn (
CollIndex& index, /* out */
const ValueId& inputColumn, /* in */
NAColumnArray& partKeyColArray /* in */
) const
{
// single column partitioned table
if (partKeyColArray.entries() <= 1)
return (getColStatDescIndexForColumn(index, inputColumn));
// if multi-column partition key, find an MC with columns that are prefix to the
// partition column list
for ( CollIndex i = 0; i < entries(); i++ )
{
if (((*this)[i]->getColStats()->isMCforHbasePartitioning()) &&
(partKeyColArray.entries() >= (*this)[i]->getColStats()->getStatColumns().entries()))
{
index = i;
return TRUE;
}
}
return FALSE;
}
// Get the index of the ColStatDesc for a particular valueId.
// The match is based on the VEGColumn field.
NABoolean
ColStatDescList::getColStatDescIndex (CollIndex& index, /* out */
const ValueId& value) const /* in */
{
for ( CollIndex i = 0; i < entries(); i++ )
{
// Obtain the info for the current ColStatDesc:
ColStatDescSharedPtr currentColStatDesc = (*this)[i];
const ValueId & veg = currentColStatDesc->getVEGColumn();
if ( veg == value )
{ // found!
index = i;
return TRUE;
}
}
return FALSE;
}
// -----------------------------------------------------------------------
// This routine returns the ColStats (i.e. the histogram) that corresponds
// to the given inputColumn.
//
// Input:
// ======
// const ValueId& inputColumn : a ValueId denoting the wanted histogram
//
// Output:
// =======
// A NON-NULL ColStatsSharedPtr for the histogram that describes the given
// inputColumn (if such histogram exists)
//
// NULL if a histogram for the given inputColumn does not exist in the
// ColStatDescList
// -----------------------------------------------------------------------
ColStatsSharedPtr
ColStatDescList::getColStatsPtrForColumn (const ValueId& inputColumn) const
{
ColStatsSharedPtr colStatsPtr;
CollIndex index = 0;
NABoolean found = getColStatDescIndexForColumn( index, inputColumn );
if ( NOT found )
{
return NULL;
}
else
{
if ((index < 0) || (index >= entries()) )
{
// if the index is out side the range of histogram list, return
// NULL pointer indicating that the histogram is not found in the
// list
CCMPASSERT( (index >= 0) AND (index < entries() ));
return NULL;
}
return (*this)[index]->getColStats();
}
}
// -----------------------------------------------------------------------
// This method returns the ColStatsSharedPtr for the ColStats that references
// the given predicate if it exists, otherwise it returns NULL
// -----------------------------------------------------------------------
ColStatsSharedPtr
ColStatDescList::getColStatsPtrForPredicate (const ValueId& predicate) const
{
ColStatsSharedPtr colStatsPtr;
const ItemExpr *predIE = predicate.getItemExpr();
if (!predIE->isAPredicate())
return NULL;
const Int32 arity = predIE->getArity(); // for debugging
switch ( arity )
{
case 3:
case 2:
{
// a join predicate of the form col1 op col2 or similar
ItemExpr *leftExpr = predIE->child(0);
ItemExpr *rightExpr = predIE->child(1);
const ValueId &leftChildVid = leftExpr->getValueId();
const ValueId &rightChildVid = rightExpr->getValueId();
// ------------------------------------------------------------------
// Process left child:
// ------------------------------------------------------------------
if ( leftExpr->isAPredicate() )
{
colStatsPtr = getColStatsPtrForPredicate( leftChildVid );
}
else if ( leftExpr->getOperatorType() == ITM_VEG_REFERENCE )
{
ValueIdSet vidSet;
vidSet.insert(
((VEGPredicate *)leftExpr)->getVEG()->getAllValues() );
colStatsPtr = getColStatsPtrForVEGGroup( vidSet );
}
else
{
// It is not a predicate NOR a reference, then it
// must be an expression, get the columns it refers
// to
leftExpr = leftExpr->getLeafValueIfUseStats();
const ValueId &leftVid = leftExpr->getValueId();
colStatsPtr = getColStatsPtrForColumn( leftVid );
}
if ( colStatsPtr == NULL )
{
// ---------------------------------------------------------------
// ColStats not found in left child, try to find it in right:
// ---------------------------------------------------------------
if ( rightExpr->isAPredicate() )
{
colStatsPtr = getColStatsPtrForPredicate( rightChildVid );
}
else if ( rightExpr->getOperatorType() == ITM_VEG_REFERENCE )
{
ValueIdSet vidSet;
vidSet.insert(
((VEGPredicate *)rightExpr)->getVEG()->getAllValues() );
colStatsPtr = getColStatsPtrForVEGGroup( vidSet );
}
else
{
// It is not a predicate NOR a reference, then it
// must be an expression, get the columns it refers
// to
rightExpr = rightExpr->getLeafValueIfUseStats();
const ValueId &rightVid = rightExpr->getValueId();
colStatsPtr = getColStatsPtrForColumn( rightVid );
}
} // if colstats not found in left child
} // if arity is 2
break;
case 0:
{
if ( predIE->getOperatorType() == ITM_VEG_PREDICATE )
{
const ValueIdSet & vegGroup =
((VEGPredicate *)predIE)->getVEG()->getAllValues();
colStatsPtr = getColStatsPtrForVEGGroup( vegGroup );
}
else
{
CCMPASSERT( predIE->getOperatorType() == ITM_VEG_PREDICATE );
// add code to handle case here...
return NULL;
}
}
break;
default:
{
return NULL; // For unary logic predicates, there is nothing to return
}
} // case getArity()
return colStatsPtr;
}
ColStatsSharedPtr
ColStatDescList::getColStatsPtrForVEGGroup(const ValueIdSet& VEGGroup) const
{
ColStatsSharedPtr colStatsPtr = NULL;
// Get the first ColStats for any value that is a column:
ValueIdSet leafValuesForExpr;
for ( ValueId vid = VEGGroup.init();
VEGGroup.next( vid );
VEGGroup.advance( vid ) )
{
ItemExpr * vidIePtr = vid.getItemExpr();
switch ( vidIePtr->getOperatorType() )
{
case ITM_BASECOLUMN:
case ITM_INDEXCOLUMN:
{
colStatsPtr = getColStatsPtrForColumn( vid );
if ( colStatsPtr != NULL ) return colStatsPtr;
break;
}
case ITM_INSTANTIATE_NULL:
{
InstantiateNull *inst = (InstantiateNull *)vidIePtr->castToItemExpr();
if ( NOT inst->NoCheckforLeftToInnerJoin )
{
// if not a left join transformation
colStatsPtr = getColStatsPtrForColumn( vid );
if ( colStatsPtr != NULL ) return colStatsPtr;
break;
}
else
{
const ValueId & childVid = vidIePtr->child(0).getValueId();
const ItemExpr * childVidIePtr = childVid.getItemExpr();
if ( childVidIePtr->getOperatorType() == ITM_VEG_REFERENCE )
{
VEG *veg = ((VEGReference *)childVidIePtr)->getVEG();
if (veg->seenBefore())
break;
veg->markAsSeenBefore();
const ValueIdSet& vegGroup = veg->getAllValues();
colStatsPtr = getColStatsPtrForVEGGroup( vegGroup );
veg->markAsNotSeenBefore();
if ( colStatsPtr != NULL ) return colStatsPtr;
break;
}
else if ( childVidIePtr->getOperatorType() == ITM_BASECOLUMN
OR childVidIePtr->getOperatorType() == ITM_INDEXCOLUMN
)
{
colStatsPtr = getColStatsPtrForColumn( childVid );
if ( colStatsPtr != NULL ) return colStatsPtr;
break;
}
}
break;
}
case ITM_VEG_REFERENCE:
{
VEG *veg = ((VEGReference *)vidIePtr)->getVEG();
if (veg->seenBefore())
break;
veg->markAsSeenBefore();
// Get all members of VEGRef:
const ValueIdSet& vegGroup = veg->getAllValues();
colStatsPtr = getColStatsPtrForVEGGroup( vegGroup );
veg->markAsNotSeenBefore();
if ( colStatsPtr != NULL ) return colStatsPtr;
break;
}
case ITM_PI:
case ITM_CACHE_PARAM:
case ITM_CONSTANT:
case ITM_HOSTVAR:
case ITM_DYN_PARAM:
case ITM_CURRENT_USER:
case ITM_SESSION_USER:
case ITM_CURRENT_TIMESTAMP:
case ITM_UNIX_TIMESTAMP:
case ITM_UNIQUE_ID:
case ITM_GET_TRIGGERS_STATUS:
case ITM_UNIQUE_EXECUTE_ID:
continue;
default:
// couldn't find the histogram, continue to look for other
// values in a VEG group. Also collect the leaf values if
// it is an expression for which we can use histograms.
// These may be used later if we are unable to find any histograms
ItemExpr * leafValue = vidIePtr->getLeafValueIfUseStats();
if (leafValue != vidIePtr)
{
ValueIdSet lvSet;
if (leafValue->getOperatorType() == ITM_CASE)
{
leafValue->getLeafValueIdsForCaseExpr(lvSet);
}
else
leafValue->findAll(ITM_BASECOLUMN, lvSet, TRUE, TRUE);
leafValuesForExpr.addSet(lvSet);
}
continue;
} // end case
} // end for
// if we did not find any histograms till now, and there were some
// expressions in the VEG for which we can use stats,
if ((colStatsPtr == NULL) && (leafValuesForExpr.entries() > 0))
{
CollIndex idx;
if (getColStatDescIndexForColWithMaxUec(idx, leafValuesForExpr))
colStatsPtr = (*this)[idx]->getColStats();
}
return colStatsPtr; // if we get here, this should be NULL
} // ColStatDescList::getColStatsPtrForVEGGroup(const ValueIdSet& VEGGroup)
CostScalar
ColStatDescList::getUecOfJoiningCols(ValueIdSet & joinedColSet) const
{
CostScalar minUec = COSTSCALAR_MAX;
CostScalar currUec = csOne;
for (ValueId vid = joinedColSet.init();
joinedColSet.next(vid);
joinedColSet.advance(vid) )
{
ColStatsSharedPtr colStats = this->getColStatsPtrForColumn(vid);
if (colStats)
{
currUec = colStats->getTotalUec();
if (currUec < minUec)
minUec = currUec;
} // if ColStats
} // end for joinedColSet
return minUec;
} // ColStatDescList::getUecOfJoiningCols
void
ColStatDescList::print (ValueIdList selectListCols,
FILE *ofd,
const char * prefix,
const char * suffix,
CollHeap *c, char *buf,
NABoolean hideDetail) const
{
NABoolean atLeastOnePrinted = FALSE;
NABoolean runningShowQueryStatsCmd = (selectListCols.entries() > 0);
Space * space = (Space *)c;
char mybuf[1000];
sprintf(mybuf,
"**************************************************************\n");
PRINTIT(ofd, c, space, buf, mybuf);
for (CollIndex colStatDescIndex=0;
colStatDescIndex < entries();
colStatDescIndex++)
{
ColStatDescSharedPtr statDesc = (*this)[colStatDescIndex];
if(runningShowQueryStatsCmd)
{
if(!selectListCols.contains(statDesc->getVEGColumn()) &&
(atLeastOnePrinted || colStatDescIndex < (entries()-1)))
continue;
}
if (atLeastOnePrinted)
{
sprintf(mybuf,
"-------------------------------------------------------\n") ;
PRINTIT(ofd, c, space, buf, mybuf);
}
else
atLeastOnePrinted = TRUE;
statDesc->print(ofd,prefix,suffix,c,buf,hideDetail);
}
sprintf(mybuf,
"**************************************************************\n");
PRINTIT(ofd, c, space, buf, mybuf);
}
void
ColStatDescList::display() const
{
ValueIdList emptySelectList;
print(emptySelectList);
}
void
ColStatDescList::verifyInternalConsistency(CollIndex start, CollIndex end) const
{
CCMPASSERT ( start <= end ); // misuse of function!
if ( start >= entries() ) return;
if ( end > entries() ) return;
const CostScalar & matchRowcount =
(*this)[start]->getColStats()->getRowcount();
CollIndex i = start + 1;
for ( ; i < end; i++ )
{
const CostScalar & rc = (*this)[i]->getColStats()->getRowcount();
if ( NOT ( rc == matchRowcount ) )
{
#ifdef MONITOR_SAMEROWCOUNT
this->display() ;
CCMPASSERT ( (*this)[i]->getColStats()->getRowcount() == matchRowcount );
#endif
break ;
}
}
// now handle the case of histograms with zero entries
for ( i = start; i < end; i++ )
{
if ( (*this)[i]->getColStats()->getHistogram()->entries() == 0 )
{
ColStatsSharedPtr stats = (*this)[i]->getColStatsToModify();
stats->setToSingleInterval (
stats->getMinValue(),
stats->getMaxValue(),
stats->getRowcount(),
stats->getTotalUec()
);
stats->setRedFactor ( csOne );
stats->setUecRedFactor ( csOne );
stats->setFakeHistogram();
}
}
}
// this function has a very simple algorithm:
// (1) if the histograms all have the same rowcount, done
// (2) otherwise, find the first that's not a fake histogram; set all to
// have its rowcount
// (3) otherwise, find average of all rowcounts, set all to this value
void
ColStatDescList::enforceInternalConsistency(CollIndex start,
CollIndex end,
NABoolean printNoStatsWarning)
{
CCMPASSERT ( start <= end ); // misuse of function!
if ( start >= entries() ) return;
if ( end > entries() ) return;
// first handle the case of histograms with zero entries
//
// --> At the same time, if param "printNoStatsWarning" is TRUE, then
// fire off a warning message for every fake Colstats that has the
// isUpStatsNeeded() flag set. Note that we leave this flag set in
// order to enable other we-need-stats code in other routines.
CollIndex i = start;
for (; i < end; i++ )
{
ColStatDescSharedPtr cdesc = (*this)[i];
ColStatsSharedPtr cs = cdesc->getColStats();
if (cs == NULL)
{
CCMPASSERT(cs != NULL);
continue;
}
if (cs->getHistogram() == NULL)
{
CCMPASSERT("Histogram is NULL");
cs->insertZeroInterval();
}
if ( cs->getHistogram()->entries() == 0 )
{
cs = (*this)[i]->getColStatsToModify() ; // why we did the const-cast
cs->setToSingleInterval (
cs->getMinValue(),
cs->getMaxValue(),
cs->getRowcount(),
cs->getTotalUec()
) ;
cs->setRedFactor ( csOne );
cs->setUecRedFactor ( csOne );
cs->setFakeHistogram();
}
// Warning 6008 was earlier given for all columns, even if they did
// not participate in the query. It is now given for only those columns whose
// histograms are needed.
// All missing Stats warning are controlled by the CQD HIST_MISSING_STATS_WARNING_LEVEL
// Warnings are displayed only if the value of the CQD is greater than 0
if(cs->getStatColumns().entries())
{
if ( printNoStatsWarning && cs->isUpStatsNeeded() )
{
if ( ( cs->isFakeHistogram() ) || (cs->isSmallSampleHistogram()) ) // if fake or small sample histogram, fire off the warning!
{
ValueId colId = cdesc->getColumn();
BaseColumn * colExpr = colId.castToBaseColumn();
if (colExpr != NULL)
{
// By this time we have ensured that it is a base column
TableDesc * tableDescForCol = colExpr->getTableDesc();
const MultiColumnUecList * ueclist = getUecList() ;
NABoolean quickStats = FALSE;
if (cs->isSmallSampleHistogram())
quickStats = TRUE;
ueclist->displayMissingStatsWarning(tableDescForCol,
colId,
TRUE,
TRUE,
*this,
csMinusOne, // displaying missing single column warnings
quickStats);
}
}
}
}
}
NABoolean allHaveSameRowcount = TRUE;
const CostScalar & matchRowcount =
(*this)[start]->getColStats()->getRowcount();
for ( i = start+1; i < end; i++ )
{
const CostScalar & rc = (*this)[i]->getColStats()->getRowcount();
if ( NOT ( rc == matchRowcount ) )
{
allHaveSameRowcount = FALSE;
break ;
}
}
if ( allHaveSameRowcount ) return; // CASE (1), done
// OK, they're inconsistent ... rectifying ...
CollIndex firstNonFake = NULL_COLL_INDEX;
CostScalar firstNonFakeRowcount = csMinusOne;
for ( i = start; i < end; i++ )
{
if ( NOT (*this)[i]->getColStats()->isFakeHistogram() )
{
firstNonFake = i;
firstNonFakeRowcount = (*this)[i]->getColStats()->getRowcount();
break;
}
}
if ( firstNonFake != NULL_COLL_INDEX ) // CASE (2)
{
if (firstNonFakeRowcount < 0)
{
CCMPASSERT( firstNonFakeRowcount.isGreaterOrEqualThanZero() );
firstNonFakeRowcount = 0;
}
for ( i = start; i < end; i++ )
{
const CostScalar & rc = (*this)[i]->getColStats()->getRowcount();
(*this)[i]->synchronizeStats( rc, firstNonFakeRowcount );
}
return ; // done
}
// OK, they're all fake histograms ... can't do much better than averaging them ... sigh
// CASE (3)
CostScalar sumRC = csZero;
for ( i = start; i < end; i++ )
{
sumRC += (*this)[i]->getColStats()->getRowcount();
}
CostScalar avgRC = sumRC / ( end - start );
for ( i = start; i < end; i++ )
{
const CostScalar & rc = (*this)[i]->getColStats()->getRowcount();
(*this)[i]->synchronizeStats( rc, avgRC );
}
// done, finally
}
ValueIdSet ColStatDescList::appliedPreds () const
{
ValueIdSet result;
for ( CollIndex i = 0; i < entries(); i++ )
{
result += (*this)[i]->appliedPreds();
}
return result;
}
ValueIdSet ColStatDescList::VEGColumns () const
{
ValueIdSet result;
for ( CollIndex i = 0; i < entries(); i++ )
{
result += (*this)[i]->VEGColumn();
}
return result;
}
// -----------------------------------------------------------------------
// methods on MultiColumnUecList class
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
// MultiColumnUecList::HashFunction
//
// we need some sort of hashing function in order to use NAHASHDICTIONARY;
// this is a first effort, obviously fairly naive and unsophisticated.
// -----------------------------------------------------------------------
ULng32
MultiColumnUecList::HashFunction (const ValueIdSet & input)
{
ULng32 retval = 1 + input.entries();
for ( ValueId id = input.init(); input.next(id); input.advance(id) )
retval += (CollIndex) id; // add up the ValueId's
return retval ;
}
// default constructor
MultiColumnUecList::MultiColumnUecList () :
HASHDICTIONARY(ValueIdSet,CostScalar) (&(MultiColumnUecList::HashFunction),
17, // original hash size ... why not?
TRUE, // uniqueness constraint
HISTHEAP )
{ };
// -----------------------------------------------------------------------
// MultiColumnUecList :: ctor
//
// builds the MultiColumnUecList from the initial StatsList object
//
// does the NAColumn -> ValueId conversion found in the ColStatDesc ctor
//
// this function is only called from TableDesc::getTableColStats()
// -----------------------------------------------------------------------
MultiColumnUecList::MultiColumnUecList (const StatsList & initStats,
const ValueIdList & tableColumns) :
HASHDICTIONARY(ValueIdSet,CostScalar) (&(MultiColumnUecList::HashFunction),
17, // original hash size ... why not?
TRUE, // uniqueness constraint
HISTHEAP )
{
// the StatsList has two lists which it uses to store the information we
// need to fill the MultiColumnUecList with <table-col-list,uec value> pairs:
//
// LIST(NAColumnArray) groupUecColumns_
// LIST(CostScalar) groupUecValues_
CostScalar rowCount = initStats[0]->getRowcount();
// loop through the list of NAColumnArray's
for ( CollIndex i = 0; i < initStats.groupUecColumns_.entries(); i++ )
{
const NAColumnArray & uecCols = initStats.groupUecColumns_[i];
ValueIdSet insertCols;
// multiColUec
CostScalar multiColUec(initStats.groupUecValues_[i]);
// Upper limit to the multi column UEC
CostScalar maxMCUec = csOne;
// lower limit to the multi-col UEC
CostScalar minMCUec = csOne;
// would be set to TRUE if any histogram is missing statistics
NABoolean statsMissing = FALSE;
// for each NAColumnArray, map each NAColumn to a ValueId
for ( CollIndex j = 0; j < uecCols.entries(); j++ )
{
Lng32 position = uecCols[j]->getPosition();
const ValueId & id = tableColumns[position];
CostScalar singleColUec = initStats.getSingleColumnUECCount(position);
if (singleColUec < 1)
statsMissing = TRUE;
//MCUec cannot be less than the UEC count of any of the columns that
//are included in the MCHistogram.
minMCUec = MAXOF(minMCUec, singleColUec);
maxMCUec *= singleColUec;
insertCols.insert( id );
}
// The prodUec could be negative if it involves a column that is not
// being referenced in the query. In such cases, we shall use rowcount
// as the upper limit
if (statsMissing)
maxMCUec = rowCount;
else
maxMCUec = MINOF(maxMCUec, rowCount);
// multi-col UEC should not exceed the product of single col UEC
// or row count which ever is smaller
// And multi-col UEC should be at least equal to max single column
// UEC of columns participating in multi-col
if (multiColUec < minMCUec)
multiColUec = minMCUec;
if (multiColUec > maxMCUec)
multiColUec = maxMCUec;
// now we've converted each NAColumn->ValueId in a particular
// NAColumnArray; now insert this list and its corresponding uec
insertPair( insertCols, multiColUec );
}
}
// -----------------------------------------------------------------------
// MultiColumnUecList::insertList
//
// inserts all entries from OTHER into THIS (unless a particular entry
// already exists in THIS)
// -----------------------------------------------------------------------
void
MultiColumnUecList::insertList (const MultiColumnUecList * other)
{
if ( other == NULL ) return;
if ( other == this ) return;
if ( other->entries() == 0 ) return;
ValueIdSet * keyEntry = NULL;
CostScalar * uecEntry = NULL;
MultiColumnUecListIterator iter( *other );
iter.getNext( keyEntry, uecEntry );
while ( keyEntry != NULL && uecEntry != NULL )
{
if ( NOT contains( keyEntry ) )
insertPair( *keyEntry, *uecEntry );
iter.getNext( keyEntry, uecEntry );
}
}
// -----------------------------------------------------------------------
// MultiColumnUecList::insertMappedList
//
// inserts all entries from OTHER into THIS, after mapping the ValueIds
// in the list, using MAP. Note that we do the mapping in the "up"
// direction.
// -----------------------------------------------------------------------
void
MultiColumnUecList::insertMappedList(const MultiColumnUecList *other,
const ValueIdMap &map)
{
if ( other == NULL ) return;
if ( other->entries() == 0 ) return;
ValueIdSet * keyEntry = NULL;
CostScalar * uecEntry = NULL;
MultiColumnUecListIterator iter( *other );
iter.getNext( keyEntry, uecEntry );
while ( keyEntry != NULL && uecEntry != NULL )
{
ValueIdSet *mappedSet = new(HISTHEAP) ValueIdSet;
map.mapValueIdSetUp(*mappedSet, *keyEntry);
// Todo: CSE: This is unlikely to work, since the stats will be
// expressed in BaseColumns, while the map contains VEGRefs.
// Uncomment the assert below and run compGeneral/TEST045
// to see the problem.
// DCMPASSERT(*mappedSet != *keyEntry);
if ( NOT contains( mappedSet ) )
insertPair( *mappedSet, *uecEntry );
iter.getNext( keyEntry, uecEntry );
}
}
// -----------------------------------------------------------------------
// MultiColumnUecList::insertPair
//
// inserts a <table-column-valueidset, groupUec> pair
// -----------------------------------------------------------------------
NABoolean
MultiColumnUecList::insertPair (const ValueIdSet & columns,
const CostScalar & groupUec)
{
ValueIdSet * columnsCopy = new (HISTHEAP) ValueIdSet( columns );
CostScalar * groupUecCopy = new (HISTHEAP) CostScalar( groupUec );
if ( columnsCopy ==
(NAHashDictionary<ValueIdSet,CostScalar>::insert( columnsCopy, groupUecCopy )) )
return TRUE; // insert successful
else
return FALSE; // insert failed
}
// -----------------------------------------------------------------------
// MultiColumnUecList::updatePair
//
// updates the groupUec of <table-column-valueidset, groupUec> pair
// -----------------------------------------------------------------------
NABoolean
MultiColumnUecList::updatePair (const ValueIdSet & columns,
const CostScalar & groupUec)
{
ValueIdSet * columnsCopy = new (HISTHEAP) ValueIdSet( columns );
if ( columnsCopy ==
(NAHashDictionary<ValueIdSet,CostScalar>::remove( columnsCopy)) )
{
if ( insertPair( columns, groupUec ) )
return TRUE;
}// update successful
return FALSE; // update failed
}
// -----------------------------------------------------------------------
// MultiColumnUecList::lookup
//
// given a <table-column-valueidset>, returns the corresponding group uec
// value
// -----------------------------------------------------------------------
CostScalar
MultiColumnUecList::lookup (const ValueIdSet & key) const
{
CostScalar groupUec = csMinusOne;
if ( contains( &key ) )
groupUec = *(getFirstValue( &key ) );
return groupUec;
}
// ------------------------------------------------------------------------------
// This method is used for setting the multi-column UEC for unique indexes equal
// to the row count. If the multi_column statistics for unique indexes, does not exist
// it is created by setting the column list equal to the column list from
// the index, and uec equal to the base row count of the table.
// ------------------------------------------------------------------------------
void MultiColumnUecList::initializeMCUecForUniqueIndxes(TableDesc &table,
const CostScalar & tableRowcount)
{
const NAFileSetList indexList = table.getNATable()->getIndexList();
const ValueIdList &tableColumns = table.getColumnList();
for (CollIndex listi = 0; listi < indexList.entries(); listi++)
{
if (indexList[listi]->uniqueIndex())
{
const NAColumnArray & uecCols = indexList[listi]->getIndexKeyColumns();
ValueIdSet insertCols;
for (CollIndex j = 0; j < uecCols.entries(); j++)
{
Lng32 position = uecCols[j]->getPosition();
const ValueId & id = tableColumns[position];
insertCols.insert(id);
} // for all columns in the index
// see if the multi_column statistics exists for the given set of column
CostScalar multUecRowCount = -1;
if ((multUecRowCount = lookup(insertCols)) > 0)
{
// if the row count from statistics equal to the table rowcount
if (multUecRowCount != tableRowcount)
updatePair(insertCols,tableRowcount);
}
else
insertPair(insertCols,tableRowcount);
} // if the index is unique
} // for all indexes of the table
return;
} // initializeMCUecForUniqueIndexes
//---------------------------------------------------------------------
//MultiColumnUecList::getListOfSubsetsContainsColumns
//
//Input: list of columns
//Output: List of ValueIdSet that contains the last column in the list
//and other columns from the columnList only
//Constraints: ColumnIds that are passed in can be VegRef that contains
// the base id for that column at the first level or it can
// be a the id corresponding to a index on the table.
//---------------------------------------------------------------------
LIST(ValueIdSet) *
MultiColumnUecList::getListOfSubsetsContainsColumns(
const ValueIdList & columns/*in*/,
LIST(CostScalar)& uecCount/*out*/
) const
{
LIST(ValueIdSet) * result = new (HISTHEAP) LIST( ValueIdSet )(HISTHEAP);
ValueIdSet allValueIds;
ValueIdSet colValueIds;
CollIndex members = columns.entries();
ValueId column;
//Following we try to get all the columnids that the column ids can
//be associated with so that when we cross reference
//multi-column uec list we don't miss out. We create two list
//colValueIds -- all value Ids of the last/main column
//allValueIds -- all value Ids of the rest of the columns
for(CollIndex i=0; i<members;i++)
{
column = columns[i];
const ItemExpr * itemExprForCol = column.getItemExpr();
if( itemExprForCol->getOperatorType() == ITM_VEG_REFERENCE )
{
if(i==members-1)
colValueIds = ((VEGReference *)itemExprForCol)->getVEG()->getAllValues();
else
allValueIds += ((VEGReference *)itemExprForCol)->getVEG()->getAllValues();
}
else if ( itemExprForCol->getOperatorType() == ITM_INDEXCOLUMN )
{
const BaseColumn * bc;
bc = (BaseColumn *)((IndexColumn *)itemExprForCol)->getDefinition().getItemExpr();
if(i==members-1)
{
colValueIds.insert( bc->getEIC() );
//inserts the id this column has on base table
colValueIds.insert( bc->getValueId() );
}
else
{
//inserts other id's this column might have on other indexes
allValueIds.insert( bc->getEIC() );
//inserts the id this column has on base table
allValueIds.insert( bc->getValueId() );
}
}
//for the case when id is the id in the base table
//and for extra precaution
if(i==members-1)
colValueIds.insert(column);
else
allValueIds.insert( column );
}
#ifndef NDEBUG
if(getenv("MDAM_MCUEC"))
{
fprintf(stdout, " \n\n-----List to be considered----\n");
allValueIds.print();
}
#endif
MultiColumnUecListIterator iter(*this);
ValueIdSet * keyEntry = NULL;
CostScalar * uecEntry = NULL;
CollIndex position = 0; // position to enter in the list
// following we traverse the mulcolueclist and see if
//keyEntry contains the column, then check if it contains
//any other column other than the columns passed in, if
//it doesn't then it is a viable entry.
//Ex. if abcd are the columns passed in, we first look for
// d if the multi-columnUEC entry and if contains that then
// we look for abc and if it contains any or all of them but
// not any other column like 'e' or 'f' then it is a viable
// multi-column histogram.
for ( iter.getNext( keyEntry, uecEntry );
keyEntry != NULL && uecEntry != NULL;
iter.getNext( keyEntry, uecEntry ) )
{
ValueIdSet tempSet = *keyEntry;
tempSet.removeCoveredVIdSet(colValueIds);
if(tempSet.entries() < keyEntry->entries())
{
tempSet.removeCoveredVIdSet(allValueIds);
if(tempSet.entries()==0)
{
result->insertAt( position, *keyEntry );
uecCount.insertAt( position, *uecEntry );
position++;
}
}
}
return result;
}
// -----------------------------------------------------------------------
// MultiColumnUecList::largestSubset
//
//Input:ValueIdSet(columns) of column valueIds
//Output: Returns the a valueIdSet that matches most valueIds from ValueIdSet
//(columns) but does not contain any other valueIds.
//Ex: for valueIdSet (1, 4, 6)
// we had mulcolUec for (1,4,6,8) and (1,4,7) and (1)
// we would return (1) because the other two contains unmentioned columns
// In case of tie between two list we select the one with largest correlation
//Constraints: ValueIdSet passed in has to be base table Ids because
//multicol is stored using base table ids.
// -----------------------------------------------------------------------
ValueIdSet
MultiColumnUecList::largestSubset (const ValueIdSet & columns) const
{
ValueIdSet largestSet; // the largest subset found thus far
// This is the smallest independence factor, It determines the largest
// correlation between the columns. Smaller the IP factor, larger is
// the correlation between columns
double smallestIPFactor = 1.0;
if ( entries() == 0 OR columns.entries() == 0 )
return largestSet; // no subsets at all!
ValueIdSet * keyEntry = NULL;
CostScalar * uecEntry = NULL;
// we need to iterate through all entries in this list
MultiColumnUecListIterator iter( *this );
for ( iter.getNext( keyEntry, uecEntry );
keyEntry != NULL && uecEntry != NULL;
iter.getNext( keyEntry, uecEntry ) )
{
ValueIdSet tempSet = *keyEntry;
//we do not want extra columns so after we remove the
//columns asked for we should have 0 entries left.
tempSet.removeCoveredVIdSet( columns );
if( tempSet.entries() > 0 )
continue;
// want to find the entry in the multicolumnueclist which contains
// group uec information about the most columns
//We want to select this entry if it has more matching columns
//Or if it has same number of columns then it has to have smaller
// correlation factor
if ( keyEntry->entries() >= largestSet.entries() )
{
double independenceFactor = 0.0;
double SCproductUec = 1.0;
for (ValueId keyCol = keyEntry->init();
keyEntry->next(keyCol);
keyEntry->advance(keyCol) )
SCproductUec *= lookup(keyCol).value();
independenceFactor = uecEntry->value()/SCproductUec;
if ( ( keyEntry->entries() > largestSet.entries() )
OR
( (keyEntry->entries() == largestSet.entries())
AND (keyEntry->entries() > 0 )
AND ( independenceFactor < smallestIPFactor ) ) )
{
largestSet = *keyEntry;
smallestIPFactor = independenceFactor;
}
}
} // end for
return largestSet;
}
//---------------------------------------------------------------------
//MultiColumnUecList::findDenom
//
//Input: list of columns
//Output: Boolean. if there is a multi-column histogram exactly matching
//the input then return true or return false
//Constraints: ValueIds for the columns need to be base valueIds.
//---------------------------------------------------------------------
NABoolean
MultiColumnUecList::findDenom (const ValueIdSet & columns) const
{
if ( entries() == 0 OR columns.entries() == 0 )
return FALSE; // no subsets at all!
ValueIdSet * keyEntry = NULL;
CostScalar * uecEntry = NULL;
// we need to iterate through all entries in this list
MultiColumnUecListIterator iter( *this );
for ( iter.getNext( keyEntry, uecEntry );
keyEntry != NULL && uecEntry != NULL;
iter.getNext( keyEntry, uecEntry ) )
{
if(columns == *keyEntry)
{
return TRUE;
}
}
return FALSE;
}
// -----------------------------------------------------------------------
// MultiColumnUecList::useMCUecforCorrPreds
//
// used to calculate an adjustment in the case of multiple predicates being
// applied to highly correlated table columns
// (fn useMultiUecIfCorrelatedPreds(), subr of
// fn estimateCardinality() )
//
// given a list of <ValueId, CostScalar> pairs representing all of the
// histograms which have been reduced, and the amount (reduction factor)
// they've been reduced, return TRUE/FALSE if, in the list of these
// predicates, there are 2+ from the same table for which we have
// multi-column uec information and which are "highly correlated"
// (defined below).
//
// If both of these conditions are met, then we supply a factor
// "reductionAdjustment" which should be applied to the current rowcount
// estimate in order to increase it beyond its current value, to take
// into account the fact that we are applying multiple predicates to
// highly correlated columns, which we assume means that beyond the
// most selective predicate, the additional predicates are redundant
// in part or whole (i.e., they remove the "same rows" as the other
// predicates).
//
// We basically check if the row reduction has taken the number of rows
// below the number of rows per multicolumnUec. If so reduction adjustment
// will readjust the number of rows appropriately. Any call to this function
// does not return a reduction adjustment that will let the number of rows be
// less than one.
// Constraints: The valueIds in NAHashDictionary should be base table valueIds
// for those columns
// all tablePtrs should be 4-byte aligned, so divide by 4
// to get a better hash value
ULng32 TableDescHashFn (const TableDesc & tablePtr)
{ return (ULng32)((Long)&tablePtr/8) ; }
NABoolean
MultiColumnUecList::useMCUecForCorrPreds (
NAHashDictionary<ValueId, CostScalar> & predReductions, /* in/mod */
const CollIndex numPredicates, /* in */
const CostScalar& oldRowCount, /* in */
const CostScalar& newRowCount, /* in */
NABoolean largeTableNeedsStats,
const ColStatDescList & scHists,
CostScalar & reductionAdjustment) /* out */
{
#define USE_MULTI_COL_UEC_INFO
if ( numPredicates < 2 )
return FALSE;
// -----------------------------------------------------------------------
// First, we need to identify the table which has the most column
// references in our list of <pred column, rowcount reduction> pairs.
//
// We use a hash-dictionary to implement an association list of
// <TableDesc*, ValueIdList> pairs -- each ValueIdList indicates
// which ValueId's we have that touch the TableDesc* in question.
// -----------------------------------------------------------------------
NAHashDictionary<TableDesc,ValueIdList> tableColumns (
&TableDescHashFn, 7, TRUE, HISTHEAP );
// we need to iterate over the <ValueId, CostScalar> pairs in order
// to fill the NAHashDictionary above (tableColumns) with values
NAHashDictionaryIterator<ValueId, CostScalar> predIter( predReductions );
ValueId * predColumn = NULL;
CostScalar * reduction = NULL;
for ( predIter.getNext( predColumn, reduction );
predColumn != NULL && reduction != NULL;
predIter.getNext( predColumn, reduction ) )
{
const ValueId & iterId = *predColumn;
// initially assume ITM_BASETABLE
BaseColumn * iterExpr = iterId.castToBaseColumn();
if ( iterExpr == NULL ) return FALSE; // unexpected condition
TableDesc * iterDesc = iterExpr->getTableDesc();
if (iterDesc == NULL)
{
CCMPASSERT( iterDesc != NULL );
return FALSE;
}
// do a lookup in the hash dictionary we're currently populating
ValueIdList * colList = tableColumns.getFirstValue( iterDesc );
if ( colList != NULL )
{
colList->insert( iterId ); // the joinValueIdPair which refs this tableDesc
}
else
{
colList = new (HISTHEAP) ValueIdList;
colList->insert( iterId );
tableColumns.insert( iterDesc, colList );
}
}
// -----------------------------------------------------------------------
// We have lists of references to tableDesc's; now, we iterate through them
// to find the one with the most table columns.
//
// If there isn't a table with two column references, we quit.
//
// If there is a tie between two tables for most column refs, we
// pick the one with higher multi-col UEC.
// Table iterator to traverse the tables whose columns have been reduced
NAHashDictionaryIterator<TableDesc,ValueIdList> tableIter( tableColumns );
// This would contain the running descriptor of the table that we are
// iterating from the list of tables with predicates.
TableDesc * iterDesc = NULL;
// This would contain the columns on which we have local predicates for
// the table described in iterDesc. If iterDesc contains T1, then iterList
// would contain (a,b,c). If iterDesc is T2, iterList would be (a,b,c,d)
ValueIdList * iterList = NULL;
// This would contain the ValuedIdSet of the largest set of columns which
// have multi-col stats available. The columns are from the amongst the
// ones which have most predicates on them. Example, we have predicates
// on columns, (a,b,c) of table T1 and (a,b,c,d) of table T2. At the end of the
// loop, mostRefs would be 4, and if there is multi_col stats available for
// T2 (a, b) and T2(a,b,c), then largestSubsetAmongSubsets would contain
// (a,b,c), and mostRefdTable would contain T2
// Table with multi-column stats on most columns. It would be T2 for us.
TableDesc * mostRefdTable = NULL;
// This would contain the list of columns which have most predicates on them
// At the end of the loop it should contain T2 (a,b,c,d)
ValueIdList * superList;
// This would contain the count of columns of a table which has most local
// predicates. For the above example it would be 4, at the end of the loop
CollIndex mostSupersetRefs = 0;
// largest set of columns which have multi-col UECs for any table.
ValueIdSet largestSubsetAmongLargestSubsets;
// contains the maximum number of columns which have multi-col UECs available
CollIndex mostRefs = 0;
CollIndex count = 1;
// This will contain the multi-col UEC for largestSubsetAmongLargestSubsets.
// It will be used to pick the final multi-col UEC in case there is a tie
// between the number of columns of two tables, pick the ones with higher
// multi-col UEC
CostScalar multiColumnUec = csMinusOne;
for ( tableIter.getNext( iterDesc, iterList );
iterDesc != NULL && iterList != NULL;
tableIter.getNext( iterDesc, iterList ) )
{
// in this loop we go through all the columns of this table that were
// reduced collecting partial muli-col UEC lists. These partial multi-col
// UEC lists could be overlapping or disjoint. Preference is given to
// overlapping multi-col UECs over disjoint multi-col UECs. Partial
// multi-col UECs are combined as follows:MC UEC needed (a, b, c, d)
// For overlapping: MC-UEC available - (a, b, c) (c, d).
// MC (a, b, c, d) = MC (a, b, c) * MC (c, d) / MC (c)
// For disjoint: MC-UEC available (a, b) (c, d)
// MC (a, b, c, d) = MC(a, b) * MC(c, d)
if ( iterList->entries() > mostSupersetRefs )
{
mostSupersetRefs = iterList->entries();
superList = iterList;
}
// Less than two columns of this table have been reduced.
// Hence cannot use multi-col UEC
if (iterList->entries() < 2) continue;
// Contains the set of columns with highest UEC from amongst all tables
// It contains the cumulative set of all columns which have multi-col UEC
// available. For example, if we have predicates on column (a, b, c, d, e)
// and multi-col UEC available for (a, b), (c, d). Then this would contain
// (a, b, c, d). Later
ValueIdSet cumulativeColSetWithMCUEC;
// contains the multi col UEC for cumulativeColSetWithMCUEC, In case of partial
// multi-col UEC, it is a function of all partial multi-col UECs for columns
// in cumulativeColSetWithMCUEC, as described above.
// In case there are two tables with same number of columns competing for
// multi-colUEC this variable would contain the higher MC-UEC.
// Correspondingly cumulativeColSetWithMCUEC would contain the column
// set with higher multi-col UEC. For example, T1 (a, b, c, d) has
// multi-col UEC = 1000 and T2 (a, b, c, d) has multi-col UEC = 1200
// cumulativeColSetWithMCUEC would contain T2 (a, b, c, d) and
// maxMultiColUec = 1200
CostScalar maxMultiColUec = csOne;
// colsWithReductions contain the columns remaining to be checked for
// multi-col UEC
ValueIdSet colsWithReductions(*iterList);
// See is multi-column UEC exists for all the columns of the table which
// have local predicates on them. That is the best case.
CostScalar mcUec = lookup(colsWithReductions);
if (mcUec.isGreaterThanZero() )
{
maxMultiColUec = mcUec;
cumulativeColSetWithMCUEC.addSet(colsWithReductions);
}
else
{
// we had more than one column with reduction for this table
// but there is no multi-col UEC.
// If this iterList forms a unique index, we shall fake its multi-col UEC
// with the rowcount, and continue. Else we shall display missing stats
// warning for this.
CostScalar baseRowCount = iterDesc->getNATable()->getOriginalRowCount();
if (colsWithReductions.doColumnsConstituteUniqueIndex(iterDesc))
{
// get base rowcount of the table from original colStats
// and set the multi-col UEC equal to this rowcount.
insertPair(colsWithReductions, baseRowCount);
maxMultiColUec = baseRowCount;
cumulativeColSetWithMCUEC.addSet(colsWithReductions);
}
else
{
// we still have a possibility of finding multi-col UEC for this table
// columns
// Combine MC UEC of subset of columns from columns with reduction to get
// MC UEC of larger set
// statsCreated will return TRUE only if the MC stats for larger
// set of columns were created using overlapping subset of columns
ValueIdSet colsWithPreds = colsWithReductions;
NABoolean statsCreated = createMCStatsForColumnSet(colsWithReductions,
cumulativeColSetWithMCUEC,
maxMultiColUec,
baseRowCount
);
NABoolean displayWarning;
if (isMCStatsUseful(colsWithPreds, iterDesc))
displayWarning = TRUE;
else
displayWarning = FALSE;
// Do not display the warning if we were able to create MC Stats for the
// full set of columns from subset of overlapping column sets or
// if the subset of columns requiring MC stats are orthogonal
if (isMCStatsUseful(colsWithPreds, iterDesc))
{
// log selectivity from single column histograms in ustat log
CostScalar sel = newRowCount /oldRowCount;
displayMissingStatsWarning(iterDesc, (ValueIdSet)*iterList, largeTableNeedsStats, displayWarning, scHists, sel, FALSE, REL_SCAN);
}
} // end else (colsWithReductions.doColumnConstituteUniqueIndex(iterDesc))
} // end else (mcUec.isGreaterThanZero() )
// no more multi-col UECs for this table columns
// Compare with the earlier computed MC UEC for tables
// to see if this should be picked up. The table, which has
// MC-UEC available for most columns is chosen. In case of a
// tie between the two tables, we pick the one with higher MC-UEC.
if ( (cumulativeColSetWithMCUEC.entries() > largestSubsetAmongLargestSubsets.entries() ) ||
( (cumulativeColSetWithMCUEC.entries() == largestSubsetAmongLargestSubsets.entries() ) &&
(maxMultiColUec > multiColumnUec) ) )
{
// This table has multi-col UEC on more number of columns than
// any of the tables we have iterated till now
// largestSubsetAmongLargestSubsets now contains the largest
// column set with multi-col UEC
largestSubsetAmongLargestSubsets = cumulativeColSetWithMCUEC;
// multi-col UEC for this set
multiColumnUec = maxMultiColUec;
// Table with most columns with multi-col UEC
mostRefdTable = iterDesc;
mostRefs = cumulativeColSetWithMCUEC.entries();;
}
} // for (tableIter .... )
// no table with reduction on more than one column
if (mostSupersetRefs <= 1)
return FALSE;
// There are tables with more than one column, but none have multi-col UEC.
if ( mostRefs <= 1 )
return FALSE;
// After having gotten the largest multi-col UEC for this table, get the
// product of predReductions from single column histograms for those columns
CostScalar uecReduction = csOne;
if (largestSubsetAmongLargestSubsets.entries() >= 2)
{
// accumulate single column reductions of all columns with multi-col UEC
for ( ValueId i = largestSubsetAmongLargestSubsets.init();
largestSubsetAmongLargestSubsets.next( i );
largestSubsetAmongLargestSubsets.advance( i) )
{
uecReduction *= (*(predReductions.getFirstValue( &i )));
}
}
//Initial row count of the table before any predicates were applied.
//Need to use this because multiColumnUec is for that table only so
//need to stay consistent.
const ColStatDescList & statsList = mostRefdTable->getTableColStats();
const CostScalar & initialRowCount = statsList[0]->getColStats()->getRowcount();
// Following set of conditions are used to check for correlation:
// and subsequently decide what reduction adjustment to pick
if( ( oldRowCount * uecReduction ) < ( initialRowCount / multiColumnUec )
AND ( initialRowCount / multiColumnUec ) > csOne
AND (uecReduction.getValue() > 0.0 ) )
{
// correlation between the columns exist, and therefore we will be able
// to benefit from the multi-col UEC
reductionAdjustment = ( csOne / uecReduction ) * ( csOne / multiColumnUec );
}
else if ( (oldRowCount * uecReduction).isLessThanOne()
AND ( oldRowCount * uecReduction ) < ( initialRowCount / multiColumnUec )
AND newRowCount.isGreaterThanZero() /* != csZero */)
{
// New rowcount obtained from single-column histograms is less than zero.
// set the reductionAdjustment, so that the final rowcount is set to one
reductionAdjustment = csOne / newRowCount;
}
else
{
// Columns are not correlated, so no reduction adjustment is needed
reductionAdjustment = csOne;
}
// contains either the largest number of columns for which the
// multi-col uec exists or the number of columns which were reduced
// which ever is smaller.
count = MINOF( largestSubsetAmongLargestSubsets.entries(),
numPredicates );
return TRUE;
}
NABoolean
MultiColumnUecList::createMCStatsForColumnSet(ValueIdSet colsWithReductions,
ValueIdSet & cumulativeColSetWithMCUEC,
CostScalar & maxMultiColUec,
CostScalar baseRowCount
)
{
// accumulate all partial multi-col UEC for columns with predicates
// for this set. In case of a tie send the one with larger correlation
cumulativeColSetWithMCUEC = largestSubset(colsWithReductions);
// If largestSubset.entries() == 1, then that means there are no
// multi-col UECs available for this table, so there is nothing
// that we can do. If any multi-col UEC was returned, then we have
// a chance of availability of partial multi-col UECs, that we can
// use
if (cumulativeColSetWithMCUEC.entries() <= 1)
return FALSE;
// combine this MC UEC with any partial overlapping multi-col UECs
maxMultiColUec = lookup(cumulativeColSetWithMCUEC);
// get the remaining columns for which multi-col UEC could not be found
colsWithReductions.subtractSet(cumulativeColSetWithMCUEC);
// In the following two method we try to create multi-column UEC list
// for larger column set using multi-column UEC for smaller set of columns
// For this we need to traverse the MC list a number of times.
// In the following method, we shall create a new MC list.
// This list contains MC-UEC for only those column set, which include
// all remaining columns
MultiColumnUecList * mcListForRemainingCols = createMCListForRemainingCols(
colsWithReductions,
cumulativeColSetWithMCUEC);
// We will have two flags to indicate whether the MC stats for a larger set of
// columns was created using overlap subset of columns or disjoins set of columns
// If the stats were created using overlap set of columns, we would have been able
// to capture correlation pretty well and we need not give a warning. But if the
// stats were created using disjoint set of columns, then we should give a warning
NABoolean statsCreatedWithOverlap = FALSE;
NABoolean statsCreatedWithDisjoint = FALSE;
// Use the MC list created above to collect all relevant partial
// overlapping multi-col UECs first to get the correlation for
// larger set of columns
statsCreatedWithOverlap = mcListForRemainingCols->createMCUECWithOverlappingColSets(
colsWithReductions,
cumulativeColSetWithMCUEC,
maxMultiColUec,
baseRowCount);
// If any stats were created from partial multi-col UEC, save them in the
// multi-col UEC list for future use.
if (statsCreatedWithOverlap)
{
// After all the calculations, the MC UEC should not exceed the
// original rowcount of the table
maxMultiColUec = MINOF(maxMultiColUec, baseRowCount);
this->insertPair(cumulativeColSetWithMCUEC, maxMultiColUec);
// if MC UEC has reached the limit of rowcount, or we have been
// able to generate stats for the complete set, return TRUE
// else continue to build MC stats from smaller subset of columns
if ( (maxMultiColUec == baseRowCount) ||
(colsWithReductions.entries() <= 1) )
return TRUE;
}
// for the remaining columns, use relevant partial disjoint multi-col UECs
// if computed multi-col UEC has already reached the limit of rowcount
// then there is no need to proceed further, as we will not get any benefit
if ( ( maxMultiColUec < baseRowCount) AND
(colsWithReductions.entries() > 1) )
statsCreatedWithDisjoint = mcListForRemainingCols->createMCUECWithDisjointColSets(
colsWithReductions,
cumulativeColSetWithMCUEC,
maxMultiColUec,
baseRowCount);
// After all the calculations, the MC UEC should not exceed the
// original rowcount of the table
maxMultiColUec = MINOF(maxMultiColUec, baseRowCount);
// If any stats were created from partial multi-col UEC, save them in the
// multi-col UEC list for future use.
if (statsCreatedWithDisjoint)
this->insertPair(cumulativeColSetWithMCUEC, maxMultiColUec);
if (statsCreatedWithOverlap || statsCreatedWithDisjoint)
return TRUE;
else
return FALSE;
} // createMCStatsForColumnSet
// -----------------------------------------------------------------------
// MultiColumnUecList::getUecForMCJoin
//
// used by multi-column join code (fn CSDL::useMultiUecIfMultipleJoins(), subr of
// fn CSDL::estimateCardinality() )
//
// first, we list the struct used internally and the subroutine
// -----------------------
// struct MCJoinPairStruct
//
// this struct is used in the ::getUecForMCJoin() routine to keep track of
// the correspondence between :
//
// (1) a pair of ValueIdSets (representing the columns from two tables);
//
// (2) the larger of these two sets' multi-column uecs (needed by the
// calling function) ;
//
// (3) the join predicates that are not covered by the two ValueIdSets
// (i.e., the preds that we need to address later in order to correctly estimate
// the entire multi-column join's rowcount)
// -----------------------
struct MCJoinPairStruct
{
ValueIdSet tableOneCols_ ;
ValueIdSet tableTwoCols_ ;
CostScalar prodInitUec_ ;
CostScalar multiColUec_ ;
CostScalar leftMCUec_;
CostScalar baseRowCount_ ;
NAList<ValueIdSet> * remainingJoinPairs_;
// stupid Collections classes require this fn! argh!
NABoolean operator == (const MCJoinPairStruct & rhs)
{ return tableOneCols_ == rhs.tableOneCols_ && tableTwoCols_ == rhs.tableTwoCols_ ; }
public:
MCJoinPairStruct():remainingJoinPairs_(new (CmpCommon::statementHeap())
NAList<ValueIdSet>(CmpCommon::statementHeap()) ) {}
~MCJoinPairStruct(){ delete remainingJoinPairs_;}
} ;
// -----------------------
// global function findMatchingColumns()
//
// subroutine of MultiColumnUecList::getUecForMCJoin()
//
// Given a ValueIdSet (t1Cols) representing the columns from one table,
// figure out the corresponding columns from the second table
// (t2Cols==retval) so that a subset of the join predicates (joinPairs)
// are covered by the two sets of table columns. Any joinPairs not
// covered are placed into (remainingPairs).
//
// This is an ugly little subroutine, not placed in the main code in order
// to (attempt to) keep things a bit clearer.
// -----------------------
ValueIdSet
MultiColumnUecList::findMatchingColumns (
const ValueIdSet & t1Cols, /* in */
const LIST(ValueIdList) & joinPairs, /* in */
LIST(ValueIdList) & remainingPairs, /* out */
CostScalar & maxInitUecProduct,/* out */
CostScalar & minInitUecProduct,/* out */
NABoolean & insuffMCInfo /* out */
) const
{
ValueIdSet t2Cols; // #retval
for ( CollIndex i = 0 ; i < joinPairs.entries(); i++ )
{
NABoolean inserted = FALSE ;
if ( joinPairs[i].entries() > 2 )
{
// we cannot handle a join between many (>2) columns -- do not
// use this join predicate
remainingPairs.insert( joinPairs[i] );
continue ;
}
const ValueId & firstId = joinPairs[i][0];
const ValueId & secondId = joinPairs[i][1];
for ( ValueId id = t1Cols.init();
t1Cols.next( id );
t1Cols.advance( id ) )
{
if ( id == firstId || id == secondId )
{
// keep a running total of the product of the columns'
// initial uec. If single column stats do not exist
// for any column, then its multi-column uec should be ignored
// For example: if there is single column statistics for column I1
// then the MC UEC for any set containing I1 (eg: (I1, I2) ) cannot
// be considered for Join
CostScalar uec1;
CostScalar uec2;
uec1 = this->lookup( firstId );
if (uec1.isLessThanZero() )
{
insuffMCInfo = TRUE;
// eventhough we are returning t2Cols, it shall not be used
return t2Cols;
}
else
{
uec2 = this->lookup( secondId );
if (uec2.isLessThanZero() )
{
insuffMCInfo = TRUE;
// eventhough we are returning t2Cols, it shall not be used
return t2Cols;
}
}
if ( id == firstId )
t2Cols.insert( secondId );
else
t2Cols.insert( firstId );
inserted = TRUE ;
maxInitUecProduct *= MAXOF( uec1, uec2 );
minInitUecProduct *= MINOF( uec1, uec2 );
break;
}
}
if ( NOT inserted )
remainingPairs.insert( joinPairs[i] );
}
CCMPASSERT( t2Cols.entries() == t1Cols.entries() ); // sanity check
return t2Cols ;
}
// A help function to break the tie of two table descs. The side with
// smaller ipFactor, smaller mcUec or larger valueId value of the
// first column wins.
Int32 tieBreaker(
CostScalar ipFactor1, CostScalar mcUec1, TableDesc* tabDesc1,
CostScalar ipFactor2, CostScalar mcUec2, TableDesc* tabDesc2
)
{
if (ipFactor1 < ipFactor2 )
return 1;
if (ipFactor1 > ipFactor2 )
return -1;
if (mcUec1 < mcUec2 )
return 1;
if (mcUec1 > mcUec2 )
return -1;
if ( tabDesc1 == tabDesc2 )
return 0;
if ((CollIndex) ((tabDesc1->getColumnList())[0]) >
(CollIndex) ((tabDesc2->getColumnList())[0])
)
return 1;
else
return -1;
}
// -----------------------------------------------------------------------
// MultiColumnUecList::getUecForMCJoin
//
// Given a list of ValueIdLists representing the two (or more) join
// predicates between (hopefully) two tables, return TRUE/FALSE if we
// have the necessary multi-column uec information about some of the
// columns involved in this join.
//
// Return this multi-column uec number (the larger of the two tables's
// multi-column uec for the columns in question), and the join columns
// which weren't used to generate this number.
//
// i.e., if we do
// "sel * from T1,T2 where T1.a=T2.b AND T1.c=T2.d",
// we need MC-info on (T1.a,T1.c) and (T2.b,T2.d) -- if this exists,
// then return TRUE and set maxMultiColUec to be the larger of the two
// corresponding multi-column uec values for (T1.a,T1.c) & (T2.b, T2.d)
//
// as a more general case, if we do
// "sel * from T1,T2 where T1.c1=T2.c1 AND T1.c2=T2.c2 AND
// ... T1.cn=T2.cn", then we want to return the largest ValueIdSets
// (t1.1,...,t1.m) (t2.1,...t2.m) such that there is exactly one t2.1
// for every t1.1 -- any remaining ValueIdSets in joinValueIdPairs are
// returned to the calling function, which must then apply
// single-column selectivity for them.
//
// retval :
//
// TRUE: If we can return MC-info for at least two columns from each
// table, return TRUE and set the 2nd and 3rd parameters to be the
// column subsets from the two tables in question.
//
// FALSE: If we don't have MC-info for at least two columns from each
// table, then simply return FALSE.
//
// NB: this routine recurses if necessary. Overall, we're using a greedy
// algorithm to try to cover the join column ValueId's with multi-column
// uec information. This greedy algorithm might not find the best overall
// answer, but a more general solution will have nasty time-complexity.
// -----------------------------------------------------------------------
NABoolean
MultiColumnUecList::getUecForMCJoin (
LIST(ValueIdList) & joinValueIdPairs, /* in/out */
const NABoolean largeTableNeedsStats, /* in */
const Join * expr,
CostScalar & prodInitUec, /* out */
CostScalar & multiColUec, /* out */
CostScalar & baseRCForMCUEC, /* out */
CostScalar & leftMCUec, /* out */
NABoolean & checkForLowBound, /* out */
NABoolean & joinOnUnique, /* out */
const ColStatDescList & colStats,
CostScalar redFromSC
)
{
if ( joinValueIdPairs.entries() < 2 )
return FALSE;
// first, we need to divide the ValueId's in joinValueIdPairs into two list
// of table columns, corresponding to the two tables that are being
// joined together
// We use a list, as we want to know exactly which column is being joined
// to which column of the other table, so we can compare their base UECs
// correctly while looking for containment.
// For other purposes where we can do with a set, we will keep a copy
// of the set too
ValueIdList tableOneList, tableTwoList;
ValueIdSet tableOneSet, tableTwoSet;
CollIndex i, j;
// -----------------------------------------------------------------------
// First, we need to identify whether we have two tables T1, T2
// such that we're joining two columns (minimum) between these
// two tables
//
// We use an hash-dictionary to implement an association list of
// <TableDesc*, ValueIdList> pairs -- each ValueIdList indicates
// the list of columns from this table participating in join
// -----------------------------------------------------------------------
NAHashDictionary<TableDesc,ValueIdList> tableColumns (
&TableDescHashFn, 7, TRUE, HISTHEAP );
// hash fn, initsize, uniq, heap)
// joinValueIdPairs indicate the list of joining pairs
// example, T1.a = T2.a and T1.b = T2.b. The pairs would be
// T1.a, T2.a and T1.b, T2.b
for ( i = 0; i < joinValueIdPairs.entries(); i++ )
{
// get the list of joining columns
const ValueIdList & joinList = joinValueIdPairs[i];
// for each joining column, get its bast table and
// see if this column belongs to the current table we are looking at
for ( j = 0; j < joinList.entries(); j++ )
{
const ValueId & iterId = joinList[j];
// initially assume ITM_BASETABLE
const BaseColumn * iterExpr = iterId.castToBaseColumn();
if ( iterExpr == NULL )
return FALSE; // unexpected condition
TableDesc * iterDesc = iterExpr->getTableDesc();
if (iterDesc == NULL)
{
CCMPASSERT( iterDesc != NULL );
return FALSE;
}
// do a lookup in the hash dictionary we're currently populating
// to see if an entry for that table already exists
ValueIdList * colList = tableColumns.getFirstValue( iterDesc );
if ( colList != NULL )
{
// if the entry for that table already exists, add the column to the columnList
// of this table. This would be true while traversing the second and beyond
// joining columns of this table
colList->insert( iterId );
}
else
{
// if this is the first entry for this table in hash dictionary
// create an entry with the tableDesc and the columnId
colList = new (HISTHEAP) ValueIdList( );
colList->insert( iterId );
tableColumns.insert( iterDesc, colList );
}
}
}
// -----------------------------------------------------------------------
// We have lists of references to tableDesc's; now, we iterate through them
// and make sure that we have two tableDesc's that are referenced a minimum
// of twice each.
//
// If we don't find two tables with two references each, we quit.
//
// If there were >2 tables like this, we take the two tables which were
// referenced the most. If there are ties, we pick the one with most correlation
// If the correlation is identical we pick the one with smaller MC UECs
// -----------------------------------------------------------------------
NAHashDictionaryIterator<TableDesc,ValueIdList> tableIter( tableColumns );
TableDesc * tableDesc = NULL;
ValueIdList * colList = NULL;
ValueIdSet colSet;
CollIndex mostRefs = 0,
secondMost = 0;
ValueIdSet colSetForMostRefs, colSetForSecondMost;
CostScalar mcUecCurrent = COSTSCALAR_MAX;
CostScalar mcUecMostRefd = COSTSCALAR_MAX;
CostScalar mcUecSecondMost = COSTSCALAR_MAX;
CostScalar ipFactorCurrent = csOne;
CostScalar ipFactorMostRefd = csOne;
CostScalar ipFactorSecondMost = csOne;
// Keep a flag to use later to differentiate the conditions that MC stats
// cannot be used because they are not needed or because the stats are missing
NABoolean mcStatsPresent = TRUE;
// the table with the most refs is tableOne; the table with the 2nd most
// refs is tableTwo (not that this matters ... :-)
TableDesc * tableOneDesc = NULL;
TableDesc * tableTwoDesc = NULL;
for ( tableIter.getNext( tableDesc, colList );
tableDesc != NULL && colList != NULL;
tableIter.getNext( tableDesc, colList ) )
{
colSet = (ValueIdSet) *colList;
if (colSet.entries() < 2 )
{
// This table joined on only one column, hence cannot be
// used for MC adjustment
continue;
}
mcStatsPresent = TRUE; // assume mc stats is available for this colSet
mcUecCurrent = lookup(colSet);
// if the columns are unique use basetable rowcount as the MC UEC
NABoolean colsUnique = FALSE;
if (mcUecCurrent == csMinusOne)
{
// MC stats do not exist for the joining columns
// check to see if it is unique and if we can use rowcount as the MCUEC
colsUnique = colSet.doColumnsConstituteUniqueIndex(tableDesc);
if (colsUnique)
{
// if colSet == unique index, the MC stats for that would have been added
// much earlier at the time of fetching stats. This is for cases when
// colSet is a super set of unique index.
// get base rowcount of the table from original colStats
// and set the multi-col UEC equal to this rowcount
// save it for later use too
mcUecCurrent = tableDesc->getNATable()->getOriginalRowCount();
this->insertPair(colSet, mcUecCurrent);
}
else
{
// Since the stats are missing, use just the colSet entries to make a decision
// We will give the warning regarding missing stats later, based on other
// heuristics
mcStatsPresent = FALSE;
}
}
CostScalar SCproductUec = csOne;
// compute correlation factor for this set. Smaller the ipFactor
// larger the correlation. ipFactor =1 means there
// is no correlation, or the columns are unique.
// If there is a conflict we pick the one with larger correlation
if (!colsUnique && mcStatsPresent)
{
for (ValueId keyCol = colSet.init();
colSet.next(keyCol);
colSet.advance(keyCol) )
SCproductUec *= lookup(keyCol).value();
ipFactorCurrent = mcUecCurrent/SCproductUec;
}
else
ipFactorCurrent = csOne;
// For choosing the most referenced tables, pick the one joining on larger number
// of columns. If there are more than one tables joining on same number of columns
// pick the one with larger correlation (smaller ipFactor). If there are more than
// one table with same ipFactor and same number of joining columns, pick the one with
// smaller MCUEC.
// if MC Stats are missing just use number of columns as the guiding factor in
// deciding which tables to choose for MC adjustment.
// We are doing this here as later we use heuristics to decide whether MC stats
// warning is needed or not. We also use heuristic later to construct MC stats
// using smaller subset of columns. Hence missing stats at this point does not mean
// that we are done.
if (CmpCommon::getDefault(COMP_BOOL_41) == DF_ON)
mcStatsPresent = FALSE;
if ( ( colSet.entries() > mostRefs )
||
(
(mcStatsPresent && colSet.entries() == mostRefs) &&
(tableOneDesc == NULL ||
tieBreaker(ipFactorCurrent, mcUecCurrent,tableDesc,
ipFactorMostRefd, mcUecMostRefd, tableOneDesc) == 1)
)
)
{
secondMost = mostRefs;
colSetForSecondMost = colSetForMostRefs;
mcUecSecondMost = mcUecMostRefd;
tableTwoDesc = tableOneDesc;
ipFactorSecondMost = ipFactorMostRefd;
mostRefs = colSet.entries();
colSetForMostRefs = colSet;
mcUecMostRefd = mcUecCurrent;
tableOneDesc = tableDesc;
ipFactorMostRefd = ipFactorCurrent;
}
else if ( ( colSet.entries() > secondMost )
||
((mcStatsPresent && colSet.entries() == secondMost) &&
( tableTwoDesc == NULL ||
tieBreaker(ipFactorCurrent, mcUecCurrent,tableDesc,
ipFactorSecondMost, mcUecSecondMost, tableTwoDesc) ==
1)
)
)
{
secondMost = colSet.entries();
colSetForSecondMost = colSet;
mcUecSecondMost = mcUecCurrent;
tableTwoDesc = tableDesc;
ipFactorSecondMost = ipFactorCurrent;
}
}
// If there was only one table with MC stats, then there is nothing we can do
// return.
if ( secondMost == 0 )
return FALSE;
// did that go alright? check to make sure:
// It would not do any major harm
// even if the tableDescs are null, because later these are used for
// for finding the columns which belong to the tableDescs, and if these
// are NULL, then that means that no columns can be used for MC estimates
// which should not be fatal enough to cause the crash.
if ((tableOneDesc == NULL) || (tableTwoDesc == NULL ))
{
CCMPASSERT( tableOneDesc != NULL && tableTwoDesc != NULL );
return FALSE;
}
// now iterate again and grab the tables which had the mostRefs & secondMost
// most and second most referenced tables have their table descriptors saved
// in tableOneDesc and tableTwoDesc
tableIter.reset();
ValueIdSet joinsUsed;
for ( tableIter.getNext( tableDesc, colList );
tableDesc != NULL && colList != NULL;
tableIter.getNext( tableDesc, colList ) )
{
if ( ( tableDesc == tableOneDesc) || ( tableDesc == tableTwoDesc) )
{
joinsUsed.insert((ValueIdSet) * colList);
}
}
if ( joinsUsed.entries() < 2 ) // something weird, but legal (e.g., T1.a=T1.b=T2.a=T2.b)
return FALSE; // cannot handle this case, punt!
// -----------------------------------------------------------------------
// Now we need to create a "clean" list of joinValueIdPairs (to simplify
// future processing), removing any column references that do not refer
// to our two TableDesc*'s. Initially, we just grab the subset of the
// joinValueIdPairs that referenced the two tables.
//
// Any unusable entries in joinValueIdPairs are stored in a new list
// (joinPairsNotUsed), which we'll add to our "unused join preds" retval
// before returning from the function.
// -----------------------------------------------------------------------
// joinPairsUsed + joinPairsNotUsed = joinValueIdPairs
NAList<ValueIdList> joinPairsUsed(CmpCommon::statementHeap()),
joinPairsNotUsed(CmpCommon::statementHeap());
for ( i = 0; i < joinValueIdPairs.entries(); i++ )
{
ValueIdSet joiningCols = joinsUsed;
ValueIdSet joinColPair = joinValueIdPairs[i];
if ( joiningCols.intersectSet( joinColPair ).entries() >= 2 )
joinPairsUsed.insert( joinValueIdPairs[i] );
else
joinPairsNotUsed.insert( joinValueIdPairs[i] );
}
// -----------------------------------------------------------------------
// Now remove table references from joinPairsUsed that don't match
// either tableOneDesc or tableTwoDesc. Note that this step is
// potentially buggy; if we're doing something like
//
// {T1.a=T2.a=T3.a,T1.b=T2.b=T3.b, T1.c=T2.c}
//
// then calculating the resulting rowcount by just looking at
//
// {T1.a=T2.a,T1.b=T2.b,T1.c=T2.c}
//
// is inexact -- if we were being really careful (which would require a
// *ton* more checking, which isn't warranted due to the little we'd
// gain), then we wouldn't throw away the reference to T3. But that's
// what we're doing right now. So there.
//
// We are trying to handle the case where we do the following
// "transformation":
//
// {T1.a=T2.a=T3.a, T1.b=T4.b, T1.c=T2.c=T5.c} ==>
// {T1.a=T2.a, T1.c=T2.c}
//
// This happens in TPC-D, for example.
//
// NB: If we have a join of the form :
//
// {T1.a=T1.b=T2.a}
//
// We punt on that one -- put it into joinPairsNotUsed.
// -----------------------------------------------------------------------
ValueIdList empty;
// Go thru all pairs of joining columns
for ( i = 0; i < joinPairsUsed.entries(); i++ )
{
NABoolean tableOneRefd = FALSE,
tableTwoRefd = FALSE;
const ValueIdList & joinList = joinPairsUsed[i];
ValueIdSet joinSet( joinList ); // convenience : list->set
// For each pair get all columns. Example if T1.a = T2.a = T3.a
// then the joinlist will contain, T1.a, T2.a, T3.a
// From this list keep only those columns which have been chosen
// to do MC adjustment. These columns are referred by tableOneDesc
// and tableTwoDesc
for ( j = 0; j < joinList.entries(); j++ )
{
const ValueId & iterId = joinList[j];
// initially assume ITM_BASETABLE
const BaseColumn * iterExpr = iterId.castToBaseColumn();
if ( iterExpr == NULL )
return FALSE ; // unexpected condition
const TableDesc * iterDesc = iterExpr->getTableDesc();
if (iterDesc == NULL)
{
CCMPASSERT( iterDesc != NULL );
return FALSE;
}
if ( iterDesc == tableOneDesc )
tableOneRefd = TRUE;
else if ( iterDesc == tableTwoDesc )
tableTwoRefd = TRUE;
else
joinSet.remove( iterId );
}
// make sure that each table was ref'd exactly once (i.e., both
// bools are TRUE and there are only two entries left)
if ( NOT ( tableOneRefd AND tableTwoRefd AND joinSet.entries() == 2) )
{
joinPairsNotUsed.insert( joinPairsUsed[i] );
joinPairsUsed[i] = empty;
}
else
{
ValueIdList tmpPair = joinSet;
joinPairsUsed[i] = tmpPair;
}
}
// -----------------------------------------------------------------------
// Now filter out all of the empty lists inside joinPairsUsed -- to make
// the previous loop's logic simpler, we put off dealing with the "no
// auto increment" until now.
// -----------------------------------------------------------------------
for ( i = 0; i < joinPairsUsed.entries(); /* no auto increment */ )
if ( joinPairsUsed[i].entries() == 0 )
joinPairsUsed.removeAt( i );
else
i++ ;
// -----------------------------------------------------------------------
// Now we iterate through the "cleaned-up" list of joinPairs, and collect
// the ValueId's of joining columns from each table for which MC will be used
//
// First, we check again : if joinPairsUsed doesn't have at least two
// entries, then we quit.
// -----------------------------------------------------------------------
if ( joinPairsUsed.entries() < 2 )
return FALSE;
for ( i = 0; i < joinPairsUsed.entries(); i++ )
{
const ValueIdList & joinList = joinPairsUsed[i];
// if any column has !=2 columns, then something is very wrong in
// the previous logic, and we want to know about it!
// Not from the customer though. If the entries are not equal
// to 2, then that means we shall not be able to use MC UEC
if (joinList.entries() != 2 )
{
CCMPASSERT( joinList.entries() == 2 );
return FALSE;
}
for ( j = 0; j < joinList.entries(); j++ )
{
const ValueId & iterId = joinList[j];
// initially assume ITM_BASETABLE
const BaseColumn * iterExpr = iterId.castToBaseColumn();
if ( iterExpr == NULL )
return FALSE ; // unexpected condition
const TableDesc * iterDesc = iterExpr->getTableDesc();
if (iterDesc == NULL)
{
CCMPASSERT( iterDesc != NULL );
return FALSE;
}
if ( iterDesc == tableOneDesc )
{
tableOneSet.insert( iterId );
tableOneList.insert(iterId);
}
else if ( iterDesc == tableTwoDesc )
{
tableTwoSet.insert( iterId );
tableTwoList.insert(iterId);
}
else
{
// it's a table column not associated with either of
// the two tables we're joining -- however, we already
// tried to remove all of these references! abort!
// If there is a table column not associated with any of the two tables
// then in the worst case we shall not be able to use MC stats, but
// it should not be a work stoppage
CCMPASSERT( FALSE );
return FALSE ;
}
} // j-loop
} // i-loop
// for whatever reason, we don't have the situation where there are
// at least two candidate columns from each of two tables; abort
if ( tableOneSet.entries() < 2 OR tableTwoSet.entries() < 2)
return FALSE;
// See if the join is being done on non-key columns. If it is then
// we don't want to use MC uec. This is because through MC stats we are
// not able to correctly estimate the containment of non-unique columns
// into the other. For Example:
// T1(a,b,c,d,e ...) key is a,b,c rowcount = 1Mil
// T2(a,b,c,d,e ) key is a,b,c rowcount = 1Mil
// Uec(1.a,1.b) = 1000
// Uec(2.a,2.b) = 10000
// Using MC we force the join estimate to be 1M x 1M / 10000
// Here we assumed that the 1000 values of 1.a,1.b are subset
// of the 10000 set of 2.a,2.b. This is usually not correct
// and results in overestimation of the result of the cardinality.
// This would have been most likely correct if 2.a,2.b was the
// key for T2 as this would be foreign key join, so we allow that exception.
// CQD HIST_SKIP_MC_FOR_NONKEY_JOIN_COLUMNS is used to control
// this behaviour. 'OFF' for this CQD means we use MC stats even
// for joins on non-key columns. 08/24/04
NABoolean tableOneSetUnique = tableOneSet.doColumnsConstituteUniqueIndex(tableOneDesc);
NABoolean tableTwoSetUnique = tableTwoSet.doColumnsConstituteUniqueIndex(tableTwoDesc);
if (CURRSTMT_OPTDEFAULTS->histSkipMCUecForNonKeyCols() )
{
// Check if the primary key or a unique index of either table is fully covered
// by the columns on which this table is being joined. If none of the tables
// are being joined on primary key, return
if ( !( tableOneSetUnique || tableTwoSetUnique ) )
return FALSE;
}
if (tableOneSet.entries() != tableTwoSet.entries() )
{
// The number of columns being joined from two tables
// should be equal, if not, we cannot use multi-column UEC
CCMPASSERT( tableOneSet.entries() == tableTwoSet.entries() );
return FALSE;
}
NABoolean displayWarning = TRUE;
if ( (CmpCommon::getDefault(HIST_MC_STATS_NEEDED) == DF_ON)
AND largeTableNeedsStats )
{
NAString tableOneName(CmpCommon::statementHeap()),
tableTwoName(CmpCommon::statementHeap()); // table names
NAString tableOneCols(CmpCommon::statementHeap()),
tableTwoCols(CmpCommon::statementHeap()); // column names
if ((this->lookup( tableOneSet )).isLessThanZero() )
{
// There is no multi-col uec list for tableOneSet. If tableOneSet is unique
// fake multi-col uec, by setting its UEC equal to rowcount
if (tableOneSetUnique)
{
// get base rowcount of the table from original colStats
// and set the multi-col UEC equal to this rowcount
CostScalar baseRowCount = tableOneDesc->getNATable()->getOriginalRowCount();
this->insertPair(tableOneSet, baseRowCount);
}
else
{
if (( tableTwoSetUnique ) || (!CURRSTMT_OPTDEFAULTS->histSkipMCUecForNonKeyCols()) )
{
// But it is being joined to unique columns so display missing stats warning
// if the warning could be useful. The warning level should be
// greate than or equal to 3 for the MC warnings from Join to be displayed
if ( (CURRSTMT_OPTDEFAULTS->histMissingStatsWarningLevel() > 3) &&
(isMCStatsUseful(tableOneSet, tableOneDesc)))
displayWarning = TRUE;
else
displayWarning = FALSE;
displayMissingStatsWarning(tableOneDesc,
tableOneSet,
largeTableNeedsStats,
displayWarning,
colStats,
redFromSC,
FALSE,
REL_JOIN);
}
}
}
//cs if ( this->lookup( tableTwoSet ) < csZero ) // indicating it's not present
if ((this->lookup( tableTwoSet )).isLessThanZero() )
{
// There is no multi-col uec list for tableTwoSet. If it is unique
// so we fake it with the rowcount, else display missing stats warning.
if (tableTwoSetUnique )
{
// get base rowcount of the table from original colStats
// and set the multi-col UEC equal to this rowcount
CostScalar baseRowCount = tableTwoDesc->getNATable()->getOriginalRowCount();
this->insertPair(tableTwoSet, baseRowCount);
}
else
{
if (( tableOneSetUnique ) || (!CURRSTMT_OPTDEFAULTS->histSkipMCUecForNonKeyCols()) )
{
// But it is being joined to unique columns so display missing stats warning
// if the warning could be useful. The warning level should be greater
// than or equal to 3 for the MC warnings from Join to be displayed
if ( (CURRSTMT_OPTDEFAULTS->histMissingStatsWarningLevel() > 3) &&
(isMCStatsUseful(tableTwoSet, tableTwoDesc)))
displayWarning = TRUE;
else
displayWarning = FALSE;
displayMissingStatsWarning(tableTwoDesc,
tableTwoSet,
largeTableNeedsStats,
displayWarning,
colStats,
redFromSC,
FALSE,
REL_JOIN);
}
}
}
} // no WARNING MULTI_COLUMN_STATS_NEEDED's currently in diags
// MC adjustment for semi_joins is done little differently that inner joins.
// For semi_joins we use MIN of MC_UEC instead of MAX MC_UEC.
// We will not do any short cut MC on unique column set
NABoolean isNotASemiJoin = ((expr && !expr->isSemiJoin() && !expr->isAntiSemiJoin()) ? TRUE : FALSE);
// if any of the sides was unique, and the MCUEC of the second is contained in the
// unique side, use row count of non-unique side as the join cardinality.
// Else determine the largest MCUEC and use that to compute adjustment
// for MC UEC
CostScalar rowcountOfNonUniqSide = csMinusOne;
if ((CmpCommon::getDefault(COMP_BOOL_149) == DF_ON) &&
isNotASemiJoin &&
(tableOneSetUnique || tableTwoSetUnique) )
{
rowcountOfNonUniqSide = getRowcountOfNonUniqueColSet(expr, tableOneList, tableTwoList,
tableOneSetUnique, tableTwoSetUnique);
if (rowcountOfNonUniqSide > 0)
{
if (CmpCommon::getDefault(COMP_BOOL_147) == DF_OFF)
checkForLowBound = FALSE;
else
checkForLowBound = TRUE;
// This is a misnomer - multiColUec will be storing row count of non-unique side
// instead of MC UEC.
multiColUec = rowcountOfNonUniqSide;
joinOnUnique = TRUE;
// joinValueIdPairs as an output parameter contains the join pairs that were
// not used for MCUEC. Since we used all columns for uniqueness, we will clear
// this parameter.
joinValueIdPairs.clear();
return TRUE;
}
}
// The joining set is not unique. Compute adjustment for MC UECs
// Now the join column ValueIds are partitioned into two sets. Let's
// see whether we can find MC-info corresponding to (the largest
// possible) subsets of these sets such that one subset is being joined
// to the other (i.e., both sets together add up to a subset of the
// entries in joinPairsUsed)
// Notice, this is a fairly complicated operation! It seems that the
// best way to do this is iterate through what MC-info we have -- note
// that if we don't have actual multi-column information, then we
// shouldn't have gotten this far.
ValueIdSet * keyEntry = NULL;
CostScalar * uecEntry = NULL;
MultiColumnUecListIterator iter( *this );
LIST(ValueIdSet) tableOnePossibles(STMTHEAP); // we'll iter over these
SET(ValueIdSet) tableTwoPossibles(STMTHEAP); // we'll do lookups over these
for ( iter.getNext( keyEntry, uecEntry );
keyEntry != NULL && uecEntry != NULL;
iter.getNext( keyEntry, uecEntry ) )
{
// want to consider only those entries containing MC-info
if ( keyEntry->entries() < 2 ) continue;
// we could check to make sure they're referencing the same table
// desc, as above, but the check below should be faster and
// more-than-sufficient for our needs
if ( tableOneSet.contains( *keyEntry ) )
tableOnePossibles.insert( *keyEntry );
else if ( tableTwoSet.contains( *keyEntry ) )
tableTwoPossibles.insert( *keyEntry );
}
if ( tableOnePossibles.entries() == 0 OR
tableTwoPossibles.entries() == 0 )
{
// we are returning because of insufficient multi-column
// information for this table
checkForLowBound = TRUE;
return FALSE;
}
// OK -- now we have two lists of possibly useful MC-info that we might
// be able to use; now we need to check to see whether any pair (x,y)
// between these two lists actually satisfies the requirement that the
// columns in x are being joined to the columns in y
// Plan : iterate over the entries in tableOnePossibles
//
// For each entry in tableOnePossibles, we determine, from the joinPairsUsed,
// what multi-column uec information we need to have in tableTwoPossibles.
//
// If that entry does not exist in tableTwoPossibles, we remove the current entry
// in tableOnePossibles and continue.
//
// When we're done, whatever remains in tableOnePossibles is usable.
// Then it's a matter of determining which entry in tableOnePossibles is
// best; after we do this, we need to remove some entries from
// joinValueIdPairs (the ones that correspond to the best entry in
// tableOnePossibles) and set the value of maxMultiColUec to be the
// larger of the two tables' multi-column uec.
NAList<MCJoinPairStruct *> corrList(CmpCommon::statementHeap()); // for "correspondence list"
NAList<ValueIdList> remainingPairs(CmpCommon::statementHeap()); // tmp var
CostScalar maxInitUecProduct = csOne;
CostScalar minInitUecProduct = csOne;
for ( i = 0; i < tableOnePossibles.entries(); i++ )
{
maxInitUecProduct = csOne;
minInitUecProduct = csOne;
// need to clear the entries for this call to findMatchingColumns()
remainingPairs.clear();
ValueIdSet needInTableTwo =
this->findMatchingColumns (
tableOnePossibles[i],
joinPairsUsed,
remainingPairs,
maxInitUecProduct,
minInitUecProduct,
checkForLowBound
);
// if checkForLowBound was returned from findMatchingColumns as
// no statistics existed for some column which was a part of Multi-column
// set, then return FALSE
if (checkForLowBound )
return FALSE;
// if the table 2 MC-info doesn't exist, continue
if ( NOT tableTwoPossibles.contains( needInTableTwo ) )
continue;
MCJoinPairStruct *entry = new (CmpCommon::statementHeap()) MCJoinPairStruct();
// Start setting the entries for MC computation.
// Set the columns from the two tables for which MC UEC exist
entry->tableOneCols_ = tableOnePossibles[i];
entry->tableTwoCols_ = needInTableTwo;
if (isNotASemiJoin)
entry->prodInitUec_ = maxInitUecProduct;
else
entry->prodInitUec_ = minInitUecProduct;
CostScalar mcUEC1 = this->lookup (entry->tableOneCols_);
CostScalar mcUEC2 = this->lookup (entry->tableTwoCols_);
if (expr && !isNotASemiJoin)
{
// if it is a semi_join, save the left MCUEC
ColStatDescList leftColStatsList = ((Join *)expr)->child(0).outputLogProp((*GLOBAL_EMPTY_INPUT_LOGPROP))->colStats();
if (leftColStatsList.contains(entry->tableOneCols_))
entry->leftMCUec_ = mcUEC1;
else
entry->leftMCUec_ = mcUEC2;
}
// now set the max MC UEC.
if(CmpCommon::getDefault(COMP_BOOL_141) == DF_OFF)
{
// If COMP_BOOL_141 is OFF, set the maxMCUEC from the
// base table, and the product of max init UEC as it exists in the base table
// if it is a semi_join, use min UECs instead of max
// This is the original behaviour from denali days
if ( mcUEC1 > mcUEC2)
{
if (isNotASemiJoin)
{
entry->multiColUec_ = mcUEC1;
entry->baseRowCount_ = tableOneDesc->getNATable()->getOriginalRowCount();
}
else
{
entry->multiColUec_ = mcUEC2;
entry->baseRowCount_ = tableTwoDesc->getNATable()->getOriginalRowCount();
}
}
else
{
if (isNotASemiJoin)
{
entry->multiColUec_ = mcUEC2;
entry->baseRowCount_ = tableTwoDesc->getNATable()->getOriginalRowCount();
}
else
{
entry->multiColUec_ = mcUEC1;
entry->baseRowCount_ = tableOneDesc->getNATable()->getOriginalRowCount();
}
}
}
else
{
// If COMP_BOOL_141 is ON, set the maxMCUEC after applying reduction from local predicates,
// and the product of max init UEC maxed out to the base row count of the table for
// which the MC UEC is being picked up for all except semi joins
// for semi joins take the minimum uec
CostScalar baseRowCountForTableOne = tableOneDesc->getNATable()->getOriginalRowCount();
CostScalar baseRowCountForTableTwo = tableTwoDesc->getNATable()->getOriginalRowCount();
// get the left and the right children of Join and compute highest reduction from local predicates
// for the set of joining columns
if (expr && expr->isAnyJoin())
{
CostScalar highestUecRedByLocalPreds1 = ((Join *)expr)->highestReductionForCols(entry->tableOneCols_);
CostScalar highestUecRedByLocalPreds2 = ((Join *)expr)->highestReductionForCols(entry->tableTwoCols_);
// There is no way, the reduction should go beyond 1
if (highestUecRedByLocalPreds1 > csOne)
{
CCMPASSERT ("Reduction from local predicates is greater than 1");
highestUecRedByLocalPreds1 = csOne;
}
if (highestUecRedByLocalPreds2 > csOne)
{
CCMPASSERT ("Reduction from local predicates is greater than 1");
highestUecRedByLocalPreds2 = csOne;
}
mcUEC1 = (mcUEC1 * highestUecRedByLocalPreds1).minCsOne();
mcUEC2 = (mcUEC2 * highestUecRedByLocalPreds2).minCsOne();
}
// Now use the larger of the reduced MC UEC to do cardinality adjustments
if ( ( mcUEC1 > mcUEC2) || ( (mcUEC1 == mcUEC2) &&
(baseRowCountForTableOne > baseRowCountForTableTwo) ) )
{
if (isNotASemiJoin)
{
entry->multiColUec_ = mcUEC1;
entry->baseRowCount_ = baseRowCountForTableOne;
}
else
{
entry->multiColUec_ = mcUEC2;
entry->baseRowCount_ = baseRowCountForTableTwo;
}
}
else
{
if (isNotASemiJoin)
{
entry->multiColUec_ = mcUEC2;
entry->baseRowCount_ = baseRowCountForTableTwo;
}
else
{
entry->multiColUec_ = mcUEC1;
entry->baseRowCount_ = baseRowCountForTableOne;
}
}
}// end COMP_BOOL_141 = ON
// -----------------------------------------------------------
// want to write: entry.remainingJoinPairs_ = remainingPairs ;
entry->remainingJoinPairs_->clear() ; // probably unnecessary
for ( j = 0; j < remainingPairs.entries(); j++ )
entry->remainingJoinPairs_->insert( remainingPairs[j] );
// -----------------------------------------------------------
if ( entry->multiColUec_.isGreaterThanZero() ) // sanity check -- uec<=0 is bad ...
corrList.insert( entry );
if ( entry->remainingJoinPairs_->entries() == 0 )
break; // these columns cover all joins columns -- best we can do -- stop now
}
// if the corresponding multi-column uec information does not exist in table
// two, then we cannot do anything, abort
if ( corrList.entries() == 0 )
{
// we are returning because of insufficient multi-column
// information for table 2
checkForLowBound = TRUE;
return FALSE;
}
// Now we have a list of possible return information; find out which entry is the best
CollIndex bestIdx = 0; // index of best entry so far
for ( i = 1; i < corrList.entries(); i++ )
{
if ( corrList[ i ]->remainingJoinPairs_->entries() <
corrList[bestIdx]->remainingJoinPairs_->entries() )
bestIdx = i;
}
// -----------------------------------------------------------
// Until now, we have not changed the values of the function parameters --
// now we set these values and prepare to return
//
// Don't forget to add back those joinPairs that didn't reference either
// tableOne or tableTwo!
// -----------------------------------------------------------
// -----------------------------------------------------------
// if we had a better C++ interface, we'd write the following five lines as:
// joinValueIdPairs = *(corrList[bestIdx].remainingJoinPairs_) + joinPairsUnused ;
// ----------------------------------------------------------
LIST(ValueIdList) oldJoinValueIdPairs(joinValueIdPairs,
CmpCommon::statementHeap());
joinValueIdPairs.clear();
SET(ValueIdSet) joinValueIdPairSet(CmpCommon::statementHeap());
ValueIdSet vidset;
for ( i = 0; i < corrList[bestIdx]->remainingJoinPairs_->entries(); i++ )
{
vidset = corrList[bestIdx]->remainingJoinPairs_->at(i);
joinValueIdPairSet.insert( vidset );
}
for ( i = 0; i < joinPairsNotUsed.entries(); i++ )
{
vidset = joinPairsNotUsed[i];
joinValueIdPairSet.insert( vidset );
}
for (i=0;
i<joinValueIdPairSet.entries();
i++)
{
vidset = joinValueIdPairSet.at(i);
ValueIdList vlist = vidset;
joinValueIdPairs.insert(vlist);
}
prodInitUec = corrList[bestIdx]->prodInitUec_; // #retvar
multiColUec = corrList[bestIdx]->multiColUec_; // #retvar
baseRCForMCUEC = corrList[bestIdx]->baseRowCount_; // #retvar
leftMCUec = corrList[bestIdx]->leftMCUec_;
// ------------------------------------------------------------------
// if there are two or more join predicates that are uncovered by our
// work above, then it might be possible to find another MC-uec value
// for these remaining values -- recurse and see ...
// only make the recursive call if there is a change in
// joinValueIdPairs
// ------------------------------------------------------------------
if ( oldJoinValueIdPairs == joinValueIdPairs )
{
}
else if ( joinValueIdPairs.entries() >= 2 )
{
// $$$ for the short-term, we'll recurse; later on, if this
// $$$ appears to be a performance hit, we can avoid redoing
// $$$ a lot of the same work
CostScalar moreMultiColUec = csMinusOne,
moreInitUec = csMinusOne,
moreLeftMCUec = csOne;
NABoolean insuffMCInfo = FALSE;
CostScalar newBaseRowCount = csOne;
if ( this->getUecForMCJoin( joinValueIdPairs,
largeTableNeedsStats,
expr,
moreInitUec,
moreMultiColUec,
newBaseRowCount,
moreLeftMCUec,
insuffMCInfo,
joinOnUnique,
colStats,
csMinusOne) )
{
CCMPASSERT( moreMultiColUec.isGreaterThanZero() ); // sanity check
prodInitUec = (prodInitUec * moreInitUec).minCsOne(); // multiply the maxInit-uec's together
multiColUec = (moreMultiColUec * multiColUec).minCsOne(); // multiply the MC-uec's together
leftMCUec = (moreLeftMCUec * leftMCUec).minCsOne();
baseRCForMCUEC = newBaseRowCount.minCsOne();
}
}
// $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
// $$$ One definite problem with the above code (among many, perhaps) :
// $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
// $$$ if we do something like
// $$$
// $$$ {T1.a=T2.a=T3.a, T1.b=T2.b, T1.c=T2.c}
// $$$
// $$$ reducing it to
// $$$
// $$$ {T1.a=T2.a, T1.b=T2.b, T1.c=T2.c}
// $$$
// $$$ but only have multicolumn uec information for
// $$$
// $$$ <T1.b,T1.c>, <T2.b,T2.c>
// $$$
// $$$ then the first join, as returned from this function
// $$$ to the calling fn, is <T1.a,T2.a> (we've removed the
// $$$ reference to table T3!). This won't cause real
// $$$ problems, but it's a loss of information.
// $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
if (CmpCommon::getDefault(COMP_BOOL_147) == DF_OFF)
checkForLowBound = FALSE;
else
checkForLowBound = TRUE;
CCMPASSERT( prodInitUec.isGreaterThanZero() ); // sanity check
return TRUE;
}
// -----------------------------------------------------------------------
// MultiColumnUecList::containsMCinfo
//
// It is very possible for the MultiColumnUecList to contain only
// single-column entries. Most of the usage of this class is predicated
// on it actually containing multi-column information in this list -- if
// there isn't, then very often there's no point in continuing the current
// operation.
//
// This routine answers the question of whether any TRUE multi-column
// information exists in the MultiColumnUecList.
// -----------------------------------------------------------------------
NABoolean
MultiColumnUecList::containsMCinfo () const
{
ValueIdSet * keyEntry = NULL;
CostScalar * uecEntry = NULL;
MultiColumnUecListIterator iter( *this );
for ( iter.getNext( keyEntry, uecEntry );
keyEntry != NULL && uecEntry != NULL;
iter.getNext( keyEntry, uecEntry ) )
{
if ( keyEntry->entries() > 1 )
return TRUE;
}
return FALSE;
}
// Following method creates multi-col UEC for larger set of columns
// using partial overlapping multi-col UECs.
// For example, if MC-UEC available - (a, b, c) (c, d).
// Then MC (a, b, c, d) = MC (a, b, c) * MC (c, d) / MC (c)
// returns TRUE or FALSE depending on whether it was able to create
// any statistics from partial multi-col UECs
NABoolean MultiColumnUecList::createMCUECWithOverlappingColSets(
ValueIdSet & remainingCols, /* in / out */
ValueIdSet & cumulativeColSetWithMCUEC, /* in / out */
CostScalar & mcUec, /* in / out */
CostScalar oldRowCount)
{
NABoolean statsCreated = FALSE;
// There can be several levels of overlapping. Example there can be one
// column overlapping, or two columns overlapping and so on. Process of
// trying all overlapping combinations can be very expensive, and might
// not be very cost effective. Hence we shall look for only first level
// of overlapping, in other words look for only those partial multi-col
// UECs in which we have one column overlapping
CollIndex noOfColsInterested = remainingCols.entries() + 1;
while (noOfColsInterested > 1)
{
// traverse through all the entries of the multiColUecList for this table
ValueIdSet * keyEntry = NULL ;
CostScalar * uecEntry = NULL ;
ValueIdSet overlappingCol;
MultiColumnUecListIterator iter (*this) ;
Int32 i ;
double smallestIPFactor = 1;
ValueIdSet * setWithMinIPFactor = NULL;
for ( iter.getNext (keyEntry, uecEntry), i = 1 ;
keyEntry != NULL && uecEntry != NULL ;
iter.getNext (keyEntry, uecEntry), i++ )
{
ValueIdSet cols;
cols = *keyEntry;
if (keyEntry->entries() != noOfColsInterested)
continue;
// there is no point in looking forward if the number of entries are different
// it could be interesting set, if it contains all remaining columns, and
// one column from the largestSubset
cols.subtractSet(remainingCols);
if (cols.entries() != 1)
{
// contains no or more than one overlapping columns
// so not interested
continue;
}
if (!(cumulativeColSetWithMCUEC.contains(cols)) )
{
// does not contain overlapping columns, so continue to look for more
continue;
}
CostScalar keyUec = lookup(*keyEntry);
double ipFactor;
double SCproductUec = 1.0;
for (ValueId keyCol = keyEntry->init();
keyEntry->next(keyCol);
keyEntry->advance(keyCol))
SCproductUec *= lookup(keyCol).value();
ipFactor = keyUec.value()/SCproductUec;
if (ipFactor <= smallestIPFactor)
{
// found the overlapping column.
// There could be more entries with overlapping columns, so pick the
// one with minimum correlation factor. Example, MC UEC available -
// (x, y), (x, z), (y, z). To create (x, y, z), we could have
// (x, y) + (x, z) OR (x, y) + (y, z) OR (x, z) + (y, z)
// From the three, we shall pick the one which for which
// (multi-column UEC / product of single column UECs) is smallest
setWithMinIPFactor = keyEntry;
overlappingCol = cols;
smallestIPFactor = ipFactor;
}
}// for all entries()
// see if we can get any more smaller partial multi-col UECs
// For example, we were looking for a UEC with three columns, and we could
// not find any (setWithMinIPFactor == NULL), then look for two
// columns and continue. If we were able to find UEC for three columns,
// then check for any other remaining columns
if (setWithMinIPFactor)
{
statsCreated = TRUE;
mcUec = mcUec * lookup(*setWithMinIPFactor) / lookup(overlappingCol);
cumulativeColSetWithMCUEC.addSet(*setWithMinIPFactor);
remainingCols.subtractSet(*setWithMinIPFactor);
noOfColsInterested = remainingCols.entries() + 1;
// while processing, if the cumulative row count becomes greater than
// the rowcount, break out of the loop
if (mcUec >= oldRowCount)
{
mcUec = oldRowCount;
break;
}
}
else
noOfColsInterested--;
} // while (noOfColsInterested > 1)
return statsCreated;
} // MultiColumnUecList::createMCUECWithOverlappingColSets
// Following method creates multi-col UEC for larger set of columns
// using partial disjoint multi-col UECs.
// For example, if MC-UEC available - (a, b) (c, d).
// Then MC (a, b, c, d) = MC (a, b) * MC (c, d)
NABoolean MultiColumnUecList::createMCUECWithDisjointColSets(
ValueIdSet & remainingCols, /* in / out */
ValueIdSet & cumulativeColSetWithMCUEC, /* in / out */
CostScalar & mcUec /* in / out*/,
CostScalar oldRowCount)
{
NABoolean statsCreated = FALSE;
while (remainingCols.entries() > 1)
{
// collect all disjoint multi-col UEC column sets. In case of a conflict, get the
// one with largest correlation
const ValueIdSet & largestSubset = this->largestSubset(remainingCols);
if (largestSubset.entries() < 2)
{
// return no more multi-col UECs for this table
return statsCreated;
}
mcUec *= lookup(largestSubset);
cumulativeColSetWithMCUEC.addSet(largestSubset);
remainingCols.subtractSet(largestSubset);
statsCreated = TRUE;
// if UEC has exceeded rowcount, no need to go further
if (mcUec >= oldRowCount)
{
mcUec = oldRowCount;
break;
}
} // end while (remainingCols.entries() > 1)
return statsCreated;
} // MultiColumnUecList::createMCUECWithDisjointColSets
// In the following method, we shall create a new MC list.
// This list contains MC-UEC for only those column set, which include
// all columns of colsWithReductions and at most one column from
// cumulativeColSetWithMCUEC
MultiColumnUecList * MultiColumnUecList::createMCListForRemainingCols(
ValueIdSet remainingCols,
ValueIdSet cumulativeColSetWithMCUEC)
{
MultiColumnUecList * groupUec = new (STMTHEAP) MultiColumnUecList ();
// There can be several levels of overlapping. Example there can be one
// column overlapping, or two columns overlapping and so on. Process of
// trying all overlapping combinations can be very expensive, and might
// not be very cost effective. Hence we shall look for only first level
// of overlapping, in other words look for only those partial multi-col
// UECs in which we have one column overlapping
CollIndex noOfColsInterested = remainingCols.entries();
ValueIdSet * keyEntry = NULL ;
CostScalar * uecEntry = NULL ;
ValueIdSet overlappingCol;
MultiColumnUecListIterator iter (*this) ;
// traverse through the multi-column uec list to get the relevant mc entries
Int32 i;
for ( iter.getNext (keyEntry, uecEntry), i = 1 ;
keyEntry != NULL && uecEntry != NULL ;
iter.getNext (keyEntry, uecEntry), i++ )
{
// We identify the interesting entries in the MC list as follows:
// Example, remainingCols = (a, b, c), and cumulativeColSetWithMCUEC is (d, e, f)
// so the only entries that can interest are (a, b), (a, b, c), (a, b, d),
// (a, b, c, d) etc. The entries that are of no interest are
// (a, b, c, g) - extra column 'g'
// (a, b, d, e) - more than one overlapping columns
// (a, b, c, d, e) - more than one overlapping column. This case can be identified
// just by counting the number of entries in the MC set. Number
// of entries should not be more than number of columns remaining + 1
// so not interested
// if the number of entries in the MC is greater than (no. of cols
// remaining + 1) then we are not interested in this.
// interested (a, b, c). Available is (d, e, f)
// Current MC entry could be
// (a, b, c, d, e) - two overlapping
// or (a, b, c, d, g) - one overlapping but an extra column
// or (a, b, g, h, i) - no overlapping but extra columns
if (keyEntry->entries() > (noOfColsInterested + 1) )
continue;
// MC entry has
// less than columns interested - could be (a, b) or (a, d) or (a, g) or (g, h)..
// equal to columns interested - could be (a, b, c), (a, b, d), (a, g, d),
// (a, g, e) ..
// equal to columns interested + 1 - could be (a, b, c, d), (a, b, d, e),
// (a, b, c, e), (a, b, d, g), ....
ValueIdSet cols;
cols = *keyEntry;
// it could be intereting set, if it contains all remaining columns, and
// one column from the largestSubset
cols.subtractSet(remainingCols);
if (cols.entries() > 1)
{
// contains no or more than one overlapping columns. Or any other extra columns
// Entries like (g, h), (a, g, d), (a, g, e), (a, b, d, e), (a, b, d, g) will
// be eliminated. Since all have more than one column overlapping or some
// extra columns
continue;
}
// Entries remaining are (a, b), (a, d), (a, g), (a, b, c), (a, b, d),
// (a, b, c, d), (a, b, c, e)
if ((cols.entries() == 1) AND !(cumulativeColSetWithMCUEC.contains(cols)) )
{
// does not contain overlapping columns, so not interested. (a, g)
continue;
}
// entries that will be added are like
// (a, b), (a, b, c) - cols.entries() == 0, no columns overlapping
// (a, d), (a, b, d), (a, b, c, d), (a, b, c, e) - cols.entries() == 1
// and cumulativeColSetWithMCUEC.contains(cols)
//
groupUec->insertPair(*keyEntry, *uecEntry);
}
return groupUec;
} // MultiColumnUecList::createMCListForRemainingCols
//
// useful debugging routines
//
void
MultiColumnUecList::print (FILE *ofd,
const char * prefix,
const char * suffix) const
{
#ifndef NDEBUG
ValueIdSet * keyEntry = NULL ;
CostScalar * uecEntry = NULL ;
MultiColumnUecListIterator iter (*this) ;
fprintf (ofd, "================================================\n") ;
fprintf (ofd, "%sEntries: %i\n", prefix, this->entries() ) ;
Int32 i ;
for ( iter.getNext (keyEntry, uecEntry), i = 1 ;
keyEntry != NULL && uecEntry != NULL ;
iter.getNext (keyEntry, uecEntry), i++ )
{
fprintf (ofd, "%s (** entry %i**) ",prefix,i) ;
keyEntry->display() ;
fprintf (ofd, " ===> uec: %g\n", uecEntry->value()) ;
}
fprintf (ofd, "================================================\n") ;
#endif
}
void
MultiColumnUecList::display() const
{
print();
}
void
MultiColumnUecList::displayMissingStatsWarning(TableDesc * mostRefdTable,
ValueIdSet predCols,
NABoolean largeTableNeedsStats,
NABoolean displayWarning,
const ColStatDescList &colStats,
CostScalar redFromSC,
NABoolean quickStats,
OperatorTypeEnum op) const
{
HSLogMan *LM = HSLogMan::Instance();
LM->LogTimeDiff("START MISSING HISTOGRAM WARNING MESSAGES", TRUE);
// Do not display warning if user does not want to use multi-column
if ((predCols.entries() > 1) &&
(CmpCommon::getDefault(HIST_MC_STATS_NEEDED) == DF_OFF) )
return;
// Do not display warning if the query is an internal query from
// the executor
if (Get_SqlParser_Flags(INTERNAL_QUERY_FROM_EXEUTIL))
return;
const NATable * mostRefdNATable = mostRefdTable->getNATable();
// create a set of column positions for predCols. These are all base tables
// so we should be able to cast each one of them to BaseCols. This is necessary
// to take care of cases where we have the same columns appearing in both the
// query and its sub-query. If we go by the columns ValueIds, then, since the
// ValueIds of the column in the two places would be different, the warning
// would be inserted twice. A good example of this is TPCD query 2, where
// columns such as ps_partkey, ps_suppkey appear in both the main query and
// its sub-query. By caching with its column position we can avoid that.
// This should not be very expensive, since column numbers are cached with the
// column expression
CollIndexSet setOfColsWithMissingStats(NULL,STMTHEAP);
// define ValueId outside of for loop, to avoid c++ compiler error.
ValueId col;
for (col = predCols.init();
predCols.next(col);
predCols.advance(col) )
{
BaseColumn * baseCol = col.castToBaseColumn();
if (baseCol == NULL)
return;
CollIndex colNumber = baseCol->getColNumber();
NAColumn *column = baseCol->getTableDesc()->getNATable()->
getNAColumnArray()[colNumber];
// Don't give a warning if it is not marked for histogram
// or it is not a user column. Exception is salt column, for
// which we suppress warning, but create empty histogram if
// automation is enabled.
if ( !column->isReferencedForHistogram() ||
(!column->isUserColumn() && !column->isSaltColumn()))
return;
setOfColsWithMissingStats.insert(colNumber );
}
// Now check to see, if the warning for missing MC stats has already been
// inserted in the diags area. If it does then return
if (mostRefdNATable->doesMissingStatsWarningExist(setOfColsWithMissingStats) )
return;
HSGlobalsClass::schemaVersion = mostRefdNATable->getObjectSchemaVersion();
Int32 warningNumber;
Lng32 ustatAuto = CmpCommon::getDefaultLong(USTAT_AUTOMATION_INTERVAL);
Lng32 ustatLevel = CmpCommon::getDefaultLong(USTAT_AUTO_MISSING_STATS_LEVEL);
// Determine if the missing histogram should be inserted by the compiler into the HISTOGRAMS table
// for update statistics automation. The following must ALL be true:
// 1. NOT an MP table
// 2. Schema version of table >= 2300
// 3. Table is not volatile or automation is ON for volatile tables.
// 4. Auto missing stats CQD level is set high enough for this single or multi-col hist.
if ( ustatAuto > 0 &&
HSGlobalsClass::schemaVersion >= COM_VERS_2300 &&
(!mostRefdNATable->isVolatileTable() ||
CmpCommon::getDefault(USTAT_AUTO_FOR_VOLATILE_TABLES) == DF_ON) &&
((ustatLevel >= 1 && predCols.entries() == 1) ||
(ustatLevel >= 2 && op == REL_SCAN) ||
(ustatLevel >= 3 && op == REL_JOIN) ||
(ustatLevel >= 4 && op == REL_GROUPBY) )
)
{
// Ustat automation is on for this object.
// Insert empty entry to HISTOGRAMS table.
// Determine if this is a synonym and if so, get name of table instead.
Int64 tableUID;
NAString tableName;
NAString histogramSchemaName;
if (mostRefdNATable->getIsSynonymTranslationDone()) {
tableName = mostRefdNATable->getSynonymReferenceName();
tableUID = mostRefdNATable->getSynonymReferenceObjectUid().get_value();
// Get catalog and schema name. Find end of schema name.
QualifiedName qualName(tableName, 3);
histogramSchemaName = qualName.getSchemaNameAsAnsiString();
}
else {
tableName = mostRefdNATable->getExtendedQualName().getText();
tableUID = mostRefdNATable->objectUid().get_value();
histogramSchemaName = mostRefdNATable->getTableName().getCatalogName() + "."
+ mostRefdNATable->getTableName().getSchemaName();
}
NAString histogramTableName = getHistogramsTableLocation(histogramSchemaName,FALSE)
+ "." + HBASE_HIST_NAME;
Lng32 retcode = -1;
HSinsertEmptyHist hist(tableUID, tableName.data(), histogramTableName.data());
Lng32 colCount = 0;
// get col numbers
for ( col = predCols.init();
predCols.next(col);
predCols.advance(col))
{
BaseColumn * baseCol = col.castToBaseColumn();
if (baseCol)
{
Lng32 colNumber = baseCol->getColNumber();
retcode = hist.addColumn(colNumber);
if (retcode == 0)
colCount++;
}
}
if ( retcode == 0 && predCols.entries() == (CollIndex)colCount ) // got every col number?
{
NABoolean switched = FALSE;
CmpContext* prevContext = CmpCommon::context();
// switch to another context to avoid spawning an arkcmp process when compiling
// the user metadata queries on the histograms tables
if (IdentifyMyself::GetMyName() == I_AM_EMBEDDED_SQL_COMPILER)
if (SQL_EXEC_SWITCH_TO_COMPILER_TYPE(CmpContextInfo::CMPCONTEXT_TYPE_USTATS)) //CMPCONTEXT_TYPE_META))
{
//failed to switch/create metadata CmpContext, continue using current compiler context
}
else
{
switched = TRUE;
// send controls to the context we are switching to
sendAllControls(FALSE, FALSE, FALSE, COM_VERS_COMPILER_VERSION, TRUE, prevContext);
}
retcode = hist.insert();
// switch back to previous compiler context if we switched above
if (switched == TRUE)
SQL_EXEC_SWITCH_BACK_COMPILER();
}
if (predCols.entries() == 1)
{
if (quickStats)
warningNumber = SINGLE_COLUMN_SMALL_STATS_AUTO;
else
warningNumber = SINGLE_COLUMN_STATS_NEEDED_AUTO;
}
else
warningNumber = MULTI_COLUMN_STATS_NEEDED_AUTO;
}
// Ustat automation is NOT on.
else
if (predCols.entries() == 1)
{
if (quickStats)
warningNumber = SINGLE_COLUMN_SMALL_STATS;
else
warningNumber = SINGLE_COLUMN_STATS_NEEDED;
}
else
warningNumber = MULTI_COLUMN_STATS_NEEDED;
// Do not display warning if rows < default CQD (which could be true if
// ustat automation is on).
if (CURRSTMT_OPTDEFAULTS->ustatAutomation() &&
(mostRefdTable->getTableColStats()[0]->getColStats()->getRowcount() <
CostPrimitives::getBasicCostFactor(HIST_ROWCOUNT_REQUIRING_STATS)))
return;
// Do not display warning for salt column, although an empty histogram may
// have been added to histograms table above. Use new ValueIdSet nonSaltPredCols
// for remainder of function, which is predCols with any salt column removed.
ValueIdSet nonSaltPredCols(predCols);
ValueIdSet saltCols;
for (col = predCols.init();
predCols.next(col);
predCols.advance(col))
{
if (col.isSaltColumn())
saltCols.addElement(col.toUInt32());
}
nonSaltPredCols.remove(saltCols);
if (nonSaltPredCols.isEmpty())
return;
// Now see, if we should issue a missing single column or a missing multi-col
// stats warning. This would depend on the number of columns on which the
// statistics is missing.
// check the warning level
// Warning level set to 0, or largeTableNeeds Stats is false,
// do not display any warning
if ((CURRSTMT_OPTDEFAULTS->histMissingStatsWarningLevel() == 0) ||
(largeTableNeedsStats == FALSE) )
return;
// If warning level is one, display only single column stats warning.
if ((CURRSTMT_OPTDEFAULTS->histMissingStatsWarningLevel() == 1) &&
(nonSaltPredCols.entries() > 1) )
return;
// If this is a multi-column (MC) stat, check to see if it should be displayed as
// a warning based on the operation and the warning level.
if ((nonSaltPredCols.entries() > 1) &&
((CURRSTMT_OPTDEFAULTS->histMissingStatsWarningLevel() < 2 && op == REL_SCAN) ||
(CURRSTMT_OPTDEFAULTS->histMissingStatsWarningLevel() < 3 && op == REL_JOIN) ||
(CURRSTMT_OPTDEFAULTS->histMissingStatsWarningLevel() < 4 && op == REL_GROUPBY)) )
return;
// dump the warning in the diags area
NAString tableName(CmpCommon::statementHeap()); // table name
NAString tableCols(CmpCommon::statementHeap()); // column names
NAString predicates(CmpCommon::statementHeap()); // predicates
ValueIdSet thePreds;
thePreds.clear();
if (mostRefdNATable->getIsSynonymTranslationDone())
tableName = mostRefdNATable->getSynonymReferenceName();
else
tableName = mostRefdNATable->
getExtendedQualName().getQualifiedNameObj().getQualifiedNameAsAnsiString();
// collect all column names into a string
NABoolean first = TRUE;
NAString connectorText(", ");
tableCols += "(";
NAString opName (CmpCommon::statementHeap());
switch (op)
{
case REL_SCAN:
opName = "Scan";
break;
case REL_JOIN:
opName = "Join";
break;
case REL_GROUPBY:
opName = "GroupBy";
break;
}
for (col = nonSaltPredCols.init();
nonSaltPredCols.next(col);
nonSaltPredCols.advance(col))
{
if (first)
first = FALSE;
else
tableCols += connectorText;
BaseColumn * baseCol = col.castToBaseColumn();
if (baseCol == NULL)
return;
NAString colName(baseCol->getTableDesc()->getNATable()->
getNAColumnArray()[baseCol->getColNumber()]->
getColName(), STMTHEAP);
tableCols += ToAnsiIdentifier(colName);
// if ustat logging is ON, log the predicates for which the warning is
// being issued
if (LM->LogNeeded() && colStats.entries() > 0)
{
// also collect the predicates applied on this column
CollIndex i;
// get the histogram for the column
NABoolean found = colStats.getColStatDescIndexForColumn(i, col);
if (found)
{
// histogram for which the warning is being issued is found
LM->Log("histogram for column with missing stats found");
ColStatDescSharedPtr tempStatDesc = colStats[i];
if (tempStatDesc->getAppliedPreds().entries() > 0)
{
LM->Log("column for missing stats has been reduced by a predicate");
thePreds = tempStatDesc->getAppliedPreds();
thePreds.unparse(predicates, DEFAULT_PHASE, EXPLAIN_FORMAT, mostRefdTable);
sprintf(LM->msg, "%s",predicates.data());
LM->Log(LM->msg);
}
else
// This condition will happen either for group bys
// if that happens for scans or joins then there is a bug in the code
LM->Log("column for missing stats did not have any predicate applied");
}
else
// This would mean a bug in the code as this should never happen
LM->Log("histogram for column with missing stats NOT found");
}
}
tableCols += ")";
*CmpCommon::diags() << DgSqlCode( warningNumber )
<< DgString0( tableCols )
<< DgString1( tableName )
<< DgString2(opName);
if (LM->LogNeeded() && nonSaltPredCols.entries() > 1)
{
LM->Log("MC Warning for table, col set, operator, selectivity: ");
sprintf(LM->msg, "%s, %s, %s, %e", tableName.data(), tableCols.data(), opName.data(), redFromSC.getValue());
LM->Log(LM->msg);
}
LM->LogTimeDiff("END MISSING HISTOGRAM WARNING MESSAGES");
// insert the columns with missing stats in the table Desc, so it is not displayed again
// insert the columns with missing stats in the table Desc
mostRefdNATable->insertMissingStatsWarning(setOfColsWithMissingStats);
}
// -----------------------------------------------------------------
// isMCStatsUseful is used to determine if there is any possibility
// of optimizer benefiting from multi-column stats. The MC stats
// are said to be not helpful, if any subset of given column set
// is orthogonal. More heuristics can be added later to determine
// usefulness of MC stats
// -----------------------------------------------------------------
NABoolean MultiColumnUecList::isMCStatsUseful(ValueIdSet columnSet,
TableDesc * tableDesc) const
{
// This is used for only multi column stats, hence if columnSet
// consists of single column, return FALSE
if (columnSet.entries() == 1)
return FALSE;
// check to see if columnSet is unique, if it is, return FALSE.
if (columnSet.doColumnsConstituteUniqueIndex(tableDesc))
return FALSE;
// get the largest subset of columns for which MC UEC exist
// if MC stats exist for the given column set, then return
// column set will be identical to column set passed.
// Since the method is currently being called only when stats
// for columnSet is missing, we will get a subset of columns
ValueIdSet colSetWithMCStats = largestSubset(columnSet);
// if MC stats do not exist for any columns, the colSetWithMCStats will
// be empty. We return TRUE then to indicate that stats will be useful
if (colSetWithMCStats.entries() == 0)
return TRUE;
// if mcUec is almost equal to the rowcount, that means that columns are almost
// orthogonal. MC stats for larger set may not give enough benefit then.
// This is TRUE even if the largest subset is a single column. However
// if columnSet is equal to colSetWithMCStats then we cannot use this assumption
if (colSetWithMCStats.entries() <= columnSet.entries())
{
// get the MC UEC of the columnSet
CostScalar mcUec = lookup(colSetWithMCStats);
// get rowcount from the base table
CostScalar tableRowCount = tableDesc->tableColStats()[0]->getColStats()->getRowcount();
// if MC UEc is almost equal to the row count, we may not benefit much from the MC stats
// return FALSE in that case
CostScalar uecCushion((ActiveSchemaDB()->getDefaults()).getAsDouble(COMP_FLOAT_8));
if (mcUec >= uecCushion * tableRowCount)
return FALSE;
}
return TRUE;
} // isMCStatsUseful
CostScalar
ColStatDescList::getCardOfBusiestStreamForUnderNJ(
CANodeIdSet * outerNodeSet,
const PartitioningFunction* partFunc,
Lng32 numOfParts,
GroupAttributes * groupAttr,
Lng32 countOfCPUs)
{
// get the partitioning key and number of partitions
ValueIdSet partKey = partFunc->getPartitioningKey();
// get the total rows in the histogram
CostScalar rowCount = (*this)[0]->getColStats()->getRowcount();
// if number of partitions is 1, return rowcount
if ( ( numOfParts == 1) ||
(partFunc->isASkewedDataPartitioningFunction()) ||
( partFunc->isASinglePartitionPartitioningFunction() ) )
{
return (rowCount).minCsOne();
}
// The cardinality is based on the number of CPUs or the number of
// partitions (whichever is fewer) for a few situations:
// 1) if partitioning key is empty
// 2) the round robin partitioning scheme is used.
// 3) the skew buster partitioning scheme is used.
if ( (partKey.isEmpty()) ||
(partFunc->isASkewedDataPartitioningFunction()) ||
(partFunc->isARoundRobinPartitioningFunction()) )
{
Lng32 availableCpus = MINOF( numOfParts , countOfCPUs );
return (rowCount / availableCpus).minCsOne();
}
// In the following loop, get the min UEC from amongst the partitioning
// key to compute the number of streams. In the same loop also compute
// the accumulated frequency of the partitioning key to compute cardinality
// per stream for hash partitions
// min UEC for partitioning key
CostScalar uecForPartKey = csOne;
// accumulated freq of the partitioning key
CostScalar accFreq = csOne;
ValueIdSet myColumns;
NABoolean outerReferencesExist = FALSE;
ValueIdSet usedCols;
ValueIdSet referencedPartKeyColSet;
if (groupAttr)
{
ValueIdSet baseCols;
ValueIdSet myInputs = groupAttr->getCharacteristicInputs();
if (NOT myInputs.isEmpty())
{
outerReferencesExist = TRUE;
myInputs.findAllReferencedBaseCols(myColumns);
}
if (!outerNodeSet)
{
rowCount = rowCount.minCsOne();
return (rowCount/(MINOF((CostScalar)numOfParts, rowCount))).minCsOne();
}
ValueIdSet outerBaseCols = outerNodeSet->getUsedTableCols();
myColumns.intersectSet(outerBaseCols);
GroupAnalysis * grpAnalysis = groupAttr->getGroupAnalysis();
if (grpAnalysis)
{
CANodeIdSet treeSet = grpAnalysis->getAllSubtreeTables();
if (NOT treeSet.isEmpty() )
{
// note that UsedCols do not contain any columns
// from outer references. They are "local"
usedCols = treeSet.getUsedCols();
myColumns.addSet(usedCols);
}
}
}
for (ValueId partKeyElement = partKey.init();
partKey.next(partKeyElement);
partKey.advance(partKeyElement) )
{
// extract all base columns from the partitioning key column before
// looking for statistics
ValueIdSet baseColSet;
// this is because findAllReferencedBaseCols, is defined on ValueIdSet
ValueIdSet partKeySet(partKeyElement);
partKeySet.findAllReferencedBaseCols(baseColSet);
// from all the base columns for this partitioning key, get the
// one that belong to me. In case of joins, base column set would
// also contain the column I am joining to
if (NOT myColumns.isEmpty())
baseColSet.intersectSet(myColumns);
// if outer references are present, then it is under a nested join
// want to compute columns from the partitioning key VEGRef
// that are from the outer references
ValueIdSet partKeySourceSide;
if (outerReferencesExist)
{
//want to do this; note usedCols has no outer references
// partKeySourceSide=baseColSet - usedCols
ValueIdSet origSet = baseColSet;
origSet.subtractSet(usedCols);
partKeySourceSide = origSet;
}
CostScalar freq;
// get min of max frequency of any column from the given column set
// If outer references exist compute from amongst the columns coming
// from the outer, else do it from the inner side
// In most cases, there would be only one column in the column
// set for which the frequency is needed. In case of expressions
// there could be more than one column in the column set.
if (outerReferencesExist)
{
freq = getMinOfMaxFreqOfCol(partKeySourceSide).minCsOne();
}
else
{
freq = getMinOfMaxFreqOfCol(baseColSet).minCsOne();
}
// get minimum UEC from the given column set;
// this needs columns from outer references as well
CostScalar colUec;
if (CmpCommon::getDefault(COMP_BOOL_47) == DF_OFF)
{
if (outerReferencesExist)
colUec = getMinUec(partKeySourceSide);
else
colUec = getMinUec(baseColSet);
}
else
{
if (outerReferencesExist)
colUec = getMaxUec(partKeySourceSide);
else
colUec = getMaxUec(baseColSet);
}
if (colUec == csMinusOne)
uecForPartKey = rowCount;
else
uecForPartKey *= colUec;
accFreq *= (freq / rowCount);
} // for all partitioning key columns
// uec cannot be greater than the row count
uecForPartKey = MINOF(uecForPartKey, rowCount);
// compute the number of streams
CostScalar noOfStreams = MINOF((CostScalar)numOfParts, uecForPartKey);
// If partitioning key column is a Random number, and activeStreams_ is less
// than the current value of noOfStreams, then noOfStreams = activeStreams_;
CostScalar activeStreams = partFunc->getActiveStreams();
long randomFix = ActiveSchemaDB()->getDefaults().getAsLong(COMP_INT_26);
if ( (partKey.entries() == 1) AND (randomFix != 0) AND (activeStreams != 0) )
{
// Get first key column.
ValueId myPartKeyCol;
partKey.getFirst(myPartKeyCol);
// is it a random number?
if (myPartKeyCol.getItemExpr()->getOperatorType() == ITM_RANDOMNUM)
{
noOfStreams = MINOF(activeStreams, noOfStreams);
}
}
// based on the partitioning type, compute the cardinality of the stream
// For all but hash type partitions, cardinality = number of rows / # of streams
// such that # of streams = MINOF(# of partitions, UEC of partitioning key)
if (partFunc->isAHashPartitioningFunction() ||
partFunc->isATableHashPartitioningFunction() )
{
CostScalar cardOfFreqValue = (rowCount * accFreq).minCsOne();
CostScalar maxCardPerStream = (((rowCount - cardOfFreqValue)/noOfStreams) + cardOfFreqValue).round();
// cardinality per stream cannot be greater than the total row count
maxCardPerStream = MINOF(maxCardPerStream, rowCount);
// some of NJ plans were over-penalized by incorporate_skew_in_costing.
// provide a cqd HIST_SKEW_COST_ADJUSTMENT to soften effect
// of incorporate_skew_in_costing.
// take weighted average of uniform distribution and skewed data
// based on the CQD.
// 0 -> get RC as if uniformly distributed
// 1-> get RC as if skewed
// anything in between is the linear average
CostScalar uniformDistRowCountPerStream = rowCount / noOfStreams;
CostScalar histSkewAdjustment =
(ActiveSchemaDB()->getDefaults()).getAsDouble(HIST_SKEW_COST_ADJUSTMENT);
maxCardPerStream =
(maxCardPerStream * histSkewAdjustment) +
(uniformDistRowCountPerStream * (csOne - histSkewAdjustment));
return maxCardPerStream.minCsOne();
}
else
{
// for all other partitions
// return rows per stream
return (rowCount / noOfStreams).minCsOne();
} // for non hash partitioning functions
} // ColStatDescList::getCardOfBusiestStream
//------------------------------------------------------------------
// Determine if the join predicates consist of column sets where one
// of the sides is unique and shares relationship "similar" to PK/FK
// with the other side. If so, cardinality adjustment will be done
// while merging ColStatDescs in ColStatDesc::mergeColStatDesc method.
// This feature is OFF by default if there are more than one joining
// columns. It'll be controlled by COMP_BOOL_149
//------------------------------------------------------------------
CostScalar MultiColumnUecList::getRowcountOfNonUniqueColSet( const Join *expr,
ValueIdList colList1,
ValueIdList colList2,
NABoolean list1Unique,
NABoolean list2Unique)
{
CostScalar rowcountOfNonUniqSide = csMinusOne;
// We are using unique characteristic for Joins only, atleast till R2.5 (5/29/09)
if (!expr || !expr->isAnyJoin())
return rowcountOfNonUniqSide;
// We will keep a copy of the set too, as these are more efficient
// implementations for lookup etc.
// all variables suffixed by "1" correspond to column list 1 (colList1) and
// all suffixed by "2" correspond to colList2. There is no concept of "left"
// and "right" here since we don't know which child of Join these belong to
ValueIdSet colSet1 = colList1;
ValueIdSet colSet2 = colList2;
// indexes into the colStats list
CollIndex i;
// multi column UECs
CostScalar MCUec1 = csMinusOne, MCUec2 = csMinusOne;
// Take a cushion to take into account the fizziness that could have been introduced
// using sampling
CostScalar uecCushion ((ActiveSchemaDB()->getDefaults()).getAsDouble(COMP_FLOAT_4));
MCUec1 = this->lookup(colSet1);
MCUec2 = this->lookup(colSet2);
// if multiColUEC does not exist, then return, we cannot use to look for containment,
// hence cannot use shortcut MC UEC calculations
// Since we have already added the MCUEC of the unique side, that should always be there
// We could still have MCUEC for non-unique side missing
if ((MCUec1 <= 0) || (MCUec2 <= 0))
return rowcountOfNonUniqSide;
// the uniqueness and containment are checked on the histograms prior to this join, hence get
// output logical properties of the children
// Since we don't know which column list belongs to which child of join. We will look for
// histogram of the columns in histogram list from both children
// Create a completeList of histograms from the histograms of the left and the right children
ColStatDescList completeList = ((Join *)expr)->child(0).outputLogProp((*GLOBAL_EMPTY_INPUT_LOGPROP))->colStats();
ColStatDescList rightColStatList = ((Join *)expr)->child(1).outputLogProp((*GLOBAL_EMPTY_INPUT_LOGPROP))->colStats();
// form a complete list of histograms from both sides
completeList.makeDeepCopy(rightColStatList);
// histograms for the joining columns of col list 1 and 2
ColStatsSharedPtr colStats1 = NULL;
ColStatsSharedPtr colStats2 = NULL;
// if colList1 is unique, then we will look for containment, only if
// columnList2 or the non-unique side is contained in the unique side
if(list1Unique)
{
if( (MCUec1 * uecCushion) < MCUec2)
{
// although column set 1 is unique, the non-unique set is not contained in the
// unique side, Check to see if individually each non-unique column is contained
// in the unique side
for(i =0; i<colList1.entries(); i++)
{
colStats1 = completeList.getColStatsPtrForColumn(colList1[i]);
// get the histogram pointer for the column in list 1
if (colStats1 == NULL)
{
// if the histogram is missing in both children, then we cannot use
// uniqueness property, so return. Setting the flags to FALSE instead of
// returning -1, just so we have one point of exit.
list1Unique = FALSE;
list2Unique = FALSE;
break;
}
// found histogram for column1, now look for column 2 histogram
colStats2 = completeList.getColStatsPtrForColumn(colList2[i]);
if (colStats2 == NULL)
{
// histogram is not present with either of the children of Join, so return -1
// Setting the flags to FALSE instead of
// returning -1, just so we have one point of exit.
list1Unique = FALSE;
list2Unique = FALSE;
break;
}
if ((colStats1->getBaseUec() *uecCushion) <= colStats2->getBaseUec())
{
// For the uniqueness condition to be satisfied, non-unique side should be
// contained in the unique side. In this case, the non-unique is larger than
// the unique, hence we cannot use this containment. Look for the column 2
// uniqueness and containment feature
list1Unique = FALSE;
break;
}
}
}
}
if(list1Unique)
{
ColStatsSharedPtr colStatsOfNonUniqSide = completeList.getColStatsPtrForColumn(colList2[0]);
if(colStatsOfNonUniqSide)
rowcountOfNonUniqSide = colStatsOfNonUniqSide->getRowcount();
else
rowcountOfNonUniqSide = csMinusOne;
}
// columns from side1 are non-unique check if the columns from other side
// constitute a unique set
if(!list1Unique && list2Unique)
{
// They are use by semantics, check for containment
if( (MCUec2 * uecCushion) < MCUec1)
{
// although column set 2 is unique, the non-unique set is not contained in the
// unique side. Check to see if individually each non-unique column is contained
// in the unique side
for(i =0; i<colList2.entries(); i++)
{
colStats2 = completeList.getColStatsPtrForColumn(colList2[i]);
if (colStats2 == NULL)
{
// histogram is missing, so don't bother going forward, just return
list2Unique = FALSE;
break;
}
// histogram for list 1 found, now look for the corresponding histogram in list 2
colStats1 = completeList.getColStatsPtrForColumn(colList1[i]);
if (colStats1 == NULL)
{
list2Unique = FALSE;
break;
}
if ((colStats2->getBaseUec() * uecCushion) <= colStats1->getBaseUec())
{
// if the non-unique side is not contained in the unique side, we cannot use
// the uniqueness feature
list2Unique = FALSE;
break;
}
}
} // if left side is contained in the right side
if(list2Unique)
{
ColStatsSharedPtr colStatsOfNonUniqSide = completeList.getColStatsPtrForColumn(colList1[0]);
if(colStatsOfNonUniqSide)
rowcountOfNonUniqSide = colStatsOfNonUniqSide->getRowcount();
else
rowcountOfNonUniqSide = csMinusOne;
}
}
return rowcountOfNonUniqSide;
}
void
ColStatDescList::computeRowRedFactor(MergeType mergeMethod, // input
CollIndex numOuterColStats, // input
CostScalar rowcountBeforePreds, // input
CollIndex & predCountSC, // output
CollIndex & predCountMC, // output
CostScalar & rowRedProduct) // output
{
// If this is a semi-join, it should be the case that only columns
// in the outer table need to be scaled to the proper cardinality.
CollIndex loopLimit = ( ( mergeMethod == SEMI_JOIN_MERGE ||
mergeMethod == ANTI_SEMI_JOIN_MERGE ) ?
numOuterColStats : entries() );
// need to calculate the product of the row reduction factors
CollIndex i;
CostScalar rowCnt;
// do floor of rowcount before preds to take into account small rounding errorrs that
// may creep up due to cost scalar arithmetic. Use this adjusted value for computing
// predCount for MC adjustments only
CostScalar tempRCBeforePreds = floor(rowcountBeforePreds.getValue());
for ( i = 0; i < loopLimit; i++ )
{
ColStatsSharedPtr tempColStats = (*this)[i]->getColStats();
rowCnt = tempColStats->getRowcount();
if (( rowCnt < rowcountBeforePreds ) || (tempColStats->isARollingColumn()))
{
predCountSC++;
rowRedProduct *= rowCnt / rowcountBeforePreds;
}
// do a ceil of row count from histograms to take into account
// any rounding issues that may creep up due to cost scalar arithmetic
// example:
// if rowCnt = 1.04 and rowcountBeforePreds = 1 then tempRC = 2 and tempRCBeforePreds = 1 -> do not increment predCountMC
// if rowCnt = 1 and rowcountBeforePreds = 1 then tempRC = 1 and tempRCBeforePreds = 1 -> do not increment predCountMC
// if rowCnt = 1 and rowcountBeforePreds = 1.04 then tempRC = 1 and tempRCBeforePreds = 1 -> do not increment predCountMC
// if rowCnt = 0.99 and rowcountBeforePreds = 1 then tempRC = 1 and tempRCBeforePreds = 1 -> do not increment predCountMC
// if rowCnt = 1 and rowcountBeforePreds = 0.99 then tempRC = 1 and tempRCBeforePreds = 0 -> do not increment predCountMC
// if rowCnt = 1.04 and rowcountBeforePreds = 0.99 then tempRC = 2 and tempRCBeforePreds = 0 -> do not increment predCountMC
// if rowCnt = 0.99 and rowcountBeforePreds = 1.04 then tempRC = 1 and tempRCBeforePreds = 1 -> do not increment predCountMC
// if rowCnt = 1 and rowcountBeforePreds = 2 then tempRC = 1 and tempRCBeforePreds = 2 -> increment predCountMC
// if rowCnt = 1.04 and rowcountBeforePreds = 1.9 then tempRC = 2 and tempRCBeforePreds = 1 -> do not increment predCountMC
CostScalar tempRC = ceil(rowCnt.getValue());
if (tempRC < tempRCBeforePreds)
predCountMC++;
}
// -----------------------------------------------------------------------
// in the case of an OUTER_JOIN_MERGE, we need to now undo part of that
// reduction
// -----------------------------------------------------------------------
if ( mergeMethod == OUTER_JOIN_MERGE )
{
CostScalar rowred;
ValueIdSet alreadyMergedHistograms;
for( i = 0; i < numOuterColStats; i++ )
{
alreadyMergedHistograms.insert( (*this)[i]->getMergeState() );
}
for( i = numOuterColStats; i < loopLimit; i++ )
{
if( alreadyMergedHistograms.contains( (*this)[i]->getColumn() ) )
{
rowred = csOne;
if ( rowcountBeforePreds.isGreaterThanZero() /* > csZero */ )
{
rowred =
(*this)[i]->getColStats()->getRowcount() / rowcountBeforePreds;
}
rowRedProduct /= rowred ; // remove this reduction, it's a duplicate
}
}
}
}
void ColStatDescList::insertByPosition(const StatsList & other,
const NAColumnArray &columnList,
const ValueIdList &tableColList)
{
for (CollIndex i = 0; i < columnList.entries(); i++)
{
NAColumn * column = columnList[i];
short position = (short) column->getPosition();
for(UInt32 i = 0; i < other.entries(); i++)
{
ColStatsSharedPtr otherStats(other[i]);
const NAColumnArray &otherColumns = otherStats->getStatColumns();
// Skip to the next ColStats if these stats don't contain
// this column position.
// position is checked for only single column histograms
if ( (otherColumns.entries() == 1) &&
(!otherColumns.getColumnByPos(position)) )
continue;
ColStatDescSharedPtr tmpStatDesc(new (CmpCommon::statementHeap())
ColStatDesc (other[i], tableColList), CmpCommon::statementHeap());
if (otherColumns.entries() == 1)
{
// if the histogram requires only a single interval, compress it before inserting
// it into the colStats list
NAColumnArray tempArray = other[i]->getStatColumns();
if ((tempArray.entries() == 1) && (tempArray[0]->isReferencedForSingleIntHist() ) )
{
// only for single column histograms. If only single histogram
// is needed insert the compressed copy of the histogram, else
// insert the full histogram
tmpStatDesc->getColStatsToModify()->compressToSingleInt();
}
}
this->insertAt(this->entries(), tmpStatDesc);
break;
}
} // for each column in the column array
}
// This method traverses the histograms and returns the highest UEC
// reduction applied by local predicates among the columns in the
// input valueid set. This information is used during multi-col UEC
// adjustment for join cardinality estimation
CostScalar
ColStatDescList::getHighestUecReductionByLocalPreds(ValueIdSet &cols) const
{
CostScalar highestUecReductionByLocalPreds = csOne;
ColStatsSharedPtr tempColStat;
for (ValueId vid = cols.init(); cols.next(vid); cols.advance(vid))
{
// Reduction from columns for which histogram is not present can be ignored
tempColStat = this->getColStatsPtrForColumn(vid);
if (!tempColStat)
continue;
CostScalar uecReductionFromLocalPreds = (tempColStat->getBaseUec()/tempColStat->getUecBeforePreds()).maxCsOne();;
// Take the highest reduction, that is the one giving least number of rows out
if(uecReductionFromLocalPreds < highestUecReductionByLocalPreds)
highestUecReductionByLocalPreds = uecReductionFromLocalPreds;
}
return highestUecReductionByLocalPreds;
}
// ---------------------------------------------------------------------------
// It is a helper method used while computing left joins. The method locates
// histograms that have been joined to right child using inner join and
// now need to be null augmented to simulate the left join
// ---------------------------------------------------------------------------
NABoolean
ColStatDescList::locateHistogramToNULLAugment(ValueIdSet EqLocalPreds /*in*/,
NAList<CollIndex> &statsToMerge /*out*/,
CollIndex &rootStatIndex /*out*/,
CollIndex outerRefCount /*in*/)
{
// Contains the maximum of number of intervals from amongst the left
// histograms that participated in equijoins and would be null instantiated
// Example for query like T1 left join T2 on T1.a = T2.a and T1.b = T2.b
// If the resultant histograms after join on column 'a' contains 50 intervals
// and the resultant histograms after join on column 'b' contains 60
// intervals, the optimizer will choose index for column 'b' and return that
// in rootStatIndex. saveNumBuckets will be used to contain the number of
// largest count of intervals so far in the iteration.
CollIndex saveNumBuckets = 0;
// contains a flag to indicate whether the histogram that participated
// in the equijoin has been found
NABoolean foundFlag = FALSE;
// --------------------------------------------------------------
// Equijoins allow us to best determine the number of null-
// augmented rows to be added back into the histogram(s) that
// describe the join results.
// First, locate histograms that capture the results of the
// one-or-more equijoins between the inner and outer tables.
// --------------------------------------------------------------
for (ValueId id = EqLocalPreds.init();
EqLocalPreds.next (id);
EqLocalPreds.advance (id))
{
ItemExpr *pred = id.getItemExpr();
// We are interested only in equi join predicates
// skip any other predicates
if (pred->getOperatorType() != ITM_VEG_PREDICATE)
continue;
// look thru all left histograms to find the one that
// participated in this join
// outerRefCount contains the count of left histograms,
// some of which may be the result of inner join
for (CollIndex i = 0; i < (CollIndex)outerRefCount; i++)
{
// We are not interested in considering histograms created for virtual
// columns, such as tuples, for joins so skip to the next histogram
if ((*this)[i]->getColStats()->isVirtualColForHist() )
continue;
// See if the leading column of any histograms reference
// the VEGReference id. Among those that do, remember
// the one with the most intervals.
// That one will be used to calculate how many rows from
// the original outer (left) table did not meet the join
// condition.
NABoolean foundCandidate = FALSE;
ItemExpr * columnFromJoinList = (*this)[i]->getVEGColumn().getItemExpr();
if ( columnFromJoinList->getOperatorType() != ITM_VEG_REFERENCE )
{
// the left columns appear as VEG_REFERENCES in the predicate
// If it is not a VEG_REFERENCE, skip this ColStatDesc and
// continue looping
continue;
}
// Check to see if the column of this histogram participated
// in this equi-join predicate
foundCandidate = pred->containsTheGivenValue(columnFromJoinList->getValueId());
// if it did not, skip to next histogram
if (!foundCandidate)
continue;
ColStatDescSharedPtr statDesc = (*this)[i];
ColStatsSharedPtr colStats = statDesc->getColStats();
// save the index of this histogram, to be merged later
statsToMerge.insert(i);
foundFlag = TRUE;
// Save the histogram with most intervals.
if (( saveNumBuckets == 0 ) || // anything is better than this
(colStats->getHistogram()->entries() > saveNumBuckets )) // for VEG Ref
{ //mar: need this because of TEST014, q14, where
// an assertion failed because rootStatIndex didn't
// change from its init value of 1000
saveNumBuckets = colStats->getHistogram()->entries() ;
rootStatIndex = i ;
}
} // for outerRefCount
} // for EqLocalPreds
if ( !foundFlag )
{
// --------------------------------------------------------------
// No usable equality predicates found.
// Use alternative approach to guess-timating the outer join's
// impact:
// Examine the histograms whose shape was changed, if any, to
// determine the number of rows from the left that need to be
// null augmented.
// Again, find the one with the greatest number of intervals.
// --------------------------------------------------------------
statsToMerge.clear(); // reset.
for (CollIndex i = 0; i < (CollIndex)outerRefCount; i++)
{
ColStatDescSharedPtr statDesc = (*this)[i];
ColStatsSharedPtr colStats = statDesc->getColStats();
// We are not interested in considering virtual columns for joins
// so skip to the next histogram
if (colStats->isVirtualColForHist() )
continue;
if (colStats->isShapeChanged())
{
statsToMerge.insert (i);
foundFlag = TRUE;
if ((saveNumBuckets == 0) // we should accept any value for
|| (colStats->getHistogram()->entries() > saveNumBuckets))
{ // rootStatIndex if we have none thus far!
saveNumBuckets = colStats->getHistogram()->entries() ;
rootStatIndex = i ;
}
}
} // for outerRefCount
} // if !foundFlag
// if foundFlag is TRUE,
// statsToMerge will contain a list of indexes of the histogram to be merged
// rootStatIndex will contain the index of the histogram of the joining
// column with largest number of intervals
return foundFlag;
}
// --------------------------------------------------------------------
// It is a helper method used by left joins. It merges the rows
// from the left side that did not match the right child back into
// the joined histograms.
// -------------------------------------------------------------------
CostScalar
ColStatDescList::computeLeftOuterJoinRC(NABoolean &foundFlag /*out*/,
const ColStatDescList &leftColStatsList /*in*/,
CollIndex rootStatIndex /*in*/)
{
ColStatDescSharedPtr joinStatDesc;
ColStatDescSharedPtr origStatDesc;
// ------------------------------------------------------------
// Either an equality-predicate or some other shape-changing
// predicate was involved in this join. Determine the number
// of rows that result from merging the original left table
// with the join result.
// ------------------------------------------------------------
// rootStatIndex contains the index of the histogram that participated
// in Join and amongst more than one joining histograms had most number
// of intervals
joinStatDesc = (*this)[rootStatIndex];
ColStatsSharedPtr joinColStats = joinStatDesc->getColStatsToModify();
CostScalar innerJoinRC = joinColStats->getRowcount();
ItemExpr * statExpr = joinStatDesc->getVEGColumn().getItemExpr() ;
OperatorTypeEnum statOper = statExpr->getOperatorType() ;
// sanity check
// We shall not assert for virtual columns, even though later we
// shall not consider them for join
// only the following histograms can participate in left joins
// These could be
// 1. VEG_REFERENCE,
// 2. result of another left or semi-join (ITM_INSTANTIATE_NULL)
// 3. result of UNION (ITM_VALUEIDUNION)
// 4. result of rowset (ITM_UNPACKCOL or ITM_ROWSETARRAY_SCAN)
// 5. IN list of more than COMP_INT_22 elements (ITM_NATYPE)
// For any other column type, return LEFT rowcount as the final
// left join RC
if (NOT ( statOper == ITM_VEG_REFERENCE ||
statOper == ITM_INSTANTIATE_NULL ||
statOper == ITM_VALUEIDUNION ||
statOper == ITM_UNPACKCOL ||
statOper == ITM_ROWSETARRAY_SCAN ||
joinColStats->isVirtualColForHist() ||
statOper == ITM_NATYPE) )
{
CCMPASSERT ( "Incorrect expression participating in Join") ;
// join result is not reliable any more
joinColStats->setFakeHistogram(TRUE);
// set the foundFlag to FALSE to indicate some error happened
// so the row counts can be set appropriately
foundFlag = FALSE;
// in case of an error return the inner join row count as the final rowcount
// from join.
return (innerJoinRC);
}
// ------------------------------------------------------------
// Once we have verified the correctness of the histogram
// find the matching entry in the leftColStatsList....
// It doesn't Have to be there, but it should be.
// ------------------------------------------------------------
NABoolean goodMatch = FALSE;
for (CollIndex i = 0; i < leftColStatsList.entries(); i++)
{
// Skip any histograms created for virtual columns
if (leftColStatsList[i]->getColStats()->isVirtualColForHist())
continue;
origStatDesc = leftColStatsList[i];
ItemExpr * originalExpr = origStatDesc->getVEGColumn().getItemExpr() ;
OperatorTypeEnum originalOper = originalExpr->getOperatorType() ;
if (originalOper == ITM_ROWSETARRAY_SCAN)
continue;
// sanity check for the original left histogram
if (NOT ( originalOper == ITM_VEG_REFERENCE ||
originalOper == ITM_INSTANTIATE_NULL ||
originalOper == ITM_VALUEIDUNION ||
originalOper == ITM_UNPACKCOL ||
originalOper == ITM_NATYPE) )
{
CCMPASSERT ( "Incorrect expression participating in Join") ;
// join result is not reliable any more
joinColStats->setFakeHistogram(TRUE);
// set the foundFlag to FALSE to indicate some error happened
// so the row counts can be set appropriately
foundFlag = FALSE;
// in case of an error return the inner join row count as the final rowcount
// from join.
return (innerJoinRC);
}
if ( originalExpr == statExpr ) // this is a ValueId comparison
{
if (originalOper != statOper)
{
// sanity check to ensure that the histogram type was not modified during inner join
CCMPASSERT( "Two mismatched expressions being joined" ) ; // no reason this should fail
// join result is not reliable any more
joinColStats->setFakeHistogram(TRUE);
// set the foundFlag to FALSE to indicate some error happened
// so the row counts can be set appropriately
foundFlag = FALSE;
// in case of an error return the inner join row count as the final rowcount
// from join.
return (innerJoinRC);
}
goodMatch = TRUE ;
break ;
}
} // for loop over leftColStatsList
if (goodMatch)
{
// if all conditions satisfied, do an OR merge of the join result
// to the original left histograms. This will add the rows from
// the left histograms that did not match the rigth side
joinStatDesc->mergeColStatDesc( origStatDesc,
LEFT_JOIN_OR_MERGE,
FALSE,
REL_JOIN,
FALSE/*dontMergeFVs*/);
// In estimateCardinality, we ensure that the result of
// Join is at least one. There is a possibility for cases
// when the result is actually zero, that the mergeColStatDesc
// reverts the row count back to 0. But because we want the minimum
// cardinality to be one, we shall, once again uplift it to one.
CostScalar oJoinResultRows = joinColStats->getRowcount();
if (oJoinResultRows < csOne)
{
joinColStats->setRowsAndUec (csOne, csOne);
oJoinResultRows = csOne;
}
// return the resultant rowcount. That would be result
// of inner join + number of unmatched rows from the left side
foundFlag = TRUE;
return oJoinResultRows;
}
else
{
// Original histogram for the joined histogram not found.
foundFlag = FALSE;
// return leftRowCount as the final rowcount
return innerJoinRC;
}
}
// --------------------------------------------------------------------
// It is a helper method used by full outer joins. It merges the rows
// from the right side that did not match the left child back into
// the joined histograms.
// -------------------------------------------------------------------
CostScalar
ColStatDescList::computeFullOuterJoinRC(NABoolean &foundFlag /*out*/,
const ColStatDescList &origColStatsList /*in*/,
CollIndex rootStatIndex /*in*/)
{
ColStatDescSharedPtr joinStatDesc;
ColStatDescSharedPtr origStatDesc;
// ------------------------------------------------------------
// Either an equality-predicate or some other shape-changing
// predicate was involved in this join. Determine the number
// of rows that result from merging the original left table
// with the join result.
// ------------------------------------------------------------
// rootStatIndex contains the index of the histogram that participated
// in Join and amongst more than one joining histograms had most number
// of intervals
joinStatDesc = (*this)[rootStatIndex];
ColStatsSharedPtr joinColStats = joinStatDesc->getColStatsToModify();
CostScalar innerJoinRC = joinColStats->getRowcount();
// since the right side would have been merged into the left, the right
// column reference should appear in the mergedState of the joined result
ValueIdSet mergedSet = joinStatDesc->getMergeState();
// ------------------------------------------------------------
// Once we have verified the correctness of the histogram
// find the matching entry in the leftColStatsList....
// It doesn't Have to be there, but it should be.
// ------------------------------------------------------------
NABoolean goodMatch = FALSE;
for (CollIndex i = 0; i < origColStatsList.entries(); i++)
{
// Skip any histograms created for virtual columns
if (origColStatsList[i]->getColStats()->isVirtualColForHist())
continue;
origStatDesc = origColStatsList[i];
ValueId originalCol = origStatDesc->getColumn();
OperatorTypeEnum originalOper = originalCol.getItemExpr()->getOperatorType() ;
if (originalOper == ITM_ROWSETARRAY_SCAN)
continue;
if ( mergedSet.containsTheGivenValue(originalCol)) // this is a ValueId comparison
{
goodMatch = TRUE ;
break ;
}
} // for loop over leftColStatsList
if (goodMatch)
{
// if all conditions satisfied, do an OR merge of the join result
// to the original left histograms. This will add the rows from
// the left histograms that did not match the right side
joinStatDesc->mergeColStatDesc( origStatDesc,
LEFT_JOIN_OR_MERGE,
FALSE,
REL_JOIN,
FALSE/*dontMergeFVs*/);
// In estimateCardinality, we ensure that the result of
// Join is at least one. There is a possibility for cases
// when the result is actually zero, that the mergeColStatDesc
// reverts the row count back to 0. But because we want the minimum
// cardinality to be one, we shall, once again uplift it to one.
CostScalar oJoinResultRows = joinColStats->getRowcount();
if (oJoinResultRows < csOne)
{
joinColStats->setRowsAndUec (csOne, csOne);
oJoinResultRows = csOne;
}
// return the resultant rowcount. That would be result
// of inner join + number of unmatched rows from the left side
foundFlag = TRUE;
return oJoinResultRows;
}
else
{
CCMPASSERT("Original histogram for the joined histogram in full outer join not found");
// Original histogram for the joined histogram not found.
foundFlag = FALSE;
// return inner join RC as the final rowcount
return innerJoinRC;
}
}
// ----------------------------------------------------------------------------------
// This is a helper method used by left joins.
// The method is called after the the rows from the left histograms of the joining column,
// that did not match the right side are merged back into the join result.
// In the following method, the histograms from the remaining columns are synchronized
// to have the same row count
// --------------------------------------------------------------------------------
void
ColStatDescList::synchronizeHistsWithOJRC(NAList<CollIndex> &statsToMerge /*in */,
CollIndex startIndex /*in*/,
CollIndex stopIndex /*in*/,
CollIndex rootStatIndex /*in*/,
const ColStatDescList &origColStatsList /*in*/,
CostScalar &oJoinResultRows /*out*/,
CostScalar &baseRows /*out*/)
{
// ---------------------------------------------------------------
// Force All histograms to have the rowCount predicted previously:
// either by mergeColStatDesc, or by the sWAG.
//
// Examine each histogram from the Left table. If it had a shape-
// changing predicate applied, OR the join result with the original
// histogram for that column, as in the root case.
// Otherwise, just changing their reduction factor to scale them to
// the sWAG'ed result size.
// ---------------------------------------------------------------
CollIndex i = 0;
for (i = startIndex; i < stopIndex; i++)
{
// indicates whether this histogram had a predicate applied to it
NABoolean inPredicate = FALSE;
// indicates whether we were able to find a match for the joined
// histogram from the list of original histograms
NABoolean goodMatch = FALSE;
// The histogram with rootStatIndex has already been merged
// so skip that
if ( rootStatIndex == i )
continue ;
// See if this column was involved in a predicate.
// statsToMerge contain all the histogram indexes
// that participated in join
// Break out of the loop that
CollIndex j = 0;
for (j = 0; j < statsToMerge.entries() && !inPredicate; j++)
{
if ( i == statsToMerge[j] )
inPredicate = TRUE;
}
// ------------------------------------------------------
// If involved in a predicate: do merge based calculation.
// And, in either case, Tweek the resulting row count to
// match that predicted above.
// ------------------------------------------------------
ColStatDescSharedPtr jStatDesc = (*this)[i];
ColStatsSharedPtr jColStats = jStatDesc->getColStatsToModify();
// if the histogram is for virtual columns, or from rowsets,
// skip mergebased calculations, simply synchronize the histogram
// with the rowcount computed earlier.
if (jColStats->isVirtualColForHist() )
{
CostScalar oldCount = jColStats->getRowcount();
if (oldCount != oJoinResultRows)
jStatDesc->synchronizeStats (oldCount, oJoinResultRows);
continue;
}
ItemExpr * statExpr = jStatDesc->getVEGColumn().getItemExpr() ;
OperatorTypeEnum statOper = statExpr->getOperatorType() ;
if (statOper == ITM_ROWSETARRAY_SCAN)
{
CostScalar oldCount = jColStats->getRowcount();
if (oldCount != oJoinResultRows)
jStatDesc->synchronizeStats (oldCount, oJoinResultRows);
continue;
}
// sanity check to see if we have correct column type
// participating in left join. If not, then set the rowcount
// of all histograms equal to the rowcount obtained so far
// that is from an inner join + merging original left histogram
// to the left histogram after inner join and use that to synchronize
// all histograms and return
if (NOT(statOper == ITM_VEG_REFERENCE ||
statOper == ITM_INSTANTIATE_NULL ||
statOper == ITM_VALUEIDUNION ||
statOper == ITM_UNPACKCOL ||
statOper == ITM_NATYPE) )
{
CCMPASSERT ( "Incorrect column type participating in Join") ;
// join result is not reliable any more
jColStats->setFakeHistogram(TRUE);
// don't assert for customers, synchronize histograms with
// left rowcount and continue
CostScalar oldCount = jColStats->getRowcount();
if (oldCount != oJoinResultRows)
jStatDesc->synchronizeStats (oldCount, oJoinResultRows);
continue;
}
// if there is any issue with the joining histograms
// do not bother to
// --------------------------------------------------------
// find the matching entry in the leftColStatsList....
// It doesn't Have to be there, but it should be.
// --------------------------------------------------------
CollIndex matchPoint = 0;
for (j = 0; j < origColStatsList.entries() ; j++)
{
ColStatDescSharedPtr oStatDesc = origColStatsList[j];
ColStatsSharedPtr oColStats = oStatDesc->getColStats();
// skip any histograms created for virtual columns, We do not
// want to join on these. continue, so that the joined histogram
// can be synchronized with the previous joined result
if (oColStats->isVirtualColForHist() )
continue;
ItemExpr * originalExpr = oStatDesc->getVEGColumn().getItemExpr() ;
OperatorTypeEnum originalOper = originalExpr->getOperatorType() ;
// skip any histograms created for rowsets, We do not
// want to join on these. continue, so that the joined histogram
// can be synchronized with the previous joined result
if (originalOper == ITM_ROWSETARRAY_SCAN)
continue;
// sanity check
if (NOT( originalOper == ITM_VEG_REFERENCE ||
originalOper == ITM_INSTANTIATE_NULL ||
originalOper == ITM_VALUEIDUNION ||
originalOper == ITM_UNPACKCOL ||
originalOper == ITM_NATYPE ))
{
CCMPASSERT ( "Incorrect expression participating in Join") ;
// We found an incorrect column type in the list of histograms
// assert for debug, and skip for release compiler
continue;
}
if (statExpr == originalExpr) // ValueId comparison
{
if (statOper != originalOper)
{
CCMPASSERT( "Mismatched expressions being joined" ) ; // no reason this should fail
// We found an incorrect column type in the list of histograms
// assert for debug, and skip for release compiler
continue;
}
goodMatch = TRUE;
matchPoint = j;
break ;
}
} // all entries of leftColStatsList
if (goodMatch)
{
ColStatDescSharedPtr oStatDesc = origColStatsList[matchPoint];
ColStatsSharedPtr oColStats = oStatDesc->getColStats();
if ( inPredicate )
{
jStatDesc->mergeColStatDesc (oStatDesc,
LEFT_JOIN_OR_MERGE,
FALSE,
REL_JOIN);
CostScalar oldCount = jColStats->getRowcount();
if (oldCount != oJoinResultRows)
jStatDesc->synchronizeStats (oldCount, oJoinResultRows);
}
else
{
// apply change in selectivity from the predicate to
// all histograms not in the predicate. Only the
// rowcount reduction factor is changed
jStatDesc->synchronizeStats (baseRows,
oJoinResultRows,
ColStatDesc::DO_NOT_REDUCE_UEC) ;
}
}
else
{
CCMPASSERT( "Can't find the left histogram to associate in outer join" ) ;
// somehow can't find a left entry to associate... should
// not happen, but if it does....
CostScalar oldCount = jColStats->getRowcount();
if (oldCount != oJoinResultRows)
jStatDesc->synchronizeStats (oldCount, oJoinResultRows);
}
} // for outerRefCount
}
// -----------------------------------------------------------------------
// This is a helper method for left joins. It is used to
// NULL instantiate the right histogram rows from the other side with NULLs
// ------------------------------------------------------------------------
void
ColStatDescList::nullInstantiateHists(CollIndex startIndex,
CollIndex stopIndex,
CostScalar &oJoinResultRows,
ValueIdList &nulledVIds)
{
// ------------------------------------------------------------
// Next, Instantiate Nulls in histograms from the right table.
// Also, alter the descriptions the histograms have of themselves
// such that they indicate that they are null-instantiated.
// ------------------------------------------------------------
// Histograms from right table start from the outerRefCount index
for (CollIndex i = startIndex; i < stopIndex; i++)
{
ColStatDescSharedPtr jStatDesc = (*this)[i];
ColStatsSharedPtr jColStats = jStatDesc->getColStatsToModify();
// before computing the number of NULLs to add to the histogram
// apply any reduction factor to the intervals that is remaining
jColStats->scaleHistogram(1);
// Skip any histograms created for virtual columns
if (jColStats->isVirtualColForHist())
continue;
// $$$ BETA_NOT_FCS
// $$$ kludge to fix genesis case 10-980224-5150
// $$$ (this should never happen!)
if ( oJoinResultRows < jColStats->getRowcount() )
{
jStatDesc->synchronizeStats (jColStats->getRowcount(), // baseRows
oJoinResultRows, // newRows
ColStatDesc::DO_NOT_REDUCE_UEC) ;
jColStats = jStatDesc->getColStatsToModify() ;
}
// $$$ BETA_NOT_FCS
jColStats->nullAugmentHistogram(oJoinResultRows);
ItemExpr *pred = jStatDesc->VEGColumn().getItemExpr();
for (CollIndex j=0; j < nulledVIds.entries() ; j++)
{
ItemExpr *nulledExpr = nulledVIds[j].getItemExpr();
if (nulledExpr->getOperatorType() != ITM_INSTANTIATE_NULL)
{
CCMPASSERT(nulledExpr->getOperatorType() == ITM_INSTANTIATE_NULL);
continue;
}
// NULL instantiate only those histograms which have not participated
// in any join. The reason is that we do not keep original left histograms
ValueId nulledValueId = nulledExpr->child(0).getValueId();
if (pred->getValueId() == nulledValueId)
{
// overwrite column-stats VEGColumn()'s valueId
// with the valueId of the Instantiate Null
jStatDesc->VEGColumn() = nulledVIds[j];
// we need to update the merge state of these InstantiateNulls
// because otherwise we will get into a rut when we hit the
// ColStatDesc::mergeColStatDesc() -- similar to the situation
// with ValueIdUnions, handled in Union::synthEstLogProp()
// replace the base column from the merge state by null
// instantiated column to reflect the histogram is now null
// instantiated
if (jStatDesc->mergeState().entries() ==1)
jStatDesc->mergeState().clear();
else
{
// remove the corresponding column from merge state and replace that
// with the null instantiated one. Rest of the merge state is not impacted
ValueIdSet mergeState = jStatDesc->mergeState();
// merge state contains either base columns or null instantiated column
// references. Hence if we don't find the nulledvalueid directly
// get the base column from it and compare that
if ((!mergeState.contains(nulledValueId)) &&
(nulledValueId.getItemExpr()->getOperatorType() == ITM_VEG_REFERENCE))
{
mergeState.removeCoveredVIdSet((ValueIdSet) nulledValueId);
}
else
mergeState.remove(nulledValueId);
jStatDesc->mergeState().clear();
jStatDesc->mergeState().insert(mergeState);
}
jStatDesc->mergeState().insert( nulledExpr->getValueId() ) ;
break ;
// what, if anything should be done with the raw column
// list associated with this col stat desc?? cmf
}
} // for j - nullVIds
} // for i - outerRefCount -> joinStatDescList
}
ULng32
MultiColumnSkewedValueLists::HashFunction (const ValueIdList & input)
{
ULng32 retval = 1 + input.entries();
for (CollIndex i=0; i < input.entries(); i++)
{
retval += (CollIndex) input[i]; // add up the ValueId's
}
return retval ;
}
MultiColumnSkewedValueLists::MultiColumnSkewedValueLists () :
HASHDICTIONARY(ValueIdList,MCSkewedValueList) (&(MultiColumnSkewedValueLists::HashFunction),
5,
TRUE,
HISTHEAP )
{ };
// -----------------------------------------------------------------------
// MultiColumnSkewedValueLists :: constructor
//
// This class is modeled after MultiColumnUecList class and mirrors its
// interfaces and usage
// -----------------------------------------------------------------------
MultiColumnSkewedValueLists::MultiColumnSkewedValueLists (const StatsList & initStats,
const ValueIdList & tableColumns) :
HASHDICTIONARY(ValueIdList,MCSkewedValueList) (&(MultiColumnSkewedValueLists::HashFunction),
5,
TRUE,
HISTHEAP )
{
// loop through the list of NAColumnArray's
for ( CollIndex i = 0; i < initStats.groupUecColumns_.entries(); i++ )
{
const NAColumnArray & cols = initStats.groupUecColumns_[i];
if(cols.entries() == 1)
continue;
ValueIdList groupCols;
for ( CollIndex j = 0; j < cols.entries(); j++ )
{
Lng32 position = cols[j]->getPosition();
const ValueId & id = tableColumns[position];
groupCols.insert( id );
}
ValueIdList * key = new (HISTHEAP) ValueIdList( groupCols );
MCSkewedValueList * value = new (HISTHEAP) MCSkewedValueList (initStats.groupMCSkewedValueLists_[i], HISTHEAP);
insert( key, value );
}
}
const MCSkewedValueList * MultiColumnSkewedValueLists::getMCSkewedValueList(ValueIdSet columns, ValueIdList &colGroup)
{
ValueIdList * keyEntry = NULL;
MCSkewedValueList * valueEntry = NULL;
CollIndex noOfCols = columns.entries();
// we need to iterate through all entries in this list
MultiColumnSkewedValueListsIterator iter( *this );
for ( iter.getNext( keyEntry, valueEntry );
keyEntry != NULL && valueEntry != NULL;
iter.getNext( keyEntry, valueEntry ) )
{
if(noOfCols != (*keyEntry).entries())
continue;
if(columns == ValueIdSet(*keyEntry))
{
colGroup = *keyEntry;
return valueEntry;
}
}
return NULL;
}
const MCSkewedValueList * ColStatDescList::getMCSkewedValueListForCols(ValueIdSet inputCols, ValueIdList &colGroup)
{
if(mcSkewedValueLists_)
return mcSkewedValueLists()->getMCSkewedValueList(inputCols, colGroup);
else
return NULL;
}
CostScalar ColStatDescList::getAvgRowcountForNonSkewedMCValues(ValueIdSet cols, MCSkewedValueList* mCSkewedValueList)
{
CostScalar avgRowcountForNonSkewedMCValues = 1;
if(mCSkewedValueList && getUecList())
{
CostScalar mcUecForCols = getUecList()->lookup(cols);
CollIndex noOfMCSkewValues = mCSkewedValueList->entries();
if(mcUecForCols - noOfMCSkewValues > csZero)
{
CostScalar totalRowcountOfMCSkewedValues;
for(CollIndex i=0; i<noOfMCSkewValues; i++)
totalRowcountOfMCSkewedValues += mCSkewedValueList->at(i)->getFrequency();
CostScalar totalRowcount = (*this)[0]->getColStats()->getRowcount();
avgRowcountForNonSkewedMCValues = (totalRowcount - totalRowcountOfMCSkewedValues)/(mcUecForCols - noOfMCSkewValues);
}
}
return avgRowcountForNonSkewedMCValues;
}
// eof