blob: c7b024b48ffa167d207d044fd53c1776c19465eb [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: ScanOptimizer.cpp
* RCS:
* Description: Costing for leaf operators
* Code location: ScanOptimizer.C
*
* Created: //96
* Language: C++
* Purpose: Simple Cost Vector Reduction
*
*
*
*****************************************************************************
*/
#include "stdlib.h"
#include <sys/io.h>
#include "MdamDebug.h"
#include "ScanOptimizer.h"
#include "SimpleScanOptimizer.h"
#include "NAFileSet.h"
#include "ItemColRef.h"
#include "NATable.h"
#include "ItemOther.h"
#include "CmpContext.h"
#include "Sqlcomp.h"
#include "ControlDB.h"
#include "ItemLog.h"
#include "../exp/exp_ovfl_ptal.h" //to check overflow
#include "CmpStatement.h"
#include "mdam.h"
#include "OptRange.h"
// -----------------------------------------------------------------------
// These defines are set because as of today there is no
// mechanism to assert for preconditions. CMPASSERT is an
// internal error reporting system and it does not get disabled
// in release code.
// This is my own solution but a general solution must be found
// an agreed upon.
// -----------------------------------------------------------------------
#undef FSOWARNING
#ifndef NDEBUG
#define FSOWARNING(x) fprintf(stdout, "FileScan optimizer warning: %s\n", x);
#else
#define FSOWARNING(x)
#endif
// LCOV_EXCL_START :dpm
#ifdef MDAM_TRACE
THREAD_P FILE *MdamTrace::outputFile_ = NULL;
THREAD_P NABoolean MdamTrace::doPrint_ = FALSE;
THREAD_P NABoolean MdamTrace::initialized_ = FALSE;
THREAD_P const char* MdamTrace::msgHeader_ = NULL;
THREAD_P const char* MdamTrace::overrideHeader_ = NULL;
THREAD_P const char* MdamTrace::indent_ = "\t";
THREAD_P Int32 MdamTrace::hStdOut_ = -1;
THREAD_P NABoolean MdamTrace::okToRedirectStdOut_ = FALSE;
THREAD_P FILE* MdamTrace::console_ = NULL;
THREAD_P enum MdamTraceLevel MdamTrace::level_ = MDAM_TRACE_LEVEL_NONE;
// use MTL3 to debug MDAM issues
//THREAD_P enum MdamTraceLevel MdamTrace::level_ = MTL3;
void MdamTrace::setHeader(const char *override)
{
overrideHeader_ = override;
}
const char* MdamTrace::getHeader()
{
if(overrideHeader_)
return overrideHeader_;
else if(msgHeader_)
return msgHeader_;
else
return "";
}
void MdamTrace::mayInit(){
if(initialized_)
return;
initialized_ = TRUE;
// Note: the "MDAM_DEBUG" string is split into two adjacent strings so
// C++ preprocessor does not perform macro substitution on MDAM_DEBUG.
//
if (getenv("MDAM_""DEBUG"))
{
doPrint_ = TRUE;
}
const char *debugFileName = getenv("MDAM_""DEBUG_""FILE");
if (debugFileName)
{
outputFile_ = fopen(debugFileName, "a");
if(!outputFile_)
outputFile_ = stdout;
}
else
outputFile_ = stdout;
msgHeader_ = "[MDAM_DEBUG] ";
if(outputFile_ == stdout){
okToRedirectStdOut_ = FALSE; // no need to redirect
}
else if((hStdOut_ = dup(fileno(stdout))) == -1){
okToRedirectStdOut_ = FALSE; // cannot duplicate stdout desc, don't redirect
}
else{
console_ = fdopen(hStdOut_, "w");
if(console_ == NULL){
okToRedirectStdOut_ = FALSE; // cannot create stream, don't redirect
}
else{
okToRedirectStdOut_ = TRUE; // ok to redirect
}
}
}
void MdamTrace::redirectStdOut()
{
mayInit();
if(okToRedirectStdOut_){
*stdout = *outputFile_;
ios::sync_with_stdio();
}
}
void MdamTrace::restoreStdOut()
{
mayInit();
if(okToRedirectStdOut_){
*stdout = *console_;
ios::sync_with_stdio();
}
}
void MdamTrace::print(const char *formatString, ...)
{
mayInit();
if (doPrint_)
{
va_list args;
va_start(args, formatString);
fprintf(outputFile_, "%s", getHeader());
vfprintf(outputFile_, formatString, args);
fprintf(outputFile_, "\n");
fflush(outputFile_);
}
}
void MdamTrace::printTaskMonitor(TaskMonitor &mon,
const char *msg)
{
mayInit();
if(!mon.goodcount())
return;
if(!doPrint_)
return;
ostringstream mon_text;
mon_text << mon;
fprintf(outputFile_, "%s%s %s\n", getHeader(), msg, mon_text.str().c_str());
fflush(outputFile_);
}
void MdamTrace::printCostObject(ScanOptimizer *opt,
const Cost *cost,
const char *msg)
{
mayInit();
printf("%s%s >>\n", getHeader(), msg);
opt->printCostObject(cost);
printf("\n");
fflush(stdout);
}
void MdamTrace::printFileScanInfo(ScanOptimizer *opt,
const ValueIdSet &partKeyPreds)
{
mayInit();
printf("\n%sScan Information:\n", getHeader());
if (opt->getContext()
.getReqdPhysicalProperty()->getPerformanceGoal() == NULL
OR
opt->getContext().getReqdPhysicalProperty()->getPerformanceGoal() ==
CURRSTMT_OPTDEFAULTS->getDefaultPerformanceGoal())
{
printf("%sOptimizing for last row.\n", indent_);
}
else
{
printf("%sOptimizing for first row.\n", indent_);
}
printf("%sIndex Key: ", indent_);
opt->getIndexDesc()->getIndexKey().display();
if (opt->getIndexDesc()->getNAFileSet()->isKeySequenced())
{
printf("%s%sTable is key sequenced.\n", indent_, indent_);
}
else
{
printf("%s%sTable is NOT key sequenced.\n", indent_, indent_);
}
printf("%sSelection predicates: ", indent_);
opt->getRelExpr().getSelectionPred().display();
printf("%sPartitioning Key Predicates: ", indent_);
partKeyPreds.display();
printf("%sExternal inputs: ", indent_);
opt->getExternalInputs().display();
printf("%sCharacteristic outputs: ", indent_);
opt->getRelExpr().getGroupAttr()->getCharacteristicOutputs().display();
printf("%sReceiving [%.4f] rows (probes)\n", indent_,
opt->getContext().getInputLogProp()->
getResultCardinality().value());
if ((opt->getContext().getInputPhysicalProperty() != NULL) AND
(opt->getContext().getInputPhysicalProperty()->getNjOuterOrder() != NULL))
{
printf("%sIncoming rows with ordering: ", indent_);
opt->getContext().getInputPhysicalProperty()->
getNjOuterOrder()->display();
}
else
{
printf("%sIncoming rows unordered", indent_);
}
if ((opt->getContext().getInputLogProp()
->getResultCardinality()).isGreaterThanZero() )
{
if (opt->getContext().getInputLogProp()->getColStats().entries() > 0 )
{
printf("%sStatistics: ", indent_);
opt->getContext().getInputLogProp()->getColStats().display();
}
else
{
printf("%sNO statistics.\n", indent_);
}
}
printf("\n");
fflush(stdout);
}
void MdamTrace::printBasicCost(FileScanOptimizer *opt,
SimpleCostVector &prefixFR,
SimpleCostVector &prefixLR,
const char *msg)
{
Cost *costPtr = opt->computeCostObject(prefixFR, prefixLR);
printf("%s%s\n", getHeader(), msg);
opt->printCostObject(costPtr);
delete costPtr;
fflush(stdout);
}
MdamTraceLevel MdamTrace::level()
{
return level_;
}
void MdamTrace::setLevel(enum MdamTraceLevel l)
{
level_ = l;
}
#endif // if MDAM_TRACE
// LCOV_EXCL_STOP
enum restrictCheckStrategy { MAJORITY_WITH_PREDICATES=1, TOTAL_UECS=2, BOTH=3 };
static NABoolean checkMDAMadditionalRestriction(
const ColumnOrderList& keyPredsByCol,
const CollIndex& lastColumnPosition,
const Histograms& hist,
restrictCheckStrategy strategy,
CollIndex& noOfmissingKeyColumns, CollIndex& presentKeyColumns)
{
KeyColumns::KeyColumn::KeyColumnType typeOfRange = KeyColumns::KeyColumn::EMPTY;
CollIndex index = 0;
NABoolean checkLeadingDivColumns =
(CmpCommon::getDefault(MTD_GENERATE_CC_PREDS) == DF_ON);
Lng32 mtd_mdam_uec_threshold = (Lng32)(ActiveSchemaDB()->getDefaults()).
getAsLong(MTD_MDAM_NJ_UEC_THRESHOLD);
if ( mtd_mdam_uec_threshold < 0 )
checkLeadingDivColumns = FALSE;
CostScalar totalRC = hist.getRowCount().getCeiling();
float totalUEC_threshold = 1;
Lng32 minRC = (ActiveSchemaDB()->getDefaults()).getAsLong(MDAM_TOTAL_UEC_CHECK_MIN_RC_THRESHOLD);
if ( totalRC > minRC )
(ActiveSchemaDB()->getDefaults()).getFloat(MDAM_TOTAL_UEC_CHECK_UEC_THRESHOLD, totalUEC_threshold);
totalUEC_threshold *= totalRC.getValue();
NABoolean isLeadingDivisionColumn = FALSE;
NABoolean isLeadingSaltColumn = FALSE;
CostScalar totalUecsForPredicatelessKeyColumns = 1;
CostScalar totalUecsForCurrentPredicatelessKeyColumnGroup = 1;
ValueIdSet currentPredicatelessKeyColumnGroup;
for (index = 0; index < lastColumnPosition; index++)
{
if (keyPredsByCol.getPredicateExpressionPtr(index) != NULL)
{
typeOfRange = keyPredsByCol.getPredicateExpressionPtr(index)->getType();
}
else
typeOfRange= KeyColumns::KeyColumn::EMPTY;
isLeadingDivisionColumn = FALSE;
isLeadingSaltColumn = FALSE;
ValueId columnVid = keyPredsByCol.getKeyColumnId(index);
if ( checkLeadingDivColumns )
{
// Check if the key column is a leading divisioning column
isLeadingDivisionColumn = columnVid.isDivisioningColumn();
// Check if the key column is a leading salted column
isLeadingSaltColumn = columnVid.isSaltColumn();
}
if (typeOfRange == KeyColumns::KeyColumn::EMPTY) {
// if not check leading DIV columns or check leading div columns
// and the current key column is not divisioning, increase the
// the non-key-predicate columns.
if ( checkLeadingDivColumns == FALSE ||
(!isLeadingDivisionColumn && !isLeadingSaltColumn)
)
noOfmissingKeyColumns++;
// accumulate the product of uecs for columns without predicates in the current
// group
totalUecsForCurrentPredicatelessKeyColumnGroup *= hist.getColStatsForColumn(
columnVid).getTotalUec().getCeiling();
// accumulate the column valud Id at the same time for MC UEC lookup later on.
currentPredicatelessKeyColumnGroup.insert(columnVid);
} else {
checkLeadingDivColumns = FALSE;
presentKeyColumns++;
// If the set of key columns without predicate is not empty, fetch the MC UEC
// for the entire set. If the MC UEC exists, replace the current accumualted
// total UEC with the MC UEC.
//
// We will set the set to empty so that the fetching MC UEC logic will not kick in
// until a new key column without predicates is seen.
if ( currentPredicatelessKeyColumnGroup.entries() > 1 ) {
// fetch MC UEC from key coluymns for column set currentPredicatelessKeyColumnGroup
const MultiColumnUecList* MCUL = hist.getColStatDescList().getUecList();
ValueIdSet theLargestSubset =
MCUL->largestSubset(currentPredicatelessKeyColumnGroup.convertToBaseIds());
if ( theLargestSubset.entries() == currentPredicatelessKeyColumnGroup.entries() )
{
CostScalar mcUEC = MCUL->lookup(theLargestSubset);
if ( mcUEC != csMinusOne )
totalUecsForCurrentPredicatelessKeyColumnGroup = mcUEC;
}
currentPredicatelessKeyColumnGroup.clear();
}
totalUecsForPredicatelessKeyColumns *=
totalUecsForCurrentPredicatelessKeyColumnGroup;
totalUecsForCurrentPredicatelessKeyColumnGroup = 1;
}
}
switch ( strategy ) {
case MAJORITY_WITH_PREDICATES:
return (presentKeyColumns > noOfmissingKeyColumns);
case TOTAL_UECS:
return ( totalUecsForPredicatelessKeyColumns < totalUEC_threshold );
case BOTH:
return ( presentKeyColumns > noOfmissingKeyColumns &&
totalUecsForPredicatelessKeyColumns < totalUEC_threshold );
default:
return FALSE;
}
return FALSE;
}
// stack allocated only
// work area to find out the MDAM cost
class MDAMCostWA
{
public:
MDAMCostWA(FileScanOptimizer & optimizer,
NABoolean mdamForced,
MdamKey *mdamKeyPtr,
const Cost *costBoundPtr,
const ValueIdSet & exePreds,
SimpleCostVector & disjunctsFR,
SimpleCostVector & disjunctsLR);
void compute();
NABoolean isMdamWon() const;
NABoolean hasNoExePreds() const;
const CostScalar & getNumKBytes() const;
void computeDisjunct();
Cost * getScmCost();
private:
// output
NABoolean mdamWon_;
NABoolean noExePreds_;
CostScalar numKBytes_;
// input
const NABoolean mdamForced_;
MdamKey *mdamKeyPtr_;
const Cost *costBoundPtr_;
FileScanOptimizer & optimizer_;
// -- side effects
SimpleCostVector & disjunctsFR_;
SimpleCostVector & disjunctsLR_;
Cost * scmCost_;
// scrach space
const ValueIdSet & exePreds_;
// estimated # of rows upper bound of the scan
const CostScalar innerRowsUpperBound_;
// estimated # of blocks upper bound of the scan
// This is computed from innerRowsUpperBound_ and estimatedRecordsPerBlock_
// Consider making it const and move the computation to the constructor
CostScalar innerBlocksUpperBound_;
// estimated # of records per block of the scan
const CostScalar estimatedRecordsPerBlock_;
const NABoolean isMultipleProbes_;
const ScanForceWildCard *scanForcePtr_;
// Consider making it const and move the computation to the constructor
CostScalar incomingProbes_;
// Histograms to apply first column predicates from different disjunct
// This is used to compute whether there is disjunct overlaps.
IndexDescHistograms firstColumnHistogram_;
// Outer histograms is used in multiple probes, where it is joined with
// the disjunct histograms and also used to compute # of failed probes
const Histograms outerHistograms_;
CollIndex disjunctIndex_;
// -- optimal prefix cost factors of each disjunct
CostScalar disjunctOptRows_;
CostScalar disjunctOptRqsts_;
CostScalar disjunctOptProbesForSubsetBoundaries_;
CostScalar disjunctOptSeeks_;
CostScalar disjunctOptSeqKBRead_;
ValueIdSet disjunctOptKeyPreds_;
NABoolean disjunctMdamOK_;
CollIndex disjunctNumLeadingPartPreds_;
CostScalar disjunctFailedProbes_;
};
// stack allocated only
// work area to find out the optimal disjunct prefix
class MDAMOptimalDisjunctPrefixWA{
public:
MDAMOptimalDisjunctPrefixWA(
FileScanOptimizer & optimizer,
const ColumnOrderList & keyPredsByCol,
const ValueIdSet & disjunctKeyPreds,
const ValueIdSet & exePreds,
const Histograms & outerHistograms,
IndexDescHistograms & firstColumnHistogram,
NABoolean & noExePreds,
MdamKey *mdamKeyPtr,
const ScanForceWildCard *scanForcePtr,
NABoolean mdamForced,
NABoolean isMultipleProbes,
const CostScalar & incomingProbes,
const CostScalar & estimatedRecordsPerBlock,
const CostScalar & innerRowsUpperBound,
const CostScalar & innerBlocksUpperBound,
CollIndex disjunctIndex);
~MDAMOptimalDisjunctPrefixWA();
CollIndex getStopColumn() const;
CollIndex getNumLeadingPartPreds() const;
const CostScalar & getFailedProbes() const;
const CostScalar & getOptRows() const;
const CostScalar & getOptRqsts() const;
const CostScalar & getOptProbesForSubsetBoundaries() const;
const CostScalar & getOptSeeks() const;
const CostScalar & getOptSeqKBRead() const;
const ValueIdSet & getOptKeyPreds() const;
void compute();
const CostScalar rcAfterApplyFirstKeyPreds() const
{ return rcAfterApplyFirstKeyPreds_; }
private:
void processLeadingColumns();
void processNonLeadingColumn();
void applyPredsToHistogram();
void computeDensityOfColumn();
void updatePositions();
void updateStatistics();
void updateMinPrefix();
void processSingleSubsetPrefix();
void computeProbesDisjunct();
NABoolean missingKeyColumnExists() const;
// --------------------------- output --------------------------
// # of failed probes
CostScalar failedProbes_;
// optimal prefix's # of rows
CostScalar optRows_;
// optimal prefix's # of requests: #effective probes * #subsets
CostScalar optRqsts_;
// optimal prefix's # of requests to find subset boudaries
CostScalar optRqstsForSubsetBoundaries_;
// optimal prefix's # of seeks
CostScalar optSeeks_;
// optimal prefix's # of KB read
CostScalar optSeqKBRead_;
// optimal prefix's ey predicates
ValueIdSet optKeyPreds_;
// The last column of the optimal prefix, which is 0 based
CollIndex stopColumn_;
// The # of partition predicates on the 1st column
CollIndex numLeadingPartPreds_;
// ---------------------------- input -----------------------------
FileScanOptimizer & optimizer_;
// array of pointers to key predicates ordered on key columns
const ColumnOrderList & keyPredsByCol_;
// key predicates of the disjunct
const ValueIdSet & disjunctKeyPreds_;
// executor predicates of the disjunct
const ValueIdSet & exePreds_;
// Outer histograms is used in multiple probes, where it is joined with
// the disjunct histograms and also used to compute # of failed probes
const Histograms & outerHistograms_;
// User specified scan force pattern
const ScanForceWildCard * scanForcePtr_;
const NABoolean isMultipleProbes_;
const NABoolean mdamForced_;
// # of probes
const CostScalar incomingProbes_;
// estimated # of records per block of the scan
const CostScalar estimatedRecordsPerBlock_;
// estimated # of rows upper bound of the scan
const CostScalar innerRowsUpperBound_;
// estimated # of blocks upper bound of the scan
const CostScalar innerBlocksUpperBound_;
const CollIndex disjunctIndex_;
// ---------------------------- input with side effects -----------
IndexDescHistograms & firstColumnHistogram_;
NABoolean & noExePreds_;
MdamKey * mdamKeyPtr_;
// ------------------------------ scrach space ---------------------
IndexDescHistograms disjunctHistograms_;
const NABoolean multiColUecInfoAvail_;
// Last key column position we need to consider
// when computing optimal prefix. It is one based
const CollIndex lastColumnPosition_;
// Whether the multiple subsets overlaps on the first column
// when comparing to the previous disjunct
NABoolean firstColOverlaps_;
// Estimated rows iff multiple probes
CostScalar multiProbesDataRows_;
// >>>>>>>>>>>>>>>>> Current prefix related members <<<<<<<<<<<<<<<<<
// # of subsets of each effective probe at the current level
CostScalar prefixSubsets_;
// cumulative # of subsets of each effective probe
// Why do we care? MDAM is a recursive algorithm. It first materializes
// values for the first key column. For each of those, it materializes
// values for the second key column. And so on. Each of these levels adds
// progressively more cost which we must take into account. If we look
// only at prefixSubsets_ (that is, the current column level), we may be
// misled into thinking that adding more levels of column traversal is
// free. Which it is not. Moreover, as the number of rows approaches the
// total number of rows in the table, it is akin to adding an additional
// table scan.
CostScalar cumulativePrefixSubsets_;
// # of subset seeks of each effective probe
CostScalar prefixSubsetsAsSeeks_;
// # of rows of all probes at the current column level
CostScalar prefixRows_;
// # of seeks of all probes.
CostScalar prefixRqsts_;
// # of additional seeks of all probes to read index blocks
// to find subset boundaries for a sparse column
CostScalar prefixRqstsForSubsetBoundaries_;
// # of seeks of all probes
CostScalar prefixSeeks_;
// # of KB read of all probes
CostScalar prefixKBRead_;
// accumulated key prdicates for the current prefix
ValueIdSet prefixKeyPreds_;
// pointer to key predicates to be applied to the disjunct histograms
const ValueIdSet *keyPredsPtr2Apply2Histograms_;
// Current column position
CollIndex prefixColumnPosition_;
// Whether we have an equal predicate on the current column
NABoolean curPredIsEqual_;
// >>>>>>>>>>>>>>>>>> Previous column related memebers <<<<<<<<<<<<<<<
// Whether we had an equal predicate on the previous column
NABoolean prevPredIsEqual_;
// The uec for previous column after applying predicate on the column
CostScalar uecForPreviousCol_;
// The uec for previous column before applying predicate on teh column
CostScalar uecForPreviousColBeforeAppPreds_;
// Take into account the distance betweem the uecs and effects of that on
// cache hit
CostScalar uecForPrevColForSeeks_;
NABoolean firstRound_;
NABoolean crossProductApplied_;
NABoolean prevColChosen_;
CostScalar sumOfUecs_; // The sum of the uecs for all columns
CostScalar sumOfUecsSoFar_;
CostScalar blocksToReadPerUec_;
Cost* pMinCost_;
// the rowcount as a result of applying the first non-empty key predicate
// (if any). The value is useful to estimate the number of rows returned
// for all probes into the inner table with leading key columns absent
// of predicates.
CostScalar rcAfterApplyFirstKeyPreds_;
};
// LCOV_EXCL_START :dpm
#ifndef NDEBUG
static Int32
ScanOptimizerTest1(const FileScan& associatedFileScan
,const CostScalar& resultSetCardinality
,const Context& myContext
,const ValueIdSet &externalInputs
,CollHeap* heap)
{
// Test shared basic cost objects;
// Don't bother if sharing is disabled
//
if (CURRSTMT_OPTDEFAULTS->reuseBasicCost()) {
FileScanOptimizer scanOpt(associatedFileScan,
resultSetCardinality,
myContext,
externalInputs);
// Attempt to get a shared basic cost object.
//
SearchKey *searchKey = NULL;
MdamKey *mdamKey = NULL;
Cost *cost1 = scanOpt.optimize(searchKey,
mdamKey);
// Make sure that this time the basic cost object is regenerated
//
NABoolean reUseBasicCost = CURRSTMT_OPTDEFAULTS->reuseBasicCost();
CURRSTMT_OPTDEFAULTS->setReuseBasicCost(FALSE);
searchKey = NULL;
mdamKey = NULL;
Cost *cost2 = scanOpt.optimize(searchKey,
mdamKey);
CURRSTMT_OPTDEFAULTS->setReuseBasicCost(reUseBasicCost);
if(cost1->compareCosts(*cost2) != SAME) {
fprintf(stdout,"Test1 Failed ========\n");
fprintf(stdout,"Cost1 (shared)\n");
cost1->print();
fprintf(stdout,"Cost2\n");
cost2->print();
} else {
fprintf(stdout,"Test1 Passed ====\n");
}
delete cost1;
delete cost2;
}
return 0;
}
static Int32
ScanOptimizerTest2(const FileScan& associatedFileScan
,const CostScalar& resultSetCardinality
,const Context& myContext
,const ValueIdSet &externalInputs
,CollHeap* heap)
{
// Test that the simple scan optimizer produces the same cost as the
// original.
static Int32 test2Cnt = 0;
static Int32 totalCnt = 0;
static Int32 failCnt = 0;
totalCnt++;
if(ScanOptimizer::useSimpleFileScanOptimizer(associatedFileScan,
myContext,
externalInputs)) {
test2Cnt++;
SimpleFileScanOptimizer simpleScanOpt(associatedFileScan,
resultSetCardinality,
myContext,
externalInputs);
FileScanOptimizer complexScanOpt(associatedFileScan,
resultSetCardinality,
myContext,
externalInputs);
// Make sure that the basic cost object is regenerated
//
NABoolean reUseBasicCost = CURRSTMT_OPTDEFAULTS->reuseBasicCost();
CURRSTMT_OPTDEFAULTS->setReuseBasicCost(FALSE);
SearchKey *searchKey1 = NULL;
MdamKey *mdamKey1 = NULL;
Cost *cost1 = simpleScanOpt.optimize(searchKey1,
mdamKey1);
SearchKey *searchKey2 = NULL;
MdamKey *mdamKey2 = NULL;
Cost *cost2 = complexScanOpt.optimize(searchKey2,
mdamKey2);
CURRSTMT_OPTDEFAULTS->setReuseBasicCost(reUseBasicCost);
if(mdamKey2) {
fprintf(stdout,"Test 2.5 Failed MDAM ================\n");
fprintf(stdout,"Key Columns\n");
searchKey1->getKeyColumns().display();
fprintf(stdout,"Key Predicates\n");
searchKey1->getKeyPredicates().display();
fprintf(stdout,"Exec Predicates\n");
searchKey1->getExecutorPredicates().display();
failCnt++;
} else if(cost1->compareCosts(*cost2) != SAME) {
ElapsedTime et1 (cost1->convertToElapsedTime(NULL));
ElapsedTime et2 (cost2->convertToElapsedTime(NULL));
CostScalar perDiff;
if(et1 != et2) {
perDiff = ((et1 > et2) ? (et1-et2) : (et2-et1))/et2;
} else {
CostScalar tc_et1 = cost1->getTotalCost().getElapsedTime
(*(CURRSTMT_OPTDEFAULTS->getResourcePerformanceGoal()),
CURRSTMT_OPTDEFAULTS->getDefaultCostWeight());
CostScalar tc_et2 = cost2->getTotalCost().getElapsedTime
(*(CURRSTMT_OPTDEFAULTS->getResourcePerformanceGoal()),
CURRSTMT_OPTDEFAULTS->getDefaultCostWeight());
perDiff = ((tc_et1 > tc_et2) ? (tc_et1-tc_et2) : (tc_et2-tc_et1))/tc_et2;
}
perDiff = perDiff * 100;
if (perDiff > 2.0) {
fprintf(stdout,"Test 2 Failed %6.4f %6.4f %6.3f ",
et1.value(), et2.value(), perDiff.value());
Int32 i = (Int32)(perDiff.getCeiling().getValue());
i = ((i > 70) ? 70 : i);
for(; i > 0; i--) {
fprintf(stdout, "=");
}
fprintf(stdout, "\n");
failCnt++;
// fprintf(stdout,"Simple Cost\n");
// cost1->print();
// fprintf(stdout,"Complex Cost\n");
// cost2->print();
fprintf(stdout,"Test2 Stats (%d:%d:%d)(%d) ====\n", totalCnt, test2Cnt, failCnt,
(Int32)(100*((float)test2Cnt/(float)totalCnt)));
} else {
fprintf(stdout,"Test2 Passed (%d:%d:%d)(%d) %6.3f\n",
totalCnt, test2Cnt, failCnt, (Int32)(100*((float)test2Cnt/(float)totalCnt)),
perDiff.value());
}
} else {
fprintf(stdout,"Test2 Passed (%d:%d:%d)(%d) ====\n", totalCnt, test2Cnt, failCnt,
(Int32)(100*((float)test2Cnt/(float)totalCnt)));
}
delete cost1;
delete cost2;
}
return 0;
}
//THREAD_P TaskMonitor* simpleFSOMonPtr = NULL;
//THREAD_P TaskMonitor* complexFSOMonPtr = NULL;
static Int32
ScanOptimizerTest3(const FileScan& associatedFileScan
,const CostScalar& resultSetCardinality
,const Context& myContext
,const ValueIdSet &externalInputs
,CollHeap* heap)
{
// Test the performance of the simple scan optimizer compared to the
// original.
if(ScanOptimizer::useSimpleFileScanOptimizer(associatedFileScan,
myContext,
externalInputs)) {
SimpleFileScanOptimizer simpleScanOpt(associatedFileScan,
resultSetCardinality,
myContext,
externalInputs);
FileScanOptimizer complexScanOpt(associatedFileScan,
resultSetCardinality,
myContext,
externalInputs);
// Make sure that the basic cost object is regenerated
//
NABoolean reUseBasicCost = CURRSTMT_OPTDEFAULTS->reuseBasicCost();
CURRSTMT_OPTDEFAULTS->setReuseBasicCost(FALSE);
CURRENTSTMT->getSimpleFSOMonPtr()->enter();
Int32 i;
for(i = 0; i < 20; i++) {
SearchKey *searchKey1 = NULL;
MdamKey *mdamKey1 = NULL;
Cost *cost1 = simpleScanOpt.optimize(searchKey1,
mdamKey1);
delete cost1;
}
CURRENTSTMT->getSimpleFSOMonPtr()->exit();
CURRENTSTMT->getComplexFSOMonPtr()->enter();
for(i = 0; i < 20; i++) {
SearchKey *searchKey2 = NULL;
MdamKey *mdamKey2 = NULL;
Cost *cost2 = complexScanOpt.optimize(searchKey2,
mdamKey2);
delete cost2;
}
CURRENTSTMT->getComplexFSOMonPtr()->exit();
CURRSTMT_OPTDEFAULTS->setReuseBasicCost(reUseBasicCost);
cout << "Test3 simpleFSO : "<< *(CURRENTSTMT->getSimpleFSOMonPtr()) << endl;
cout << "Test3 complexFSO : "<< *(CURRENTSTMT->getComplexFSOMonPtr()) << endl;
}
return 0;
}
static Int32
ScanOptimizerTest4(const FileScan& associatedFileScan
,const CostScalar& resultSetCardinality
,const Context& myContext
,const ValueIdSet &externalInputs
,CollHeap* heap)
{
// Test that the simple scan optimizer produces the same cost as the
// original.
static Int32 test4Cnt = 0;
static Int32 totalCnt = 0;
static Int32 failCnt = 0;
if(!ScanOptimizer::useSimpleFileScanOptimizer(associatedFileScan,
myContext,
externalInputs)) {
totalCnt++;
FileScanOptimizer complexScanOpt(associatedFileScan,
resultSetCardinality,
myContext,
externalInputs);
// Make sure that the basic cost object is regenerated
//
NABoolean reUseBasicCost = CURRSTMT_OPTDEFAULTS->reuseBasicCost();
CURRSTMT_OPTDEFAULTS->setReuseBasicCost(FALSE);
SearchKey *searchKey2 = NULL;
MdamKey *mdamKey2 = NULL;
Cost *cost2 = complexScanOpt.optimize(searchKey2,
mdamKey2);
CURRSTMT_OPTDEFAULTS->setReuseBasicCost(reUseBasicCost);
// If didn't choose SimpleScanOptimizer, but could have (should have)...
//
if(searchKey2 &&
(myContext.getInputLogProp()->getColStats().entries() == 0))
{
test4Cnt++;
SimpleFileScanOptimizer simpleScanOpt(associatedFileScan,
resultSetCardinality,
myContext,
externalInputs);
// Make sure that the basic cost object is regenerated
//
NABoolean reUseBasicCost = CURRSTMT_OPTDEFAULTS->reuseBasicCost();
CURRSTMT_OPTDEFAULTS->setReuseBasicCost(FALSE);
SearchKey *searchKey1 = NULL;
MdamKey *mdamKey1 = NULL;
Cost *cost1 = simpleScanOpt.optimize(searchKey1,
mdamKey1);
CURRSTMT_OPTDEFAULTS->setReuseBasicCost(reUseBasicCost);
if(cost1->compareCosts(*cost2) != SAME) {
ElapsedTime et1 (cost1->convertToElapsedTime(NULL));
ElapsedTime et2 (cost2->convertToElapsedTime(NULL));
CostScalar perDiff;
if(et1 != et2) {
perDiff = ((et1 > et2) ? (et1-et2) : (et2-et1))/et2;
} else {
CostScalar tc_et1 = cost1->getTotalCost().getElapsedTime
(*(CURRSTMT_OPTDEFAULTS->getResourcePerformanceGoal()),
CURRSTMT_OPTDEFAULTS->getDefaultCostWeight());
CostScalar tc_et2 = cost2->getTotalCost().getElapsedTime
( *(CURRSTMT_OPTDEFAULTS->getResourcePerformanceGoal()),
CURRSTMT_OPTDEFAULTS->getDefaultCostWeight() );
perDiff = ((tc_et1 > tc_et2) ? (tc_et1-tc_et2)
: (tc_et2-tc_et1))/tc_et2;
}
perDiff = perDiff * 100;
if (perDiff > 2.0) {
fprintf(stdout,"Test 4 Failed %6.4f %6.4f %6.3f ",
et1.value(), et2.value(), perDiff.value());
Int32 i = (Int32)(perDiff.getCeiling().getValue());
i = ((i > 70) ? 70 : i);
for(; i > 0; i--) {
fprintf(stdout, "=");
}
fprintf(stdout, "\n");
failCnt++;
} else {
fprintf(stdout,"Test4 Passed (%d:%d:%d) %6.3f\n",
totalCnt, test4Cnt, failCnt, perDiff.value());
}
} else {
fprintf(stdout,"Test4 Passed (%d:%d:%d) ====\n",
totalCnt, test4Cnt, failCnt);
}
delete cost1;
}
delete cost2;
}
return 0;
}
static Int32
ScanOptimizerTest5(const FileScan& associatedFileScan
,const CostScalar& resultSetCardinality
,const Context& myContext
,const ValueIdSet &externalInputs
,CollHeap* heap)
{
// Test that the simple scan optimizer produces the same cost as the
// original for cases of Multiprobe.
static Int32 test5Cnt = 0;
static Int32 totalCnt = 0;
static Int32 failCnt = 0;
totalCnt++;
if(ScanOptimizer::useSimpleFileScanOptimizer(associatedFileScan,
myContext,
externalInputs)) {
CostScalar repeatCount = myContext.getPlan()->getPhysicalProperty()->
getDP2CostThatDependsOnSPP()->getRepeatCountForOperatorsInDP2();
if ((repeatCount > 1.0) OR
(myContext.getInputLogProp()->getColStats().entries() > 0)) {
test5Cnt++;
SimpleFileScanOptimizer simpleScanOpt(associatedFileScan,
resultSetCardinality,
myContext,
externalInputs);
FileScanOptimizer complexScanOpt(associatedFileScan,
resultSetCardinality,
myContext,
externalInputs);
// Make sure that the basic cost object is regenerated
//
NABoolean reUseBasicCost = CURRSTMT_OPTDEFAULTS->reuseBasicCost();
CURRSTMT_OPTDEFAULTS->setReuseBasicCost(FALSE);
SearchKey *searchKey1 = NULL;
MdamKey *mdamKey1 = NULL;
Cost *cost1 = simpleScanOpt.optimize(searchKey1,
mdamKey1);
SearchKey *searchKey2 = NULL;
MdamKey *mdamKey2 = NULL;
Cost *cost2 = complexScanOpt.optimize(searchKey2,
mdamKey2);
CURRSTMT_OPTDEFAULTS->setReuseBasicCost(reUseBasicCost);
if(mdamKey2) {
fprintf(stdout,"Test 5.5 Failed MDAM ================\n");
fprintf(stdout,"Key Columns\n");
searchKey1->getKeyColumns().display();
fprintf(stdout,"Key Predicates\n");
searchKey1->getKeyPredicates().display();
fprintf(stdout,"Exec Predicates\n");
searchKey1->getExecutorPredicates().display();
failCnt++;
} else if(cost1->compareCosts(*cost2) != SAME) {
ElapsedTime et1 (cost1->convertToElapsedTime(NULL));
ElapsedTime et2 (cost2->convertToElapsedTime(NULL));
CostScalar perDiff;
if(et1 != et2) {
perDiff = ((et1 > et2) ? (et1-et2) : (et2-et1))/et2;
} else {
CostScalar tc_et1 = cost1->getTotalCost().getElapsedTime
(*(CURRSTMT_OPTDEFAULTS->getResourcePerformanceGoal()),
CURRSTMT_OPTDEFAULTS->getDefaultCostWeight());
CostScalar tc_et2 = cost2->getTotalCost().getElapsedTime
(*(CURRSTMT_OPTDEFAULTS->getResourcePerformanceGoal()),
CURRSTMT_OPTDEFAULTS->getDefaultCostWeight());
perDiff = ((tc_et1 > tc_et2) ? (tc_et1-tc_et2) : (tc_et2-tc_et1))/tc_et2;
}
perDiff = perDiff * 100;
if (perDiff > 2.0) {
fprintf(stdout,"Test 5 Failed %6.4f %6.4f %6.3f ",
et1.value(), et2.value(), perDiff.value());
Int32 i = (Int32)(perDiff.getCeiling().getValue());
i = ((i > 70) ? 70 : i);
for(; i > 0; i--) {
fprintf(stdout, "=");
}
fprintf(stdout, "\n");
failCnt++;
// fprintf(stdout,"Simple Cost\n");
// cost1->print();
// fprintf(stdout,"Complex Cost\n");
// cost2->print();
fprintf(stdout,"Test5 Stats (%d:%d:%d)(%d) ====\n", totalCnt, test5Cnt, failCnt,
(Int32)(100*((float)test5Cnt/(float)totalCnt)));
} else {
fprintf(stdout,"Test5 Passed (%d:%d:%d)(%d) %6.3f\n",
totalCnt, test5Cnt, failCnt, (Int32)(100*((float)test5Cnt/(float)totalCnt)), perDiff.value());
}
} else {
fprintf(stdout,"Test5 Passed (%d:%d:%d)(%d) ====\n", totalCnt, test5Cnt, failCnt,
(Int32)(100*((float)test5Cnt/(float)totalCnt)));
}
delete cost1;
delete cost2;
}
}
return 0;
}
static Int32
ScanOptimizerAllTests(const FileScan& associatedFileScan
,const CostScalar& resultSetCardinality
,const Context& myContext
,const ValueIdSet &externalInputs
,CollHeap* heap)
{
NADefaults &defs = ActiveSchemaDB()->getDefaults();
ULng32 testsToRun = defs.getAsULong(FSO_RUN_TESTS);
if(testsToRun & 0x01) {
ScanOptimizerTest1(associatedFileScan
,resultSetCardinality
,myContext
,externalInputs
,heap);
}
if(testsToRun & 0x02) {
ScanOptimizerTest2(associatedFileScan
,resultSetCardinality
,myContext
,externalInputs
,heap);
}
if(testsToRun & 0x04) {
ScanOptimizerTest3(associatedFileScan
,resultSetCardinality
,myContext
,externalInputs
,heap);
}
if(testsToRun & 0x08) {
ScanOptimizerTest4(associatedFileScan
,resultSetCardinality
,myContext
,externalInputs
,heap);
}
if(testsToRun & 0x10) {
ScanOptimizerTest5(associatedFileScan
,resultSetCardinality
,myContext
,externalInputs
,heap);
}
return 0;
}
#endif
// LCOV_EXCL_STOP
// -----------------------------------------------------------------------
// getDp2CacheSizeInBlocks
// Given a block size, this method returns the number of blocks in
// cache for blocks of that size.
// -----------------------------------------------------------------------
CostScalar
getDP2CacheSizeInBlocks(const CostScalar& blockSizeInKb)
{
CostScalar cacheSizeInBlocks = csZero;
if (blockSizeInKb >= 64.)
cacheSizeInBlocks =
CostPrimitives::getBasicCostFactor(NCM_CACHE_SIZE_IN_BLOCKS);
else if (blockSizeInKb == 32.)
cacheSizeInBlocks =
CostPrimitives::getBasicCostFactor(DP2_CACHE_32K_BLOCKS);
// LCOV_EXCL_START :cnu
else if (blockSizeInKb == 16.)
cacheSizeInBlocks =
CostPrimitives::getBasicCostFactor(DP2_CACHE_16K_BLOCKS);
else if (blockSizeInKb == 8.)
cacheSizeInBlocks =
CostPrimitives::getBasicCostFactor(DP2_CACHE_8K_BLOCKS);
else if (blockSizeInKb == 4.)
cacheSizeInBlocks =
CostPrimitives::getBasicCostFactor(DP2_CACHE_4096_BLOCKS);
else if (blockSizeInKb == 2.)
cacheSizeInBlocks =
CostPrimitives::getBasicCostFactor(DP2_CACHE_2048_BLOCKS);
else if (blockSizeInKb == 1.)
cacheSizeInBlocks =
CostPrimitives::getBasicCostFactor(DP2_CACHE_1024_BLOCKS);
else if (blockSizeInKb == 0.5)
cacheSizeInBlocks =
CostPrimitives::getBasicCostFactor(DP2_CACHE_512_BLOCKS);
else
cacheSizeInBlocks =
CostPrimitives::getBasicCostFactor(NCM_CACHE_SIZE_IN_BLOCKS);
// LCOV_EXCL_STOP
return cacheSizeInBlocks;
}
// -----------------------------------------------------------------------
// removeConstantsFromTargetSortKey
//
// This method removes columns that are covered by constants on the left
// (source) side of a binary operator from the passed in right child
// (target) sort key. This is useful for example, for the following scenario:
// create table foo (pnum int, primary key pnum);
// create table bar (empnum int, pnum int, primary key (empnum, pnum));
// insert into bar select 100,pnum from foo;
//
// Target column empnum is equivalent to 100 on the source side but
// not on the target side. Still, all the PROBES to the target table
// will have their empnum value equal to 100. If we can realize this on
// the target side then the ordersMatch method will be able to determine
// that the probes are completely in order. This is genesis case
// 10-000925-2516. If we don't do this the ordersMatch method could
// fail an assertion because we are counting on columns on one side
// of the map that are covered by constants are also covered by
// constants on the other side of the map.
//
//
// INPUT PARAMS:
// targetSortKey: The right child sort key columns
// map : the map that maps values from one side of the op to the other
//
// OUTPUT PARAMS:
// rightChildSortKey, possibly modified.
//
// RETURN VALUE:
// None.
// -----------------------------------------------------------------------
void
removeConstantsFromTargetSortKey(ValueIdList* targetSortKey,
ValueIdMap* map)
{
// Map the target sort key cols to the source
ValueIdList mappedTargetSortKey;
map->rewriteValueIdListDown(
*targetSortKey,
mappedTargetSortKey);
// Remove from the mapped sort key any cols that are equal to constants
ValueIdSet charInputs; // empty : we only want to remove constants
mappedTargetSortKey.removeCoveredExprs(charInputs);
if (mappedTargetSortKey.entries() < targetSortKey->entries())
{
// Map back the mapped target sort key cols
ValueIdList remappedTargetSortKey;
map->rewriteValueIdListUp(
remappedTargetSortKey,
mappedTargetSortKey);
// Set the target sort key to the version with columns covered by
// constants now removed
*targetSortKey = remappedTargetSortKey;
}
}
// isOrderedNJFeasible
//
// This method checks to see if leading column of left child and
// right child of a NJ are same. This method gets called from
// insert, update, delete computeOperatorCost methods if IPP is
// being passed. The reason for calling this method is these operators
// manipulate left and right sort keys. If we don't make sure atleast
// leading key cols are same then ordersMatch method will assert.
// INPUT PARAMS:
// leftKeys: The left child sort key columns
// rightKeys: The right child sort key columns
//
//
// RETURN VALUE:
// TRUE if leading cols are same, FALSE otherwise.
NABoolean
isOrderedNJFeasible (ValueIdList leftKeys, ValueIdList rightKeys)
{
if (leftKeys.isEmpty() || rightKeys.isEmpty())
return FALSE;
ValueId lKeyCol = leftKeys[0];
// Remove any inverse node on the leading left table
ValueId noInverseLKeyCol =
lKeyCol.getItemExpr()->removeInverseOrder()->getValueId();
NABoolean lKeyColIsDesc = FALSE;
if (noInverseLKeyCol != lKeyCol)
lKeyColIsDesc = TRUE;
ValueId rKeyCol = rightKeys[0];
// Remove any inverse node on the leading right table
ValueId noInverseRKeyCol =
rKeyCol.getItemExpr()->removeInverseOrder()->getValueId();
NABoolean rKeyColIsDesc = FALSE;
if (noInverseRKeyCol != rKeyCol)
rKeyColIsDesc = TRUE;
// Return orderedNJ as not feasible for divisioning columns
// where the order is set to DESCENDING.
if(lKeyColIsDesc || rKeyColIsDesc)
{
if (lKeyColIsDesc)
{
BaseColumn *lBaseColumn = noInverseLKeyCol.castToBaseColumn();
if(lBaseColumn && lBaseColumn->getNAColumn()->isComputedColumn())
return FALSE;
}
if (rKeyColIsDesc)
{
BaseColumn *rBaseColumn = noInverseRKeyCol.castToBaseColumn();
if(rBaseColumn && rBaseColumn->getNAColumn()->isComputedColumn())
return FALSE;
}
}
// Leading column of the left table sort key and the
// leading column of the right table sort key must be
// the same. If one is DESC, they must both be DESC.
if ((noInverseLKeyCol == noInverseRKeyCol) AND
(lKeyColIsDesc == rKeyColIsDesc))
return TRUE;
else
return FALSE;
}
// -----------------------------------------------------------------------
// ordersMatch method
// This method determines if the inner table sort key is in the same
// order as the outer table sort key. Only applicable when processing
// the right child of a nested join operator.
//
// INPUT PARAMS:
// ipp: The input physical properties
// indexDesc: The index descriptor of the access path
// innerOrder : the sort key for the access path
// charInputs: The characteristic inputs for this operator
// partiallyInOrderOK: TRUE if the order can be used even if the probes
// are not completely in order. This will be TRUE
// for read, update and delete, and FALSE for insert.
// OUTPUT PARAMS:
// probesForceSynchronousAccess: TRUE if the probes are completely
// in order across multiple partitions and the clustering key is
// the same as the partitioning key. FALSE otherwise.
// RETURN VALUE:
// TRUE if there is a match between the probes order and the
// table's (or index's) order. FALSE otherwise.
// -----------------------------------------------------------------------
NABoolean
ordersMatch(const InputPhysicalProperty* ipp,
const IndexDesc* indexDesc,
const ValueIdList* innerOrder,
const ValueIdSet& charInputs,
NABoolean partiallyInOrderOK,
NABoolean& probesForceSynchronousAccess,
NABoolean noCmpAssert)
{
ValueIdList innerOrderProbeCols;
// temporary var to keep column Ids without inverse for possible
// use to get UEC for this column from histograms
ValueIdList innerOrderProbeColsNoInv;
CollIndex numInOrderCols = 0;
NABoolean partiallyInOrder = FALSE;
NABoolean fullyInOrder = FALSE;
probesForceSynchronousAccess = FALSE;
if ((ipp != NULL) AND (!(ipp->getAssumeSortedForCosting())) AND
(!(ipp->getExplodedOcbJoinForCosting())))
{
// LCOV_EXCL_START :rfi
// Shouldn't have an ipp if there are no outer order columns!
if ((ipp->getNjOuterOrder() == NULL) OR
ipp->getNjOuterOrder()->isEmpty())
{
if (NOT noCmpAssert)
CCMPASSERT(FALSE);
return FALSE;
}
// An ipp should also have the outer expression partitioning function!
if (ipp->getNjOuterOrderPartFunc() == NULL)
{
if (NOT noCmpAssert)
CCMPASSERT(FALSE);
return FALSE;
}
// Should not have passed the ipp if this access path could not
// use the outer order, so there MUST be at least one sort key column!
if (innerOrder->isEmpty())
{
if (NOT noCmpAssert)
CCMPASSERT(FALSE);
return FALSE;
}
// LCOV_EXCL_STOP
// Get the physical partitioning function for the access path
const PartitioningFunction* physicalPartFunc =
indexDesc->getPartitioningFunction();
// If the outer order is not a DP2 sort order, and the access path
// is range partitioned, then need to check if the probes will force
// synchronous access to the partitions of this access path. The
// probes will force synchronous access if the leading partitioning
// key column is the same as the leading clustering key column.
if ((ipp->getNjDp2OuterOrderPartFunc() == NULL) AND
(physicalPartFunc != NULL)) // will be NULL for unpart tables
{
const RangePartitioningFunction* rpf =
physicalPartFunc->castToRangePartitioningFunction();
if (rpf != NULL)
{
ValueIdList partKeyAsList;
// Get the partitioning key as a list
partKeyAsList = rpf->getKeyColumnList();
CCMPASSERT(NOT partKeyAsList.isEmpty());
// Get the leading partitioning key column
ValueId leadingPartKeyCol = partKeyAsList[0];
// Get the leading clustering key column - remove any INVERSE node
ValueId leadingClustKeyCol = (*innerOrder)[0];
leadingClustKeyCol =
leadingClustKeyCol.getItemExpr()->removeInverseOrder()->getValueId();
if (leadingClustKeyCol == leadingPartKeyCol)
probesForceSynchronousAccess = TRUE;
} // end if a range partitioned table
} // end if not a DP2 sort order and a partitioned table
// Determine which columns of the index sort key are probe columns,
// up to the first column not covered by a constant or probe column.
// To do this we call the "findNJEquiJoinCols" method. The equijoin
// cols are the probe columns, i.e. the values coming from the outer
// child. For read these will be the equijoin columns, for write
// they are the key values of the records that need to be written.
//
// Note that for write, all the call to this method really does
// is eliminate any columns that are covered by constants or
// params/host vars. This is because all key columns will
// always be probe columns, since we always need all the key cols
// to accurately determine the record to insert/update/delete. So,
// we could just call the method "removeCoveredExprs" for write.
// This would require adding another parameter to the ordersMatch
// method to distinguish write from read. This is considered
// undesirable, and so is not done.
ValueIdList uncoveredCols;
innerOrderProbeCols =
innerOrder->findNJEquiJoinCols(
ipp->getNjOuterCharOutputs(),
charInputs,
uncoveredCols);
// There MUST be some probe columns, otherwise there should not
// have been an ipp!
// LCOV_EXCL_START :rfi
if (innerOrderProbeCols.isEmpty())
{
if (NOT noCmpAssert)
CCMPASSERT(FALSE);
return FALSE;
}
// LCOV_EXCL_STOP
ValueIdList njOuterOrder = *(ipp->getNjOuterOrder());
// Sol 10-040303-3781. The number of entries of innerOrderProbCols(5)
// could be greater than of njOuterOrder(3). In this case we hit ABORT
// in Collections.cpp for unused element of njOuterOrder. The
// following restriction on loop iteration prevents it. We need to
// investigate details why we got innerOredrProbCols bigger than
// njOuetrOredr iin the first place. That corresponding case will
// be created.
CollIndex maxInOrderCols =
MINOF(njOuterOrder.entries(), innerOrderProbeCols.entries());
fullyInOrder = TRUE;
// Determine if the leading inner order column and the leading
// column of the outer order are the same.
ValueId innerOrderCol;
ValueId noInverseInnerOrderCol;
NABoolean innerOrderColIsDesc = FALSE;
ValueId outerOrderCol;
ValueId noInverseOuterOrderCol;
NABoolean outerOrderColIsDesc = FALSE;
do
{
// Remove any inverse node on the inner order column
// and remember if there was one.
innerOrderCol = innerOrderProbeCols[numInOrderCols];
noInverseInnerOrderCol =
innerOrderCol.getItemExpr()->removeInverseOrder()->getValueId();
innerOrderProbeColsNoInv.insert(noInverseInnerOrderCol);
innerOrderColIsDesc = FALSE;
if (noInverseInnerOrderCol != innerOrderCol)
innerOrderColIsDesc = TRUE;
// Remove any inverse node on the leading outer order column
// and remember if there was one.
outerOrderCol = njOuterOrder[numInOrderCols];
noInverseOuterOrderCol =
outerOrderCol.getItemExpr()->removeInverseOrder()->getValueId();
outerOrderColIsDesc = FALSE;
if (noInverseOuterOrderCol != outerOrderCol)
outerOrderColIsDesc = TRUE;
// The column of the inner table sort key and the
// the column of the outer table sort key must be
// the same. If one is DESC, they must both be DESC.
if ((noInverseInnerOrderCol != noInverseOuterOrderCol) OR
(innerOrderColIsDesc != outerOrderColIsDesc))
fullyInOrder = FALSE;
else if (numInOrderCols == 0)
{
// The leading inner order column is in the same order as the
// leading outer order column, so the probes are at least
// partially in order. If all remaining inner order columns
// are in order then the probes will be completely in order.
partiallyInOrder = TRUE;
// If there are fewer inner order columns than outer order columns,
// then it is possible the probes are completely in order. Set
// the flag so we will continue looping to compare any
// remaining inner order columns.
//if (innerOrderProbeCols.entries() <= njOuterOrder.entries())
// fullyInOrder = TRUE;
}
numInOrderCols++; // advance to the next sortkey column, if any
} while ((numInOrderCols < maxInOrderCols) AND fullyInOrder);
// Since there is an ipp, the probes must be at least partially in the
// same order, because we checked this before passing the ipp!
if (NOT partiallyInOrder)
{
if (NOT noCmpAssert)
CCMPASSERT(FALSE); // LCOV_EXCL_LINE :rfi
return FALSE;
}
} // end if ipp exists
if (fullyInOrder)
{
return TRUE;
}
else if (partiallyInOrder AND partiallyInOrderOK)
{
// Compute the cardinality of the in-order inner order columns.
// This is the rowcount of the table divided by the unique entry
// count (uec) of the in-order columns, i.e. the total number
// of rows for each in-order column value. Divide this by the
// rows per block to arrive at the total number of blocks that
// might need to be read before all rows from a given block
// are read. If this number of blocks is smaller than the cache
// size, then we can cost this the same as the completely
// in order case. So return TRUE. Otherwise, we must cost the
// same as in the completely un-ordered case, so return FALSE.
// Get a list of colstats. Each colstats contains the
// histogram data for one column of the table.
const ColStatDescList& csdl =
indexDesc->getPrimaryTableDesc()->getTableColStats();
CollIndex baseTableColIndex;
CostScalar currentColUec;
CostScalar totalInOrderColsUec = csOne;
// We could get the rowcount from any of the columns, so just
// arbitrarily get it from column 0.
CostScalar rowcount = csdl[0]->getColStats()->getRowcount();
// Compute the total number of unique values for all the in-order
// key columns.
for (CollIndex keyColIndex = 0; keyColIndex < numInOrderCols; keyColIndex++)
{
// Sol 10-040303-3781. Previously we were retrieving columns from
// innerOrderProbeCols list. The column 0 of innerOrderProbCols had the
// type ITM_INVERSE. This type was not processed in getColStatDescForColumn,
// as a result baseTableColIndex was left uninitialized and we hit
// ABORT in Collections.cpp. Another change - if column cannot be found
// and getColStatDescIndexForColumn returns FALSE we should consider it
// as situation with m=not matching columns and return FALSE as we did
// already many times in this method.
if (NOT csdl.getColStatDescIndexForColumn(
baseTableColIndex, innerOrderProbeColsNoInv[keyColIndex])
)
{
// LCOV_EXCL_START :rfi
if (NOT noCmpAssert)
CCMPASSERT(FALSE);
return FALSE;
// LCOV_EXCL_STOP
}
currentColUec = csdl[baseTableColIndex]->getColStats()->getTotalUec();
totalInOrderColsUec = totalInOrderColsUec * currentColUec;
}
// Divide the rowcount by the total uec for the in-order cols to
// get the # of rows for each unique in-order key value.
CostScalar rowcountPerKeyValue =
(rowcount / totalInOrderColsUec).getCeiling();
CostScalar blockSizeInKb = indexDesc->getBlockSizeInKb();
// Get the # of blocks for each unique in-order key value.
CostScalar rowsPerBlock =
(blockSizeInKb / indexDesc->getRecordSizeInKb()).getFloor();
CostScalar blocksPerKeyValue =
(rowcountPerKeyValue / rowsPerBlock).getCeiling();
// Get the # of cache blocks available for this size of data blocks.
CostScalar cacheSizeInBlocks = getDP2CacheSizeInBlocks(blockSizeInKb);
// If the # of blocks for each unique in-order key value fits in
// cache, then we will never have to read any block twice. Return TRUE.
if (blocksPerKeyValue <= cacheSizeInBlocks)
return TRUE;
else
return FALSE;
} // end if partially in order
else
{
return FALSE;
}
} // ordersMatch()
// This should be a member of ItemExpr...
// -----------------------------------------------------------------------
// INPUT:
// columnId: A valueId that represents the column. It has to
// be either a VegRef, a base column, or a index column.
//
// equalityPredId: A valueid that represents the predicate
// it has to be either a VegPred or an Equality pred.
//
// OUTPUT:
// TRUE if the pred references the
// columnId in any of its operands.
//
// -----------------------------------------------------------------------
// LCOV_EXCL_START :cnu
static NABoolean
predReferencesColumn(const ItemExpr *predIEPtr
,const ValueId& columnId)
{
NABoolean itDoes = FALSE;
DCMPASSERT(predIEPtr->isAPredicate());
// If the columnId is a VEGRef expand it
// and prove that some of its members
// reference the VEGPred, if it is a join
// pred:
const ItemExpr *colIdIEPtr = columnId.getItemExpr();
ValueIdSet columnSet;
if (colIdIEPtr->getOperatorType()
==
ITM_VEG_REFERENCE)
{
const VEG *exprVEGPtr =
((VEGReference *)colIdIEPtr)->getVEG();
columnSet = exprVEGPtr->getAllValues();
}
else
{
// it must be a column:
columnSet.insert(columnId);
}
// This loop is only here because of the
// posibility of the columnId being
// a VEGReference, in which case
// we need to test each member of its
// VEGGroup for being referenced by the
// predicate:
for(ValueId colId=columnSet.init();
columnSet.next(colId);
columnSet.advance(colId))
{
if (predIEPtr->referencesTheGivenValue(colId))
{
// exit the loop:
itDoes = TRUE;
break;
}
} // for every referenced column
return itDoes;
}
// LCOV_EXCL_STOP
// -----------------------------------------------------------------------
// This method computes an upper bound for the total blocks in
// a table
// -----------------------------------------------------------------------
static void
computeBlocksUpperBound(
CostScalar &totalBlocksUpperBound /* out */
,const CostScalar& totalRows
,const CostScalar& recordsPerBlock)
{
totalBlocksUpperBound =
CostScalar( totalRows / recordsPerBlock).getCeiling();
}
// -----------------------------------------------------------------------
// This method computes how many subsets will be read
// -----------------------------------------------------------------------
static void
computeBeginBlocksLowerBound(
CostScalar &beginBlocksLowerBound /* out */
,const CostScalar& uniqueSuccDataRqsts
,const CostScalar& blocksUpperBound)
{
beginBlocksLowerBound =
CostScalar(MINOF(uniqueSuccDataRqsts.getCeiling().getValue(),
blocksUpperBound.getValue()));
}
// -----------------------------------------------------------------------
// This method computes the blocks to be read
// -----------------------------------------------------------------------
static void
computeTotalBlocksLowerBound(
CostScalar &totalBlocksLowerBound /* out */
,const CostScalar& uniqueSuccDataRqsts
,const CostScalar& rowsPerSuccRqst
,const CostScalar& recordsPerBlock
,const CostScalar& innerBlocksUpperBound
)
{
// -----------------------------------------------------------------------
// This formula was updated after the code review of Scan costing
// The 0.5 below (and the
// getFloor()) are added to compute the ROUND of the expression.
// -----------------------------------------------------------------------
totalBlocksLowerBound =
MINOF( ( uniqueSuccDataRqsts
+
(uniqueSuccDataRqsts
*
((rowsPerSuccRqst.getCeiling()-1)/recordsPerBlock)
+
CostScalar(0.5)).getFloor() ),
innerBlocksUpperBound );
}
// LCOV_EXCL_START :dpm
#ifndef NDEBUG
void
ScanOptimizer::printCostObject(const Cost * costPtr) const
{
if (costPtr)
{
// printf("Cost Vector: ");
// costPtr->print();
printf("Elapsed time: ");
ElapsedTime et =
costPtr->
convertToElapsedTime(getContext().
getReqdPhysicalProperty());
if (et <= 0.0)
{
FSOWARNING("negative or zero elapsed time");
}
else
{
printf("%f", et.getValue());
}
}
else
{
printf("NULL Cost");
}
printf("\n");
}
// LCOV_EXCL_STOP
#endif
//-------------------------------------------------------
// Methods for Histograms
//-----------------------------------------------------
// -----------------------------------------------------------------------
// Input:
// const TableDesc& tableDesc: the tableDesc for the base table
// associated with this Scan
// -----------------------------------------------------------------------
Histograms::Histograms( const ColStatDescList& colStatDescList )
:colStatDescList_(CmpCommon::statementHeap())
{
// -----------------------------------------------------------------------
// Create a list of Histogram for the
// columns of tableDesc
// -----------------------------------------------------------------------
// The following creates a deep copy of all the ScanHistograms
// associated with the table:
colStatDescList_.makeDeepCopy (colStatDescList) ;
} // Histograms(...)
void
Histograms::append(const ColStatDescSharedPtr& colStatDesc)
{
colStatDescList_.insertDeepCopyAt(entries(), colStatDesc);
}
Histograms::~Histograms()
{
// Delete every ColStatDescList that was inserted into the histogram list:
// colStatDescList_ should be responsible for this!
/*
for (CollIndex i=0; i <= entries(); i++)
{
delete colStatDescList_[i];
}
*/
}
CostScalar
Histograms::getRowCount() const
{
DCMPASSERT(getColStatDescList().entries() > 0);
return getColStatDescList()[0]->getColStats()->getRowcount();
}
const ColStatDescList&
Histograms::getColStatDescList() const
{
return colStatDescList_;
}
NABoolean
Histograms::containsAtLeastOneFake() const
{
return colStatDescList_.containsAtLeastOneFake();
} // containsAtLeastOneFake()
// LCOV_EXCL_START :cnu
NABoolean
Histograms::getColStatDescForColumn(CollIndex index,
const ValueId& column) const
{
return getColStatDescList().getColStatDescIndexForColumn(index,column);
}
// LCOV_EXCL_STOP
const ColStats&
Histograms::getColStatsForColumn(const ValueId& column) const
{
ColStatsSharedPtr colStatsPtr =
getColStatDescList().getColStatsPtrForColumn(column);
// There must be statistics for ALL columns
// (even though some of them may contain fake histograms)
DCMPASSERT(colStatsPtr != NULL);
return *colStatsPtr;
}// getColStatsForColumn(...)
CostScalar Histograms::getUecCountForColumns(const ValueIdSet& key) const
{
if(key.entries() >1)
if ( getColStatDescList().getUecList() )
return getColStatDescList().getUecList()->lookup(key);
else
return csZero;
else
{
ValueId vid;
key.getFirst(vid);
return getColStatsForColumn(vid).getTotalUec().getCeiling();
}
}
ColStatsSharedPtr
Histograms::getColStatsPtrForColumn(const ValueId& column) const
{
// $$$ This was added to cover the case when
// $$$ no col stat is available. This is true
// $$$ today sometimes due to prototype code...
ColStatsSharedPtr colStatsPtr =
getColStatDescList().getColStatsPtrForColumn(column);
return colStatsPtr;
}// getColStatsForColumn(...)
// LCOV_EXCL_START :cnu
void
Histograms::displayHistogramForColumn(const ValueId& column) const
{
getColStatsForColumn(column).display();
}// getColStatsForColumn(...)
// LCOV_EXCL_STOP
void
Histograms::applyPredicates(const ValueIdSet& predicates,
const RelExpr & scan,
const SelectivityHint * selHint,
const CardinalityHint * cardHint,
OperatorTypeEnum opType
)
{
CostScalar initialRowCount = getRowCount();
// No need to apply preds to empty histograms:
if (initialRowCount.isGreaterThanZero())
{
ValueIdSet outerRefs;
CollIndex numOuterColStats = 0;
ValueIdSet unresolvedPreds;
colStatDescList_.estimateCardinality(initialRowCount,
predicates,
outerRefs,
NULL,
selHint,
cardHint,
numOuterColStats,
unresolvedPreds,
INNER_JOIN_MERGE,
opType,
NULL);
}
// check to see if the rowcount computed from histogram lies within
// the range obtained after executing predicates on a sample
// do that only if no hint provided
if ((scan.getOperatorType() == REL_SCAN) && !cardHint && !selHint)
{
NABoolean flag = ((Scan &)scan).checkForCardRange(predicates, initialRowCount);
}
}// applyPredicates(...)
// -----------------------------------------------------------------------
// The following method is based on method
// RelExpr::synthEstLogPropForUnaryLeafOp (...)
// which is on file OptLogRelExpr.cpp
//
// -----------------------------------------------------------------------
void
Histograms::applyPredicatesWhenMultipleProbes(
const ValueIdSet& predicates
,const EstLogProp& inputEstLogProp
,const ValueIdSet& inputValues
,const NABoolean isMDAM
,const SelectivityHint * selHint
,const CardinalityHint * cardHint
,NABoolean *indexJoin
,OperatorTypeEnum opType
)
{
CostScalar innerRowCount = getRowCount();
MergeType mergeMethod = INNER_JOIN_MERGE; // starting assumption
// -----------------------------------------------------------------
// First, determine if we seem to be in an index-driven nested join.
// If so, remove base table column statistics that are
// provided as outer references from the index table.
// -----------------------------------------------------------------
const ColStatDescList &outerColStatsList = inputEstLogProp.getColStats();
ColStatDescList &innerColStatsList = colStatDescList_;
CollIndex outerRefCount = outerColStatsList.entries();
NAList<CollIndex> innerIndexColumnsToRemove(CmpCommon::statementHeap());
NABoolean indexNestedJoin = FALSE;
if ( NOT isMDAM
AND
(indexNestedJoin=
isAnIndexJoin(inputEstLogProp, &innerIndexColumnsToRemove)) )
{
DCMPASSERT(innerIndexColumnsToRemove.entries() > 0);
for (CollIndex i=0; i < innerIndexColumnsToRemove.entries(); i++)
{
innerColStatsList.removeAt(i);
}
}
if (indexJoin != NULL)
*indexJoin = indexNestedJoin;
// -----------------------------------------------------------------
// Second, pre-pend the outer reference column statistics onto the
// [remaining] child's column statistics.
// -----------------------------------------------------------------
CostScalar innerScale;
if (indexNestedJoin OR inputEstLogProp.getInputForSemiTSJ())
{
innerScale = 1.;
}
else
{
innerScale = innerRowCount;
}
innerColStatsList.prependDeepCopy( outerColStatsList, // source
outerRefCount, // limit
innerScale ); // scale
if (entries() > 0)
innerRowCount = ((*this)[0]).getColStats()->getRowcount();
// -----------------------------------------------------------------
// Third, scale the original entries in the child's column stats
// The actual determination of how this is done is based on knowing
// whether or not this is an index-driven nested join.....
// I.e., if it's an index-based NJ, then both RowCount and UEC of
// the 'base' table are scaled; if it's not index-based, then the
// scaling depends upon whether or not this scan is the right child
// of a semi-TSJ. If not scaling is done as with join cross-products,
// otherwise the right side isn't scaled.
// -----------------------------------------------------------------
CostScalar outerScale = 1;
if (indexNestedJoin)
{
// ColStatDesc's from 0 up to outerRefCount came from outer,
// so no need to synchronize them (why?)
for (CollIndex i = outerRefCount; i < entries(); i++)
{
ColStatDescSharedPtr columnStatDesc = innerColStatsList[i];
ColStatsSharedPtr columnStats = columnStatDesc->getColStatsToModify();
columnStats->copyAndScaleHistogram ( 1 );
CostScalar oldCount =
columnStatDesc->getColStats()->getRowcount();
if (oldCount != innerRowCount)
columnStatDesc->synchronizeStats (oldCount, innerRowCount);
}
}
else // not an indexNestedJoin
{
if (outerRefCount > 0)
{
// Either the method for doing the join, or the scale factor of
// the remaining columns in the StatDescList, must be updated.
if ( inputEstLogProp.getInputForSemiTSJ() )
mergeMethod = SEMI_JOIN_MERGE;
else
outerScale = outerColStatsList[0]->getColStats()->getRowcount();
}
for (CollIndex i = outerRefCount; i < entries(); i++)
{
ColStatDescSharedPtr columnStatDesc = innerColStatsList[i];
ColStatsSharedPtr columnStats = columnStatDesc->getColStatsToModify();
columnStats->copyAndScaleHistogram( outerScale );
}
}
ValueIdSet outerReferences;
// Get the set of outer references. If input value is a VEGReference,then
// include this VEGReference only if the group consists of no constants.
if (inputValues.entries() > 0)
inputValues.getOuterReferences (outerReferences);
ValueIdSet dummySet;
// Apply the effect of my selection predicates on columnStats,
// returning the resultant rowcount.
innerRowCount =
innerColStatsList.estimateCardinality(innerRowCount, /*in*/
predicates,/*in*/
outerReferences, /*in*/
NULL,
selHint, /*in*/
cardHint, /*in*/
outerRefCount, /*in/out*/
dummySet,
/*in/out*/
mergeMethod, /*in*/
opType,
NULL);
} // Histograms::applyPredicatesWhenMultipleProbes(...)
// LCOV_EXCL_START :cnu
void
Histograms::applyPredicate(const ValueId& predicate,
const RelExpr & scan,
const SelectivityHint * selHint,
const CardinalityHint * cardHint,
OperatorTypeEnum opType
)
{
ValueIdSet vis;
vis.insert(predicate);
applyPredicates(vis, scan, selHint, cardHint, opType);
} // applyPredicate(...)
// LCOV_EXCL_STOP
NABoolean
Histograms::isAnIndexJoin(const EstLogProp& inputEstLogProp
,LIST(CollIndex) *innerIndexColumnListPtr) const
{
// --------------------------------------------------------------------
// We are in an index join if
// - We are receiving multiple probes
// AND
// -- one of the outer column statistics is the same as one of
// the inner statistics
// --------------------------------------------------------------------
NABoolean indexNestedJoin = FALSE;
if ( (inputEstLogProp.getResultCardinality().isGreaterThanZero())
AND NOT inputEstLogProp.getColStats().isEmpty() )
{
const ColStatDescList &outerColStatsList =
inputEstLogProp.getColStats();
const ColStatDescList& innerColStatsList = colStatDescList_;
ColStatDescSharedPtr innerColumnStatDesc;
for (CollIndex i = 0; i < outerColStatsList.entries(); i++)
{
ColStatDescSharedPtr outerStatDesc = outerColStatsList[i];
for (CollIndex j=0; j < innerColStatsList.entries(); j++)
{
innerColumnStatDesc = innerColStatsList[j];
if (outerStatDesc->getMergeState().contains(
innerColumnStatDesc->getMergeState()) )
{
indexNestedJoin = TRUE;
if (innerIndexColumnListPtr != NULL)
{
innerIndexColumnListPtr->insert(i);
}
else
{
// All we want is to know whether we are in an index
// join, thus break early
break;
}
}
} // for every inner column
if ( (innerIndexColumnListPtr != NULL)
AND
indexNestedJoin )
{
// All we want is to know whether we are in an index join,
// thus break early
break;
}
} // for every outer column
} // if we are receiving multiple probes
return indexNestedJoin;
} // Histograms::isAnIndexJoin() const
// LCOV_EXCL_START :dpm
void
Histograms::display() const
{
print();
}// display()
void
Histograms::print (FILE *ofd,
const char * prefix,
const char * suffix) const
{
#ifndef NDEBUG
ValueIdList emptySelectList;
getColStatDescList().print(emptySelectList) ;
#endif
}// print()
// LCOV_EXCL_STOP
//-------------------------------------------------------
// Methods for IndexDescHistograms
//-----------------------------------------------------
// -----------------------------------------------------------------------
// Input:
// const IndexDesc& indexDesc: the indexDesc for the base table
// associated with this Scan. This index desc is stored through
// a reference, thus it must survive as long this instance is alive
// ("association" relationship)
//
// columnPosition: a one (1) based position of the column. The first
// position is one (1).
// If columnPosition is cero, then an empty IndexDescHistogram is
// created.
// -----------------------------------------------------------------------
IndexDescHistograms::IndexDescHistograms(const IndexDesc& indexDesc,
const CollIndex columnPosition):
indexDesc_(indexDesc)
{
const ValueIdList &keyColumns = indexDesc.getIndexKey();
// only add the ColStatDesc's for the index columns UP to
// the column position: (for SQL/MP secondary indexes,
// ignore the keytag column, which is the first keyColumn)
CollIndex localColPosition = columnPosition;
CollIndex columnIndex = 1;
for (CollIndex i=columnIndex; i <= localColPosition; i++)
{
appendHistogramForColumnPosition(i);
} // for every key column
} // IndexDescHistograms::IndexDescHistograms(const IndexDesc& indexDesc)
void
IndexDescHistograms::appendHistogramForColumnPosition(
const CollIndex& columnPosition)
{
// columnPosition == 0 means an empty colstatdesclist
if (columnPosition > 0)
{
// Get the ColStatDescList for the base table:
DCMPASSERT(getIndexDesc().getPrimaryTableDesc());
const ColStatDescList &primaryTableCSDL =
getIndexDesc().getPrimaryTableDesc()->getTableColStats();
const ValueIdList &keyColumns = getIndexDesc().getIndexKey();
DCMPASSERT(columnPosition > 0
AND
columnPosition <= keyColumns.entries());
CollIndex j;
// remember that columnPosition is one based:
if (primaryTableCSDL.
getColStatDescIndexForColumn(j, keyColumns[columnPosition-1]))
{
// There could be a single colStatDesc for two columns,
// thus check whether it was already inserted:
ValueId column = primaryTableCSDL[j]->getColumn();
if (NOT contains(column))
{
append(primaryTableCSDL[j]);
setCSDLUecList (primaryTableCSDL.getUecList()) ;
// synchronize with existing histograms:
if (entries() > 1)
{
ColStatDesc& lastHistogram =
*getColStatDescList()[entries()-1];
const ColStatDesc& previousToLastHistogram =
*getColStatDescList()[entries()-2];
lastHistogram.
synchronizeStats(lastHistogram.
getColStats()->getRowcount(),
previousToLastHistogram.
getColStats()->getRowcount());
}
}
}
else
{
// There must be a ColStatDesc for every key column!
CMPABORT; // LCOV_EXCL_LINE :rfi
}
// propagate all base-table multi-col uec info : easiest way
// to do it, and there's no reason not to point to the complete
// list
setCSDLUecList (primaryTableCSDL.getUecList()) ;
} // columnPosition > 0
} // IndexDescHistograms::appendHistogramForColumnPosition(...)
// returns true if we have multicolumnuec information available,
// otherwise false.
NABoolean IndexDescHistograms::isMultiColUecInfoAvail() const
{
return getIndexDesc().getPrimaryTableDesc()->
getTableColStats().getUecList()->containsMCinfo();
}
//-------------------------------------------------------------------
//This function is used to get a better estimate of how many uec's
//that need to be skipped by MDAM for a specific column.
//It can compute this number there is multi column uec information for
//the columns till the column under consideration. Then it needs
//uec/multi-col uec information of some preceding columns.
//Example : Columns a, b, c, d . We are trying to compute the estimated
//uec for d
//Answer: can be multicolUec for ((a and/or b and/or c) and d)
// ----------------------------
// multicolUec for a and/or b and/or c(best we can do is a,b,c)
//
//if the denominator is a,b,c then the numerator must be a,b,c,d
// we must have comparable sets as numerator and denominator
//Input: columnOrderList which has the columnIdList for the index/table
// indexOfcolum in the order list that we have to compute uec info for
//Output: estimateduec for the column and true as return value
// if there isn't enough information then we return false.
//-----------------------------------------------------------------------------
NABoolean
IndexDescHistograms::estimateUecUsingMultiColUec(
const ColumnOrderList& keyPredsByCol,/*in*/
const CollIndex& indexOfColumn,/*in*/
CostScalar& estimatedUec/*out*/)
{
ValueIdList columnList= keyPredsByCol.getColumnList();
// LCOV_EXCL_START :dpm
#ifndef NDEBUG
if(getenv("MDAM_MCUEC")){
fprintf(stdout,"\n\n---columnList before reduction \n\n----");
columnList.print();
}
#endif
// LCOV_EXCL_STOP
//remove everything beyond the column under consideration
for ( CollIndex i = keyPredsByCol.entries()-1; i>indexOfColumn; i--){
columnList.removeAt(i);
}
ValueIdSet columnSet(columnList);
NAList<CostScalar> uecCount(CmpCommon::statementHeap());
const MultiColumnUecList * MCUL =
getIndexDesc().getPrimaryTableDesc()->getTableColStats().getUecList();
// LCOV_EXCL_START :dpm
#ifndef NDEBUG
if(getenv("MDAM_MCUEC")){
fprintf(stdout,"\n\n---columnList for Index[%d]: \n", indexOfColumn);
columnList.print();
fprintf(stdout, "\n\n---List of MCUEC info.----\n\n");
MCUL->print();
}
#endif
// LCOV_EXCL_STOP
//get all the valueIdSets that contains the column and columns from the
//columnList only
LIST(ValueIdSet) * listOfSubsets=
MCUL->getListOfSubsetsContainsColumns(columnList,uecCount);
if(listOfSubsets->entries()==0) return FALSE;
// LCOV_EXCL_START :dpm
#ifndef NDEBUG
if(getenv("MDAM_MCUEC")){
fprintf(stdout,"\n\n---Got my value ID list-----\n\n");
for(CollIndex k=0;k<listOfSubsets->entries();k++){
fprintf(stdout,"\n\n----Set no.[%d]:----\n\n",k);
(*listOfSubsets)[k].print();
}
}
#endif
// LCOV_EXCL_STOP
//Trying to find the matching denominator for the numerator
CostScalar uecWithoutColumn=0;
CollIndex entriesInSubset=0;
NABoolean perfectDenom = FALSE;
ValueIdSet vidSet;
CollIndex indexForUecCount=0;
ValueId idInBaseCol;
//corresponding valueId in the base table for this column
//this is necessary because multicolUec lists are always stored
//in base table valueIds.
idInBaseCol=((BaseColumn *)((IndexColumn *)
(columnList[indexOfColumn].getItemExpr()))
->getDefinition().getItemExpr())->getValueId();
// It is not clear to me how the above formual to compute MCUEC works.
// But, it's very clear it didn't compute the correct MCUEC for the query.
// In this case the keyCols were (A,B,C,D) and we wanted MCUEC(A,B). UEC(A) = 250K
// and UEC(B) = 50Mil, but there existed a single Interval for MC(A,B) with 55Mil UEC.
// Instead of returning 55mil UEC, the above formula MC(A,B)/MC(A) returned 221
// (55727500/252136 -> 221). The new code below is similar to how we compute MCUEC in the
// rest of the compiler code.
for (CollIndex j=0; j<listOfSubsets->entries();j++)
{
vidSet = (*listOfSubsets)[j];
#ifndef NDEBUG
if(getenv("MDAM_MCUEC"))
{
fprintf(stdout, "Now checkout this set\n");
vidSet.print();
}
#endif
if (vidSet.entries() == columnList.entries())
{
estimatedUec = MCUL->lookup(vidSet);
#ifndef NDEBUG
if(getenv("MDAM_MCUEC"))
{
fprintf(stdout, "entry size matches\n");
fprintf(stdout, "MC UEC=%f\n", estimatedUec.value());
}
#endif
return TRUE;
}
}
return FALSE;
// The following code will never be exercised and it's intentional.
for (CollIndex j=0; j<listOfSubsets->entries();j++)
{
vidSet = (*listOfSubsets)[j];
//remove the column in question then see
//if we can find a match for the denominator
//To find the denominator we remove the last column from the
//vidSet(numerator), this gives us the prefix of the numerator
//or in other words the denominator.
vidSet.remove(idInBaseCol);
//Do we have a matching denominator
perfectDenom = MCUL->findDenom(vidSet);
// LCOV_EXCL_START :dpm
#ifndef NDEBUG
if(getenv("MDAM_MCUEC"))
{
fprintf(stdout, "largest subset returned\n\n");
vidSet.print();
}
#endif
// LCOV_EXCL_STOP
//choose the MC uecs with the most entries so for col d , best would
// be abcd/abc even if you have abd/ab. But if there are two of same
// entries select the one with higher uec count for the denominator.
if(perfectDenom AND (vidSet.entries()>entriesInSubset OR
(vidSet.entries()==entriesInSubset AND
MCUL->lookup(vidSet) > uecWithoutColumn)))
{
perfectDenom = FALSE;
entriesInSubset=vidSet.entries();
uecWithoutColumn = MCUL->lookup(vidSet);
indexForUecCount=j;
//best possible situation for d
//we have found a,b,c,d/a,b,c;
if(entriesInSubset==indexOfColumn) break;
}
}
if(entriesInSubset>0)
{
estimatedUec=uecCount[indexForUecCount]/uecWithoutColumn;
return TRUE;
}
return FALSE;
}
// -----------------------------------------------------------------------
// Use this function on a ScanHistograms instance to find the
// number of probes that fail to match with the scan's rows.
//
// IMPORTANT: It is assummed that this ScanHistograms are the
// result of the cross product of the outer table's ScanHistograms
// with that of the inner table's ScanHistograms and that
// appropiate key predicates have been applied.
//
// INPUT:
// outerHistograms the ScanHistograms for the probes
// keyPreds: the key predicates associated with the scan
// RETURN:
// A CostScalar >= 0 that denotes the number of probes that failed
// to match any data for the inner scan under a nested join.
// -----------------------------------------------------------------------
CostScalar
IndexDescHistograms::computeFailedProbes(const Histograms& outerHistograms
,const ValueIdSet& keyPreds
,const ValueIdSet& inputValues
,const ValueIdSet& operatorValues
) const
{
CostScalar
maxFailedProbes = csZero;
// match histograms for corresponding columns
// in joined histograms to find out the number
// of probes that match from each, then find
// the max:
ColStatsSharedPtr outerColStats;
ColStatsSharedPtr joinedColStats;
CostScalar
currentFailedProbes = 0;
for (ValueId keyPred = keyPreds.init();
keyPreds.next(keyPred);
keyPreds.advance(keyPred))
{
if ( keyPred.getItemExpr()->
isANestedJoinPredicate(inputValues,
operatorValues) )
{
// Find out if current predicate is a joined predicate,
// which it is when the predicate refers to a column in
// the outer statistics.
outerColStats =
outerHistograms.getColStatsPtrForPredicate(keyPred);
if (outerColStats !=NULL)
{
// It is a joined predicate!
// find the joined hist. for the predicate:
joinedColStats =
this->getColStatsPtrForPredicate(keyPred);
DCMPASSERT(joinedColStats != NULL);
// find the failed probes for these histograms:
currentFailedProbes =
outerColStats->countFailedProbes(joinedColStats);
}
else
{
// should never be here:
#ifndef _DEBUG
FSOWARNING("outerColStats is NULL");
#endif
currentFailedProbes = csZero;
}
if (currentFailedProbes > maxFailedProbes)
{
maxFailedProbes = currentFailedProbes;
}
} // only if it is a join pred
}// for all predicates
// maxFailedProbes contain the failed probes for that
// predicate with the most failed probes, and this is
// what we need:
//if (maxFailedProbes < 0)
// {
// FSOWARNING("maxFailedPorbes < 0");
// maxFailedProbes =0.;
// }
maxFailedProbes.minCsZero();
return maxFailedProbes;
} // IndexDescHistograms::computeFailedProbes(...)
//-------------------------------------------------------
// Methods for ScanOptimizer:
//-----------------------------------------------------
ScanOptimizer::ScanOptimizer(const FileScan& associatedFileScan
,const CostScalar& resultSetCardinality
,const Context& myContext
,const ValueIdSet& externalInputs) :
probes_(0)
,uniqueProbes_(0)
,successfulProbes_(0)
,duplicateSuccProbes_(0)
,fileScan_(associatedFileScan)
,resultSetCardinality_(resultSetCardinality)
,context_(myContext)
,externalInputs_(externalInputs)
,numberOfBlocksToReadPerAccess_(-1)
,estRowsAccessed_(0)
,inOrderProbes_(FALSE)
,probesForceSynchronousAccess_(FALSE)
,tuplesProcessed_(0)
{
}
ScanOptimizer::~ScanOptimizer()
{
};
// Determine if the SimpleFileScanOptimizer can be used for the given
// scan with the given context.
// Return : TRUE - Use the SimpleFileScanOptimizer
// FALSE - Don't use the SimpleFileScanOptimizer
//
NABoolean
ScanOptimizer::useSimpleFileScanOptimizer(const FileScan& associatedFileScan
,const Context& myContext
,const ValueIdSet &externalInputs)
{
#ifndef NDEBUG
if (CmpCommon::getDefault(MDAM_TRACING) == DF_ON )
MdamTrace::setLevel(MTL2);
//MdamTrace::setLevel(MDAM_TRACE_LEVEL_ALL);
#endif
//
NADefaults &defs = ActiveSchemaDB()->getDefaults();
ULng32 fsoToUse = defs.getAsULong(FSO_TO_USE);
// fsoToUse -
// IF 0 - Use original "Complex" File Scan Optimizer. (Default)
// IF 1 - Use logic below to determine FSO to use.
// IF 2 - Use logic below to determine FSO to use, but also use new
// executor predicate costing.
// IF >2 - Always use new "Simple" File Scan Optimizer.
// Is the complex FSO forced;
//
if(fsoToUse == 0)
{
return FALSE;
}
// Is the simple FSO forced;
//
if(fsoToUse > 2)
{
return TRUE;
}
// Milestone 3
//
//- Simple single-subset scans
//- with or without predicates
//- With or without partKeyPreds for logical SubPartitioning.
//- MDAM will not be considered
//
//- with or without predicates
//
ValueIdSet vs;
ValueIdSet exePreds(associatedFileScan.getSelectionPred());
if((CmpCommon::getDefault(RANGESPEC_TRANSFORMATION) == DF_ON ) &&
exePreds.entries())
{
ItemExpr *inputItemExprTree = exePreds.rebuildExprTree(ITM_AND,FALSE,FALSE);
ItemExpr* resultOld = revertBackToOldTree(CmpCommon::statementHeap(),
inputItemExprTree);
exePreds.clear();
resultOld->convertToValueIdSet(exePreds, NULL, ITM_AND, FALSE);
doNotReplaceAnItemExpressionForLikePredicates(resultOld,exePreds,resultOld);
}
const Disjuncts *curDisjuncts = &(associatedFileScan.getDisjuncts());
//- With or without partKeyPreds for logical SubPartitioning.
//
const LogPhysPartitioningFunction *logPhysPartFunc =
myContext.getPlan()->getPhysicalProperty()->getPartitioningFunction()->
castToLogPhysPartitioningFunction();
ValueIdSet partKeyPreds;
if ( logPhysPartFunc != NULL )
{
LogPhysPartitioningFunction::logPartType
logPartType = logPhysPartFunc->getLogPartType();
if (logPartType == LogPhysPartitioningFunction::LOGICAL_SUBPARTITIONING ||
logPartType == LogPhysPartitioningFunction::HORIZONTAL_PARTITION_SLICING)
{
partKeyPreds = logPhysPartFunc->getPartitioningKeyPredicates();
exePreds += partKeyPreds;
curDisjuncts = new HEAP MaterialDisjuncts(exePreds);
}
}
// check if MDAM can be considered
switch(getMdamStatus(associatedFileScan, myContext))
{
case ScanForceWildCard::MDAM_OFF:
// If MDAM is forced OFF, don't bother checking executor preds for
// possibility of MDAM, just use the simple file scan optimizer.
//
return TRUE;
case ScanForceWildCard::MDAM_FORCED:
// If MDAM is forced ON, must consider MDAM so don't use simple
// file scan optimizer.
//
return FALSE;
default:
// If MDAM is enabled (not forced on or off), continue on to
// examine the selection preds to see if MDAM should be
// considered.
break;
}
ValueIdSet nonKeyColumnSet;
const IndexDesc *indexDesc = associatedFileScan.getIndexDesc();
indexDesc->getNonKeyColumnSet(nonKeyColumnSet);
const ValueIdList indexKey = indexDesc->getIndexKey();
IndexDescHistograms histograms(*indexDesc,indexKey.entries());
// HBASE work. Remove after stats for Hbase is done!!!
NABoolean isHbaseTable = indexDesc->getPrimaryTableDesc()->getNATable()->isHbaseTable();
if (! isHbaseTable && histograms.containsAtLeastOneFake())
{
MDAM_DEBUG0(MTL2, "Fake histogram found, MDAM NOT considered.");
MDAM_DEBUGX(MTL2, histograms.display());
return TRUE;
}
else
{
const Disjuncts &disjuncts = associatedFileScan.getDisjuncts();
if (disjuncts.entries() > MDAM_MAX_NUM_DISJUNCTS)
{
// There are too many disjuncts. MDAM cannot be considered
return TRUE;
}
else
{
// Since there is an index join, we cannot consider MDAM
if(histograms.isAnIndexJoin(*(myContext.getInputLogProp())) )
return TRUE;
}
}
//- Simple single-subset scans
//
if (exePreds.entries() > 0)
{
SearchKey searchKey(indexDesc->getIndexKey()
,indexDesc->getOrderOfKeyValues()
,externalInputs
,(NOT associatedFileScan.getReverseScan())
,exePreds
,*curDisjuncts
,nonKeyColumnSet
,indexDesc
);
if ((! searchKey.isUnique()) &&
(searchKey.getKeyPredicates().entries() > 0))
{
ColumnOrderList keyPredsByCol(indexDesc->getIndexKey());
searchKey.getKeyPredicatesByColumn(keyPredsByCol);
CollIndex singleSubsetPrefixColumn;
NABoolean itIsSingleSubset =
keyPredsByCol.getSingleSubsetOrder(singleSubsetPrefixColumn);
ValueIdSet singleSubsetPreds;
for (CollIndex pred = 0; pred <= singleSubsetPrefixColumn; pred++)
{
const ValueIdSet *preds = keyPredsByCol[pred];
if(preds)
{
singleSubsetPreds += *preds;
}
}
// Not a simple single subset. Could not use any key preds for a
// single subset, must check if MDAM might be better
//
if (singleSubsetPreds.isEmpty())
{
return FALSE;
}
// If not all the keys are covered by the key preds and there
// are multiple disjuncts, must consider MDAM.
//
if (CmpCommon::getDefault(RANGESPEC_TRANSFORMATION) == DF_ON )
{
if ((singleSubsetPrefixColumn+1 < keyPredsByCol.entries()) &&
(curDisjuncts->entries() >= 1) && curDisjuncts->containsAndorOrPredsInRanges())
{
return FALSE;
}
}
else
{
if ((singleSubsetPrefixColumn+1 < keyPredsByCol.entries()) &&
(curDisjuncts->entries() > 1))
{
return FALSE;
}
}
}
// If there are executor preds that could not be used as key predicates
// and there are multiple disjuncts..
if (CmpCommon::getDefault(RANGESPEC_TRANSFORMATION) == DF_ON )
{
if ((searchKey.getKeyPredicates().entries() == 0) &&
(curDisjuncts->entries() >= 1) && curDisjuncts->containsAndorOrPredsInRanges())
{
return FALSE;
}
}
else
{
if ((searchKey.getKeyPredicates().entries() == 0) &&
(curDisjuncts->entries() > 1))
{
return FALSE;
}
}
}
// Use SimpleFileScanOptimizer
//
return TRUE;
}
NABoolean ScanOptimizer::canStillConsiderMDAM(const ValueIdSet partKeyPreds,
const ValueIdSet nonKeyColumnSet,
const Disjuncts &curDisjuncts,
const IndexDesc * indexDesc,
const ValueIdSet externalInputs,
NABoolean mdamFlag)
{
NABoolean canDoMdam = TRUE;
const ValueIdList indexKey = indexDesc->getIndexKey();
// -----------------------------------------------------------------------
// Check whether MDAM can be considered for this node:
// -----------------------------------------------------------------------
if(CURRSTMT_OPTDEFAULTS->indexEliminationLevel() != OptDefaults::MINIMUM
AND mdamFlag == MDAM_OFF)
canDoMdam = FALSE;
else
if (NOT indexDesc->getNAFileSet()->isKeySequenced())
{
// -----------------------------------------------------------
// Don't consider MDAM for relative and entry-sequence files:
// -----------------------------------------------------------
canDoMdam = FALSE;
MDAM_DEBUG0(MTL2, "File is not key sequenced, MDAM NOT considered.");
}
else
{
// -----------------------------------------------------------
// Don't consider MDAM when one of the disjunct does
// not contain key predicates:
// -----------------------------------------------------------
// If at least one of the disjunts in the mdam key does not contain
// key predicates then using MDAM results in a full table
// scan, thus don't bother to consider it in this case:
CollIndex i = 0;
MdamKey mdamKey(indexKey
,externalInputs
,curDisjuncts
,nonKeyColumnSet
,indexDesc
);
for (i=0; i < mdamKey.getKeyDisjunctEntries(); i++)
{
ColumnOrderList keyPredsByCol(indexKey);
mdamKey.getKeyPredicatesByColumn(keyPredsByCol,i);
ValueIdSet preds;
keyPredsByCol.getAllPredicates(preds);
if (preds.isEmpty())
{
// This disjunct does not have key preds,
// don't consider MDAM
canDoMdam = FALSE;
MDAM_DEBUG1(MTL2,
"disjunct[%d] has no key predicate, MDAM NOT considered", i);
break;
}
}
}
return canDoMdam;
}
ScanOptimizer *
ScanOptimizer::getScanOptimizer(const FileScan& associatedFileScan
,const CostScalar& resultSetCardinality
,const Context& myContext
,const ValueIdSet &externalInputs
,CollHeap* heap)
{
#ifndef NDEBUG
NADefaults &defs = ActiveSchemaDB()->getDefaults();
if(defs.getAsULong(FSO_RUN_TESTS) > 0) {
// Run all Scan Optimizer Tests before constructing one
//
ScanOptimizerAllTests(associatedFileScan
,resultSetCardinality
,myContext
,externalInputs
,heap);
}
#endif
if (useSimpleFileScanOptimizer(associatedFileScan,
myContext,
externalInputs))
{
return new(heap) SimpleFileScanOptimizer(associatedFileScan,
resultSetCardinality,
myContext,
externalInputs);
}
else
{
return new(heap) FileScanOptimizer(associatedFileScan,
resultSetCardinality,
myContext,
externalInputs);
}
}
// Checks for overflow before setting the numberOfBlocksToReadPerAccess_.
// If overflow, set to INT_MAX
//
void
ScanOptimizer::setNumberOfBlocksToReadPerAccess(const CostScalar& blocks)
{
//overflow occuring while casting it to long
//the below is a check to avoid the overflow
//CR 10-010815-4585
const double val = blocks.getValue();
if(val < double(INT_MAX)) {
Lng32 lval = Lng32(val);
DCMPASSERT(lval > -1);
numberOfBlocksToReadPerAccess_ = lval;
}
else
numberOfBlocksToReadPerAccess_ = INT_MAX;
}
CollIndex ScanOptimizer::getNumActivePartitions() const
{
// This is a patch to fix plan cgange in TPCC Q7 during Neo_Compiler
// integration. Since we try to use estimated number of AP in costing
// we need to be consistent and use it everywhere in costing. Hopefully
// it won't break anything to define correcttly the number of PAs.
// All active partition usage need a separate project to clean it up.
if ( CmpCommon::getDefault(COMP_BOOL_32) == DF_OFF )
{
return getEstNumActivePartitionsAtRuntime();
}
PartitioningFunction *pf =
getContext().getPlan()->getPhysicalProperty()->getPartitioningFunction();
DCMPASSERT(pf != NULL);
// All this casting away is because mutable is not supported, thuse NodeMap has
// non-mutable cached members....
CollIndex actParts = ((NodeMap *)(pf->getNodeMap()))->getNumActivePartitions();
return actParts;
}
CollIndex ScanOptimizer::getEstNumActivePartitionsAtRuntime() const
{
PartitioningFunction *pf =
getContext().getPlan()->getPhysicalProperty()->getPartitioningFunction();
DCMPASSERT(pf != NULL);
CollIndex actParts = ((NodeMap *)(pf->getNodeMap()))->getNumActivePartitions();
//Check if the partitioning function is of type LOGPHYS_PARTITIONING_FUNCTION.
//If we have this node then we are sure that this partitioning function will
//have both logical and physical partitioning function.
if(pf->isALogPhysPartitioningFunction())
{
PartitioningFunction *phyPartFunc = NULL;
phyPartFunc = ((LogPhysPartitioningFunction *)pf)->getPhysPartitioningFunction();
if(phyPartFunc != NULL )
{
//We use the estimated active partitions only for range and hash
//partitioning functions.
if ((phyPartFunc->isARangePartitioningFunction())
OR
(phyPartFunc->isATableHashPartitioningFunction()))
{
actParts = ((NodeMap *)(pf->getNodeMap()))->getEstNumActivePartitionsAtRuntime();
}
}
}
if ( actParts > 1 )
actParts = MINOF(actParts, getFileScan().getComputedNumOfActivePartiions());
return actParts;
}
// look at physical partition function of indexDesc to determine active partitions(AP)
// for salted table. Adjust AP count if partitionkey predicate is a constant
CollIndex ScanOptimizer::getEstNumActivePartitionsAtRuntimeForHbaseRegions() const
{
CollIndex actParts;
PartitioningFunction *pf =
getContext().getPlan()->getPhysicalProperty()->getPartitioningFunction();
DCMPASSERT(pf != NULL);
// get estimated active partition count
CollIndex estActParts = ((NodeMap *)(pf->getNodeMap()))->getEstNumActivePartitionsAtRuntime();
// if partition key predicate is constant, then estimated active partition count will
// be set to one by computeDP2CostDataThatDependsOnSPP() method.
// Use this value for costing REL_HBASE_ACCESS operator
if (estActParts == 1 AND (CmpCommon::getDefault(NCM_HBASE_COSTING) == DF_ON))
return estActParts;
// return the #region servers via the partition function describing the physical Hbase table
PartitioningFunction * physicalPartFunc = getIndexDesc()->getPartitioningFunction();
if (physicalPartFunc == NULL) // single region
actParts = 1;
else // multi-region case
actParts = ((NodeMap *)(physicalPartFunc->getNodeMap()))->getNumActivePartitions();
// if partition key predicate is constant, then estimated active partition count will
// be one, use that value
if (estActParts == 1 AND (CmpCommon::getDefault(NCM_HBASE_COSTING) == DF_ON))
actParts = estActParts;
if ( actParts > 1 )
actParts = MINOF(actParts, getFileScan().getComputedNumOfActivePartiions());
return actParts;
}
CollIndex ScanOptimizer::getNumActiveDP2Volumes() const
{
PartitioningFunction *pf =
getContext().getPlan()->getPhysicalProperty()->getPartitioningFunction();;
DCMPASSERT(pf != NULL);
// All this casting away is because mutable is not supported, thuse NodeMap has
// non-mutable cached members....
CollIndex actVols = ((NodeMap *)(pf->getNodeMap()))->getNumActiveDP2Volumes();
// Assume at least one active volume even if node map indicates otherwise.
return MIN_ONE(actVols);
}
// Static method, used by useSimpleFileScanOptimizer()
// Determine whether MDAM is Forced ON, Forced OFF, or ENABLED.
//
// MDAM can be forced ON by:
// CONTROL QUERY SHAPE
// CONTROL TABLE
// INLINING
//
// MDAM can be forced OFF by:
// CONTROL QUERY DEFAULT MDAM_SCAN_METHOD 'OFF'
// CONTROL QUERY SHAPE
// CONTROL TABLE
// MDAM Flag (from index elimination project)
// Too many disjuncts
//
ScanForceWildCard::scanOptionEnum
ScanOptimizer::getMdamStatus(const FileScan& fileScan
,const Context& myContext)
{
// Soln:10-050620-8905
// Executor does not support MDAM for streams due to some limitations in executor and DP2,
// hence MDAM is disabled for streams.
if( fileScan.getGroupAttr()->isStream() )
return ScanForceWildCard::MDAM_OFF;
// Soln:10-050620-8905
//----------------------------------------------------
// First we check that MDAM is not disabled globaly by
// set define MDAM OFF
//----------------------------------------------------
#ifndef NDEBUG
// quick force for MDAM for QA:
char *cstrMDAMStatus = getenv("MDAM");
if ((cstrMDAMStatus != NULL) &&
( strcmp(cstrMDAMStatus,"OFF")==0 ))
{
return ScanForceWildCard::MDAM_OFF;
}
else if ((cstrMDAMStatus != NULL) &&
( strcmp(cstrMDAMStatus,"ON")==0 ))
{
return ScanForceWildCard::MDAM_FORCED;
}
#endif
// First check of MDAM is forced on by a CONTROL QUERY SHAPE,
// CONTROL TABLE
// Now, Check if MDAM is Forced (which is done via Control Query Shape)
// The forcing information is passed through the context.
//
const ReqdPhysicalProperty* propertyPtr = myContext.getReqdPhysicalProperty();
if ( propertyPtr
&& propertyPtr->getMustMatch()
&& (propertyPtr->getMustMatch()->getOperatorType()
== REL_FORCE_ANY_SCAN))
{
ScanForceWildCard* scanForcePtr =
(ScanForceWildCard*)propertyPtr->getMustMatch();
if (scanForcePtr->getMdamStatus() == ScanForceWildCard::MDAM_FORCED)
return ScanForceWildCard::MDAM_FORCED;
}
// Is Mdam Forced ON by a CONTROL TABLE statement for this table.
//
const NAString * val =
ActiveControlDB()->getControlTableValue(
fileScan.getIndexDesc()->getNAFileSet()->getExtFileSetName(), "MDAM");
if ((val) && (*val == "ON")) {
return ScanForceWildCard::MDAM_FORCED;
}
// During inlining the binder may force mdam on a table.
//
if (fileScan.getInliningInfo().isMdamForcedByInlining())
{
return ScanForceWildCard::MDAM_FORCED;
}
// Check if the CQD forces MDAM OFF.
//
if (CmpCommon::getDefault(MDAM_SCAN_METHOD) == DF_OFF)
return ScanForceWildCard::MDAM_OFF;
// Now, Check if MDAM is Forced OFF (which is done via Control Query
// Shape) The forcing information is passed through the context.
//
if ( propertyPtr
&& propertyPtr->getMustMatch()
&& (propertyPtr->getMustMatch()->getOperatorType()
== REL_FORCE_ANY_SCAN))
{
ScanForceWildCard* scanForcePtr =
(ScanForceWildCard*)propertyPtr->getMustMatch();
if (scanForcePtr->getMdamStatus() == ScanForceWildCard::MDAM_OFF)
return ScanForceWildCard::MDAM_OFF;
}
// Is Mdam Forced OFF by a CONTROL TABLE statement for this table.
//
val =
ActiveControlDB()->getControlTableValue(
fileScan.getIndexDesc()->getNAFileSet()->getExtFileSetName(), "MDAM");
if ((val) && (*val == "OFF")) {
return ScanForceWildCard::MDAM_OFF;
}
// The Index elimination project added the MdamFlag which can force
// MDAM off
//
if(CURRSTMT_OPTDEFAULTS->indexEliminationLevel() != OptDefaults::MINIMUM
AND fileScan.getMdamFlag() == MDAM_OFF
&& (CmpCommon::getDefault(RANGESPEC_TRANSFORMATION) != DF_ON )
)
{
return ScanForceWildCard::MDAM_OFF;
}
// If the number of disjuncts exceeds the maximum, MDAM is forced OFF
//
if(fileScan.getDisjuncts().entries() > MDAM_MAX_NUM_DISJUNCTS) {
return ScanForceWildCard::MDAM_OFF;
}
// If MDAM is not forced ON or OFF, then it will be determined by
// the system.
//
return ScanForceWildCard::MDAM_SYSTEM;
}
// Static version of isMdamForced(), used by useSimpleFileScanOptimizer()
//
NABoolean
ScanOptimizer::isMdamForced(const FileScan& fileScan
,const Context& myContext)
{
// Soln:10-050620-8905
// Executor does not support MDAM for streams due to some limitations in executor and DP2,
// hence MDAM is disabled for streams.
if( fileScan.getGroupAttr()->isStream() )
return FALSE;
// Soln:10-050620-8905
//----------------------------------------------------
// First we check that MDAM is not disabled globaly by
// set define MDAM OFF
//----------------------------------------------------
#ifndef NDEBUG
// quick force for MDAM for QA:
char *cstrMDAMStatus = getenv("MDAM");
if ((cstrMDAMStatus != NULL) &&
( strcmp(cstrMDAMStatus,"OFF")==0 ))
{
return FALSE;
}
else if ((cstrMDAMStatus != NULL) &&
( strcmp(cstrMDAMStatus,"ON")==0 ))
{
return TRUE;
}
#endif
//---------------------------------------------------------------
// Now, Check if MDAM is Forced (which is done via Control Query Shape)
// The forcing information is passed through the context.
//---------------------------------------------------------------
const ReqdPhysicalProperty* propertyPtr = myContext.getReqdPhysicalProperty();
if ( propertyPtr
&& propertyPtr->getMustMatch()
&& (propertyPtr->getMustMatch()->getOperatorType()
== REL_FORCE_ANY_SCAN))
{
ScanForceWildCard* scanForcePtr =
(ScanForceWildCard*)propertyPtr->getMustMatch();
if (scanForcePtr->getMdamStatus() == ScanForceWildCard::MDAM_FORCED)
return TRUE;
//else return FALSE;
}
//else
//return FALSE;
const NAString * val =
//ct-bug-10-030102-3803 -Begin
ActiveControlDB()->getControlTableValue(
fileScan.getIndexDesc()->getPrimaryTableDesc()
->getCorrNameObj().getUgivenName(), "MDAM");
//ct-bug-10-030102-3803 -End
if ((val) && (*val == "ON"))
return TRUE;
// During inlining the binder may force mdam on a table.
if (fileScan.getInliningInfo().isMdamForcedByInlining())
{
return TRUE;
}
return FALSE;
}
NABoolean
ScanOptimizer::isMdamEnabled() const
{
// give support to MDAM defines:
// MDAM is enabled by default
// MDAM ON: enable MDAM
// MDAM OFF: disable MDAM
// -----------------------------------------------------------------------
// Mdam is enabled by default. Its only disabled globaly by
// setting the default MDAM_SCAN_METHOD to "OFF"
// You can re-enable it by
// setting the MDAM_SCAN_METHOD to "ON"
// Note that this code assumes MDAM is ON by default and
// thus will enable MDAM if anything other than "OFF" is set
// in the default MDAM_SCAN_METHOD
// $$$ A warning should be printed if MDAM_SCAN_METHOD is
// $$$ set to something other than "OFF" or "ON"
// -----------------------------------------------------------------------
NABoolean mdamIsEnabled = TRUE;
// Soln:10-050620-8905
// Executor does not support MDAM for streams due to some limitations in executor and DP2,
// hence MDAM is disabled for streams.
if( getRelExpr().getGroupAttr()->isStream() )
mdamIsEnabled = FALSE;
// Soln:10-050620-8905
// -----------------------------------------------------------------------
// Check the status of the enabled/disabled flag in
// the defaults:
// -----------------------------------------------------------------------
if (CmpCommon::getDefault(MDAM_SCAN_METHOD) == DF_OFF)
mdamIsEnabled = FALSE;
// -----------------------------------------------------------------------
// Mdam can also be disabled for a particular scan via Control
// Query Shape. The information is passed by the context.
// -----------------------------------------------------------------------
if (mdamIsEnabled)
{
const ReqdPhysicalProperty* propertyPtr =
getContext().getReqdPhysicalProperty();
if ( propertyPtr
&& propertyPtr->getMustMatch()
&& (propertyPtr->getMustMatch()->getOperatorType()
== REL_FORCE_ANY_SCAN))
{
ScanForceWildCard* scanForcePtr =
(ScanForceWildCard*)propertyPtr->getMustMatch();
if (scanForcePtr->getMdamStatus() == ScanForceWildCard::MDAM_OFF)
mdamIsEnabled = FALSE;
}
}
// -----------------------------------------------------------------------
// Mdam can also be disabled for a particular table via a Control
// Table command.
// -----------------------------------------------------------------------
if (mdamIsEnabled)
{
const NAString * val =
//ct-bug-10-030102-3803 -Begin
ActiveControlDB()->getControlTableValue(
getIndexDesc()->getPrimaryTableDesc()
->getCorrNameObj().getUgivenName(), "MDAM");
//ct-bug-10-030102-3803 -End
if ((val) && (*val == "OFF")) // CT in effect
{
mdamIsEnabled = FALSE;
}
}
const IndexDesc* idesc = getIndexDesc();
NABoolean isHbaseNativeTable =
idesc->getPrimaryTableDesc()->getNATable()->isHbaseCellTable() ||
idesc->getPrimaryTableDesc()->getNATable()->isHbaseRowTable();
if (isHbaseNativeTable)
mdamIsEnabled = FALSE;
// If the table to be optimized is the base table and has divisioning
// columns defined on it, no more check is necessary.
if ( idesc == idesc->getPrimaryTableDesc()->getClusteringIndex() ) {
const ValueIdSet divisioningColumns =
idesc->getPrimaryTableDesc()->getDivisioningColumns();
if ( divisioningColumns.entries() > 0 &&
(CmpCommon::getDefault(MTD_GENERATE_CC_PREDS) == DF_ON) )
return mdamIsEnabled;
}
if (mdamIsEnabled)
{
CostScalar repeatCount = getContext().getPlan()->getPhysicalProperty()->
getDP2CostThatDependsOnSPP()->getRepeatCountForOperatorsInDP2() ;
// If # of probes for NJ is <= cqd(MDAM_UNDER_NJ_PROBES_THRESHOLD),
// we will use MDAM regardless of the setting of CQD MDAM_SCAN_METHOD.
// When the CQD is 0, MDAM under NJ is not considered.
Lng32 mdam_under_nj_probes =
(ActiveSchemaDB()->getDefaults()).getAsLong(
MDAM_UNDER_NJ_PROBES_THRESHOLD);
if ( mdam_under_nj_probes > 0 )
{
if ( repeatCount <= mdam_under_nj_probes &&
getContext().getInputLogProp()->getColStats().entries() > 0
)
return TRUE;
}
// If the input logical property indicates exactly one, then we
// allow MDAM in spite of NJ.
NABoolean isInputCardinalityOne =
getContext().getInputLogProp()->isCardinalityEqOne();
if (isInputCardinalityOne)
return TRUE;
/*
Right side Scan of a Nested Join will use MDAM disjuncts if and only if
MDAM_SCAN_METHOD CQD's value is MAXIMUM.
*/
if ((repeatCount.isGreaterThanOne()) OR
(getContext().getInputLogProp()->getColStats().entries() > 0)
&& (CmpCommon::getDefault(MDAM_SCAN_METHOD) != DF_MAXIMUM))
mdamIsEnabled = FALSE;
}
return mdamIsEnabled;
} // isMdamEnabled()
// LCOV_EXCL_START :cnu
const CostScalar
ScanOptimizer::getIndexLevelsSeeks() const
{
//CostScalar indexLevelsSeeks =
// CostScalar(getIndexDesc()->getIndexLevels()) - csOne;
//indexLevelsSeeks =
// (indexLevelsSeeks < 0 ? csZero : indexLevelsSeeks);
//return indexLevelsSeeks;
if ( getIndexDesc()->getIndexLevels() > 1 )
return CostScalar( getIndexDesc()->getIndexLevels() - 1);
else
return csZero;
}
// LCOV_EXCL_STOP
// -----------------------------------------------------------------------
// INPUT:
// firstRow = the first row cost per scan
// lastRow = the last row cost per scan
// OUTPUT:
// The Cost object. This Cost object takes into account the
// fact that the scan may be reading the data synchronously.
// -----------------------------------------------------------------------
Cost*
ScanOptimizer::computeCostObject(
const SimpleCostVector& firstRow /* IN */
,const SimpleCostVector& lastRow /* IN */
) const
{
// -----------------------------------------------------------------------
// This routine computes the Cost object for the physical scan out of the
// simple cost vectors for the first and last row.
//
// The cost vector needs two pieces of information to
// compute the total cost:
// countOfCpus
// planFragmentsPerCPU.
//--------------------------------------------------------------------------
// With the new AP code scan costing is always done after synthesis:
DCMPASSERT(getContext().getPlan()->getPhysicalProperty());
const CostScalar activePartitions = getNumActivePartitions();
Lng32 countOfCPUsExecutingDP2s = MINOF((Lng32)activePartitions.getValue(),
getContext().getPlan()->getPhysicalProperty()->getCurrentCountOfCPUs());
// -----------------------------------------------------------------------
// Compute the degree of I/O parallelism:
// -----------------------------------------------------------------------
// get the logPhysPartitioningFunction, which we will use
// to get the number of PAs:
const LogPhysPartitioningFunction* lppf =
getContext().getPlan()->getPhysicalProperty()->
getPartitioningFunction()->castToLogPhysPartitioningFunction();
Lng32 countOfPAs = 1;
SimpleCostVector
tempFirst = firstRow
,tempLast = lastRow
;
// get partitions per CPU
CostScalar partitionsPerCPU = csOne;
// Compute the number of PAs and see if synchronous access is
// occuring. If there is synchronous access, then we need to
// adjust the cost, since the cost was computed assuming that
// asynchronous access would occur.
if (lppf == NULL) // Unpartitioned, Trafodion, native HBase or hive tables?
{
PartitioningFunction* partFunc = getContext().getPlan()
->getPhysicalProperty()-> getPartitioningFunction();
if ( partFunc->castToSinglePartitionPartitioningFunction() )
countOfPAs = 1;
else {
const FileScan& fileScan = getFileScan();
if ( fileScan.isHbaseTable() || fileScan.isHiveTable() ||
fileScan.isSeabaseTable())
countOfPAs = partFunc->getCountOfPartitions();
else
DCMPASSERT(FALSE);
}
}
else
{
// This is a partitioned table. Figure out
// how much parallelism is involved and penalize
// if not fully parallel:
// Get the number of PAs.
countOfPAs = lppf->getNumOfClients();
// If we are under a nested join, and the probes are in a complete
// ESP process order, and the clustering key and partitioning
// key are the same, then the probes will force each ESP to
// access all it's partitions synchronously. So, the level of
// asynchronous access is limited to the number of ESPs. So, limit
// the number of PAs by the number of ESPs, i.e. the number of
// partitions in the logical partitioning function.
if (getProbesForceSynchronousAccessFlag())
{
PartitioningFunction* logPartFunc =
lppf->getLogPartitioningFunction();
Lng32 numParts = logPartFunc->getCountOfPartitions();
countOfPAs = MINOF(numParts,countOfPAs);
}
// Limit the number of PAs by the number of active partitions.
// This won't need to be done here when we start doing it when
// we synthesize the physical properties. It is not being done
// there now because the active partitions estimate is inaccurate
// sometimes and so we cannot rely on it now for setting
// something that will be relied upon by the executor.
countOfPAs =
MINOF((Lng32)activePartitions.getValue(),countOfPAs);
Lng32 numOfDP2Volumes =
#pragma nowarn(1506) // warning elimination
MIN_ONE( ((NodeMap *)(lppf->getNodeMap()))->getNumActiveDP2Volumes() );
#pragma warn(1506) // warning elimination
// Limit the number of PAs by the number of DP2 volumes.
// This won't need to be done here when we start doing it when
// we synthesize the physical properties. It is not being done
// there now because the number of DP2 volumes estimate is inaccurate
// sometimes and so we cannot rely on it now for setting
// something that will be relied upon by the executor.
// Assume at least one DP2 volume even if node map indicates otherwise.
countOfPAs =
MINOF(numOfDP2Volumes,countOfPAs);
// Now compute the number of partitions that will be processed
// by each stream (PA). If this number is greater than 1, then
// it means that some streams (PAs) are accessing more than
// one partition synchronously.
const CostScalar partsPerPA = activePartitions/countOfPAs;
if (partsPerPA.isGreaterThanOne())
{
// ---------------------------------------------------------------
// One PA is accessing more than one partition.
// this means that every PA is doing:
// ******SEQUENTIAL ACCESS********
// (it is accessing partsPerPA partitions sequentially).
// Thus we need to create a cost vector that
// adds up the CPU and I/O respectively.
//
// We do it here because
// the number of plan fragments is used by the Cost factor
// to convert the "per-stream" cost in the simple cost vectors
// to a "per-cpu" cost. This is done by "overlapped" adding each
// simple cost vector "plan fragment" times. This is OK for the
// case of asynchronous acces of partitions. But if we need
// to access the partitions synchronously, i.e sequentially, then
// we want to do "full" addition because no overlapping will
// exists for partitions in the same CPU. We do this below.
// ----------------------------------------------------------------
tempLast.setCPUTime(tempLast.getCPUTime() * partsPerPA);
tempLast.setIOTime (tempLast.getIOTime() * partsPerPA);
// The first row costs should NOT be increased, they
// are already for one row only and they do not show any
// asynchronous cost savings, so no need to increase them
// for synchronous access!
// tempFirst.setCPUCost(tempFirst.getCPUCost() * partsPerPA);
// tempFirst.setNumKBytes(tempLast.getNumKBytes() * partsPerPA);
// tempFirst.setNumSeeks(tempLast.getNumSeeks() * partsPerPA);
}
else
{
// -------------------------------------------------------
// Else there is one partition per PA, thus it is
// ******ASYNCHRONOUS ACCESS********
// let the cost object do its work,
// that is, overlapp add the partitionsPerCPU plan fragments
// to obtain the cost per cpu
// -------------------------------------------------------
}
} // lppf != NULL
// Compute the number of streams per CPU:
partitionsPerCPU =
((CostScalar)countOfPAs/(CostScalar)countOfCPUsExecutingDP2s).getCeiling();
// Save the idle time of LR and FR and then reset it
// after the Cost object has been constructed. Do this
// because scan uses idle time to store the cost of
// opening a table and in the case of multiple
// partitions the Cost constructor modifies idle time.
// the idle time for FR and LR is the same, so get it from any of them:
CostScalar idleTime = tempLast.getIdleTime();
// IOTime is already per volume and should not be incremented
CostScalar lrIOTime = tempLast.getIOTime();
CostScalar frIOTime = tempFirst.getIOTime();
// LCOV_EXCL_START :dpm
#ifndef NDEBUG
Lng32 planFragmentsPerCPU = (Lng32)partitionsPerCPU.getValue();
if ( planFragmentsPerCPU > 1 AND CURRSTMT_OPTGLOBALS->synCheckFlag )
(*CURRSTMT_OPTGLOBALS->asynchrMonitor).enter();
#endif //NDEBUG
// LCOV_EXCL_STOP
tempLast.setIdleTime(0.);
tempFirst.setIdleTime(0.);
Cost * costPtr = new HEAP Cost(&tempFirst,
&tempLast,
NULL,
countOfCPUsExecutingDP2s,
(Lng32)partitionsPerCPU.getValue());
costPtr->cpfr().setIdleTime(idleTime);
costPtr->cplr().setIdleTime(idleTime);
costPtr->totalCost().setIdleTime(idleTime);
costPtr->cpfr().setIOTime(frIOTime);
costPtr->cplr().setIOTime(lrIOTime);
costPtr->totalCost().setIOTime(lrIOTime);
// LCOV_EXCL_START :dpm
#ifndef NDEBUG
if ( planFragmentsPerCPU > 1 AND CURRSTMT_OPTGLOBALS->synCheckFlag)
(*CURRSTMT_OPTGLOBALS->asynchrMonitor).exit();
#endif //NDEBUG
// LCOV_EXCL_STOP
DCMPASSERT(costPtr != NULL);
return costPtr;
} // computeCostObject(...)
// -----------------------------------------------------------------------
// Use this routine to find a basic cost object to share.
// INPUT:
// implicitly uses Context, IndexDesc and scanBasicCosts_ of indexDesc
// OUTPUT
// sharedCostFound is TRUE if on the basic cost objects can be reused
// function returns pointer to this object
// if sharedCostFound is FALSE the function returns the pointer to
// the new object in the list and its simple cost vectors need to be
// recomputed
// -----------------------------------------------------------------------
FileScanBasicCost *
ScanOptimizer::shareBasicCost(NABoolean &sharedCostFound)
{
const Context & currentContext = getContext();
IndexDesc * indexDesc = (IndexDesc *)getIndexDesc();
FileScanCostList *
fileScanCostList = (FileScanCostList *)indexDesc->getBasicCostList();
if ( fileScanCostList == NULL )
{
// first call to the function for this index descriptor,
// create a new FileScanCostList object and save pointer to it
// in index descriptor attribute.
fileScanCostList = new (STMTHEAP) FileScanCostList (STMTHEAP);
DCMPASSERT(fileScanCostList != NULL);
indexDesc->setBasicCostList(fileScanCostList);
}
// go through the list of BasicCost objects trying to find
// basic cost to reuse
CollIndex maxc = fileScanCostList->entries();
for (CollIndex i = 0; i < maxc; i++)
{
FileScanBasicCost * existingBasicCostPtr = (*fileScanCostList)[i];
if ( existingBasicCostPtr->hasSameBasicProperties(currentContext) )
{
sharedCostFound = TRUE;
return existingBasicCostPtr;
}
}
// no basic cost to share, create a new BasicCost object and
// return its pointer to the calling function. In this case
// computeCostVector function will work with this new BasicCost
// object memory when calculating the cost.
sharedCostFound = FALSE;
FileScanBasicCost * newBasicCostPtr = new (CmpCommon::statementHeap())
FileScanBasicCost((const Context *)&currentContext);
fileScanCostList->insert(newBasicCostPtr);
return newBasicCostPtr;
}
// -----------------------------------------------------------------------
// Methods for FileScanOptimizer:
// -----------------------------------------------------------------------
FileScanOptimizer::~FileScanOptimizer()
{
};
Cost *
FileScanOptimizer::optimize(SearchKey*& searchKeyPtr /* out */
, MdamKey*& mdamKeyPtr /* out */
)
{
MDAM_DEBUG0(MTL2, "BEGIN Scan Costing ********************************");
// -----------------------------------------------------------------------
// The best cost is determined amongst these cases:
// 1.- Single subset
// 2.- MDAM common
// 3,- MDAM disjuncts
//
// This method computes the cheapest key and returns its cost
//
// -----------------------------------------------------------------------
// This method computes either a MdamKey or a SearchKey,
// however, the receiving parameters must be NULL:
DCMPASSERT(searchKeyPtr == NULL);
DCMPASSERT(mdamKeyPtr == NULL);
ValueIdSet partKeyPreds;
NABoolean eliminateExePreds = TRUE;
LogPhysPartitioningFunction *logPhysPartFunc =
(LogPhysPartitioningFunction *) // cast away const
getContext().getPlan()->getPhysicalProperty()->getPartitioningFunction()->
castToLogPhysPartitioningFunction();
if ( logPhysPartFunc != NULL )
{
LogPhysPartitioningFunction::logPartType logPartType =
logPhysPartFunc->getLogPartType();
// Partitioning Key Predicates are only of interest if
// we have LOGICAL_SUBPARTITIONING or HORIZONTAL_PARTITION_SLICING
// LOGICAL_SUBPARTITIONING
// More than one PA may access a given DP2 partition and a PA may
// also access more than one DP2 partition. The PA nodes divide
// the table into exclusive ranges that are defined by the
// clustering key of the table which is also the partitioning key.
//
// HORIZONTAL_PARTITION_SLICING
// Similar to LOGICAL_SUBPARTITIONING, except that the clustering
// key is not the partitioning key of the table.
// (HORIZONTAL_PARTITION_SLICING is currently not implemented (6/20/2001)
//
//
// If we have partitioning key predicates with one of the types of partitioning
// mentioned above we will try to use these as access key predicates for
// single subset or multiple subset.
// Access keys are created from the disjuncts that were created when the
// rel expr was set up. This was only done once, since the selection
// predicates normally do not change.
// Since we want to add partitioning keys to the selection predicates we must
// materialize new disjuncts that include the partitioning keys.
// We will build the new disjuncts and we will later choose to use the new
// one or the old one based on whether or not we have partitioning key
// predicates.
if ( logPartType == LogPhysPartitioningFunction::LOGICAL_SUBPARTITIONING
OR logPartType == LogPhysPartitioningFunction::HORIZONTAL_PARTITION_SLICING
)
partKeyPreds = logPhysPartFunc->getPartitioningKeyPredicates();
}
Disjuncts * curDisjuncts = NULL; // = (Disjuncts&)getDisjuncts();
if (NOT partKeyPreds.isEmpty())
{
ValueIdSet newPreds = (ValueIdSet)getRelExpr().getSelectionPred();
newPreds += partKeyPreds;
curDisjuncts = new HEAP MaterialDisjuncts(newPreds);
eliminateExePreds = FALSE;
}
MDAM_DEBUGX(MTL2, MdamTrace::printFileScanInfo(this, partKeyPreds));
// The cost of the winning key will be put here:
Cost *winnerCostPtr = NULL;
CostScalar winnerCostPtrNumKBytes;
// To distinguish between MDAMCommon and MDAMDisjuncts methods
NABoolean mdamTypeIsCommon = FALSE;
MdamKey *sharedMdamCommonKeyPtr = NULL;
MdamKey *sharedMdamDisjunctsKeyPtr = NULL;
IndexDescHistograms histograms(*getIndexDesc()
,getIndexDesc()->getIndexKey().entries());
// All histograms must have been created for non-duplicate key columns
// This DCMPASSERT was used earlier to check for all duplicate entries too
// but that is incorrect because we do not want to have duplicate entries
// in the ColStatDescList. Hence modified the DCMPASSERT such that all
// key columns should have only one entry in the ColStatDescList -
// as part of the fix for Sol: 10-031105-1054
ValueIdSet nonDuplicateKeyColumns = getIndexDesc()->getIndexKey();
DCMPASSERT(histograms.entries() == nonDuplicateKeyColumns.entries());
NABoolean
isIndexJoin = histograms.isAnIndexJoin(*(getContext().getInputLogProp()));
#ifndef NDEBUG
if (isIndexJoin) (*CURRSTMT_OPTGLOBALS->indexJoinMonitor).enter();
#endif
// nonkeycolumns are needed by SearchKey and mdamkey:
ValueIdSet nonKeyColumnSet;
const IndexDesc * indexDesc = getIndexDesc();
indexDesc->getNonKeyColumnSet(nonKeyColumnSet);
NABoolean mdamForced = isMdamForced();
// We have already done the check to see if MDAM can be done or not, while
// testing to see if we can use simpleFileScanOptimizer. So, there is no need to
// test it again. The only condition when it would not have been done, would be
// when we force the compiler to use old FileScan optimizer. Check if MDAM can be
// performed only in that condition.
NABoolean canDoMdam = TRUE;
NADefaults &defs = ActiveSchemaDB()->getDefaults();
ULng32 fsoToUse = defs.getAsULong(FSO_TO_USE);
if (fsoToUse == 0)
{
// force the compiler to use old FileScan Optimizer
if (isMdamEnabled() AND NOT mdamForced)
{
if ( isIndexJoin || hasTooManyDisjuncts())
{
canDoMdam = FALSE;
}
else
{
if (histograms.containsAtLeastOneFake())
{
// -------------------------------------------------------------
// If at least one histogram is fake, then we can't do
// MDAM (histograms contains a ColStatDescList with
// entries for key columns only)
// --------------------------------------------------------------
canDoMdam = FALSE;
MDAM_DEBUG0(MTL2, "Fake histogram found, MDAM NOT considered.");
MDAM_DEBUGX(MTL2, histograms.display());
}
else
{
ValueIdSet externalInputs = getExternalInputs();
NABoolean mdamFlag = getMdamFlag();
if (NOT partKeyPreds.isEmpty())
{
canDoMdam = ScanOptimizer::canStillConsiderMDAM(partKeyPreds,
nonKeyColumnSet,
*curDisjuncts,
indexDesc,
externalInputs,
mdamFlag);
}
else
{
canDoMdam = ScanOptimizer::canStillConsiderMDAM(partKeyPreds,
nonKeyColumnSet,
getDisjuncts(),
indexDesc,
externalInputs,
mdamFlag);
}
}
}
}
} // fsotouse == 0
// At this point if canDoMDam is TRUE then we can consider
// MDAM. However, if we are forcing MDAM we will consider it
// regardless of the state of canDoMdam.
ValueIdSet exePred;
NABoolean mdamIsWinner = FALSE;
if (mdamForced // Force mdam case:
AND
getIndexDesc()->getNAFileSet()->isKeySequenced())
{ // Mdam is our only choice:
MDAM_DEBUG0(MTL2, "MDAM only (forced).");
// Create mdam disjuncts key:
if (partKeyPreds.isEmpty())
mdamKeyPtr = new HEAP
MdamKey(getIndexDesc()->getIndexKey()
,getExternalInputs()
,getDisjuncts()
,nonKeyColumnSet
,getIndexDesc()
);
else
mdamKeyPtr = new HEAP
MdamKey(getIndexDesc()->getIndexKey()
,getExternalInputs()
,*curDisjuncts
,nonKeyColumnSet
,getIndexDesc()
);
DCMPASSERT(mdamKeyPtr != NULL);
// Cost the mdam disjuncts:
winnerCostPtr =
computeCostForMultipleSubset
( mdamKeyPtr
,NULL
,mdamForced
,winnerCostPtrNumKBytes
,eliminateExePreds //TRUE eliminate exePreds if possible
,mdamTypeIsCommon //FALSE, this is Disjuncts case
,sharedMdamDisjunctsKeyPtr
);
mdamIsWinner = TRUE;
} // Mdam is forced
else if ( NOT isMdamEnabled()
OR
NOT canDoMdam)
{
MDAM_DEBUG0(MTL2, "Single subset only.");
// Single subset is our only choice
// (mdam is not enabled OR mdam is enabled but the
// file is not key sequenced)
// Generate the search key to be returned, then
// estimate its cost:
ValueIdSet selPreds = getRelExpr().getSelectionPred();
if ((CmpCommon::getDefault(RANGESPEC_TRANSFORMATION) == DF_ON ) &&
(selPreds.entries()))
{
ItemExpr * inputItemExprTree = selPreds.rebuildExprTree(ITM_AND,FALSE,FALSE);
ItemExpr * resultOld = revertBackToOldTree(CmpCommon::statementHeap(), inputItemExprTree);
exePred.clear();
resultOld->convertToValueIdSet(exePred, NULL, ITM_AND, FALSE);
doNotReplaceAnItemExpressionForLikePredicates(resultOld,exePred,resultOld);
}
else
{
exePred = selPreds;
}
exePred += partKeyPreds;
if (partKeyPreds.isEmpty())
searchKeyPtr = new(CmpCommon::statementHeap())
SearchKey(getIndexDesc()->getIndexKey()
,getIndexDesc()->getOrderOfKeyValues()
,getExternalInputs()
,isForwardScan()
,exePred
,getDisjuncts()
,nonKeyColumnSet
,getIndexDesc()
);
else
searchKeyPtr = new(CmpCommon::statementHeap())
SearchKey(getIndexDesc()->getIndexKey()
,getIndexDesc()->getOrderOfKeyValues()
,getExternalInputs()
,isForwardScan()
,exePred
,*curDisjuncts
,nonKeyColumnSet
,getIndexDesc()
);
winnerCostPtr =
computeCostForSingleSubset
(
*searchKeyPtr
,FALSE /* do not break */
,winnerCostPtrNumKBytes /* output */
);
} // generate search key
else
{
MDAM_DEBUG0(MTL2, "Compare single subset and MDAMs.");
//------------------------------------------------
// MDAM and SearchKey are both
// enabled do the following:
//
// 1.- Compute costs for Mdam and SingleSubset
// 2.- Compare costs
// 3.- Create the Key for the winner (only one can win)
// -----------------------------------------------------------
// Create the keys:
// ------------------------------------------------------------
DCMPASSERT(winnerCostPtr == NULL);
// Search key:
ValueIdSet selPreds = getRelExpr().getSelectionPred();
if((CmpCommon::getDefault(RANGESPEC_TRANSFORMATION) == DF_ON ) &&
(selPreds.entries()))
{
ItemExpr * inputItemExprTree = selPreds.rebuildExprTree(ITM_AND,FALSE,FALSE);
ItemExpr * resultOld = revertBackToOldTree(CmpCommon::statementHeap(), inputItemExprTree);
exePred.clear();
resultOld->convertToValueIdSet(exePred, NULL, ITM_AND, FALSE);
doNotReplaceAnItemExpressionForLikePredicates(resultOld,exePred,resultOld);
// doNotReplaceAnItemExpression(resultOld,exePred,resultOld);
// exePred.clear();
// revertBackToOldTreeUsingValueIdSet(selPreds, exePred);
// ItemExpr* resultOld = exePred.rebuildExprTree(ITM_AND,FALSE,FALSE);
// doNotReplaceAnItemExpressionForLikePredicates(resultOld,exePred,resultOld);
}
else
{
exePred = getRelExpr().getSelectionPred();
}
exePred += partKeyPreds;
if (partKeyPreds.isEmpty())
searchKeyPtr = new(CmpCommon::statementHeap())
SearchKey(getIndexDesc()->getIndexKey()
,getIndexDesc()->getOrderOfKeyValues()
,getExternalInputs()
,isForwardScan()
,exePred
,getDisjuncts()
,nonKeyColumnSet
,getIndexDesc()
);
else
searchKeyPtr = new(CmpCommon::statementHeap())
SearchKey(getIndexDesc()->getIndexKey()
,getIndexDesc()->getOrderOfKeyValues()
,getExternalInputs()
,isForwardScan()
,exePred
,*curDisjuncts
,nonKeyColumnSet
,getIndexDesc()
);
Cost
*singleSubsetCostPtr = NULL,
*mdamCommonCostPtr = NULL,
*mdamDisjunctsCostPtr = NULL;
CostScalar
singleSubsetCostPtrNumKBytes,
mdamCommonCostPtrNumKBytes,
mdamDisjunctsCostPtrNumKBytes;
MdamKey
*mdamCommonKeyPtr = NULL,
*mdamDisjunctsKeyPtr = NULL;
// -----------------------------------------------------
// Keys will compete. The following will take place:
// 1.- Only one key can win
// 2.- The winning key will be set to either
// searchKeyPtr or mdamKeyPtr
// 3.- The loser's keys and costs will be deleted
// and pointers set to NULL
// ----------------------------------------------------
// ------------------------------------------------------------
// Compute single subset cost:
// -------------------------------------------------------------
MDAM_DEBUG0(MTL2, "Compute Single Subset Cost (Cost Bound)");
winnerCostPtr = singleSubsetCostPtr =
computeCostForSingleSubset(*searchKeyPtr
,TRUE /* Break if there is a conflict */
,singleSubsetCostPtrNumKBytes /* output */
);
winnerCostPtrNumKBytes = singleSubsetCostPtrNumKBytes;
MDAM_DEBUGX(MTL2, MdamTrace::printCostObject(this, singleSubsetCostPtr,
"Single Subset Cost"));
// ------------------------------------------------------------
// Compute mdam common cost:
// -------------------------------------------------------------
// If there is only a single disjunct, let
// mdam disjuncts take care of it (it is
// a little bit cheaper to construct a mdam key
// for the disjuncts case):
ValueIdSet commonPreds;
// Chose either the new disjuncts w/partitioning keys if we have
// partitoning keys, otherwise use the original
if ((NOT partKeyPreds.isEmpty() AND
curDisjuncts->entries() > 1) OR
(getDisjuncts().entries() > 1))
{
// Construct common preds:
ValueIdSet commonPreds;
if (partKeyPreds.isEmpty())
commonPreds = getDisjuncts().getCommonPredicates();
else
commonPreds = curDisjuncts->getCommonPredicates();
// Create disjuncts for this set of preds:
if (NOT commonPreds.isEmpty())
{
MDAM_DEBUG0(MTL2, "Consider Mdam Common Costing");
Disjuncts *commonDisjunctsPtr = new HEAP
MaterialDisjuncts(commonPreds);
DCMPASSERT(commonDisjunctsPtr != NULL);
mdamCommonKeyPtr = new HEAP
MdamKey(getIndexDesc()->getIndexKey()
,getExternalInputs()
,*commonDisjunctsPtr
,nonKeyColumnSet
,getIndexDesc()
);
// Try this case only if there are key preds
// in the common preds (one example where common
// has no key pres is A=1 or A=3, where A is
// a key column):
ColumnOrderList keyPredsByCol(getIndexDesc()->getIndexKey());
mdamCommonKeyPtr->getKeyPredicatesByColumn(keyPredsByCol,0);
ValueIdSet keyPreds;
keyPredsByCol.getAllPredicates(keyPreds);
if (NOT keyPreds.isEmpty())
{
DCMPASSERT(mdamCommonKeyPtr != NULL);
mdamTypeIsCommon = TRUE;
mdamCommonCostPtr =
computeCostForMultipleSubset
( mdamCommonKeyPtr
,winnerCostPtr /* bound */
,mdamForced
,mdamCommonCostPtrNumKBytes /* output */
,FALSE /* don't eliminate exePreds */
,mdamTypeIsCommon // TRUE this is Common case
,sharedMdamCommonKeyPtr
);
MDAM_DEBUGX(MTL2, MdamTrace::printCostObject(this,
mdamCommonCostPtr,
"MDAM Common Cost"));
} // there are key preds in common preds
else
{
MDAM_DEBUG0(MTL2,
"Common predicates has no key predicate, MDAM NOT considered");
MDAM_DEBUGX(MTL2, commonPreds.display());
} // NO common key preds in common preds
} // common preds are not empty
else{
MDAM_DEBUG0(MTL2, "No Common Predicate,\n"
"MDAM Common Costing NOT considered");
}
// -----------------------------------------------------
// Either the search key or mdam common key
// must win. The loser's cost and key will be
// deleted:
// ------------------------------------------------------
if (mdamCommonCostPtr != NULL)
{
// search key lost, delete it:
winnerCostPtr = mdamCommonCostPtr;
winnerCostPtrNumKBytes = mdamCommonCostPtrNumKBytes;
mdamKeyPtr = mdamCommonKeyPtr;
delete searchKeyPtr;
searchKeyPtr = NULL;
delete singleSubsetCostPtr;
singleSubsetCostPtr = NULL;
}
else
{
// mdam common lost, delete it:
winnerCostPtr = singleSubsetCostPtr;
winnerCostPtrNumKBytes = singleSubsetCostPtrNumKBytes;
if (mdamCommonKeyPtr != sharedMdamCommonKeyPtr)
delete mdamCommonKeyPtr;
mdamCommonKeyPtr = NULL;
}
}
else{
MDAM_DEBUG0(MTL2, "MDAM Common Costing NOT applies");
}
DCMPASSERT( (searchKeyPtr == NULL AND mdamCommonKeyPtr != NULL)
OR
(searchKeyPtr != NULL AND mdamCommonKeyPtr == NULL) );
//---------------------------------
// Compute mdam disjuncts cost
//---------------------------------
MDAM_DEBUG0(MTL2, "Consider Mdam Disjuncts Costing");
if (partKeyPreds.isEmpty())
// Chose either the new disjuncts w/partitioning keys if we have
// partitoning keys, otherwise use the original
mdamDisjunctsKeyPtr = new HEAP
MdamKey(getIndexDesc()->getIndexKey()
,getExternalInputs()
,getDisjuncts()
,nonKeyColumnSet
,getIndexDesc()
);
else
mdamDisjunctsKeyPtr = new HEAP
MdamKey(getIndexDesc()->getIndexKey()
,getExternalInputs()
,*curDisjuncts
,nonKeyColumnSet
,getIndexDesc()
);
DCMPASSERT(mdamDisjunctsKeyPtr != NULL);
MDAM_DEBUG0(MTL2, "call computeCostForMultipleSubset()");
mdamTypeIsCommon = FALSE;
mdamDisjunctsCostPtr =
computeCostForMultipleSubset
( mdamDisjunctsKeyPtr
,winnerCostPtr /* cost bound */
,mdamForced
,mdamDisjunctsCostPtrNumKBytes /* output */
,eliminateExePreds //TRUE eliminate exePreds if possible
,mdamTypeIsCommon //FALSE this is Disjuntcs case
,sharedMdamDisjunctsKeyPtr
);
MDAM_DEBUGX(MTL2, MdamTrace::printCostObject(this, mdamDisjunctsCostPtr,
"MDAM Disjuncts Cost"));
if (mdamDisjunctsCostPtr != NULL)
{
// mdamDisjuncts won and search key or mdam common lost:
winnerCostPtr = mdamDisjunctsCostPtr;
winnerCostPtrNumKBytes = mdamDisjunctsCostPtrNumKBytes;
mdamKeyPtr = mdamDisjunctsKeyPtr;
if (searchKeyPtr == NULL)
{
// mdam common lost, delete it (because
// a NULL searchKeyPtr means it was not
// competing):
DCMPASSERT(mdamCommonKeyPtr != NULL);
delete mdamCommonCostPtr;
mdamCommonCostPtr = NULL;
if (mdamCommonKeyPtr != sharedMdamCommonKeyPtr)
delete mdamCommonKeyPtr;
mdamCommonKeyPtr = NULL;
}
else
{
// search key lost, delete it:
DCMPASSERT(searchKeyPtr != NULL);
delete searchKeyPtr;
searchKeyPtr = NULL;
delete singleSubsetCostPtr;
singleSubsetCostPtr = NULL;
}
}
else
{
// mdam disjuncts lost, delete it:
// the winner stays the same
if (mdamDisjunctsKeyPtr != sharedMdamDisjunctsKeyPtr)
delete mdamDisjunctsKeyPtr;
mdamDisjunctsKeyPtr = NULL;
}
if (searchKeyPtr)
{
MDAM_DEBUG0(MTL2, "Winner is Single Subset");
}
else if (mdamCommonKeyPtr)
{
MDAM_DEBUG0(MTL2, "Winner is Mdam Common");
mdamIsWinner = TRUE;
}
else if (mdamDisjunctsKeyPtr)
{
MDAM_DEBUG0(MTL2, "Winner is Mdam Disjuncts");
mdamIsWinner = TRUE;
}
} // single subset vs others
DCMPASSERT(winnerCostPtr != NULL); // somebody must win
// one of the keys must be set:
DCMPASSERT(searchKeyPtr != NULL OR mdamKeyPtr != NULL);
// but not both:
DCMPASSERT(NOT(searchKeyPtr != NULL AND mdamKeyPtr != NULL));
MDAM_DEBUGX(MTL2, MdamTrace::printCostObject(this, winnerCostPtr,
"Winner Cost"));
// compute blocks read per access
computeNumberOfBlocksToReadPerAccess(*winnerCostPtr
,mdamIsWinner, winnerCostPtrNumKBytes);
// LCOV_EXCL_START :dpm
#ifndef NDEBUG
if (CURRSTMT_OPTDEFAULTS->optimizerHeuristic2()) {
if ( isIndexJoin )
(*CURRSTMT_OPTGLOBALS->indexJoinMonitor).exit();
}
#endif //NDEBUG
// LCOV_EXCL_STOP
MDAM_DEBUG0(MTL2, "END Scan Costing ********************************\n\n");
return winnerCostPtr;
} // FileScanOptimizer::optimize()
// Pass the join histograms when available
void
FileScanOptimizer::computeNumberOfBlocksToReadPerAccess(
const Cost& scanCost, NABoolean &isMDAM, CostScalar numKBytes)
{
// $$ Needs code to check performance goal, for now
// $$ assume it is last row.
CostScalar blockSizeInKb = getIndexDesc()->getBlockSizeInKb();
// KB for index blocks plus data blocks for one partition for all
// probes:
CostScalar blocksForAllProbes =
(numKBytes/blockSizeInKb).getCeiling();
CostScalar probes = MIN_ONE(scanCost.getCplr().getNumProbes());
// Substract index blocks to obtain the data blocks:
CostScalar indexBlocksForAllProbes =
getIndexDesc()->getEstimatedIndexBlocksLowerBound(probes);
CostScalar dataBlocksForAllProbes =
MIN_ONE(blocksForAllProbes - indexBlocksForAllProbes);
// Estimate blocks per probe:
CostScalar dataBlocksPerProbe = MIN_ONE(dataBlocksForAllProbes / probes);
CostScalar dp2CacheSize = getDP2CacheSizeInBlocks(blockSizeInKb);
// -------------------------------------------------------------
// Compute the blocks in the inner table:
// -------------------------------------------------------------
const CostScalar totalRowsInInnerTable =
getRawInnerHistograms().getRowCount().getCeiling();
const CostScalar estimatedRecordsPerBlock =
getIndexDesc()->getEstimatedRecordsPerBlock();
CostScalar totalBlocksInFullTableScan;
computeBlocksUpperBound(
totalBlocksInFullTableScan /* out */
,totalRowsInInnerTable /* in */
,estimatedRecordsPerBlock /* in */
);
CostScalar blocksToRead;
if (NOT probes.isGreaterThanOne() AND NOT isMDAM)
{
// One probe, non-mdam, is always one access, so
// the blocks to read are the data blocks hit by the probe.
blocksToRead = dataBlocksPerProbe;
}
// Below we handle the multiple probe case, MDAM and non-MDAM.
// We put together MDAM with non-MDAM because we see MDAM access
// as an instance of a "unique access"". This is, we think of
// MDAM as if all of the blocks read in a single MDAM access were
// contigous. This assumption results in that the minimum number
// of blocks to read in the MDAM case will be given by the total
// blocks read per MDAM access (i.e. per probe). This is a good
// compromise.
else if (getInOrderProbesFlag())
{
// Multiple probe case (NJ), in-order probes,
// maybe single probe MDAM or multiple in-order probe MDAM
// We read all of the blocks that the probes hit if the
// probes are "near" or just the blocks per probe if the probes
// are "apart". The reason is that if the probes are near read-ahead
// will be of benefit because probes that are waiting to be served
// will hit the cache when there is read ahead
const CostScalar density =
CostPrimitives::getBasicCostFactor(COST_PROBE_DENSITY_THRESHOLD);
DCMPASSERT(density >= 0 AND density <= 1.);
// The density tells how "apart" are the probes
// If the ratio of the total blocks that the
// probes are hitting to the total blocks in
// the table is greater than the "density" then
// we say the blocks that the probes are
// hitting are "close" to each other and thus
// it is better to read all of the blocks for all
// of the probes (Note that we are implicitly assuming
// that every probe hits a different block. This is
// obviously not the case in general. We could use
// histograms to decide this)
if (dataBlocksForAllProbes/totalBlocksInFullTableScan >= density)
{
// The probes are "near" each other, read all of the
// blocks they touch (limit the blocks by the
// table size):
blocksToRead = MINOF(totalBlocksInFullTableScan.getValue()
,dataBlocksForAllProbes.getValue());
}
else
{
// Blocks are apart, thus only read the blocks that
// a single probe hits:
blocksToRead = dataBlocksPerProbe;
}
} // MP, in-order
else
{ // NJ, single subset, random, maybe multiple random probe MDAM
// Because we are randonmly probing the table
// the probes cannot benefit by reading ahead, thus
// no need to do read-ahead (unless the full table fits
// in the DP2 cache!)
if (totalBlocksInFullTableScan <= dp2CacheSize)
{
blocksToRead = totalBlocksInFullTableScan;
}
else
{
blocksToRead = dataBlocksPerProbe;
}
}
DCMPASSERT(blocksToRead.isGreaterThanZero());
//overflow occuring while casting it to long
//the below is a check to avoid the overflow
//CR 10-010815-4585
if(blocksToRead.getValue() < double(INT_MAX))
setNumberOfBlocksToReadPerAccess(Lng32(blocksToRead.getValue()));
else
setNumberOfBlocksToReadPerAccess(INT_MAX);
} // FileScanOptimizer::computeNumberOfBlocksToReadPerAccess(...)
// -----------------------------------------------------------------------
// Use this routine to compute the cost of a given SearchKey
// INPUT:
// sarchKey: the SearchKey to cost
// histograms: Raw (i.e. predicates have not been applied)
// histograms for the scan table
// breakOnConflict: If TRUE then the Cost* returned will be NULL
// to indicate that there is a conflict in
// the predicate expression associated with the key,
// If FALSE the routine returns the cost of the
// SearchKey
//
// OUTPUT:
// A NON-NULL Cost* indicating the Cost for searchKey if breakOnConflict
// was not true OR (if breakOnConflict was true and there was
// a conflict in the predicate expression)
//
// A NULL Cost* indicating that breakOnConflict was true and there
// was a conflict in the predicate expression.
// -----------------------------------------------------------------------
Cost*
FileScanOptimizer::computeCostForSingleSubset(
SearchKey& searchKey,
const NABoolean& weAreConsideringMDAM,
CostScalar & numKBytes)
{
if (CmpCommon::getDefault(SIMPLE_COST_MODEL) == DF_ON)
{
return scmComputeCostForSingleSubset();
}
// LCOV_EXCL_START :cnu -- OCM code
MDAM_DEBUG0(MTL2, "BEGIN Single Subset Costing --------");
// This was added as part of the project
// to reduce compilation time by reusing simple cost vectors. 09/18/00
NABoolean sharedCostFound = FALSE;
FileScanBasicCost *
fileScanBasicCostPtr = shareBasicCost(sharedCostFound);
SimpleCostVector &
firstRow = fileScanBasicCostPtr->getFRBasicCostSingleSubset();
SimpleCostVector &
lastRow = fileScanBasicCostPtr->getLRBasicCostSingleSubset();
#ifndef NDEBUG
(*CURRSTMT_OPTGLOBALS->singleSubsetCostMonitor).enter();
#endif //NDEBUG
// $$$ This only works for key sequenced files, remove later
// DCMPASSERT(getIndexDesc()->getNAFileSet()->isKeySequenced());
// $$$ Add special code for entry sequence and relative
// $$$ sequence files
// -----------------------------------------------------------------------
// Compute SearchKey dependant info:
// -----------------------------------------------------------------------
const ValueIdSet &exePreds = searchKey.getExecutorPredicates();
ColumnOrderList keyPredsByCol(getIndexDesc()->getIndexKey());
searchKey.getKeyPredicatesByColumn(keyPredsByCol);
ValueIdSet keyPredicates;
keyPredsByCol.getAllPredicates(keyPredicates);
// -----------------------------------------------------------------------
// Chech for a conflicting predicate expression and compute the
// single subset predicates.
//
// If there is a conflict and we are considering MDAM, favor mdam
// by returning a NULL cost for the single subset:
//
// The single subset preds are all those key preds up to
// (but not including) the first missing key column
// -----------------------------------------------------------------------
// Compute the column position just before the first missing
// key column (singleSubsetPrefixColumn, if it is greater than cero,
// otherwise the code needs to explicitly check for the possibility
// of not having predicates in the zeroth order):
CollIndex singleSubsetPrefixColumn;
NABoolean itIsSingleSubset =
keyPredsByCol.getSingleSubsetOrder(singleSubsetPrefixColumn);
DCMPASSERT(itIsSingleSubset);
// return NULL if conflict, compute single subset preds:
const ValueIdSet *nonMissingKeyColumnPredsPtr = NULL;
ValueIdSet singleSubsetPreds;
const ValueIdSet *partPreds = NULL;
CollIndex leadingPartPreds = 0;
for (CollIndex i=0; i <= singleSubsetPrefixColumn; i++)
{
if (i == leadingPartPreds)
{
partPreds = keyPredsByCol[i];
if (partPreds AND NOT partPreds->isEmpty())
{
ValueId predId;
BiRelat * predIEptr;
for(predId=partPreds->init();
partPreds->next(predId);
partPreds->advance(predId))
{
if (predId.getItemExpr()->getArity() == 2)
{
predIEptr=(BiRelat *)predId.getItemExpr();
if (predIEptr->isaPartKeyPred())
{
leadingPartPreds += 1;
break;
}
}
}
}
}
if (keyPredsByCol.getPredicateExpressionPtr(i))
{
NABoolean unUsablePred =
((keyPredsByCol.getPredicateExpressionPtr(i)->getType() ==
KeyColumns::KeyColumn:: CONFLICT)
OR
(keyPredsByCol.getPredicateExpressionPtr(i)->getType() ==
KeyColumns::KeyColumn::CONFLICT_EQUALS));
if ( weAreConsideringMDAM
AND
unUsablePred )
{
#ifndef NDEBUG
(*CURRSTMT_OPTGLOBALS->singleSubsetCostMonitor).exit();
#endif //NDEBUG
MDAM_DEBUG0(MTL2,
"Single Subset Cost is NULL, conflicts in predicate.");
MDAM_DEBUG0(MTL2, "END Single Subset Costing --------\n");
return NULL;
}
nonMissingKeyColumnPredsPtr = keyPredsByCol[i];
if (unUsablePred)
{
ValueIdSet setOfPreds = searchKey.getExecutorPredicates();
setOfPreds.insert(*nonMissingKeyColumnPredsPtr);
searchKey.setExecutorPredicates(setOfPreds);
}
if (nonMissingKeyColumnPredsPtr != NULL)
{
singleSubsetPreds.insert(*nonMissingKeyColumnPredsPtr);
}
}
else
{
// The only case when an order less than or equal
// to the single subset prefix order is empty is
// when the order is cero:
DCMPASSERT(singleSubsetPrefixColumn==0);
break;
}
} // for every key col in the sing. subset prefix
// If we reach here then singleSubsetPreds have been correctly computed
// -----------------------------------------------------------------------
// Estimate seeks and sequential_kb_read:
// -----------------------------------------------------------------------
CostScalar
seeks // total number of disk arm movements
,seq_kb_read=csZero; // total number of blocks that were read sequentially
// Do the shared cost return here because of the possibility of resetting
// the executor predicates above.
if ( sharedCostFound AND
firstRow.getCPUTime() > csZero AND
lastRow.getCPUTime() > csZero
)
{
if ( CURRSTMT_OPTDEFAULTS->reuseBasicCost() )
{
Cost * costPtr = computeCostObject(firstRow, lastRow);
numKBytes = fileScanBasicCostPtr->getSingleSubsetNumKBytes();
MDAM_DEBUG0(MTL2, "Reuse Basic Cost");
MDAM_DEBUG0(MTL2, "END Single Subset Costing --------\n");
return costPtr;
}
else
{
firstRow.reset();
lastRow.reset();
}
}
CostScalar temp_dataRows=csZero;
CostScalar * dataRows=&temp_dataRows; // the number of rows that match
// Are we under a nested join?
// -----------------------------------------------------------------------
// The probes are the TOTAL probes coming from the scan's parent. But
// depending on the key predicates and the partitioning key, the scan
// may be actually receiving fewer probes. The probes that the
// scan is actually receiving is given by the repeat count.
// -----------------------------------------------------------------------
CostScalar repeatCount = getContext().getPlan()->getPhysicalProperty()->
getDP2CostThatDependsOnSPP()->getRepeatCountForOperatorsInDP2() ;
// Costing works estimating the resources of the whole, unpartitioned
// table, thus get the total probes by multiplying by the active
// partitions
CostScalar
probes = repeatCount * getNumActivePartitions()
,successfulProbes = csOne; // all the probes that matched data
CostScalar finalRows=getResultSetCardinality();
// Patch for Sol.10-031024-0755 (Case 10-031024-9406). When RI
// constraints were implemented, corresponding histograms, cardinality
// and costing were forgotten. As a result nested join into referenced
// table does not pass histogram information to the right child.
// If the right child does not have histograms in inputLogProp
// it considers this join as a cross product. So, for file_scan_unique
// the number of rows is the product of the number of right child rows
// and the number of probes. Then the cost of file_scan_unique is greatly
// overestimated. The patch will set this number by the number of
// probes which should be OK for RI constraints check.
// The second part of this patch is in categorizeProbes. The number of
// rows to cost was 0 in case of empty outputHistograms which is not
// right if we have a real cross product, I changed it to the product
// of probes and table cardinality.
if ( (CmpCommon::getDefault(COMP_BOOL_34) == DF_OFF) AND
searchKey.isUnique()
)
finalRows = probes;
DCMPASSERT(repeatCount <= probes);
const CostScalar estimatedRecordsPerBlock =
getIndexDesc()->getEstimatedRecordsPerBlock();
const CostScalar blockSizeInKb = getIndexDesc()->getBlockSizeInKb();
// Is this a Scan on the right-hand side of a Nested Join?
// Typically, for nested joins, the repeatCount will be greater than
// one AND the input ColStats will be non-empty. However, in some
// cases such as cross-products, the input ColStats are empty.
// Also, in some cases repeat count is 1, but ColStats is not emtpy.
// Here we treat this case as a multiprobe.
//
if ((repeatCount.isGreaterThanOne()) OR
(getContext().getInputLogProp()->getColStats().entries() > 0))
{
// CR 10-010822-4815
// Query 04 in OBI was producing a bad plan as the seeks for Nested Join were
// computed without taking into account if the blocks of inner table are
// consecutive or not. This is fixed by computing the seeks after getting the
// right count of blocks by getting the rows for the key predicates on the leading
// columns with a Value Equality Group referencing a constant.
// "totalPreds" is the set of all predicates of all key columns with a constant
// for all the index values find the keyPredicates, for every predicate check
// if it is a VEG and that VEG has a constant, if so then add the predicate to
// the "totalPreds". Repeat this only for the leading columns, if the column
// doesn't have a constant then stop searching. Apply the "totalPreds" to the
// histograms to get the rowcount, divide the rowcount by recordsperblock to
// get the blocks. If no constant then pass the total blocks for inner table
ValueIdSet totalPreds;
CollIndex columnWithAConstExpr = NULL_COLL_INDEX;
NABoolean hasAtleastOneConstExpr = false;
CostScalar innerBlocksInSequence; //Blocks to be passed to compute seeks
const ValueIdSet *keyPreds = NULL;
for (CollIndex Indx=0; Indx <= singleSubsetPrefixColumn; Indx++)
{
keyPreds = keyPredsByCol[Indx]; //column which is part of key
NABoolean curFound = FALSE;
if (keyPreds AND NOT keyPreds->isEmpty())
{
ValueId predId;
for(predId=keyPreds->init(); //Inner for loop for all the
keyPreds->next(predId); //the predicates of the column
keyPreds->advance(predId))
{
const ItemExpr *pred = predId.getItemExpr();
if ( ((VEGPredicate *)pred)->getOperatorType()
== ITM_VEG_PREDICATE )
{
const VEG * predVEG = ((VEGPredicate *)pred)->getVEG();
const ValueIdSet & VEGGroup = predVEG->getAllValues();
if (VEGGroup.referencesAConstExpr())
{
totalPreds += predId;
columnWithAConstExpr = Indx;
hasAtleastOneConstExpr = true;
curFound = TRUE;
}//end of check for A Constant Expression
else
break;
}//end of "if" Operator to be VEG predicate
}//end of inner loop "for(predId=partPreds->init();"
}//end of "if" predicates to be non empty
if (NOT curFound)
break;
}//end of outer loop "for (CollIndex i=0; i <= singleSubsetPre..
// it seems that we are under a nested join thus...
CostScalar
failedProbes = csZero // all the probes that fail to matched data
,uniqueSuccProbes = csZero // all the probes that don't have duplicates
,duplicateSuccProbes =csZero // duplicates among the unique succ. probes
,uniqueFailedProbes =csZero; //
// unique failed probes to compute seeks
// -------------------------------------------------------------
// Compute the blocks in the inner table:
// -------------------------------------------------------------
const CostScalar
totalRowsInInnerTable =
getRawInnerHistograms().getRowCount().getCeiling();
CostScalar innerBlocksUpperBound;
computeBlocksUpperBound(
innerBlocksUpperBound
,totalRowsInInnerTable
,estimatedRecordsPerBlock);
// -------------------------------------------------------------
// Compute innerHistograms
// -------------------------------------------------------------
// first get the histograms for the probes:
Histograms
outerHistograms(getContext().getInputLogProp()->getColStats());
// Then get the histograms for the inner table (this scan):
IndexDescHistograms innerHistograms(*getIndexDesc(),
singleSubsetPrefixColumn+1);
// It is better to initialize it to the inner table cardinality than
// left it as zero. dataRows will be adjusted in categorizeProbes
// based on outerHistograms information.
*dataRows = innerHistograms.getRowCount();
categorizeProbes(successfulProbes /* out */
,uniqueSuccProbes /* out */
,duplicateSuccProbes /* out */
,failedProbes /* out */
,uniqueFailedProbes
,probes
,singleSubsetPreds
,outerHistograms
,FALSE // this is not MDAM!
,dataRows
);
CostScalar uniqueProbes =
uniqueSuccProbes + uniqueFailedProbes;
setProbes(probes);
setSuccessfulProbes(successfulProbes);
setUniqueProbes(uniqueSuccProbes + uniqueFailedProbes);
setDuplicateSuccProbes(duplicateSuccProbes);
// Patch for Sol.10-031024-0755 (Case 10-031024-9406). See above.
// If the number of probes is relatively small then we want to use
// file_scan_unique instead of full index scan. Each probe will
// bring one block to DP2 cache.
if ( (CmpCommon::getDefault(COMP_BOOL_34) == DF_OFF) AND
searchKey.isUnique() AND
outerHistograms.isEmpty()
)
*dataRows = probes*estimatedRecordsPerBlock;
// -----------------------------------------------------------
// Compute the rows and blks per successful probe:
// -----------------------------------------------------------
// $$$ It would be best to get blocks instead of rows...
CostScalar rowsPerSuccessfulProbe = csZero;
if (NOT successfulProbes.isLessThanOne())
{
rowsPerSuccessfulProbe = *dataRows/successfulProbes;
}
CostScalar blksPerSuccProbe =
( rowsPerSuccessfulProbe / estimatedRecordsPerBlock).getCeiling();
// No successful probe can grab less than one block:
if (NOT successfulProbes.isLessThanOne())
{
blksPerSuccProbe.minCsOne();
}
// --------------------------------------------------------------
// Check whether the whole answer set fits into cache:
// --------------------------------------------------------------
// compute the blocks in the answer set after key predicates
// were applied (they cannot be more than the total blocks in
// the raw table):
// getCeiling() because it is an upper bound...
const CostScalar totalUniqueSuccProbes =
uniqueSuccProbes * getNumActivePartitions();
CostScalar totalBlocksLowerBound; // the blocks in the answer set
computeTotalBlocksLowerBound(
totalBlocksLowerBound /* out */
,totalUniqueSuccProbes
,rowsPerSuccessfulProbe
,estimatedRecordsPerBlock
,innerBlocksUpperBound /* the total blocks in the inner table */
);
CostScalar cacheSizeInBlocks =
getDP2CacheSizeInBlocks(blockSizeInKb);
CostScalar indexBlocksLowerBound =
getIndexDesc()->getEstimatedIndexBlocksLowerBound(probes);
// The begin blocks denote the starting data
// block for the subset that each succ. probe generates
CostScalar beginBlocksLowerBound;
computeBeginBlocksLowerBound(
beginBlocksLowerBound /* out */
,uniqueSuccProbes
,innerBlocksUpperBound);
// --------------------------------------------------------------------
// At this point we are looking at the selectivity for
// *ALL* partitions, thus the test for whether the answer set
// fits in the cache must account for the cache in all partitions:
// --------------------------------------------------------------------
// First determine whether probes order matches the scan's
// clustering order:
const InputPhysicalProperty* ippForMe =
getContext().getInputPhysicalProperty();
NABoolean partiallyInOrderOK = TRUE;
NABoolean probesForceSynchronousAccess = FALSE;
if ((ippForMe != NULL) AND
ordersMatch(ippForMe,
getIndexDesc(),
&(getIndexDesc()->getOrderOfKeyValues()),
getRelExpr().getGroupAttr()->getCharacteristicInputs(),
partiallyInOrderOK,
probesForceSynchronousAccess))
{
setInOrderProbesFlag(TRUE);
setProbesForceSynchronousAccessFlag(probesForceSynchronousAccess);
}
else if ((ippForMe != NULL) AND
(ippForMe->getAssumeSortedForCosting()) AND
(!(ippForMe->getExplodedOcbJoinForCosting())) AND
(ippForMe->getNjOuterOrderPartFuncForNonUpdates() == NULL))
{
// assumeSortedForCosting_ flag is set for two cases:
// case 1: when input is rowset.
// case 2: when left child partFunc njOuterPartFuncForNonUpdates_
// is passed for NJ plan 0. This is only for cost estimation
// of exchange operator and not scan operator.
// So we should come here only for case1.
// To avoid case2, we check njOuterPartFuncForNonUpdates_ for NULL.
setInOrderProbesFlag(TRUE);
}
else
{
setInOrderProbesFlag(FALSE);
}
// Determine amount of DP2 cache is available per concurrent user.
NADefaults &defs = ActiveSchemaDB()->getDefaults();
const CostScalar concurrentUsers = defs.getAsLong(NUMBER_OF_USERS);
CostScalar cacheForAllVolumes = cacheSizeInBlocks
* getNumActiveDP2Volumes()
/ concurrentUsers;
NABoolean inOrderProbes = getInOrderProbesFlag();
CostScalar seekComputedWithDp2ReadAhead;
// See if blocks to be read fit in cache.
if( hasAtleastOneConstExpr )
{
IndexDescHistograms innerHistograms(*getIndexDesc(),
columnWithAConstExpr+1);
const SelectivityHint * selHint = getIndexDesc()->getPrimaryTableDesc()->getSelectivityHint();
const CardinalityHint * cardHint = getIndexDesc()->getPrimaryTableDesc()->getCardinalityHint();
innerHistograms.applyPredicates(totalPreds, getRelExpr(), selHint, cardHint, REL_SCAN);
//GET ROW count
const CostScalar totalRowCount = innerHistograms.getRowCount();
//GET BLOCKS which are in sequence
innerBlocksInSequence = totalRowCount / estimatedRecordsPerBlock;
}
else
innerBlocksInSequence = innerBlocksUpperBound;
if (totalBlocksLowerBound <= cacheForAllVolumes)
{
// It fits, FULL cache benefit:
computeIOForFullCacheBenefit(seeks /* out */
,seq_kb_read /* out */
,beginBlocksLowerBound
,totalBlocksLowerBound
,indexBlocksLowerBound);
computeSeekForDp2ReadAheadAndProbeOrder(
seekComputedWithDp2ReadAhead
,finalRows
,uniqueProbes
,beginBlocksLowerBound
,totalBlocksLowerBound
,innerBlocksInSequence
,cacheForAllVolumes
,inOrderProbes);
seeks = indexBlocksLowerBound +
seekComputedWithDp2ReadAhead.getCeiling();
}
else
{
// the answer set does not fit in its entirety,
if (getInOrderProbesFlag())
{
// probes order and inner order match!
// We have three cases:
// 1.- inner & outer match no duplicates
// 2.- inner & outer match duplicates, data
// each successful duplicate matches fits
// in cache
// 3.- inner & outer match duplicates, data
// each successful duplicate matches does not fit
// in cache
if ( NOT duplicateSuccProbes.isGreaterThanZero() )
{
// in order, no duplicates
// blocks are read once independently of the cache:
computeIOForFullCacheBenefit(
seeks
,seq_kb_read
,beginBlocksLowerBound
,totalBlocksLowerBound
,indexBlocksLowerBound);
computeSeekForDp2ReadAheadAndProbeOrder(
seekComputedWithDp2ReadAhead
,finalRows
,uniqueProbes
,beginBlocksLowerBound
,totalBlocksLowerBound
,innerBlocksInSequence
,cacheForAllVolumes
,inOrderProbes);
seeks = indexBlocksLowerBound +
seekComputedWithDp2ReadAhead.getCeiling();
}
else // in order but duplicates exist
{
// there are some successful duplicates
if (blksPerSuccProbe <= cacheSizeInBlocks)
{
// in order, duplicates, data for duplicates
// fits in cache:
// blocks are read once independent of the cache:
computeIOForFullCacheBenefit(
seeks /* out */
,seq_kb_read /* out */
,beginBlocksLowerBound
,totalBlocksLowerBound
,indexBlocksLowerBound);
computeSeekForDp2ReadAheadAndProbeOrder(
seekComputedWithDp2ReadAhead
,finalRows
,uniqueProbes
,beginBlocksLowerBound
,totalBlocksLowerBound
,innerBlocksInSequence
,cacheForAllVolumes
,inOrderProbes);
seeks = indexBlocksLowerBound +
seekComputedWithDp2ReadAhead.getCeiling();
}
else // data blocks per duplicate does not fit in cache!
{
// in order, duplicates, data for duplicates
// does not fits in cache:
// extra blocks will need to be read for
// duplicates:
const CostScalar
extraDuplicateProbes = duplicateSuccProbes;
const CostScalar extraDuplicateBlocks =
extraDuplicateProbes * blksPerSuccProbe;
const CostScalar
seqBlocksRead =
CostScalar(
totalBlocksLowerBound
+ indexBlocksLowerBound
+extraDuplicateBlocks
).
getCeiling();
seq_kb_read = seqBlocksRead * blockSizeInKb;
// We assume that the index blocks will be there
// for the duplicate to find its data,
// thus no need to add the extraDuplicate
// probes index blocks seeks.
seeks =
CostScalar(beginBlocksLowerBound
+ indexBlocksLowerBound).getCeiling();
computeSeekForDp2ReadAheadAndProbeOrder(
seekComputedWithDp2ReadAhead
,finalRows
,uniqueProbes
,beginBlocksLowerBound
,totalBlocksLowerBound
,innerBlocksInSequence
,cacheForAllVolumes
,inOrderProbes);
seeks = indexBlocksLowerBound +
seekComputedWithDp2ReadAhead.getCeiling();
} // if data for duplicates does not fit
} // if there are duplicates
}
else // if orders don't match
{
// orders don't match, RANDOM case:
computeIOForRandomCase(seeks
,seq_kb_read
,blksPerSuccProbe
,beginBlocksLowerBound
,totalBlocksLowerBound
,successfulProbes
,failedProbes
,indexBlocksLowerBound);
computeSeekForDp2ReadAheadAndProbeOrder(
seekComputedWithDp2ReadAhead, finalRows, uniqueProbes,
beginBlocksLowerBound, totalBlocksLowerBound,
innerBlocksInSequence,cacheForAllVolumes,
inOrderProbes);
seeks = indexBlocksLowerBound +
seeks = indexBlocksLowerBound +
seekComputedWithDp2ReadAhead.getCeiling();
}
} // if answer set does not fit into cache
} // if probes > 0 and there are stats for probes
else
{
// not a nested join, thus no cache benefit is possible
// make sure probes are at least one (to satisfy Roll-up formulas):
successfulProbes = probes = csOne;
// probes = 1, no cache benefit
// Apply those key predicates that reference key columns
// before the first missing key to the histograms:
IndexDescHistograms innerHistograms(*getIndexDesc(),
singleSubsetPrefixColumn+1);
innerHistograms.applyPredicates(singleSubsetPreds, getRelExpr(), NULL, NULL, REL_SCAN);
// Now, compute the number of rows:
*dataRows = innerHistograms.getRowCount();
DCMPASSERT(*dataRows >= csZero);
seeks = seq_kb_read = csZero;
if (dataRows->isGreaterThanZero())
{
// Compute the index blocks touched:
CostScalar indexBlocks =
getIndexDesc()->getIndexLevels() - 1;
//if ( indexBlocks < CostScalar(0.0) )
// indexBlocks = CostScalar(0.0);
indexBlocks.minCsZero();
// All rows are contiguous (single subset), thus compute
// the i/o:
// and all rows are packed together:
CostScalar blocksRead =
(*dataRows/estimatedRecordsPerBlock).getCeiling()
+
indexBlocks;
seq_kb_read = blocksRead*blockSizeInKb;
// The seeks are the one for each index block traversed
// (except the root) plus one for the data block:
seeks =
indexBlocks
+
csOne;
// If we have partitioning key predicates then we will be
// reading different parts of the partition by different esps.
// There will be some seeks going from one esp reading to
// another. Assume that we will at least read ahead
// the normal amount (for now reduce possible seeks by 100).
NADefaults &defs = ActiveSchemaDB()->getDefaults();
const CostScalar maxDp2ReadInBlocks =
CostScalar(defs.getAsULong(DP2_MAX_READ_PER_ACCESS_IN_KB))/
blockSizeInKb;
if (leadingPartPreds > 0 AND blocksRead > maxDp2ReadInBlocks)
{
seeks += blocksRead / maxDp2ReadInBlocks / 100;
}
// temporary patch for solution 10-041104-1450. To make hash join
// look more expensive that nested join if right table is not very
// small we add one seek to the right scan.
if ( blocksRead > csTwo )
{
seeks++;
blocksRead++;
}
DCMPASSERT(seeks <= blocksRead);
}
} // if probes == 1.0
// -----------------------------------------------------------------------
// We have computed the transfer cost (seeks, seq_kb_read),
// now compute the cost vectors:
// -----------------------------------------------------------------------
CostScalar seqKBytesPerScan;
// Ceilings for the final values are taken inside computeCostVectors
#ifndef NDEBUG
(*CURRSTMT_OPTGLOBALS->singleVectorCostMonitor).enter();
#endif //NDEBUG
computeCostVectors(firstRow // out
,lastRow // out
,seqKBytesPerScan //out
,*dataRows
,probes
,successfulProbes
,seeks
,seq_kb_read
,keyPredicates
,exePreds
,probes
);
#ifndef NDEBUG
(*CURRSTMT_OPTGLOBALS->singleVectorCostMonitor).exit();
(*CURRSTMT_OPTGLOBALS->singleObjectCostMonitor).enter();
CURRSTMT_OPTGLOBALS->synCheckFlag = TRUE;
#endif //NDEBUG
// ---------------------------------------------------------------------
// Done!, create the cost vector:
// ---------------------------------------------------------------------
Cost *costPtr = computeCostObject(firstRow, lastRow);
#ifndef NDEBUG
CURRSTMT_OPTGLOBALS->synCheckFlag = FALSE;
(*CURRSTMT_OPTGLOBALS->singleObjectCostMonitor).exit();
(*CURRSTMT_OPTGLOBALS->singleSubsetCostMonitor).exit();
#endif //NDEBUG
numKBytes = seqKBytesPerScan;
fileScanBasicCostPtr->setSingleSubsetNumKBytes(numKBytes);
MDAM_DEBUG0(MTL2, "END Single Subset Costing --------\n");
return costPtr;
// LCOV_EXCL_STOP
}// computeCostForSingleSubset(...)
// LCOV_EXCL_START :dpm
#ifndef NDEBUG
void
FileScanOptimizer::runMdamTests
( const MdamKey* mdamKeyPtr,
const Cost * costBoundPtr,
NABoolean mdamForced,
ValueIdSet exePreds,
NABoolean checkExePreds,
NABoolean mdamTypeIsCommon
)
{
enum MdamTraceLevel origLevel = MdamTrace::level();
MdamTrace::setLevel(MTL1);
MDAM_DEBUG1(MTL1, "Consider MDAM for Query:\n%s\n",
CmpCommon::statement()->userSqlText());
NABoolean reUseBasicCost = CURRSTMT_OPTDEFAULTS->reuseBasicCost();
CURRSTMT_OPTDEFAULTS->setReuseBasicCost(FALSE);
CostScalar numKBytesOld;
MdamKey *sharedMdamKeyPtrOld = NULL;
MdamKey *mdamKeyPtrOld =
new HEAP MdamKey(mdamKeyPtr->getKeyColumns(),
mdamKeyPtr->getOperatorInputs(),
mdamKeyPtr->getDisjuncts(),
mdamKeyPtr->getNonKeyColumnSet(),
mdamKeyPtr->getIndexDesc());
SET_MDAM_TRACE_HEADER("[Old Mdam Costing] ");
DECLARE_MDAM_MONITOR(oldMdamMon);
ENTER_MDAM_MONITOR(oldMdamMon);
Cost *costOld = oldComputeCostForMultipleSubset(mdamKeyPtrOld,
costBoundPtr,
mdamForced,
numKBytesOld,
checkExePreds,
mdamTypeIsCommon,
sharedMdamKeyPtrOld);
EXIT_MDAM_MONITOR(oldMdamMon);
PRINT_MDAM_MONITOR(oldMdamMon, "Old MDAM Costing Time: ");
CostScalar numKBytesNew;
MdamKey *sharedMdamKeyPtrNew = NULL;
MdamKey *mdamKeyPtrNew =
new HEAP MdamKey(mdamKeyPtr->getKeyColumns(),
mdamKeyPtr->getOperatorInputs(),
mdamKeyPtr->getDisjuncts(),
mdamKeyPtr->getNonKeyColumnSet(),
mdamKeyPtr->getIndexDesc());
SET_MDAM_TRACE_HEADER("[New Mdam Costing] ");
DECLARE_MDAM_MONITOR(newMdamMon);
ENTER_MDAM_MONITOR(newMdamMon);
Cost *costNew = newComputeCostForMultipleSubset(mdamKeyPtrNew,
costBoundPtr,
mdamForced,
numKBytesNew,
exePreds,
checkExePreds,
mdamTypeIsCommon,
sharedMdamKeyPtrNew);
EXIT_MDAM_MONITOR(newMdamMon);
PRINT_MDAM_MONITOR(newMdamMon, "New MDAM Costing Time: ");
SET_MDAM_TRACE_HEADER(NULL);
if(costOld && costNew)
{
COMPARE_RESULT result = costOld->compareCosts(*costNew);
if(result != SAME)
{
MDAM_DEBUG0(MTL1, "Different MDAM Cost Results");
MDAM_DEBUGX(MTL1, MdamTrace::printCostObject(this, costOld, "Old Cost"));
MDAM_DEBUGX(MTL1, MdamTrace::printCostObject(this, costNew, "New Cost"));
}
else {
MDAM_DEBUG0(MTL1, "Same MDAM Cost Results");
MDAM_DEBUGX(MTL1,
MdamTrace::printCostObject(this, costOld, "Old/New Cost"));
}
}
else if((costOld && !costNew) || (!costOld && costNew))
{
MDAM_DEBUG0(MTL1, "Different MDAM Cost Results");
MDAM_DEBUGX(MTL1, MdamTrace::printCostObject(this, costOld, "Old Cost"));
MDAM_DEBUGX(MTL1, MdamTrace::printCostObject(this, costNew, "New Cost"));
}
if(numKBytesNew != numKBytesOld){
MDAM_DEBUG0(MTL1, "Different MDAM numKBytes value");
MDAM_DEBUG1(MTL1, "Old Value is %f", numKBytesOld.getValue());
MDAM_DEBUG1(MTL1, "New Value is %f", numKBytesNew.getValue());
}
CURRSTMT_OPTDEFAULTS->setReuseBasicCost(reUseBasicCost);
MdamTrace::setLevel(origLevel);
}
#endif
// LCOV_EXCL_STOP
NABoolean FileScanOptimizer::isMDAMFeasibleForHBase(const IndexDesc* idesc, ValueIdSet& preds)
{
Lng32 threshold = (Lng32)(ActiveSchemaDB()->getDefaults()).
getAsLong(MDAM_NO_STATS_POSITIONS_THRESHOLD);
if ( preds.isEmpty() || threshold == 0 )
return FALSE;
const ColStatDescList& csdl =
idesc->getPrimaryTableDesc()->getTableColStats();
// If any key column has real stats, then go with the normal mdamp code path
for (CollIndex k=0; k<idesc->getIndexKey().entries(); k++) {
ColStatsSharedPtr colStat =
csdl.getColStatsPtrForColumn((idesc->getIndexKey()[k]));
if ( colStat && colStat->isFakeHistogram() == FALSE )
return FALSE;
}
ARRAY(Int32) uecsByKeyColumns(HEAP);
ARRAY(Int32) rangesByKeyColumns(HEAP);
NABoolean possiblyUseMdam = FALSE;
for (ValueId e=preds.init(); preds.next(e); preds.advance(e))
{
if (e.getItemExpr()->getOperatorType() == ITM_RANGE_SPEC_FUNC)
{
RangeSpecRef* rangeIE = (RangeSpecRef *) e.getItemExpr();
OptNormRangeSpec* destObj = rangeIE->getRangeObject();
ItemExpr* rangeCol = destObj->getRangeExpr();
// check whether this RangeSpecRef is on one of the key columns
for (CollIndex k=0; k<idesc->getIndexKey().entries(); k++)
if (rangeCol->containsTheGivenValue(idesc->getIndexKey()[k]))
{
possiblyUseMdam = TRUE;
Int32 totalUec = destObj->getTotalDistinctValues(HEAP);
if ( totalUec == -1 ) {
return FALSE; // can not determine #distinct values covered
}
// compute and store #uecs
if ( !uecsByKeyColumns.used(k) || totalUec > uecsByKeyColumns[k] )
uecsByKeyColumns.insert(k, totalUec);
// compute and store #ranges
Int32 ranges = destObj->getTotalRanges();
if ( !rangesByKeyColumns.used(k) || ranges > rangesByKeyColumns[k] )
rangesByKeyColumns.insert(k, ranges);
break;
}
}
}
if ( possiblyUseMdam && uecsByKeyColumns.used(0) && uecsByKeyColumns[0] > 0 ) {
// find the last key column with range spec.
CollIndex last;
for (last=rangesByKeyColumns.entries()-1; last>=0; last--) {
if ( rangesByKeyColumns.used(last) )
break;
}
Int32 numMDAMColumns = 1;
for (CollIndex j=0; j<last; j++) {
if ( uecsByKeyColumns.used(j) )
numMDAMColumns *= uecsByKeyColumns[j];
else
return FALSE;
}
numMDAMColumns *= rangesByKeyColumns[last];
return ( numMDAMColumns <= threshold );
}
return FALSE;
}
Cost*
FileScanOptimizer::computeCostForMultipleSubset
( MdamKey* mdamKeyPtr,
const Cost * costBoundPtr,
NABoolean mdamForced,
CostScalar & numKBytes,
NABoolean checkExePreds,
NABoolean mdamTypeIsCommon,
MdamKey *&sharedMdamKeyPtr
)
{
ValueIdSet exePreds;
ValueIdSet selPreds = getRelExpr().getSelectionPred();
const IndexDesc *indexDesc = getFileScan().getIndexDesc();
NABoolean isHbaseTable = indexDesc->getPrimaryTableDesc()->getNATable()->isHbaseTable();
if ( isHbaseTable && isMDAMFeasibleForHBase(indexDesc, selPreds) ) {
return new (HEAP) Cost();
}
if ((CmpCommon::getDefault(RANGESPEC_TRANSFORMATION) == DF_ON ) &&
(selPreds.entries()))
{
ItemExpr *inputItemExprTree = selPreds.rebuildExprTree(ITM_AND,FALSE,FALSE);
ItemExpr* resultOld = revertBackToOldTree(CmpCommon::statementHeap(),
inputItemExprTree);
resultOld->convertToValueIdSet(exePreds, NULL, ITM_AND, FALSE);
doNotReplaceAnItemExpressionForLikePredicates(resultOld,exePreds,resultOld);
//revertBackToOldTreeUsingValueIdSet(selPreds, exePreds);
//ItemExpr* resultOld = exePreds.rebuildExprTree(ITM_AND,FALSE,FALSE);
//doNotReplaceAnItemExpressionForLikePredicates(resultOld,exePreds,resultOld);
}
else
{
exePreds = selPreds;
}
// need to change this exePreds= exePreds - disjunct predicates.
Disjunct disjunct;
if ( CmpCommon::getDefault(RANGESPEC_TRANSFORMATION) == DF_ON )
{
for (CollIndex i=0; i < mdamKeyPtr->getDisjuncts().entries(); i++)
{
mdamKeyPtr->getDisjuncts().get(disjunct,i);
ValueIdSet vdset = disjunct.getAsValueIdSet();
ValueIdSet temp;
for (ValueId predId = vdset.init();
vdset.next(predId);
vdset.advance(predId) )
{
if (predId.getItemExpr()->getOperatorType() == ITM_RANGE_SPEC_FUNC)
{
if (predId.getItemExpr()->child(1)->castToItemExpr()->getOperatorType() == ITM_AND)
{
predId.getItemExpr()->child(1)->convertToValueIdSet(temp, NULL, ITM_AND, FALSE);
exePreds -= temp;
}
else
exePreds -= predId.getItemExpr()->child(1)->castToItemExpr()->getValueId();
// New method use: exePreds -= predId.getItemExpr()->child(1)->castToItemExpr()->getValueId();
}
else
exePreds -= predId;
}// for (1)
}// for(2)
}// if
if (CmpCommon::getDefault(SIMPLE_COST_MODEL) == DF_ON)
{
return scmComputeCostForMultipleSubset(mdamKeyPtr,
costBoundPtr,
mdamForced,
numKBytes,
exePreds,
checkExePreds,
mdamTypeIsCommon,
sharedMdamKeyPtr);
}
// LCOV_EXCL_START :cnu OCM code
#ifndef NDEBUG
if(getenv("MDAM_TEST"))
{
runMdamTests(mdamKeyPtr,
costBoundPtr,
mdamForced,
exePreds,
checkExePreds,
mdamTypeIsCommon);
}
#endif
if(CURRSTMT_OPTDEFAULTS->useNewMdam())
{
return newComputeCostForMultipleSubset(mdamKeyPtr,
costBoundPtr,
mdamForced,
numKBytes,
exePreds,
checkExePreds,
mdamTypeIsCommon,
sharedMdamKeyPtr);
}
else
{
return oldComputeCostForMultipleSubset(mdamKeyPtr,
costBoundPtr,
mdamForced,
numKBytes,
checkExePreds,
mdamTypeIsCommon,
sharedMdamKeyPtr);
}
// LCOV_EXCL_STOP
}
// LCOV_EXCL_START :cnu OCM code
// -----------------------------------------------------------------------
// Use this routine to compute the cost of a given MdamKey
// INPUT:
// mdamKeyiPtr: pointer to the MdamKey to cost
// costBoundPtr: A cost bound. If the cost of this MdamKey ever
// exceeds this cost bound, then return NULL
// mdamForced:
// checkExePred:
// mdamTypeIsCommon: TRUE if called to cost MDAMCommon,
// FALSE if called to cost MDAMDisjuncts
// the value of mdamTypeIsCommon is used to save if necessary
// (and share later) the corresponding stopColumn and sparceColumns
// information about the current mdamKey
//
// OUTPUT:
// Return value:
// A NON-NULL Cost* indicating the Cost for mdamKey if the cost did not
// exceed the given cost bound.
// A NULL Cost* indicating that the Cost for mdamKey exceeded
// the given cost bound.
// sharedMdamKeyPtr: pointer to mdamKey used or shared,
// if sharedMdamKeyPtr == &mdamKey then the key should not be deleted
// even if it lost right now, because we may reuse BasicCost, stopColumn
// and sparceCoilum information for this mdamKey later
// -----------------------------------------------------------------------
#pragma nowarn(262) // warning elimination
Cost*
FileScanOptimizer::oldComputeCostForMultipleSubset
( MdamKey* mdamKeyPtr,
const Cost * costBoundPtr,
NABoolean mdamForced,
CostScalar & numKBytes,
NABoolean checkExePreds,
NABoolean mdamTypeIsCommon,
MdamKey *&sharedMdamKeyPtr
)
{
MDAM_DEBUG0(MTL2,"BEGIN MDAM Costing --------");
MDAM_DEBUGX(MTL2, MdamTrace::printCostObject(this, costBoundPtr, "Cost Bound"));
// This was added as part of the project
// to reduce compilation time by reusing simple cost vectors.
NABoolean sharedCostFound = FALSE;
SimpleCostVector * disjunctsFRPtr;
SimpleCostVector * disjunctsLRPtr;
FileScanBasicCost * fileScanBasicCostPtr = shareBasicCost(sharedCostFound);
if ( mdamTypeIsCommon )
{
disjunctsFRPtr = &(fileScanBasicCostPtr->getFRBasicCostMdamCommon());
disjunctsLRPtr = &(fileScanBasicCostPtr->getLRBasicCostMdamCommon());
numKBytes = fileScanBasicCostPtr->getMdamCommonNumKBytes();
}
else
{
disjunctsFRPtr = &(fileScanBasicCostPtr->getFRBasicCostMdamDisjuncts());
disjunctsLRPtr = &(fileScanBasicCostPtr->getLRBasicCostMdamDisjuncts());
numKBytes = fileScanBasicCostPtr->getMdamDisjunctsNumKBytes();
}
SimpleCostVector & disjunctsFR = *disjunctsFRPtr;
SimpleCostVector & disjunctsLR = *disjunctsLRPtr;
sharedMdamKeyPtr = fileScanBasicCostPtr->getMdamKeyPtr(mdamTypeIsCommon);
if ( sharedCostFound AND
sharedMdamKeyPtr AND
disjunctsFRPtr->getCPUTime() > csZero AND
disjunctsLRPtr->getCPUTime() > csZero
)
{
if ( CURRSTMT_OPTDEFAULTS->reuseBasicCost() )
{
MDAM_DEBUG0(MTL2, "Use cached MDAM cost");
Cost * costPtr = computeCostObject(disjunctsFR, disjunctsLR);
if ( costBoundPtr != NULL )
{
COMPARE_RESULT result =
costPtr->compareCosts(*costBoundPtr,
getContext().getReqdPhysicalProperty());
if ( result == MORE OR result == SAME )
{
delete costPtr;
MDAM_DEBUG0(MTL2, "MDAM Costing returning NULL cost");
MDAM_DEBUG0(MTL2, "END MDAM Costing --------\n");
return NULL;
}
}
mdamKeyPtr->reuseMdamKeyInfo(sharedMdamKeyPtr);
MDAM_DEBUGX(MTL2, MdamTrace::printCostObject(this, costPtr,
"Returning cached MDAM Cost"));
MDAM_DEBUG0(MTL2, "END MDAM Costing --------\n");
return costPtr;
}
else
{
disjunctsFR.reset();
disjunctsLR.reset();
}
}
#ifndef NDEBUG
(*CURRSTMT_OPTGLOBALS->multSubsetCostMonitor).enter();
#endif
// MDAM only works for key sequenced files.
DCMPASSERT(getIndexDesc()->getNAFileSet()->isKeySequenced());
DCMPASSERT(getIndexDesc()->getIndexKey().entries() > 0);
const ValueIdSet &exePreds = getRelExpr().getSelectionPred();
// -----------------------------------------------------------------------
// For every disjunct:
// Note:
// $$$ Need to add code to detect disjunct overlapping
// -----------------------------------------------------------------------
// create the histograms for this disjunct:
IndexDescHistograms
firstColumnHistogram(*getIndexDesc(),CollIndex(1));
// Declare counters that keep track of counters for the
// MDAM tree created from all the disjuncts:
CostScalar
// rows that all subsets of all disjuncts contain:
totalRows
// estimated subsets of contigous data that the MDAM tree created
// from all disjuncts contains (for all probes):
,totalRqsts
// incoming probes that create an empty mdam tree:
,totalFailedProbes
// probes for data to get the next subset boundary:
,totalProbesForSubsetBoundaries
,totalSeeks // total number of disk arm movements
,totalDisjunctPreds=0 // the sum of predicates in all disjuncts
,totalSeq_kb_read; // total number of blocks that were read sequentially
CostScalar seqKBytesPerScan;
ValueIdSet totalKeyPreds;
CollIndex leadingPartPreds = 0;
// -----------------------------------------------------------------------
// The probes are the TOTAL probes coming from the scan's parent. But
// depending on the key predicates and the partitioning key, the scan
// may be actually receiving fewer probes. The probes that the
// scan is actually receiving is given by the repeat count.
// -----------------------------------------------------------------------
CostScalar repeatCount = getContext().getPlan()->getPhysicalProperty()->
getDP2CostThatDependsOnSPP()->getRepeatCountForOperatorsInDP2() ;
// Costing works estimating the resources of the whole, unpartitioned
// table, thus get the total probes by multiplying by the active
// partitions
CostScalar
incomingProbes = repeatCount * getNumActivePartitions();
DCMPASSERT(repeatCount <= incomingProbes);
// Is this a Scan on the right-hand side of a Nested Join?
// Typically, for nested joins, the repeatCount will be greater than
// one AND the input ColStats will be non-empty. However, in some
// cases such as cross-products, the input ColStats are empty.
// Also, in some cases repeat count is 1, but ColStats is not emtpy.
// Here we treat this case as a multiprobe.
//
const NABoolean isMultipleProbes =
( (repeatCount.isGreaterThanOne()) OR
(getContext().getInputLogProp()->getColStats().entries() > 0) );
// -------------------------------------------------------------
// Compute upper bounds:
// -------------------------------------------------------------
CostScalar innerRowsUpperBound =
getRawInnerHistograms().getRowCount().getCeiling();
const CostScalar estimatedRecordsPerBlock =
getIndexDesc()->getEstimatedRecordsPerBlock();
CostScalar innerBlocksUpperBound;
computeBlocksUpperBound(
innerBlocksUpperBound /* out*/
,innerRowsUpperBound
,estimatedRecordsPerBlock);
// -----------------------------------------------------------
// scanForcePtr (if exists)
// Find if the scan is being forced
// -----------------------------------------------------------
ScanForceWildCard* scanForcePtr = NULL;
const ReqdPhysicalProperty* propertyPtr =
getContext().getReqdPhysicalProperty();
if ( propertyPtr
&& propertyPtr->getMustMatch()
&& (propertyPtr->getMustMatch()->getOperatorType()
== REL_FORCE_ANY_SCAN))
{
scanForcePtr =
(ScanForceWildCard*)propertyPtr->getMustMatch();
}
// -----------------------------------------------------------------------
// Set up outer statistics and adjust incoming probes to one if
// needed:
// -----------------------------------------------------------------------
const Histograms *outerHistogramsPtr = NULL;
if (isMultipleProbes)
{
MDAM_DEBUG0(MTL2, "Mdam scan is multiple probes");
// The outer histograms will be used if we are under a nested join
outerHistogramsPtr = new HEAP
Histograms(getContext().getInputLogProp()->getColStats());
DCMPASSERT(outerHistogramsPtr != NULL);
}
else {
MDAM_DEBUG0(MTL2, "Mdam scan is a single probe");
// make sure that probes are at least one, to satisfy
// Roll-up formulas....
incomingProbes.minCsOne();
}
//the following is the flag identifying if exepreds can be empty or not.
NABoolean noExePreds = TRUE;
NABoolean proceedViaCosting = TRUE;
// -----------------------------------------------------------------------
// Loop through every disjunct and:
// 1.- Find the optimal disjunct prefix for the disjunct
// 2.- If the optimal disjunct prefix exceeds the cost bound
// return with NULL (signaling that MDAM is too expensive)
// 3.- Keep track of the sum of the costs of all disjuncts
// -----------------------------------------------------------------------
for (CollIndex disjunctIndex=0;
disjunctIndex < mdamKeyPtr->getKeyDisjunctEntries();
disjunctIndex++)
{
// -------------------------------------------------------------
// current disjunct counters:
// -------------------------------------------------------------
CostScalar
// rows that all subsets of this disjunct contain:
disjunctRows
// subsets of contigous data that this disjunct is made up
// (for a single probe):
,disjunctSubsets = csOne
// subsets of contigous data that this disjunct is made up
// (for *all* probes):
,disjunctSubsetsAsSeeks = csOne
,disjunctRqsts = csOne
// incoming probes that create an empty mdam tree:
,disjunctFailedProbes = csZero
// probes for data to get the next subset boundary:
,disjunctProbesForSubsetBoundaries = csZero
// total number of seeks for the current disjunct:
,disjunctSeeks
// total number of kb transfered from the physical file to the
// DP2 current disjunct:
,uniqueProbes=csOne
,disjunctSeq_kb_read;
CostScalar sumOfUecs = csOne;
CostScalar sumOfUecsSoFar = csOne;
CostScalar blocksToReadPerUec = csZero;
// The stop column keeps track of the optimal prefix
CollIndex stopColumn = 0;
CostScalar temp_dataRows = csZero;
CostScalar * dataRows= &temp_dataRows;
// -------------------------------------------------------------
// Set up MdamKey dependant info:
// -------------------------------------------------------------
// Create empty list of histograms (histograms for every
// column are added as needed)
IndexDescHistograms disjunctHistograms(*getIndexDesc(),0);
//Need to know if we can use multi column uec to better estimate
//disjunct subsets.
NABoolean multiColUecInfoAvail =
disjunctHistograms.isMultiColUecInfoAvail();
NABoolean temp=FALSE;
NABoolean * allKeyPredicates=&temp;
// get the key preds for this disjunct:
ValueIdSet disjunctKeyPreds;
mdamKeyPtr->getKeyPredicates(disjunctKeyPreds,
allKeyPredicates,
disjunctIndex);
//keeps a eye on the possibility of having no executorpredicates
if(NOT (*allKeyPredicates))
noExePreds = FALSE;
// return with a NULL cost if there are no key predicates
// and we are not forcing MDAM (a null costBound is only
// given when MDAM is being forced):
if (disjunctKeyPreds.isEmpty() AND costBoundPtr != NULL)
{
MDAM_DEBUG1(MTL2, "disjunct[%d] without any key predicate,"
"MDAM is worthless, returning NULL!\n", disjunctIndex);
MDAM_DEBUGX(MTL2, getDisjuncts().print());
#ifndef NDEBUG
(*CURRSTMT_OPTGLOBALS->multSubsetCostMonitor).exit();
#endif
MDAM_DEBUG0(MTL2, "MDAM Costing returning NULL cost");
MDAM_DEBUG0(MTL2, "END MDAM Costing --------\n");
return NULL; // full table scan, MDAM is worthless here
}
// Tabulate the key predicates using the key columns as
// the index:
ColumnOrderList keyPredsByCol(getIndexDesc()->getIndexKey());
mdamKeyPtr->getKeyPredicatesByColumn(keyPredsByCol,disjunctIndex);
MDAM_DEBUG1(MTL2, "Disjunct: %d, keyPredsByCol:", disjunctIndex);
MDAM_DEBUGX(MTL2, keyPredsByCol.print());
// At this point, the disjunct MUST contain key preds:
// DCMPASSERT(keyPredsByCol.containsPredicates());
// -------------------------------------------------------------------
// If we are receiving probes, find out how many of the probes
// will generate a non-empty MDAM-tree:
// --------------------------------------------------------------------
if (isMultipleProbes)
{
// -------------------------------------------------------------
// we are receiving probes, compute which are
// successful...
// We assume each disjunct is independant and that the
// probes being received apply to the current disjunct
// only. In reality, the probes being received apply
// to the whole MDAM tree built from all the disjuncts,
// because, in general, the disjuncts are overlapping.
//
// In the executor, a new MDAM tree is built for every
// probe. Thus a failed probe in MDAM really means a probe
// that produces a tree with a single, empty, interval.
// Consider the case:
// select * from t1,t2 where t2.a=t1.a OR t2.a=t1.b;
// and we are evaluting the cost of the inner table of
// the NJoin:
// NJ
// / \
// t1 t2
//
// t1 has a primary key on (a,b) and t2 has a primary key on a.
// The tables are like:
// t1({a,b}) = {(1,3),(5,11),(10,11)}
// t2({a}) = {1,2,3,4,6,7,8,9,10,12,13,14,15}
// Thus, t2 is receiving 3 probes: {(1,1),(5,5),(10,10)}
// For probe 1, the mdam tree built for t2 is
// made up of the intervals (for a): [1,1],[3,3]
// for probe 2: EMPTY
// for probe 3: [10,10]
// Thus, probes 1 and 3 are "successful" and probe 2 is a
// a "failed" probe. Note that a "failed" probe in MDAM
// has zero cost (other than the overhead in finding out
// that the tree is empty) since empty intervals *do not*
// generate data lookups.
//
// To get some estimate on the "successful probes", we assume
// that each disjunct gives raise to an independant MDAM tree.
// In other words, we assume that every disjunct's data
// does not overlap with each other.
// -------------------------------------------------------------
// --------------------------------------------------------------
// Compute the disjunct key predicates:
// --------------------------------------------------------------
MDAM_DEBUG0(MTL2, "disjunctKeyPreds before recomputing");
MDAM_DEBUGX(MTL2, disjunctKeyPreds.display());
disjunctKeyPreds.clear();
keyPredsByCol.getAllPredicates(disjunctKeyPreds);
MDAM_DEBUG0(MTL2, "disjunctKeyPreds after recomputing");
MDAM_DEBUGX(MTL2, disjunctKeyPreds.display());
// there must be preds in order to consider MDAM:
DCMPASSERT( disjunctKeyPreds.entries() > 0 );
CostScalar
disSuccProbes
,disUniSucPrbs /* dummy */
,disDupSucPrbs /* dummy */
,disFldPrbs /* dummy */
,disUniFailedProbes;
categorizeProbes(disSuccProbes /* out */
,disUniSucPrbs /* out, but don't care */
,disDupSucPrbs /* out, but don't care */
,disFldPrbs /* out, but don't care */
,disUniFailedProbes
,incomingProbes
,disjunctKeyPreds
,*outerHistogramsPtr
,TRUE // this is MDAM!
,dataRows
);
MDAM_DEBUG1(MTL2, "Incoming Probes %f", incomingProbes.value());
MDAM_DEBUGX(MTL2, outerHistogramsPtr->print());
MDAM_DEBUGX(MTL2, disjunctKeyPreds.display());
MDAM_DEBUG1(MTL2, "categorizeProbes returns rows: %f", dataRows->value());
uniqueProbes = disUniSucPrbs + disUniFailedProbes;
disjunctFailedProbes = incomingProbes - disSuccProbes;
DCMPASSERT(disjunctFailedProbes >= csZero);
} // if we are receiving probes
// ---------------------------------------------------------
// We need to compute the
// prefix of this disjunct such that its cost is minimum
// and set the stop column accordingly.
//
// Will will find that
// prefix of the list of key columns such that
// the subsets produced by the predicate expression that is formed by
// predicates referring to columns in that prefix and
// the histogram data for their columns
// is of the least cost.
//
// We do this by advancing
// the column position, recomputing the prefix cost,
// and keeping track of the minimum prefix.
// ---------------------------------------------------------
// -------------------------------------------------------
// Declare data needed to keep track of the minimum prefix:
// -------------------------------------------------------
Cost *minimumPrefixCostPtr = NULL;
CostScalar
minimumRows = csZero
,minimumRqsts
,minimumFailedProbes
,minimumProbesForSubsetBoundaries
,minimumSeeks = csZero
,minimumSeq_kb_read = csZero;
ValueIdSet minimumKeyPreds;
SimpleCostVector
prefixFR
,prefixLR
;
const ValueIdSet *predsPtr = NULL;
Cost *prefixBoundPtr = NULL;
ValueIdSet singleSubsetPrefixPredicates;
// -------------------------------------------------------
// Advance position by position and keep track of
// minimum prefix:
// -------------------------------------------------------
// First find the last column position:
// The order (i.e., column position) must be varied
// up to the last column position that references
// key predicates. Find the lastColumnPosition:
// walk from the last key column until you find
// a column position that references a key pred:
ValueId keyCol;
NABoolean duplicateFound=FALSE;
const ItemExpr* predIEPtr = NULL;
NABoolean foundLastColumn = FALSE;
CollIndex lastColumnPosition = keyPredsByCol.entries() - CollIndex(1);
for (;
#pragma nowarn(270) // warning elimination
lastColumnPosition >= 0;
#pragma warn(270) // warning elimination
lastColumnPosition--)
{
// don't bother if the key contains only one column:
if (lastColumnPosition > CollIndex(0))
{
keyCol = getIndexDesc()->getIndexKey()[lastColumnPosition];
//following is guard against the situation where we can have
//key columns(a,b,a) and predicate a=3 we would chose second
//'a' as last coulumn whereas we know that we would never skip
//the first 'a' and apply the predicate to the second 'a'
for( CollIndex otherColumns=lastColumnPosition-CollIndex(1);
(otherColumns+1)>=1;otherColumns--)
{
if(keyCol==getIndexDesc()->getIndexKey()[otherColumns])
{
duplicateFound=TRUE;
break;
}
}
if(duplicateFound)
{
duplicateFound=FALSE;
continue;
}
// The predsPtr may be NULL, and keyPredsBy Col has already
// sorted the preds by columns, no need to check again
// through predIEPtr, this is fixed in the new code.
predsPtr = keyPredsByCol[lastColumnPosition];
// any predicate in the set may refer to the key column:
for (ValueId predId = predsPtr->init();
predsPtr->next(predId);
predsPtr->advance(predId))
{
predIEPtr = predId.getItemExpr();
if (predReferencesColumn(predIEPtr, keyCol))
{
foundLastColumn = TRUE;
break;
}
}
}
else
{
// We've reached the first column, it MUST reference a
// predicate:
foundLastColumn = TRUE;
}
if (foundLastColumn)
{
break;
}
} // while we haven't found the lastColumnPosition
predsPtr = NULL;
// make column position start from one:
lastColumnPosition++;
DCMPASSERT(foundLastColumn);
DCMPASSERT(lastColumnPosition > 0);
// Now compute the optimum prefix:
NABoolean firstRound = TRUE;
// save the row count of the first column of the
// previous (or the first) disjunct so that we
// can correctly compute the sparse positionings
// for the first column:
CostScalar firstColRowCntAfter;
CostScalar firstColRowCntBefore;
// use this variable to test for overlapping disjunct:
NABoolean firstColOverlaps = FALSE;
// The disjunct key preds will actually represent the
// prefix key preds:
disjunctKeyPreds.clear();
CostScalar uecForPreviousCol = csOne;
CostScalar uecForPrevColForSeeks = csOne;
CostScalar uecForPreviousColBeforeAppPreds = csOne;
CostScalar orderRowCount;
NABoolean crossProductApplied = FALSE;
CollIndex leadingPartPreds = 0;
CollIndex minLeadingPartPreds = 2000; // Can not be 2000 - used to get min
const ColStatDescList &primaryTableCSDL =
getIndexDesc()->getPrimaryTableDesc()->getTableColStats();
const ValueIdList &keyColumns = getIndexDesc()->getIndexKey();
for (CollIndex colNum=0;colNum < keyColumns.entries();colNum++)
{
CollIndex indexInList;
primaryTableCSDL.
getColStatDescIndexForColumn(indexInList, keyColumns[colNum]);
sumOfUecs = sumOfUecs + primaryTableCSDL[indexInList]->getColStats()->
getTotalUec().getCeiling();
}
//Following two variable are used to keep track of the predicate on the
//current column to be used by the next column. It is reasonable to use
//MDAM on the later column if the current column has equality predicate
//on it. Without special code it does not work because the cost of pred
//application out does the reduction in row send to executor in dp2
NABoolean prevPredIsEqual= FALSE;
NABoolean curPredIsEqual= FALSE;
NABoolean prevColChosen= FALSE;
CollIndex previousColumn=0;
for (CollIndex prefixColumnPosition = 0;
prefixColumnPosition < lastColumnPosition;
prefixColumnPosition++)
{
// Because of a VEG predicate can contain more than one
// predicate encoded, add histograms incrementally so that
// the reduction of a VEG predicate for a later column
// than the current column position does not affect the
// distribution of the current column.
// Note that the append method receives a one-based column position,
// so add one to the prefix because the prefix is zero based:
disjunctHistograms.
appendHistogramForColumnPosition(prefixColumnPosition+1);
// The very first time,
// Apply preds to firs column histograms
// (in order to detect disjunt overlapping) and
// if there is a single
// subset prefix, advance the counter to its last
// column position and gather its predicates.
if (prefixColumnPosition == 0 )
{
// update first col. hist:
DCMPASSERT(prefixColumnPosition==0);
firstColRowCntBefore =
firstColumnHistogram.getColStatsForColumn(
getIndexDesc()->getIndexKey()[0]).
getRowcount().getCeiling();
predsPtr = keyPredsByCol[0];
const SelectivityHint * selHint = getIndexDesc()->getPrimaryTableDesc()->getSelectivityHint();
const CardinalityHint * cardHint = getIndexDesc()->getPrimaryTableDesc()->getCardinalityHint();
if (predsPtr AND NOT predsPtr->isEmpty())
{
firstColumnHistogram.
applyPredicates(*predsPtr, getRelExpr(), selHint, cardHint, REL_SCAN);
ValueId predId;
BiRelat * predIEptr;
for(predId=predsPtr->init();
predsPtr->next(predId);
predsPtr->advance(predId))
{
if (predId.getItemExpr()->getArity() == 2)
{
predIEptr=(BiRelat *)predId.getItemExpr();
if (predIEptr->isaPartKeyPred())
{
leadingPartPreds += 1;
break;
}
}
}
}
firstColRowCntAfter =
firstColumnHistogram.getColStatsForColumn(
getIndexDesc()->getIndexKey()[0]).
getRowcount().getCeiling();
// compute the order of the single subset prefix:
// This order is the column position for that
// column that denotes a single subset
// i.e., consider a table t1(A,B,C,D)
// with pk (A,B,C)
// for A=1, the order is 0
// for A=1 AND B=2 AND C=3, the order is 2.
// itIsSingleSubset is FALSE only for the case
// that there is a IN pred in the first column.
CollIndex singleSubsetPrefixColumn;
NABoolean itIsSingleSubset =
keyPredsByCol.
getSingleSubsetOrder(singleSubsetPrefixColumn);
// Apply single subset preds to histograms and compute
// statistics:
if (itIsSingleSubset)
{
for (CollIndex singleSubsetColPosition=0;
singleSubsetColPosition <= singleSubsetPrefixColumn;
singleSubsetColPosition++)
{
// Obtain the predicates in i-th order and insert
// them into the sing. subset preds:
predsPtr = keyPredsByCol[singleSubsetColPosition];
if (predsPtr AND NOT predsPtr->isEmpty())
{
singleSubsetPrefixPredicates.insert(*predsPtr);
}
} // for every key col in the sing. subset prefix
// Set the preds ptr to all preds. in the
// prefix:
predsPtr = &singleSubsetPrefixPredicates;
// Advance the column counter to the
// last column of the prefix:
prefixColumnPosition = singleSubsetPrefixColumn;
for (CollIndex i=1; i <= prefixColumnPosition; i++)
{
disjunctHistograms.appendHistogramForColumnPosition(i+1);
}
// A single subset prefix has only
// one positioning by definition:
disjunctSubsets = csOne;
// These are the subsets that each probe is going
// to read
// (the subsets cannot be higher that the number
// of rows in the raw table):
disjunctSubsets = MINOF(disjunctSubsets.getValue(),
innerRowsUpperBound.getValue());
disjunctSubsetsAsSeeks = disjunctSubsets;
// Since this is a single subset,
// no boundary probes
// are necessary:
disjunctProbesForSubsetBoundaries = csZero;
} // it is prefixColumnPosition==0 and singlesubsets
// The uec is used as a multiplier, thus initialize it
// to one for the column previous to the very first column:
uecForPreviousCol = csOne;
uecForPrevColForSeeks = csOne;
} // if prefix column position is inside a single subset prefix
else
{ // The prefix column position is in a column after
// the single subset prefix
// gather predicates for columns after single subset
// prefix:
if (keyPredsByCol[prefixColumnPosition])
{
// the column has predicates,
// accumulate them in the pred set:
predsPtr = keyPredsByCol[prefixColumnPosition];
}
// $$$ if the column is of type IN list, then
// $$$ the uec should be equal to the IN list entries
// $$$ times the previous UEC
// $$$ If the hists can handle IN lists then
// $$$ the next line is correct.
uecForPreviousCol = disjunctHistograms.getColStatsForColumn(
getIndexDesc()->
getIndexKey()[prefixColumnPosition-1]).getTotalUec().
getCeiling();
CostScalar estimatedUec = csOne;
if(multiColUecInfoAvail AND
uecForPreviousCol.isGreaterThanOne() AND
prefixColumnPosition>1 AND
disjunctHistograms.estimateUecUsingMultiColUec(
keyPredsByCol,/*in*/
prefixColumnPosition-1,/*in*/
estimatedUec/*out*/))
{
uecForPreviousCol =
(uecForPreviousCol/uecForPreviousColBeforeAppPreds)
*estimatedUec;
uecForPreviousColBeforeAppPreds = estimatedUec;
}
sumOfUecsSoFar = sumOfUecsSoFar + uecForPreviousColBeforeAppPreds;
uecForPrevColForSeeks = uecForPreviousCol;
CostScalar uecPerBlock = uecForPreviousColBeforeAppPreds
/ blocksToReadPerUec;
// The following formula was added so that we do not count every
// subset as a seek. For example let's say that so far we have
// computed 200 blocks for 20 subsets then so far each subset
// would access 10 blocks. Now if the next column has 20 uecs
// a naive algorithm would increase the subset by a factor of
// 20 and we would have 400 subsets equivalent to 400 seeks.
// But in reality these 20 uecs are going to create seeks in the
// 10 blocks so we know that it cannot create more than 10 seeks
// we further more reduce it by the location of the column and how
// "together" each of these seeks are.
if(uecForPrevColForSeeks.isGreaterThanOne() AND
NOT uecPerBlock.isLessThanOne() )
{
uecForPrevColForSeeks = MIN_ONE(uecForPrevColForSeeks / uecPerBlock);
uecForPrevColForSeeks = MIN_ONE(uecForPrevColForSeeks *
MAXOF((sumOfUecs - sumOfUecsSoFar)/sumOfUecs,1/16));
}
else if (uecForPrevColForSeeks.isGreaterThanOne())
{
uecForPrevColForSeeks = MIN_ONE(uecForPrevColForSeeks *
MAXOF((sumOfUecs - sumOfUecsSoFar)/sumOfUecs,1/16));
}
} // prefix column position greater than zero
// ---------------------------------------------------------
// The disjunctKeyPreds, at this point, must reflect the
// keypreds in the current prefix,
// therefore append this column's key preds:
// ---------------------------------------------------------
if (predsPtr)
{
DCMPASSERT(predsPtr != NULL);
disjunctKeyPreds.insert(*predsPtr);
}
// At this point
// *predsPtr contain the key predicates
// for the prefix.
MDAM_DEBUG1(MTL2, "Prefix: %d:", prefixColumnPosition);
NABoolean getRowCount=TRUE;
// ----------------------------------------------------------------
// Apply predicates:
// Apply *predsPtr to the histograms for this disjunct
// so that we can obtain the positionings and the
// rows produced by the current disjunct:
// ----------------------------------------------------------------
uecForPreviousColBeforeAppPreds =
disjunctHistograms.getColStatsForColumn(
getIndexDesc()->getIndexKey()[prefixColumnPosition]).
getTotalUec().getCeiling();
if ( predsPtr AND
(NOT predsPtr->isEmpty())
)
{
MDAM_DEBUG0(MTL2, "Applying predicates to disjunct histograms");
MDAM_DEBUGX(MTL2, predsPtr->print());
if (NOT crossProductApplied
AND
isMultipleProbes)
{
MDAM_DEBUG0(MTL2, "Applying cross product");
// Only apply the cross product once
crossProductApplied = TRUE;
if(prefixColumnPosition == lastColumnPosition-1)
{
getRowCount=FALSE;
orderRowCount = *dataRows;
MDAM_DEBUG1(MTL2, "orderRowCount comes from outerHist: %f",
orderRowCount.value());
}else
{
const SelectivityHint * selHint = getIndexDesc()->getPrimaryTableDesc()->getSelectivityHint();
const CardinalityHint * cardHint = getIndexDesc()->getPrimaryTableDesc()->getCardinalityHint();
disjunctHistograms.applyPredicatesWhenMultipleProbes(
*predsPtr
,*(getContext().getInputLogProp())
,getRelExpr().getGroupAttr()->getCharacteristicInputs()
,TRUE // doing MDAM!
,selHint
,cardHint
,NULL
,REL_SCAN);
}
}
else
{
const SelectivityHint * selHint = getIndexDesc()->getPrimaryTableDesc()->getSelectivityHint();
const CardinalityHint * cardHint = getIndexDesc()->getPrimaryTableDesc()->getCardinalityHint();
disjunctHistograms.applyPredicates(
*predsPtr, getRelExpr(), selHint, cardHint, REL_SCAN
);
}
}
prevPredIsEqual = curPredIsEqual;
curPredIsEqual = FALSE;
if (keyPredsByCol.getPredicateExpressionPtr(prefixColumnPosition) AND
(keyPredsByCol.getPredicateExpressionPtr(prefixColumnPosition)->
getType()==KeyColumns::KeyColumn::CONFLICT_EQUALS OR
keyPredsByCol.getPredicateExpressionPtr(prefixColumnPosition)->
getType()== KeyColumns::KeyColumn::EQUAL))
{
curPredIsEqual = TRUE;
previousColumn=prefixColumnPosition;
}
if(getRowCount){
orderRowCount = disjunctHistograms.getRowCount();
MDAM_DEBUG1(MTL2, "orderRowCount comes from disjunctHist: %f",
orderRowCount.value());
}
if (prefixColumnPosition == 0)
{
// Below, we test a sufficient condition for
// dijunct overlap.
//
// This disjunct overlaps the previous if:
//
// The application of preds did not change
// the rowcount of the first col hist.
// AND
// there is a previous disjunct
// AND
// the rowcount of the first col hist.
// is the same as the rowcount of
// the first col. hist.
// after first col. preds. were applied.
// NOTE that we can only detect overlap
// for the very first column (prefixColumnPosotion == 0))
firstColOverlaps =
( (firstColRowCntBefore == firstColRowCntAfter)
AND
(disjunctIndex > 0)
AND
(firstColRowCntAfter == orderRowCount) );
} // compute overlap flag for first column
//-----------------------------------------------------
// Compute density of this column
//-----------------------------------------------------
// Sparse dense force flags:
NABoolean sparseForced = FALSE;
NABoolean denseForced = FALSE;
// Check if scan is being forced
// if so check if density is forced too
if (scanForcePtr && mdamForced)
{
sparseForced =
((scanForcePtr->getEnumAlgorithmForColumn(prefixColumnPosition)
== ScanForceWildCard::COLUMN_SPARSE)
? TRUE : FALSE);
denseForced =
((scanForcePtr->getEnumAlgorithmForColumn(prefixColumnPosition)
== ScanForceWildCard::COLUMN_DENSE)
? TRUE : FALSE);
}
if (sparseForced OR denseForced)
{
if (sparseForced)
{
mdamKeyPtr->setColumnToSparse(prefixColumnPosition);
}
else if (denseForced)
{
mdamKeyPtr->setColumnToDense(prefixColumnPosition);
}
}
else
{
// -------------------------------------------------------
// Sparse/dense was not forced, calculate it from
// histograms:
// -------------------------------------------------------
// With single col. histograms we can only do
// a good job estimating thisfor the first column...
if (prefixColumnPosition == 0)
{
const ColStats &firstColumnColStats =
disjunctHistograms.
getColStatsForColumn(getIndexDesc()->getIndexKey()[0]);
// $$$ We may want to put the threshold in the
// $$$ defaults table:
const CostScalar DENSE_THRESHOLD = 0.90;
const CostScalar density =
(firstColumnColStats.getTotalUec().getCeiling()) /
( firstColumnColStats.getMaxValue().getDblValue()
- firstColumnColStats.getMinValue().getDblValue()
+ 1.0 );
if ( density > DENSE_THRESHOLD )
{
// It is dense!!!
mdamKeyPtr->setColumnToDense(prefixColumnPosition);
}
else
{
// It is sparse!!!
mdamKeyPtr->setColumnToSparse(prefixColumnPosition);
}
} // if order == 0
else
{
// order > 0, always sparse
mdamKeyPtr->setColumnToSparse(prefixColumnPosition);
}
} // dense/sparse not forced
// -------------------------------------------------------
// Update positionings:
//
// Assume that the all of the distinct elements for this
// column exist for every distinct element of
// the previous column.
// -------------------------------------------------------
// Note that there cannot be more positionings and more
// subsets than there are rows in the table
// The positionings include probing for the
// next subset
if (prefixColumnPosition == 0)
{
disjunctSubsets = uecForPreviousCol;
disjunctSubsets =
MINOF(innerRowsUpperBound.getValue(),
disjunctSubsets.getValue());
disjunctSubsetsAsSeeks = disjunctSubsets;
disjunctProbesForSubsetBoundaries = csZero;
}
else // prefixColumnPosition > 0
{
// Do not add subsets for the first column
// (i.e. we are in order 1) if we already added them
// in a previous subset:
if ( (prefixColumnPosition == 1 AND NOT (firstColOverlaps) )
OR
prefixColumnPosition > 1)
{
// the UEC for the previous column
// may be zero (if the table was empty
// or all the rows are eliminated after
// preds were applied.) In this
// case, don't multiply:
if (uecForPreviousCol.isGreaterThanZero())
{
disjunctSubsets *= uecForPreviousCol;
disjunctSubsets =
MINOF(innerRowsUpperBound.getValue(),
disjunctSubsets.getValue());
}
if( uecForPrevColForSeeks.isGreaterThanZero())
{
disjunctSubsetsAsSeeks *= uecForPrevColForSeeks;
disjunctSubsetsAsSeeks =
MINOF(innerRowsUpperBound.getValue(),
disjunctSubsetsAsSeeks .getValue());
}
// If the previous column is sparse, then for each subset
// we need to make an extra probe to find the subset
// boundary IF we are not in the second column
// and the first column overlaps:
if (mdamKeyPtr->isColumnSparse(prefixColumnPosition-1))
{
if ( NOT disjunctProbesForSubsetBoundaries.isGreaterThanZero() )
{
disjunctProbesForSubsetBoundaries = csOne;
}
disjunctProbesForSubsetBoundaries *= uecForPreviousCol;
disjunctProbesForSubsetBoundaries =
MINOF(innerRowsUpperBound.getValue(),
disjunctProbesForSubsetBoundaries.getValue());
}
} // non-overlapping
} // order > 0
// -------------------------------------------------------------
// Update statistics:
// --------------------------------------------------------------
// the subsets requests are issued for every probe:
CostScalar effectiveProbes = incomingProbes - disjunctFailedProbes;
disjunctRqsts = disjunctSubsets * effectiveProbes;
// the disjunct rows are those rows from the
// inner table that are kept after the
// application of predicates.
// We need the rows for *all* probes,
// thus we should not divide over the number of
// probes:
disjunctRows = orderRowCount;
// --------------------------------------------------------------
// A successful request is equivalent to a successful probe
// in the SearchKey case (single subset), thus we need
// the rows per successful request, which we compute
// below.
// --------------------------------------------------------------
CostScalar subsetsPerBlock = csOne;
CostScalar rowsPerSubset = csZero;
if( NOT disjunctRqsts.isLessThanOne() AND
disjunctRows.isGreaterThanZero() )
{
rowsPerSubset = disjunctRows / disjunctRqsts;
subsetsPerBlock = estimatedRecordsPerBlock / rowsPerSubset;
} // if condition is FALSE we don't change these values
// ------------------------------------------------------------
// Compute seq_kb_read and seeks for this disjunct
//
// The i/o is computed in a per-probe basis (thus the
// use of disjunctSubsets instead of disjunctRequests).
// It will be scaled up to number of probes below.
//
// -------------------------------------------------------------
// Get the blocks read per probe:
CostScalar disjunctBlocksToRead;
computeTotalBlocksLowerBound(
disjunctBlocksToRead
,disjunctSubsetsAsSeeks
,rowsPerSubset
,estimatedRecordsPerBlock
,innerBlocksUpperBound);
blocksToReadPerUec = MIN_ONE(disjunctBlocksToRead / disjunctSubsets);
CostScalar beginBlocksLowerBound = csZero;
computeBeginBlocksLowerBound(
beginBlocksLowerBound
,MINOF( (disjunctSubsetsAsSeeks /subsetsPerBlock).getCeiling(),
disjunctSubsetsAsSeeks.getValue())
,innerBlocksUpperBound);
// disjunctSubsets + disjunctProbesForSubsetBoundaries are basically the number of probes
//to all the appropriate values. Thus can be used to compute the number of index blocks
//that will be used in for MDAM access.
CostScalar indexBlocksLowerBound = getIndexDesc()->
getEstimatedIndexBlocksLowerBound(
MINOF(((disjunctSubsets +
disjunctProbesForSubsetBoundaries)
/subsetsPerBlock).getCeiling(),
disjunctSubsets.getValue()));
// Assume that every disjunct does not overlap
// with the previous. Since we computed all
// rows to read (and not a lower bound),
// the following routine will compute
// the correct cost, despite its name.
computeIOForFullCacheBenefit(disjunctSeeks /* out */
,disjunctSeq_kb_read /* out */
,beginBlocksLowerBound
,disjunctBlocksToRead
,indexBlocksLowerBound);
// -------------------------------------------------------------
// The total cost for the disjunct is the cost of all
// the probes times the cost for this disjunct:
// -------------------------------------------------------------
NABoolean changeBack = FALSE;
if((disjunctSeeks-beginBlocksLowerBound).getValue()>=5)
{
changeBack = TRUE;
disjunctSeeks = disjunctSeeks-5;
disjunctSeq_kb_read = disjunctSeq_kb_read-CostScalar(5)
*getIndexDesc()->getBlockSizeInKb();
}
disjunctSeeks *= effectiveProbes;
disjunctSeq_kb_read *= effectiveProbes;
if(changeBack)
{
disjunctSeeks = disjunctSeeks+5;
disjunctSeq_kb_read = disjunctSeq_kb_read+CostScalar(5)
*getIndexDesc()->getBlockSizeInKb();
}
//-----------------------------------------------------
// Update the minimum prefix:
//-----------------------------------------------------
// Compute current prefix's costs:
prefixFR.reset();
prefixLR.reset();
// getCeiling()'s for final values are calculated
// inside computeCostVectors...
#ifndef NDEBUG
(*CURRSTMT_OPTGLOBALS->multVectorCostMonitor).enter();
#endif //NDEBUG
MDAM_DEBUG2(MTL2, "Disjunct: %d, Prefix Column: %d",
disjunctIndex, prefixColumnPosition);
MDAM_DEBUG1(MTL2, "Incoming Probes: %f:", incomingProbes.value());
MDAM_DEBUG1(MTL2, "Prefix Failed Probes: %f:",
disjunctFailedProbes.value());
MDAM_DEBUG1(MTL2, "Prefix Subsets: %f:", disjunctSubsets.value());
MDAM_DEBUG1(MTL2, "Prefix Requests (probes * Subsets): %f:",
disjunctRqsts.value());
MDAM_DEBUG1(MTL2, "Prefix Rows: %f:", disjunctRows.value());
MDAM_DEBUG1(MTL2, "Prefix Seeks %f:", disjunctSeeks.value());
MDAM_DEBUG1(MTL2, "Prefix KB Read: %f:", disjunctSeq_kb_read.value());
computeCostVectorsForMultipleSubset(
prefixFR
,prefixLR
,seqKBytesPerScan
,disjunctRows
,disjunctRqsts + disjunctProbesForSubsetBoundaries
,disjunctFailedProbes
,disjunctSeeks
,disjunctSeq_kb_read
,disjunctKeyPreds
,exePreds
,incomingProbes
,CostScalar(disjunctKeyPreds.entries())
);
#ifndef NDEBUG
(*CURRSTMT_OPTGLOBALS->multVectorCostMonitor).exit();
#endif //NDEBUG
// Does the prefix exceeds the minimum prefix cost:
// If MDAM is forced then the user can specify
// up to which key column predicates will be included
// in the mdam key. If the user does not specify the
// column and MDAM is forced, all of the key predicates
// in the disjunct are included in the mdam key.
NABoolean minimumExceedsFrAndLrCosts = FALSE;
#ifndef NDEBUG
// -------------------------------------------------------
// Back door to force mdam for QA tests:
// -------------------------------------------------------
char *cstrMDAMStatus = getenv("MDAM");
if ((cstrMDAMStatus != NULL) &&
( strcmp(cstrMDAMStatus,"ON")==0 ))
{
minimumExceedsFrAndLrCosts = TRUE;
proceedViaCosting = FALSE;
}
else
{
#endif
if (scanForcePtr && mdamForced)
{
proceedViaCosting = FALSE;
if(noExePreds AND scanForcePtr
->getNumMdamColumns()<lastColumnPosition)
noExePreds=FALSE;
if (prefixColumnPosition < scanForcePtr->getNumMdamColumns())
{
// The user wants this column as part
// of the mdam key,
// simulate that the cost is exceeded
// so that it is included:
minimumExceedsFrAndLrCosts = TRUE;
}
else
{
if (scanForcePtr->getMdamColumnsStatus()
== ScanForceWildCard::MDAM_COLUMNS_ALL)
{
// Unconditionally force all of the columns:
minimumExceedsFrAndLrCosts = TRUE;
// proceedViaCosting is FALSE
}
else if (scanForcePtr->getMdamColumnsStatus()
== ScanForceWildCard::MDAM_COLUMNS_NO_MORE)
{
// Unconditionally reject this and later columns:
minimumExceedsFrAndLrCosts = FALSE;
curPredIsEqual = FALSE;
prevPredIsEqual = FALSE;
// proceedViaCosting is FALSE;
}
else if (scanForcePtr->getMdamColumnsStatus()
==
ScanForceWildCard::MDAM_COLUMNS_REST_BY_SYSTEM)
{
// Let the system decide based on costing:
proceedViaCosting = TRUE;
}
}
} // if scanForcePtr
#ifndef NDEBUG
} // try force statament
#endif
if (NOT proceedViaCosting)
{
MDAM_DEBUG0(MTL2, "Order not considered due to forcing");
}
MDAM_DEBUGX(MTL2,
MdamTrace::printBasicCost(this, prefixFR, prefixLR, "Cost for prefix in oldComputeCostForMultipleSubset():"));
if (proceedViaCosting)
{
// Mdam has not been forced, or forced but with the choice
// of system decision for MDAM column. proceed with costing:
minimumExceedsFrAndLrCosts =
NOT exceedsBound(minimumPrefixCostPtr,
prefixFR
,prefixLR
);
}
// If it is the first time we go around this
// loop update the minimum.
// Also updated if the current minimum exceeded
// the current prefix:
// This if is simplifed in the new MDAM costing code
if (firstRound
OR minimumExceedsFrAndLrCosts
OR (prevColChosen
AND keyPredsByCol[prefixColumnPosition]
AND NOT predsPtr->isEmpty()
AND ((prevPredIsEqual AND prefixColumnPosition==previousColumn+1)
OR (curPredIsEqual AND prevPredIsEqual))))
{
// The if below seems redundant, removed in the new MDAM costing code
if(prefixColumnPosition>previousColumn)prevPredIsEqual=FALSE;
// We found a new minimum, initialize its data:
firstRound = FALSE;
minimumRows = disjunctRows;
// the successful probes must be multiplied by
// the positions since every probe must be charged
// by the disjunct positionings...
minimumRqsts = disjunctRqsts;
minimumFailedProbes = disjunctFailedProbes;
minimumProbesForSubsetBoundaries =
disjunctProbesForSubsetBoundaries;
minimumSeeks = disjunctSeeks;
minimumSeq_kb_read = disjunctSeq_kb_read;
// The new minimum is always a longer prefix
// never a shorter one, so minimumKeyPreds
// always contains the prev. predicates,
// thus just insert the key preds,
// dont'clear them:
minimumKeyPreds.insert(disjunctKeyPreds);
delete minimumPrefixCostPtr;
minimumPrefixCostPtr = computeCostObject(prefixFR,prefixLR);
DCMPASSERT(minimumPrefixCostPtr != NULL);
stopColumn = prefixColumnPosition;
prevColChosen = TRUE;
MDAM_DEBUG1(MTL2, "Minimum prefix column position: %d",
prefixColumnPosition);
MDAM_DEBUGX(MTL2, MdamTrace::printCostObject(this,
minimumPrefixCostPtr, "Minimum so far"));
} // update minimum prefix
else if (proceedViaCosting AND NOT minimumExceedsFrAndLrCosts) {
if(prefixColumnPosition == lastColumnPosition-1)
{//we have reached the lastcolumnPosition and its cost
//was exceeded for the lastcolumn so
//there will be additional exepreds
noExePreds = FALSE;
}
else // This is not the last column and it wasn't chosen
prevColChosen = FALSE;
}
} // for every Column Position after the prefix order (but the last)
// delete the the minimum prefix cost, is not needed anymore:
delete minimumPrefixCostPtr;
// --------------------------------------------------------------
// Sum up the statistics for all the disjuncts costed so far
// --------------------------------------------------------------
totalRows += minimumRows;
// Note that the total requests will be potentially be
// as large as # of disjuncts times the number of
// requests per disjunct
totalRqsts += minimumRqsts+minimumProbesForSubsetBoundaries;
totalFailedProbes += minimumFailedProbes;
totalSeeks += minimumSeeks;
// If we have partitioning key predicates then we will be reading different
// parts of the partition by different esps. There will be some seeks going
// from one esp reading to another. Assume that we will at least read ahead
// the normal amount (for now reduce possible seeks by 100).
const CostScalar blockSizeInKb = getIndexDesc()->getBlockSizeInKb();
NADefaults &defs = ActiveSchemaDB()->getDefaults();
const CostScalar maxDp2ReadInBlocks =
CostScalar(defs.getAsULong(DP2_MAX_READ_PER_ACCESS_IN_KB))/blockSizeInKb;
if (leadingPartPreds == mdamKeyPtr->getKeyDisjunctEntries()
AND (totalRows / estimatedRecordsPerBlock) > maxDp2ReadInBlocks)
{
totalSeeks +=
(totalRows / estimatedRecordsPerBlock)/maxDp2ReadInBlocks/100;
}
totalSeq_kb_read += minimumSeq_kb_read;
totalKeyPreds.insert(minimumKeyPreds);
totalDisjunctPreds += minimumKeyPreds.entries();
// Set the stop column for current disjunct:
mdamKeyPtr->setStopColumn(disjunctIndex, stopColumn);
// Compute the cost of all the disjuncts analyzed so far:
disjunctsFR.reset();
disjunctsLR.reset();
// Since we saved this mdamKey information - don't delete it
// in FileScanOptimizer::optimize(), return this key for checking
sharedMdamKeyPtr = mdamKeyPtr;
fileScanBasicCostPtr->setMdamKeyPtr(mdamKeyPtr,mdamTypeIsCommon);
// Is this restricted to the first column and it either has no predicate
// or is a single disjunct - not using mdam OR
// Has the input cost bound been exceeded by the disjuncts costed
// so far?
// Detect if MDAM make sense
NABoolean mdamMakeSense = TRUE;
if (NOT mdamForced AND
stopColumn == 0) {
if (keyPredsByCol.getPredicateExpressionPtr(0) == NULL )
mdamMakeSense = FALSE;
else if (mdamKeyPtr->getKeyDisjunctEntries() == 1) {
// When there is a conflict in single subset, mdam should handle it
const ColumnOrderList::KeyColumn* currKeyColumn =
keyPredsByCol.getPredicateExpressionPtr(0);
NABoolean conflict =
( (currKeyColumn->getType() == KeyColumns::KeyColumn:: CONFLICT)
OR
(currKeyColumn->getType() ==
KeyColumns::KeyColumn::CONFLICT_EQUALS));
if( NOT conflict ) // single subset should be chosen.
mdamMakeSense = FALSE;
}
}
if ( mdamMakeSense )
{
#ifndef NDEBUG
(*CURRSTMT_OPTGLOBALS->multVectorCostMonitor).enter();
#endif //NDEBUG
computeCostVectorsForMultipleSubset(disjunctsFR /* out */
,disjunctsLR /* out */
,seqKBytesPerScan /* out */
,totalRows
,totalRqsts
,totalFailedProbes
,totalSeeks
,totalSeq_kb_read
,totalKeyPreds
,exePreds
,incomingProbes
,totalDisjunctPreds
);
#ifndef NDEBUG
(*CURRSTMT_OPTGLOBALS->multVectorCostMonitor).exit();
#endif //NDEBUG
if ( exceedsBound(costBoundPtr,disjunctsFR,disjunctsLR) )
{
mdamMakeSense = FALSE;
if ( disjunctIndex+1 <
mdamKeyPtr->getKeyDisjunctEntries() )
{
// If we didn't go through all disjuncts yet -
// invalidate computed basic cost to prevent
// using this incomplete cost later.
disjunctsFR.reset();
disjunctsLR.reset();
}
}
}
if ( NOT mdamMakeSense )
{
#ifndef NDEBUG
(*CURRSTMT_OPTGLOBALS->multSubsetCostMonitor).exit();
#endif //NDEBUG
// Yes!, no need to continue, MDAM does not make sense
// for this scan:
MDAM_DEBUG0(MTL2, "MDAM Costing returning NULL cost");
MDAM_DEBUG0(MTL2, "END MDAM Costing --------\n");
return NULL;
}
MDAM_DEBUG1(MTL2, "Stop Column: %d", stopColumn);
} // for every disjunct
MDAM_DEBUG1(MTL2, "Total rows for this MDAM scan: %f:", totalRows.value());
#ifndef NDEBUG
(*CURRSTMT_OPTGLOBALS->multObjectCostMonitor).enter();
CURRSTMT_OPTGLOBALS->synCheckFlag = TRUE;
#endif //NDEBUG
// ---------------------------------------------------------------------
// Done!, MDAM won! create the cost vector:
// ---------------------------------------------------------------------
Cost *costPtr = computeCostObject(disjunctsFR
,disjunctsLR
);
#ifndef NDEBUG
CURRSTMT_OPTGLOBALS->synCheckFlag = FALSE;
(*CURRSTMT_OPTGLOBALS->multObjectCostMonitor).exit();
#endif //NDEBUG
//noExePreds is true, great set the flag in MDAM
if(noExePreds AND checkExePreds)
{
mdamKeyPtr->setNoExePred();
}
#ifndef NDEBUG
(*CURRSTMT_OPTGLOBALS->multSubsetCostMonitor).exit();
#endif //NDEBUG
numKBytes = seqKBytesPerScan; //totalSeq_kb_read;
if ( checkExePreds )
fileScanBasicCostPtr->setMdamDisjunctsNumKBytes(numKBytes);
else
fileScanBasicCostPtr->setMdamCommonNumKBytes(numKBytes);
MDAM_DEBUGX(MTL2, MdamTrace::printCostObject(this, costPtr,
"Returning MDAM Cost"));
MDAM_DEBUG0(MTL2, "END MDAM Costing --------\n");
return costPtr;
} // oldComputeCostForMultipleSubset(...)
#pragma warn(262) // warning elimination
// LCOV_EXCL_STOP
// -----------------------------------------------------------------------
// This function will check if current context has the same basic physical
// properties as the one of existing BasicCost objects. First it checks
// the groupId, sinice the goupId defines the set of row produced by the
// group members. This will take into account all predicates pushed to the
// scan. Then it checks the number of incoming probes tthat could be
// different for the same groupId. Finally it checks input logical property
// passed from the left child of nested join to the right one. If both
// pointers currentIPP and existingIPP are NULL it returns TRUE, if
// only one of them is NULL, it returns FALSE. If both are not NULL
// and not equal to eash other, it compares corresponding lists of
// NjOuterOrder columns
// -----------------------------------------------------------------------
NABoolean
FileScanBasicCost::hasSameBasicProperties(const Context & currentContext) const
{
if ( basicCostContext_->getGroupId() != currentContext.getGroupId() )
return FALSE;
if ( (basicCostContext_->getInputLogProp())->getResultCardinality()
!= (currentContext.getInputLogProp())->getResultCardinality() )
return FALSE;
if ((basicCostContext_->getInputLogProp()->getColStats().entries() > 0)
!= (currentContext.getInputLogProp()->getColStats().entries() > 0))
return FALSE;
if ( (basicCostContext_->getReqdPhysicalProperty())->getOcbEnabledCostingRequirement()
!= (currentContext.getReqdPhysicalProperty())->getOcbEnabledCostingRequirement() )
return FALSE;
LogPhysPartitioningFunction *logPhysPartFunc =
(LogPhysPartitioningFunction *) // cast away const
currentContext.getPlan()->getPhysicalProperty()->getPartitioningFunction()->
castToLogPhysPartitioningFunction();
if ( logPhysPartFunc != NULL )
{
LogPhysPartitioningFunction::logPartType logPartType =
logPhysPartFunc->getLogPartType();
if ( logPartType == LogPhysPartitioningFunction::LOGICAL_SUBPARTITIONING
OR logPartType == LogPhysPartitioningFunction::HORIZONTAL_PARTITION_SLICING
)
{
// If we have LOGICAL_SUBPARTITIONING then extra predicates have
// been added to selection preds. We don't want to steal plans
// without the extra predicates.
// The problem is to find a plan with the extra predicates -
// the commented out code did not do this right
//if (NOT basicCostContext_->getPlan())
return FALSE;
}
}
const InputPhysicalProperty*
currentIPP = currentContext.getInputPhysicalProperty();
const InputPhysicalProperty*
existingIPP = basicCostContext_->getInputPhysicalProperty();
if ( currentIPP == NULL OR currentIPP->getAssumeSortedForCosting())
{
if ( existingIPP == NULL OR existingIPP->getAssumeSortedForCosting())
return TRUE;
else
return FALSE;
}
// currentIPP != NULL
if ( existingIPP == NULL OR existingIPP->getAssumeSortedForCosting())
return FALSE;
// currentIPP != NULL AND existingIPP != NULL
return ( currentIPP == existingIPP OR
*(currentIPP->getNjOuterOrder()) == *(existingIPP->getNjOuterOrder()) );
}
// LCOV_EXCL_START :cnu
// -----------------------------------------------------------------------
// Use this routine to compare the current cost with a given bound.
// INPUT:
// costBoundPtr: the bound to exceed
// firstRow: A first row simple cost vector
// lastRow: A last row simple cost vector
// OUTPUT:
// TRUE if the cost vector created from firstRow and lastRow is of the
// cost as *costBoundPtr or if it is more expensive than *costBoundPtr
// -----------------------------------------------------------------------
NABoolean
FileScanOptimizer::exceedsBound(const Cost *costBoundPtr,
const SimpleCostVector& firstRow
,const SimpleCostVector& lastRow
) const
{
if (costBoundPtr == NULL)
{
return FALSE;
}
else
{
const Cost *costSoFarPtr = computeCostObject(firstRow, lastRow);
COMPARE_RESULT result =
costSoFarPtr->compareCosts(*costBoundPtr,
getContext().getReqdPhysicalProperty());
delete costSoFarPtr;
// since doing MDAM adds overhead also say that we exceeded
// a bound when costs match exactly:
if (result == MORE OR result == SAME)
{
MDAM_DEBUG0(MTL2, "Cost exceeded");
return TRUE; // cost bound has been exceeded, return
}
} // cost bound comparison
return FALSE;
} // FileScanOptimizer::exceedsBound(...)
// LCOV_EXCL_STOP
// LCOV_EXCL_START : OCM code
/////////////////////////////////////////////////////////////////////
// fix the estimation
// for seeks beginBlocksLowerBound = no. of unique entries WHICH ARE
// SUCCESSFUL in matching with inner table totalBlocksLowerBound = no.
// of blocks returning from the OUTER TABLE after applying the predicate
// innerBlocksUpperBound = Total size of inner table
// DBB = Distance between the blocks
// MSD = Distance for average seek
// CostScalar(0.025) = (seek time is between .2ms and 8ms ignore latency,
// so fixed is .2ms/8ms=0.025) (0.008) Limiting the rate of increase upto
// twice that of inner blocks and then using a step function
// STEP FUNCTION so as to decrease the rate of increase after twice of
// inner blocks cost for fixed seeks is the, % of probes which can be read
// with minimum seek time finalRows are the final resulting rows, matched
// with outertable and qualified with < predicate To the readAhead cost
// the Mrows * finalRows is added, this is done in order to calculate the
// cost of moving the rows to the executor. Once the rows from the outer
// table come in and they are compared with the innertable rows, the
// successful rows will be returned to the executor, so the
// more the number of rows returned to the executor the more the cost.
////////////////////////////////////////////////////////////////////////
void
FileScanOptimizer::computeSeekForDp2ReadAheadAndProbeOrder(
CostScalar& seekComputedWithDp2ReadAhead,
const CostScalar& finalRows,
const CostScalar& uniqueProbes,
const CostScalar& beginBlocksLowerBound,
const CostScalar& totalBlocksLowerBound,
const CostScalar& innerBlocksUpperBound,
const CostScalar& dp2CacheSize,
const NABoolean inOrderProbes
)const
{
NADefaults &defs = ActiveSchemaDB()->getDefaults();
const CostScalar blockSizeInKb = getIndexDesc()->getBlockSizeInKb();
const CostScalar maxDp2ReadInBlocks =
CostScalar(defs.getAsULong(DP2_MAX_READ_PER_ACCESS_IN_KB))/blockSizeInKb;
const CostScalar blocksForMoreThanOneSeek =
defs.getAsLong(DP2_MINIMUM_FILE_SIZE_FOR_SEEK_IN_BLOCKS);
if(inOrderProbes)
{
//uniqueProbes calculated from CategorizeProbes should be atleast one.
CCMPASSERT(uniqueProbes > csZero);
const CostScalar DBB=(innerBlocksUpperBound / uniqueProbes);
const CostScalar MSD = defs.getAsULong(NJ_MAX_SEEK_DISTANCE);
const CostScalar Ulimit = defs.getAsDouble(NJ_INC_UPTOLIMIT);
const CostScalar Alimit = defs.getAsDouble(NJ_INC_AFTERLIMIT);
const CostScalar Mrows = defs.getAsDouble(NJ_INC_MOVEROWS);
CostScalar fixedSeeks;
CostScalar Limit = csTwo * innerBlocksUpperBound;
if (uniqueProbes > Limit)
{
CostScalar extraProbes = uniqueProbes - Limit;
fixedSeeks = Ulimit * Limit + Alimit * extraProbes;
}
else
{
fixedSeeks = Ulimit * (uniqueProbes-1);
}
CostScalar variableSeeks = uniqueProbes - fixedSeeks;
seekComputedWithDp2ReadAhead = csOne + fixedSeeks +
(variableSeeks) * (DBB/MSD) + Mrows * finalRows;
}
else
{
if(innerBlocksUpperBound <= dp2CacheSize)
{
seekComputedWithDp2ReadAhead =
MINOF(beginBlocksLowerBound,
innerBlocksUpperBound/maxDp2ReadInBlocks);
}
else
{
seekComputedWithDp2ReadAhead = beginBlocksLowerBound;
}
}
}
// LCOV_EXCL_STOP
void
FileScanOptimizer::computeIOForFullCacheBenefit(
CostScalar& seeks /* out */
,CostScalar& seq_kb_read /* out */
,const CostScalar& beginBlocksLowerBound
,const CostScalar& totalBlocksLowerBound
,const CostScalar& indexBlocks) const
{
// -----------------------------------------------------------------------
// No block will be read more than once.
// -----------------------------------------------------------------------
//For the sake of a cleaner interface lets have indexBlocks as a constant
//reference parameter. But that means we have to load it into a cost scalar
//that can be updated.
seq_kb_read =
(totalBlocksLowerBound
+ indexBlocks) * getIndexDesc()->getBlockSizeInKb();
// There cannot be more index blocks than the log of the total data blocks:
seeks =
beginBlocksLowerBound + indexBlocks;
}
void
FileScanOptimizer::computeIOForRandomCase(
CostScalar& seeks /* out */
,CostScalar& seq_kb_read /* out */
,const CostScalar& blksPerSuccProbe
,const CostScalar& beginBlocksLowerBound
,const CostScalar& totalBlocksLowerBound
,const CostScalar& successfulProbes
,const CostScalar& failedProbes
,const CostScalar& indexBlocksLowerBound) const
{
const CostScalar cacheInBlocks =
getDP2CacheSizeInBlocks(getIndexDesc()->getBlockSizeInKb());
ULng32 ulCache = (ULng32)(cacheInBlocks.getCeiling().getValue());
ULng32 ulBlks = (ULng32)(blksPerSuccProbe.getValue());
if (ulBlks == (ULng32)0)
{
FSOWARNING("Bad cast in FileScanOptimizer::computeIOForRandomCase(...)");
ulBlks = UINT_MAX;
}
CostScalar extraMemory = ulCache % ulBlks;
const CostScalar availableCacheInBlocks = cacheInBlocks - extraMemory;
// -----------------------------------------------------------------------
// If the cache is bigger or equal than the total blocks to be read, then
// the hit probability is trivially one. Else, it is the fraction
// of the available cache over the total blocks to be read:
// -----------------------------------------------------------------------
CostScalar cacheHitProbability = csOne;
if (availableCacheInBlocks < totalBlocksLowerBound )
{
cacheHitProbability =
availableCacheInBlocks / totalBlocksLowerBound;
}
// sanity check:
DCMPASSERT(cacheHitProbability >= 0 AND cacheHitProbability <= 1.0);
const CostScalar extraBeginBlocks =
successfulProbes - beginBlocksLowerBound;
const CostScalar extraDataBlocks =
successfulProbes * blksPerSuccProbe
- totalBlocksLowerBound;
// -----------------------------------------------------------------------
// Failed probes is considered below because we assume that
// to know that a probe failed we need to reach to the
// bottom of the b-tree and grab the first data block.
// -----------------------------------------------------------------------
seq_kb_read =
(
(totalBlocksLowerBound + indexBlocksLowerBound + failedProbes)
+ (csOne - cacheHitProbability)
*extraDataBlocks
) * getIndexDesc()->getBlockSizeInKb();
// We assume that once an index block is loaded, it remains
// in cache:
seeks =
beginBlocksLowerBound // data seeks for all probes
+ indexBlocksLowerBound // index seeks
+ failedProbes
+ (csOne - cacheHitProbability) * extraBeginBlocks;
}
// LCOV_EXCL_START :cnu
void
FileScanOptimizer::computeIOForFullTableScan(
CostScalar& dataRows /* out */
,CostScalar& seeks /* out */
,CostScalar& seq_kb_read /* out */
,const CostScalar& probes
) const
{
const CostScalar indexBlocksLowerBound = getIndexDesc()->
getEstimatedIndexBlocksLowerBound(probes);
// Assume that every record in the table will be read:
dataRows = getRawInnerHistograms().getRowCount();
const CostScalar blksInTable =
CostScalar( dataRows
/ getIndexDesc()->getEstimatedRecordsPerBlock());
seq_kb_read = (probes * blksInTable) * getIndexDesc()->getBlockSizeInKb();
seeks =
probes // data seeks
+
indexBlocksLowerBound; // index seeks
} // FileScanOptimizer::computeIOForFullTableScan(...)
// LCOV_EXCL_STOP
void
FileScanOptimizer::computeCostVectorsForMultipleSubset(
SimpleCostVector& firstRow /* out */
,SimpleCostVector& lastRow /* out */
,CostScalar& seqKBytesPerScan /* out */
,const CostScalar& totalRows
,const CostScalar& subsetRequests // a request for a subset of data
,const CostScalar& failedProbes // produce and EMPTY mdam tree
,const CostScalar& seeks
,const CostScalar& seq_kb_read
,const ValueIdSet& keyPreds
,const ValueIdSet& exePreds
,const CostScalar& incomingProbes // probes incoming to the operator
,const CostScalar& mdamNetPredCnt // the sum of predicates in all disjuncts
) const
{
// charge the cost of building the mdam network:
// We assume that the cost of building
// the mdam network in the executor is
// a linear function of the mdam key predicates:
// The cost it takes to start building the mdam network:
CostScalar mdamNetCostOvh;
// The cost that takes to build the mdam network per predicate:
// (we assume that the cost to build the mdam network is a linear function
// of the key predicates)
CostScalar mdamNetCostPerPred;
if ( CmpCommon::getDefault(RANGESPEC_TRANSFORMATION) == DF_ON )
{
mdamNetCostOvh = 0.0;
mdamNetCostPerPred = 0.0;
}
else
{
mdamNetCostOvh = CostPrimitives::getBasicCostFactor(MDAM_CPUCOST_NET_OVH);
mdamNetCostPerPred = CostPrimitives::getBasicCostFactor(MDAM_CPUCOST_NET_PER_PRED);
}
// The cost to start up the mdam network and to finish building it,
// for one probe:
CostScalar mdamNetBuildCost =
mdamNetCostOvh + mdamNetCostPerPred * mdamNetPredCnt;
// The cost per partition:
const CostScalar activePartitions = getNumActivePartitions();
mdamNetBuildCost = mdamNetBuildCost/activePartitions;
// The first row must build the net no matter what, but only for the
// first probe (because we assume that the first probe finds the first
// row):
firstRow.addInstrToCPUTime(mdamNetBuildCost);
// Also add it to the idle time so that it gets reflected
// on the elapsed time and is a factor in comparison with
// the single subset case:
CostScalar mdamNetOverheadTime =
mdamNetBuildCost
*
CostPrimitives::getBasicCostFactor(MSCF_ET_CPU);
firstRow.addToIdleTime(mdamNetOverheadTime);
// But the last row builds it for *all* probes:
// the successful probes pay full price:
mdamNetBuildCost = mdamNetBuildCost * MIN_ONE(incomingProbes-failedProbes);
// fail probes only pay initial overhead:
mdamNetBuildCost += failedProbes * mdamNetCostOvh;
lastRow.addInstrToCPUTime(mdamNetBuildCost);
// Add also to idel time so that it makes a difference compared
// to search key (usually IO dominates over CPU, so if we add
// only to CPU then it won't make a difference an MDAM may look
// like a winner when in fact it is a loser)
mdamNetOverheadTime =
mdamNetBuildCost
*
CostPrimitives::getBasicCostFactor(MSCF_ET_CPU);
lastRow.addToIdleTime(mdamNetOverheadTime);
// finish costing:
computeCostVectors(firstRow // out
,lastRow // out
,seqKBytesPerScan
,totalRows
,subsetRequests
,subsetRequests // all are successful
,seeks
,seq_kb_read
,keyPreds
,exePreds
,incomingProbes
);
} // FileScanOptimizer::computeCostVectorsForMultipleSubset(...)
void
FileScanOptimizer::computeCostVectors(
SimpleCostVector& firstRow /* out */
,SimpleCostVector& lastRow /* out */
,CostScalar& seqKBytesPerScan /* out */
,const CostScalar& totalRows
,const CostScalar& subsetRequests // a request for a subset of data
,const CostScalar& successfulSubsetRequests // requests that hit data
,const CostScalar& seeks
,const CostScalar& seq_kb_read
,const ValueIdSet& keyPreds
,const ValueIdSet& exePreds
,const CostScalar& incomingProbes // probes incoming to the operator
) const
{
DCMPASSERT(totalRows >= 0);
DCMPASSERT(subsetRequests >= 0);
DCMPASSERT(successfulSubsetRequests <= subsetRequests);
// -----------------------------------------------------------------------
// The cost that we need to compute is that of a single scan.
// However, the data that we are getting corresponds
// to that for scanning all the data to answer the
// original query. This method partitions that data so
// that the cost per scan can be created.
// It takes into account:
// - several scans may be working in the same cpu (cpu contention)
// - the scan is being done syncronously or asynchronously
// - several CPUS are available for scanning the data (data parallelism)
//
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
// To estimate the cost per scan we need to know how many
// partitions are actually doing work. Then, the cost per
// scan will be computed in general by dividing the total cost
// over the active partitions.
// -----------------------------------------------------------------------
//
// -----------------------------------------------------------------------
// getEstNumActivePartitionsAtRuntime() will return the estimated number of
// active partitions at runtime.This variable is set to the maximum number
// of partitions that can be active,if a query has an equality predicate
// with a host/parameter variable on leading partition column.This is done
// is method OptPysRelExpr::computeDP2CostThatDependsonSPP().
// If this is not set, this function will return the active partition
// determined by getNumActivePartition() function.
// This is currently used only for costing of scans.
const CostScalar activePartitions = getEstNumActivePartitionsAtRuntime();
CostScalar totalRowsInResultTable = (CostScalar)getResultSetCardinality();
// Patch for Sol.10-031024-0755 (Case 10-031024-9406).
// This change can be made unconditional. TotalRowsInResultTable (which
// is selected number of rows, so it is a misnomer) cannot be greater
// than the total number of rows scanned, defined by input dataRows
if ( CmpCommon::getDefault(COMP_BOOL_35) == DF_OFF )
totalRowsInResultTable = MINOF(totalRowsInResultTable,totalRows);
// -----------------------------------------------------------------------
// Scale down the total factors to single scan:
// We assume uniform distribution, i.e. all active partitions are
// doing the same ammount of work.
// -----------------------------------------------------------------------
const CostScalar
rowsPerScan = CostScalar(totalRows/activePartitions).getCeiling()
,selectedRowsPerScan =
CostScalar(totalRowsInResultTable/activePartitions).getCeiling()
,requestsPerScan = CostScalar(subsetRequests/activePartitions).getCeiling()
,successfulRequestsPerScan =
CostScalar(successfulSubsetRequests/activePartitions).getCeiling()
,seeksPerScan = CostScalar(seeks/activePartitions).getCeiling()
,probesPerScan = CostScalar(incomingProbes/activePartitions).getCeiling()
;
CostScalar seq_kb_readPerScan =
CostScalar(MAXOF(seq_kb_read/activePartitions,4.0));
// -----------------------------------------------------------------------
// Set up some parameters in preparation for costing:
// -----------------------------------------------------------------------
const CostScalar
recordsPerBlock =
getIndexDesc()->getEstimatedRecordsPerBlock().getCeiling();
const CostScalar
kbPerBlock = getIndexDesc()->getBlockSizeInKb();
const CostScalar
dp2MessageBufferSizeInKb =
CostPrimitives::getBasicCostFactor(DP2_MESSAGE_BUFFER_SIZE)
,recordSizeInKb = getIndexDesc()->getRecordSizeInKb();
// rows that fit in dp2 buffer (rfdp2):
const double
rfdp2 =
(dp2MessageBufferSizeInKb / recordSizeInKb).getFloor().getValue();
// Assume that the first row is obtained in the first
// successful request:
const CostScalar
rowsPerScanForFirstRow =
(successfulRequestsPerScan.isGreaterThanZero() ?
MINOF(
(rowsPerScan/successfulRequestsPerScan).getCeiling()
, rfdp2)
: csZero);
const CostScalar
selectedRowsPerScanForFirstRow =
(successfulRequestsPerScan.isGreaterThanZero() ?
MINOF(
(selectedRowsPerScan/successfulRequestsPerScan).getCeiling()
, rfdp2)
: csZero);
// -----------------------------------------------------------------------
// Now cost each bucket of the simple cost vector:
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
// CPU:
// Instructions to traverse the B-Tree for successful requests
// +
// Instructions to traverse the B-Tree for unsuccessful requests
// -----------------------------------------------------------------------
CostScalar
cpuInstructionsFR = csZero
,cpuInstructionsLR = csZero;
if (requestsPerScan.isGreaterThanZero())
{
// We do a binary search after we get the data block in memory to find
// the matching row. So in worst case scenario it would just be
// ln(recordsPerBlock). log function will give us the natural logarithm.
// se to get logX = lnX/log2 1.44 * lnX.
const CostScalar cpuForBTreeComparisons =
( 1.44 * log((getIndexDesc()->
getEstimatedRecordsPerBlock()).getValue())
* getIndexDesc()->getIndexKey().entries()
* CostPrimitives:: getBasicCostFactor(CPUCOST_PREDICATE_COMPARISON)
* getIndexDesc()->getIndexLevels() );
if (successfulRequestsPerScan.isGreaterThanZero())
{
// Successful requests:
// We assume that the cpu cost for *each* successful request is
// a constant overhead plus a
// function of the number of index blocks traversed and the
// number of key predicates.
//
// Number of comparisons that are
// performed to traverse the non-leaf
// levels and arrive at the leaf. We assume that the search within
// the index, for each succ. probe,
// has to perform key comparisons with half the number
// of rows on each page (root + non-leaf pages + leaf page that
// contains the begin key value). We take into account the number
// of key columns that participate in the comparison.
cpuInstructionsLR += // LR CPU INSTRUCTION CALC:
successfulRequestsPerScan
*
( cpuForBTreeComparisons
+
CostPrimitives::getBasicCostFactor(CPUCOST_DATARQST_OVHD) );
}
// unsuccessful requests:
const CostScalar
failedRequestsPerScan =
requestsPerScan - successfulRequestsPerScan;
if (failedRequestsPerScan.isGreaterThanZero())
{
// Assume failed requests traverse half the B-Tree to find
// out they failed
cpuInstructionsLR += // LR CPU INSTRUCTION CALC:
failedRequestsPerScan
*
( (cpuForBTreeComparisons/2).getCeiling()
+
CostPrimitives::getBasicCostFactor(CPUCOST_DATARQST_OVHD) );
}
// assume that the first row gets its data in the successful request:
// FR CPU INSTRUCTION CALC:
cpuInstructionsFR += // FR CPU INSTRUCTION CALC:
cpuInstructionsLR/requestsPerScan;
// ------------------------------------------------------------
// There is one copy of every row to be made to bring the
// rows into the exeindp2: (more copies are done to ship
// to the master, but these copies are costed by the exchange)
// ------------------------------------------------------------
const CostScalar numberOfCopiesOfRows = csOne;
CostScalar instrPerRowMovedOutOfTheCache =
CostScalar(CostPrimitives
::getBasicCostFactor(CPUCOST_COPY_ROW_OVERHEAD))
+
numberOfCopiesOfRows
*
recordSizeInKb
*
CostScalar(1024.) // convert kb to bytes
*
CostScalar(CostPrimitives
::getBasicCostFactor(CPUCOST_COPY_ROW_PER_BYTE))
;
// locking per row:
// $$ in the future set this var with info obtained from the
// $$ enviroment (it should have the value 0 or 1):
const CostScalar locking = csZero;
instrPerRowMovedOutOfTheCache +=
locking * CostScalar(CostPrimitives
::getBasicCostFactor(CPUCOST_LOCK_ROW));
// key encoding/decoding per row:
// $$$ In the future we need info from the IndexDesc regarding
// the key length:
const CostScalar keyLength = csZero;
instrPerRowMovedOutOfTheCache +=
keyLength * CostScalar(CostPrimitives
::getBasicCostFactor(CPUCOST_SCAN_KEY_LENGTH));
// Add a per kb cost for the cost of moving
// every block from disk to DP2:
CostScalar instrToCopyDataFromDiskToDP2Cache =
seq_kb_readPerScan
*
CostScalar(CostPrimitives::getBasicCostFactor(
CPUCOST_SCAN_DSK_TO_DP2_PER_KB));
// Add a per seek cost for the cost of moving
// data from disk to DP2:
instrToCopyDataFromDiskToDP2Cache +=
seeksPerScan
*
CostScalar(CostPrimitives::getBasicCostFactor(
CPUCOST_SCAN_DSK_TO_DP2_PER_SEEK));
// $$$ First row
// Add this later
// LR CPU INSTRUCTION CALC:
cpuInstructionsLR += instrToCopyDataFromDiskToDP2Cache;
// CPUCOST_SCAN_OVH_PER_KB
// is a catch all cost *per KB*
// This catches all overhead that happens when data is moved
// from the dp2 cache into the exe in dp2.
instrPerRowMovedOutOfTheCache +=
recordSizeInKb
*
// Catch all overhead per KB:
// this is a per kb read overhead:
CostScalar(CostPrimitives::getBasicCostFactor(
CPUCOST_SCAN_OVH_PER_KB))
;
// Add a a per-row overhead:
instrPerRowMovedOutOfTheCache +=
CostScalar(CostPrimitives::getBasicCostFactor(
CPUCOST_SCAN_OVH_PER_ROW));
// Only rows that are selected are moved out of the cache:
// First row
cpuInstructionsFR += // FR CPU INSTRUCTION CALC:
instrPerRowMovedOutOfTheCache
*
selectedRowsPerScanForFirstRow;
// LR CPU INSTRUCTION CALC:
cpuInstructionsLR += // LR CPU INSTRUCTION CALC:
instrPerRowMovedOutOfTheCache
*
selectedRowsPerScan;
// Compute cost for executor predicates
const CostScalar instrPerExePredEvaluationPerRow =
CostPrimitives::getBasicCostFactor(CPUCOST_PREDICATE_COMPARISON ) *
exePreds.entries();
// First row
cpuInstructionsFR += // FR CPU INSTRUCTION CALC:
instrPerExePredEvaluationPerRow
*
rowsPerScanForFirstRow;
// LR CPU INSTRUCTION CALC:
cpuInstructionsLR += // LR CPU INSTRUCTION CALC:
instrPerExePredEvaluationPerRow
*
rowsPerScan;
} // if requests per scan > 0
DCMPASSERT(cpuInstructionsFR <= cpuInstructionsLR);
firstRow.addInstrToCPUTime(cpuInstructionsFR);
lastRow.addInstrToCPUTime(cpuInstructionsLR);
// -----------------------------------------------------------------------
// SEEKS:
// -----------------------------------------------------------------------
CostScalar
seeksFR = csZero
,seeksLR = csZero;
// no seeks if no requests:
if (requestsPerScan.isGreaterThanZero())
{
// Assume first row is obtained in the first request:
seeksFR = (seeksPerScan/requestsPerScan).getCeiling();
seeksLR = seeksPerScan;
}
firstRow.addSeeksToIOTime(seeksFR);
lastRow.addSeeksToIOTime(seeksLR);
DCMPASSERT(seeksFR <= seeksLR);
// -----------------------------------------------------------------------
// KB TRANSFERED
// -----------------------------------------------------------------------
CostScalar
seqKBFR = csZero
,seqKBLR = csZero;
// no kb transfered if no requests:
if (successfulRequestsPerScan.isGreaterThanZero())
{
// Compute the kb that need to be transfered for first row:
// The first row must read just enough to fill the DP2 buffer:
// This is the minimum of:
// seq_kb_read for all rows that qualify the query and
// seq_kb_read needed to fill the DP2 buffer:
const CostScalar maxBlksPerScanForFirstRow =
CostScalar(rowsPerScanForFirstRow/recordsPerBlock).getCeiling();
CostScalar
maxKbReadPerScanForFirstRow = maxBlksPerScanForFirstRow * kbPerBlock;
// Add bytes due to the effect of selectivity on read-ahead:
const CostScalar totalRowsInRawTable =
getRawInnerHistograms().getRowCount().getCeiling();
// But don't go into this logic if the fraction is zero
const CostScalar ioTransferCostPrefetchMissesFraction =
getDefaultAsDouble(IO_TRANSFER_COST_PREFETCH_MISSES_FRACTION);
if (ioTransferCostPrefetchMissesFraction.isGreaterThanZero()
AND
totalRowsInRawTable.isGreaterThanZero())
{
// 03/16/98: Observation:
// It takes around 8 seconds to scan 100,000 wisconsin rows
// when there is an executor predicate that selects only
// one record.
// It takes around 12 seconds to scan 100,000 wisconsin rows
// when there is no executor predicates.
// In both cases the KB transfered is the same (full table
// scan). Thus it seems that the speed of transfer depends
// on the selectivity of the executor predicates.
// Note that is in only I/O time, not CPU. This
// anomaly could be explained by the fact that as
// the selectivity of the executor predicate decrease
// the DP2 has to do more work to manage the buffers
// that will be sent to the ESP, thus stopping the
// data transfer and making the disk miss the read ahead.
// When the executor is very selective, the disk read ahead
// is not interrupted and the speed is faster.
// Currently, the speed of transfer is a constant, not
// a function. Therefore, to accommodate this fact,
// I'll add bytes to the sequential read result as
// the selectivity of the exe. preds compared to
// the sequential read increases.
// the maximum read ahead is when only one row is selected
// the minimum when all rows are selected
// read ahead reduction happens because when the dp2 buffer
// fills out, dp2 must stop reading until it sends the
// buffer to the ESP requesting data. Thus, there cannot
// be read ahead reduction when we read less (per
// probe) than the
// dp2 buffer's size.
CostScalar kbReadAgainstKbSelectedRatio = csZero;
CostScalar seq_kb_readPerScanPerRqst =
(seq_kb_readPerScan / activePartitions).getCeiling()
/
successfulRequestsPerScan;
if (seq_kb_readPerScanPerRqst
>
CostPrimitives::getBasicCostFactor(DP2_MESSAGE_BUFFER_SIZE))
{
// Read ahead reduction, figure out reduction factor:
CostScalar rowsInResultTablePerScan =
(totalRowsInResultTable/activePartitions).getCeiling();
CostScalar blocksInResultTablePerScan =
(rowsInResultTablePerScan
/
getIndexDesc()->getEstimatedRecordsPerBlock()).getCeiling();
CostScalar kbInResultTablePerScan =
blocksInResultTablePerScan * getIndexDesc()->getBlockSizeInKb();
kbReadAgainstKbSelectedRatio =
kbInResultTablePerScan / seq_kb_readPerScan;
// The ratio above should be alwasy less than 1, but
// because of synthesis problems it may be greater than
// one. This would screw costing so correct it
// (best we can do is to make it zero)
//DCMPASSERT(seq_kb_readPerScan >= kbInResultTablePerScan);
if ( kbReadAgainstKbSelectedRatio.isGreaterThanOne() )
{
kbReadAgainstKbSelectedRatio = csZero;
}
}
// As the scan executor predicates are more selective,
// the scan needs to read more data because of prefetch misses:
seq_kb_readPerScan +=
seq_kb_readPerScan *
ioTransferCostPrefetchMissesFraction * kbReadAgainstKbSelectedRatio;
maxKbReadPerScanForFirstRow +=
maxKbReadPerScanForFirstRow *
ioTransferCostPrefetchMissesFraction * kbReadAgainstKbSelectedRatio;
}
// we can't read more blocks for the
// first row than for the entire scan:
seqKBFR =
MINOF(maxKbReadPerScanForFirstRow, seq_kb_readPerScan);
seqKBLR =
seq_kb_readPerScan;
} // requestsPerScan > 0
// else if no successful requests then some data
// may still be read (i.e. to scan the index levels)
firstRow.addKBytesToIOTime(seqKBFR);
lastRow.addKBytesToIOTime(seqKBLR);
seqKBytesPerScan = seqKBLR;
DCMPASSERT(seqKBFR <= seqKBLR);
// -----------------------------------------------------------------------
// NORMAL MEMORY
// -----------------------------------------------------------------------
// A buffer to evaluate expressions:
/*j Currently not used 05/08/01, commented out for future
firstRow.addToNormalMemory(dp2MessageBufferSizeInKb);
lastRow.addToNormalMemory(dp2MessageBufferSizeInKb);
j*/
// -----------------------------------------------------------------------
// PERSISTENT MEMORY
// -----------------------------------------------------------------------
const CostScalar cacheSizeInBlocks =
getDP2CacheSizeInBlocks(getIndexDesc()->getBlockSizeInKb());
const CostScalar dp2CacheInKb =
cacheSizeInBlocks * getIndexDesc()->getBlockSizeInKb();
/*j Currently not used 05/08/01, commented out for future
firstRow.addToPersistentMemory(dp2CacheInKb);
lastRow.addToPersistentMemory(dp2CacheInKb);
j*/
// -----------------------------------------------------------------------
// NUM. LOCAL MESSAGES
// -----------------------------------------------------------------------
//no local messages
// -----------------------------------------------------------------------
// KB REMOTE MESSAGES TRANSFER
// -----------------------------------------------------------------------
// no local messages
// -----------------------------------------------------------------------
// NUM. REMOTE MESSAGES
// -----------------------------------------------------------------------
//no remote messages
// -----------------------------------------------------------------------
// KB REMOTE MESSAGES TRANSFER
// -----------------------------------------------------------------------
// no remote msg
// -----------------------------------------------------------------------
// IDLE TIME:*************************************************************
// -----------------------------------------------------------------------
// All scan initialization cost (opening a file, etc.) are
// added to idle since it does not overlapp with CPU.
// We cannot add it to blocking since the scan is not a
// blocking operator.
// -----------------------------------------------------------------------
// No idle time
CostScalar
idleTimeFR = csZero
,idleTimeLR = csZero;
// the openFile() method really computes all the initial time
// that it is spent by setting up the DP2 access for a given
// scan.
// CPUCOST_SUBSET_OPEN lumps together all the overhead needed
// to set-up the access to each partition. Thus it is a blocking
// cost, nothing can overlap with it.
// Since scans are not blocking, by definition, put the cost
// in idle time:
// this is the cost for opening all the partitions but the first partition.
// The first partition is opened before the ScanOptimizer is called. During
// opening the first partition, the necessary info on the file is also
// acquired so it is more expensive and cannot be overlaid.
// Root accounts for the cost of opening the first partition of all the tables.
CostScalar openFileCPU =
CostPrimitives::getBasicCostFactor(CPUCOST_SUBSET_OPEN_AFTER_FIRST)
*
MAXOF(0, (activePartitions.getValue() - 1) );
idleTimeFR = idleTimeLR =
openFileCPU * CostPrimitives::getBasicCostFactor(MSCF_ET_CPU);
firstRow.addToIdleTime(idleTimeFR);
lastRow.addToIdleTime(idleTimeLR);
// -----------------------------------------------------------------------
// DISK*******************************************************************
// -----------------------------------------------------------------------
// no disk access for scan
// -----------------------------------------------------------------------
// PROBES: ******************************************************
// -----------------------------------------------------------------------
firstRow.setNumProbes(probesPerScan);
lastRow.setNumProbes(probesPerScan);
} // computeCostVectors(...)
// -----------------------------------------------------------------------
// INPUT:
// probes: the total probes coming in
// preds: the predicates to compute the join
//
// OUTPUT:
// successfuleProbes: those probes that match any data
// uniqueSuccProbes: those successful probes that do not have duplicates
// duplicateSuccProbes: successfulProbes - uniqueSuccProbes
// failedProbes: probes - successfulProbes
// -----------------------------------------------------------------------
void
ScanOptimizer::categorizeProbes(CostScalar& successfulProbes /* out */
,CostScalar& uniqueSuccProbes /* out */
,CostScalar& duplicateSuccProbes /* out */
,CostScalar& failedProbes /* out */
,CostScalar& uniqueFailedProbes
,const CostScalar& probes
,const ValueIdSet& preds
,const Histograms& outerHistograms
,const NABoolean isMDAM
,CostScalar * dataRows
) const
{
if (outerHistograms.isEmpty())
{
successfulProbes = uniqueSuccProbes = probes;
duplicateSuccProbes = failedProbes = csZero;
// Patch for Sol.10-031024-0755 (Case 10-031024-9406). When RI
// constraints were implemented, corresponding histograms, cardinality
// and costing were forgotten. As a result nested join into referenced
// table does not pass histogram information to the right child.
// If the right child does not have histograms in inputLogProp
// it considers this join as a cross product.
// This is the second part of the patch. The number of rows to cost was
// left as 0 in case of empty outputHistograms which is not right if we
// have a real cross product or do a full scan of the table for each
// probe. Therefore the cost of index_scan was underestimated and we
// picking up index scan (containing referenced column) instead of
// file_scan_unique of primary index or index with referenced column
// as a key.
// I changed dataRows to the product of probes and table cardinality.
if ( CmpCommon::getDefault(COMP_BOOL_35) == DF_OFF )
*dataRows *= probes;
}
else
{
// If there are input values we are in a nested join case,
// else it must be a cartesian product:
const ValueIdSet& inputValues =
getRelExpr().getGroupAttr()->getCharacteristicInputs();
// get this filescan statistics:
IndexDescHistograms
joinedHistograms(*getIndexDesc(),
getIndexDesc()->getIndexKey().entries() );
// Since there are input values, apply the join preds
// to estimate the failed probes:
// Apply the join predicates to the local statistics:
const SelectivityHint * selHint = getIndexDesc()->getPrimaryTableDesc()->getSelectivityHint();
const CardinalityHint * cardHint = getIndexDesc()->getPrimaryTableDesc()->getCardinalityHint();
joinedHistograms.applyPredicatesWhenMultipleProbes(
preds
,*(getContext().getInputLogProp())
,inputValues
,isMDAM
,selHint
,cardHint
,NULL
,REL_SCAN);
failedProbes = csZero; // assume no probes will fail
if (NOT inputValues.isEmpty())
{
if (NOT preds.isEmpty())
{
// and compute the failed probes:
const ValueIdSet& operatorValues =
getIndexDesc()->getIndexKey();
failedProbes =
joinedHistograms.computeFailedProbes(outerHistograms
,preds
,inputValues
,operatorValues
);
// odbc unfriendly assertion
//DCMPASSERT(probes >= failedProbes);
// When histograms are ready, remove the block
// below and uncomment the assertion above.
if (probes < failedProbes)
{
FSOWARNING("probes < failedProbes");
failedProbes = probes;
}
}
} // if there are input values
if(dataRows) *dataRows = joinedHistograms.getRowCount();
// ------------------------------------------------------------------
// Compute successful probes:
// -------------------------------------------------------------------
successfulProbes = probes - failedProbes;
// successfulProbes.round(); // to make it zero if very small
successfulProbes.roundIfZero();
// if (successfulProbes < 0)
// {
// FSOWARNING("successfulProbes < 0.");
// successfulProbes = 0.;
// }
successfulProbes.minCsZero();
// --------------------------------------------------------------
// Compute unique probes:
// -------------------------------------------------------------
// Combine unique entry count of the probes:
// (assume independance of columns and compute
// Compute combined UEC of outer histograms):
CostScalar uniqueEntryCountOfProbes = csOne;
CostScalar histUEC = csZero;
for (CollIndex i=0; i < outerHistograms.entries(); i++)
{
histUEC = outerHistograms[i].getColStats()
->getTotalUec().getCeiling();
//we use the method EXP_REAL64_OV_MUL to get the product of the first two
//values passed in and if there occurs a overflow or underflow then
//"overflow" gets TRUE and the "tempvariable" get junk value, if there
//is no overflow then "overflow" gets false and the product is stored
//in tempvariable and later assigned it to uniqueEntryCountOfProbes
uniqueEntryCountOfProbes *= histUEC;
}
//should not be more than # of probes
if (uniqueEntryCountOfProbes > probes)
uniqueEntryCountOfProbes = probes;
//#should not be less than 1
// if (uniqueEntryCountOfProbes < 1) uniqueEntryCountOfProbes = 1;
uniqueEntryCountOfProbes.minCsOne();
// The unique successful probes are the successful
// fraction of their UEC:
uniqueSuccProbes =
MINOF(
((successfulProbes/probes)*uniqueEntryCountOfProbes).getValue(),
successfulProbes.getValue());
uniqueFailedProbes =
MINOF( ((failedProbes/probes)*uniqueEntryCountOfProbes).getValue(),
successfulProbes.getValue() );
// --------------------------------------------------------------
// Compute successful probes that are duplicates of another
// successful probe:
// --------------------------------------------------------------
duplicateSuccProbes = successfulProbes - uniqueSuccProbes;
}
} // FileScanOptimizer::categorizeProbes(...)
NABoolean FileScanOptimizer::hasTooManyDisjuncts() const
{
const Disjuncts &disjuncts = getDisjuncts();
CollIndex numOfEntries = disjuncts.entries();
if(numOfEntries > MDAM_MAX_NUM_DISJUNCTS)
return TRUE;
else
return FALSE;
}
// eof
// Implementation of MDAMCostWA methods
MDAMCostWA::MDAMCostWA
(FileScanOptimizer & optimizer,
NABoolean mdamForced,
MdamKey *mdamKeyPtr,
const Cost *costBoundPtr,
const ValueIdSet & exePreds,
SimpleCostVector & disjunctsFR,
SimpleCostVector & disjunctsLR) :
mdamWon_(FALSE)
,noExePreds_(TRUE)
,numKBytes_(0)
,mdamForced_(mdamForced)
,mdamKeyPtr_(mdamKeyPtr)
,costBoundPtr_(costBoundPtr)
,optimizer_(optimizer)
,disjunctsFR_(disjunctsFR)
,disjunctsLR_(disjunctsLR)
,scmCost_(NULL)
,exePreds_(exePreds) //optimizer.getRelExpr().getSelectionPred())
,innerRowsUpperBound_( optimizer.getRawInnerHistograms().
getRowCount().getCeiling() )
,innerBlocksUpperBound_(0)
,estimatedRecordsPerBlock_( optimizer.getIndexDesc()->
getEstimatedRecordsPerBlock() )
,isMultipleProbes_(optimizer.isMultipleProbes())
,scanForcePtr_(optimizer.findScanForceWildCard())
,incomingProbes_(optimizer.getIncomingProbes())
,firstColumnHistogram_( *(optimizer.getIndexDesc()), 1 )
,outerHistograms_(optimizer.getContext().getInputLogProp()->getColStats())
,disjunctIndex_(0)
,disjunctOptRows_(0)
,disjunctOptRqsts_(0)
,disjunctOptProbesForSubsetBoundaries_(0)
,disjunctOptSeeks_(0)
,disjunctOptSeqKBRead_(0)
,disjunctOptKeyPreds_()
,disjunctMdamOK_(FALSE)
,disjunctNumLeadingPartPreds_(0)
,disjunctFailedProbes_(0)
{}
// This function computes whether MDAM wins over the cost bound
// It sums up cost factors for all disjuncts, calculates the cost
// and compares with the cost bound.
void MDAMCostWA::compute()
{
if(isMultipleProbes_)
{
MDAM_DEBUG0(MTL2, "Mdam scan is multiple probes");
}
else {
incomingProbes_ = 1;
MDAM_DEBUG0(MTL2, "Mdam scan is a single probe");
}
// total stats counters for all disjuncts
CostScalar
totalRows // rows
,totalRqsts // probes
,totalFailedProbes // probes that create empty mdam trees
,totalProbesForSubsetBoundaries // probes to get the next subset boundary
,totalSeeks // total number of disk arm movements
,totalDisjunctPreds // the sum of predicates in all disjuncts
,totalSeqKBRead; // total number of blocks that were read sequentially
CollIndex totalNumLeadingPartPreds = 0;
CostScalar seqKBytesPerScan;
ValueIdSet totalKeyPreds;
// Compute upper bounds:
computeBlocksUpperBound(innerBlocksUpperBound_ /* out*/
,innerRowsUpperBound_ /* in */
,estimatedRecordsPerBlock_ /*in*/);
const CostScalar recordSizeInKb = optimizer_.getIndexDesc()->getRecordSizeInKb();
const CostScalar blockSizeInKb =
optimizer_.getIndexDesc()->getBlockSizeInKb();
NADefaults & defs = ActiveSchemaDB()->getDefaults();
const CostScalar maxDp2ReadInBlocks =
CostScalar(defs.getAsULong(DP2_MAX_READ_PER_ACCESS_IN_KB))/
blockSizeInKb;
// -----------------------------------------------------------------------
// Loop through every disjunct and:
// 1 Find the optimal disjunct prefix for the disjunct
// 2 Compute the sum of the costs of all disjuncts
// 3 If the cost exceeds the bound, mdam lose and return
// -----------------------------------------------------------------------
for (CollIndex disjunctIndex=0;
disjunctIndex < mdamKeyPtr_->getKeyDisjunctEntries();
disjunctIndex++)
{
disjunctIndex_ = disjunctIndex;
computeDisjunct();
if(NOT disjunctMdamOK_)
{
mdamWon_ = FALSE;
// // invalidate the cache
// disjunctsFR_.reset();
// disjunctsLR_.reset();
MDAM_DEBUG0(MTL2, "Mdam scan lost because disjunctMdamOK_ is false");
return;
}
// Sum up the statistics for all the disjuncts costed so far
totalRows += disjunctOptRows_;
totalRqsts += disjunctOptRqsts_ + disjunctOptProbesForSubsetBoundaries_;
totalSeeks += disjunctOptSeeks_;
totalSeqKBRead += disjunctOptSeqKBRead_;
totalKeyPreds.insert(disjunctOptKeyPreds_);
totalDisjunctPreds += disjunctOptKeyPreds_.entries();
totalNumLeadingPartPreds += disjunctNumLeadingPartPreds_;
// This may not be correct,
// If a probe failes for one disjunct, it may not fail for another
// Need to investigate after R2.0.
totalFailedProbes += disjunctFailedProbes_;
// If we have partitioning key predicates then we will be reading
// different parts of the partition by different esps. There will be
// some seeks going from one esp reading to another. Assume
// that we will at least read ahead
// the normal amount (for now reduce possible seeks by 100).
// If there is partition predicates in the disjunct key predicates,
// What would be the effects on the seeks ?
if (totalNumLeadingPartPreds == mdamKeyPtr_->getKeyDisjunctEntries()
AND (totalRows / estimatedRecordsPerBlock_) > maxDp2ReadInBlocks)
totalSeeks += (totalRows / estimatedRecordsPerBlock_)/
maxDp2ReadInBlocks/100;
// Compute the cost of all the disjuncts analyzed so far:
if (CmpCommon::getDefault(SIMPLE_COST_MODEL) == DF_ON)
{
if (optimizer_.getIndexDesc()->getPrimaryTableDesc()->getNATable()->isHbaseTable())
{
// Cost of sending 'probes' to materialize values of prefix key column(s) with
// missing predicate(s) should be accounted. For each such probe Hbase Region server
// returns 1 row. So, totalRqsts (probes) should be added to seeks as well as
// rows processed.
CostScalar totRowsProcessed = totalRows + totalRqsts;
CostScalar totSeeks = totalSeeks + totalRqsts;
scmCost_ = optimizer_.scmComputeMDAMCostForHbase(totRowsProcessed, totSeeks,
totalSeqKBRead, incomingProbes_);
}
else
{
CostScalar activePartitions = optimizer_.getEstNumActivePartitionsAtRuntime();
CostScalar totalRowsInResultTable = (CostScalar)optimizer_.getResultSetCardinality();
// Patch for Sol.10-031024-0755 (Case 10-031024-9406).
totalRowsInResultTable = MINOF(totalRowsInResultTable,totalRows);
CostScalar
rowsPerScan = CostScalar(totalRows/activePartitions).getCeiling()
,selectedRowsPerScan =
CostScalar(totalRowsInResultTable/activePartitions).getCeiling()
,seeksPerScan = CostScalar(totalSeeks/activePartitions).getCeiling()
,seqKBPerScan = CostScalar(MAXOF(totalSeqKBRead/activePartitions,4.0))
,probesPerScan = CostScalar(incomingProbes_/activePartitions).getCeiling();
// Factor in row sizes.
CostScalar rowSize = recordSizeInKb * csOneKiloBytes;
CostScalar outputRowSize = optimizer_.getRelExpr().getGroupAttr()->getRecordLength();
CostScalar rowSizeFactor = optimizer_.scmRowSizeFactor(rowSize);
CostScalar outputRowSizeFactor = optimizer_.scmRowSizeFactor(outputRowSize);
rowsPerScan *= rowSizeFactor;
selectedRowsPerScan *= outputRowSizeFactor;
CostScalar rowSizeFactorSeqIO = optimizer_.scmRowSizeFactor
(rowSize, ScanOptimizer::SEQ_IO_ROWSIZE_FACTOR);
CostScalar rowSizeFactorRandIO = optimizer_.scmRowSizeFactor
(rowSize, ScanOptimizer::RAND_IO_ROWSIZE_FACTOR);
CostScalar ioSeqPerScan = (seqKBPerScan/blockSizeInKb).getCeiling() * rowSizeFactorSeqIO;
CostScalar ioRandPerScan = seeksPerScan * rowSizeFactorRandIO;
scmCost_ =
optimizer_.scmCost(rowsPerScan, selectedRowsPerScan, csZero, ioRandPerScan,
ioSeqPerScan, probesPerScan, rowSize, csZero, outputRowSize, csZero);
}
// scale up mdam cost by factor of NCM_MDAM_COST_ADJ_FACTOR --default is 1
CostScalar costAdj = (ActiveSchemaDB()->getDefaults()).getAsDouble(NCM_MDAM_COST_ADJ_FACTOR);
scmCost_->cpScmlr().scaleByValue(costAdj);
if (costBoundPtr_ != NULL &&
costBoundPtr_->scmCompareCosts(*scmCost_) == LESS)
{
mdamWon_ = FALSE;
MDAM_DEBUG0(MTL2, "Mdam scan lost due to higher cost determined by scmCompareCosts()");
return;
}
}
// LCOV_EXCL_START :cnu -- OCM code
else
{
disjunctsFR_.reset();
disjunctsLR_.reset();
optimizer_.computeCostVectorsForMultipleSubset(disjunctsFR_ /* out */
,disjunctsLR_ /* out */
,seqKBytesPerScan /* out */
,totalRows
,totalRqsts
,totalFailedProbes
,totalSeeks
,totalSeqKBRead
,totalKeyPreds
,exePreds_
,incomingProbes_
,totalDisjunctPreds
);
// checking some adjustments,
// Need to introduce some Tunable factor for 100
if ( (CmpCommon::getDefault(RANGESPEC_TRANSFORMATION) == DF_ON) &&
(totalRows < CostScalar(100)) ){
disjunctsFR_ = disjunctsFR_*(totalRows/CostScalar(100));
disjunctsLR_ = disjunctsLR_*(totalRows/CostScalar(100));
}
if ( optimizer_.exceedsBound(costBoundPtr_, disjunctsFR_, disjunctsLR_) )
{
mdamWon_ = FALSE;
// // invalidate the cache
// disjunctsFR_.reset();
// disjunctsLR_.reset();
MDAM_DEBUG0(MTL2, "Mdam scan lost due to exceeding cost bound");
return;
}
}
// LCOV_EXCL_STOP
} // for every disjunct
// update rows accessed
optimizer_.setEstRowsAccessed(totalRows);
mdamWon_ = TRUE;
numKBytes_ = seqKBytesPerScan;
}
// This function computes the cost factors of a MDAM disjunct
// It computes the members below:
// disjunctMdamOK_
// disjunctOptRows_
// disjunctOptRqsts_
// disjunctOptProbesForSubsetBoundaries_
// disjunctOptSeeks_
// disjunctOptSeqKBRead_
// disjunctOptKeyPreds_
// disjunctNumLeadingPartPreds_
// disjunctFailedProbes_
// noExePreds_
void MDAMCostWA::computeDisjunct()
{
// get the key preds for this disjunct:
NABoolean allKeyPredicates = FALSE;
ValueIdSet disjunctKeyPreds;
mdamKeyPtr_->getKeyPredicates(disjunctKeyPreds /*out*/,
&allKeyPredicates /*out*/,
disjunctIndex_ /*in*/);
if( NOT (allKeyPredicates) )
noExePreds_ = FALSE;
// return with a NULL cost if there are no key predicates
// "costBoundPtr_ == NULL" means "MDAM is forced"
if (disjunctKeyPreds.isEmpty() AND costBoundPtr_ != NULL) {
MDAM_DEBUG0(MTL2, "MDAMCostWA::computeDisjunct(): disjunctKeyPreds is empty");
disjunctMdamOK_ = FALSE;
return; // full table scan, MDAM is worthless here
}
// Tabulate the key predicates using the key columns as the index
ColumnOrderList keyPredsByCol(optimizer_.getIndexDesc()->getIndexKey());
mdamKeyPtr_->getKeyPredicatesByColumn(keyPredsByCol,disjunctIndex_);
MDAM_DEBUG1(MTL2, "Disjunct: %d, keyPredsByCol:", disjunctIndex_);
MDAM_DEBUGX(MTL2, keyPredsByCol.print());
MDAM_DEBUG0(MTL2, "disjunctKeyPreds no recomputing");
MDAM_DEBUGX(MTL2, disjunctKeyPreds.display());
// compute the optimal prefix and the associated min cost
MDAMOptimalDisjunctPrefixWA prefixWA(optimizer_,
keyPredsByCol,
disjunctKeyPreds,
exePreds_,
outerHistograms_,
firstColumnHistogram_,
noExePreds_,
mdamKeyPtr_,
scanForcePtr_,
mdamForced_,
isMultipleProbes_,
incomingProbes_,
estimatedRecordsPerBlock_,
innerRowsUpperBound_,
innerBlocksUpperBound_,
disjunctIndex_);
prefixWA.compute();
CollIndex stopColumn = prefixWA.getStopColumn();
disjunctNumLeadingPartPreds_ += prefixWA.getNumLeadingPartPreds();
disjunctFailedProbes_ = prefixWA.getFailedProbes();
disjunctOptRows_ = prefixWA.getOptRows();
disjunctOptRqsts_ = prefixWA.getOptRqsts();
disjunctOptProbesForSubsetBoundaries_ =
prefixWA.getOptProbesForSubsetBoundaries();
disjunctOptSeeks_ = prefixWA.getOptSeeks();
disjunctOptSeqKBRead_ = prefixWA.getOptSeqKBRead();
disjunctOptKeyPreds_ = prefixWA.getOptKeyPreds();
// Set the stop column for current disjunct:
mdamKeyPtr_->setStopColumn(disjunctIndex_, stopColumn);
NABoolean mdamMakeSense = TRUE;
if (NOT mdamForced_ AND stopColumn == 0) {
if (keyPredsByCol.getPredicateExpressionPtr(0) == NULL )
mdamMakeSense = FALSE;
else if (mdamKeyPtr_->getKeyDisjunctEntries() == 1) {
// When there is a conflict in single subset, mdam should handle it
const ColumnOrderList::KeyColumn* currKeyColumn =
keyPredsByCol.getPredicateExpressionPtr(0);
NABoolean conflict =
( (currKeyColumn->getType() == KeyColumns::KeyColumn:: CONFLICT)
OR
(currKeyColumn->getType() ==
KeyColumns::KeyColumn::CONFLICT_EQUALS)
OR
// added for patching (since it is a single disjunct now, but not a conflict)
// where a =10 or a=20 or a=30
(currKeyColumn->getType() ==
KeyColumns::KeyColumn::INLIST) );
if( NOT conflict ) { // single subset should be chosen.
MDAM_DEBUG0(MTL2, "MDAMCostWA::computeDisjunct(): conflict predicate for single subset, force MDAM off");
mdamMakeSense = FALSE;
}
}
}
CollIndex noOfmissingKeyColumnsTot = 0;
CollIndex presentKeyColumnsTot = 0;
const IndexDesc *idesc = optimizer_.getFileScan().getIndexDesc();
const ColStatDescList& csdl = idesc->getPrimaryTableDesc()->getTableColStats();
Histograms hist(csdl);
Lng32 checkOption = (ActiveSchemaDB()->getDefaults()).getAsLong(MDAM_APPLY_RESTRICTION_CHECK);
if(CURRSTMT_OPTDEFAULTS->indexEliminationLevel() != OptDefaults::MINIMUM
&& (!mdamForced_)
&& (CmpCommon::getDefault(RANGESPEC_TRANSFORMATION) == DF_ON )
&& checkOption >= 1
&& (!checkMDAMadditionalRestriction(
keyPredsByCol,
optimizer_.computeLastKeyColumnOfDisjunct(keyPredsByCol),
hist,
(restrictCheckStrategy)checkOption,
noOfmissingKeyColumnsTot,
presentKeyColumnsTot))
)
{
MDAM_DEBUG0(MTL2, "MDAMCostWA::computeDisjunct(): MDAM additional restriction check failed");
mdamMakeSense = FALSE;
}
disjunctMdamOK_ = mdamMakeSense;
}
NABoolean MDAMCostWA::isMdamWon() const
{
return mdamWon_;
}
NABoolean MDAMCostWA::hasNoExePreds() const
{
return noExePreds_;
}
const CostScalar & MDAMCostWA::getNumKBytes() const
{
return numKBytes_;
}
Cost * MDAMCostWA::getScmCost()
{
return scmCost_;
}
// Implementation of MDAMOptimalDisjunctPrefixWA methods
MDAMOptimalDisjunctPrefixWA::MDAMOptimalDisjunctPrefixWA
(FileScanOptimizer & optimizer,
const ColumnOrderList & keyPredsByCol,
const ValueIdSet & disjunctKeyPreds,
const ValueIdSet & exePreds,
const Histograms & outerHistograms,
IndexDescHistograms & firstColumnHistogram,
NABoolean & noExePreds,
MdamKey *mdamKeyPtr,
const ScanForceWildCard *scanForcePtr,
NABoolean mdamForced,
NABoolean isMultipleProbes,
const CostScalar & incomingProbes,
const CostScalar & estimatedRecordsPerBlock,
const CostScalar & innerRowsUpperBound,
const CostScalar & innerBlocksUpperBound,
CollIndex disjunctIndex)
:
failedProbes_(0)
,optRows_(0)
,optRqsts_(0)
,optRqstsForSubsetBoundaries_(0)
,optSeeks_(0)
,optSeqKBRead_(0)
,optKeyPreds_()
,stopColumn_(0)
,numLeadingPartPreds_(0)
,optimizer_(optimizer)
,keyPredsByCol_(keyPredsByCol)
,disjunctKeyPreds_(disjunctKeyPreds)
,exePreds_(exePreds)
,outerHistograms_(outerHistograms)
,scanForcePtr_(scanForcePtr)
,isMultipleProbes_(isMultipleProbes)
,mdamForced_(mdamForced)
,incomingProbes_(incomingProbes)
,estimatedRecordsPerBlock_(estimatedRecordsPerBlock)
,innerRowsUpperBound_(innerRowsUpperBound)
,innerBlocksUpperBound_(innerBlocksUpperBound)
,disjunctIndex_(disjunctIndex)
,firstColumnHistogram_(firstColumnHistogram)
,noExePreds_(noExePreds)
,mdamKeyPtr_(mdamKeyPtr)
,disjunctHistograms_( *(optimizer.getIndexDesc()), 0 )
,multiColUecInfoAvail_(disjunctHistograms_.isMultiColUecInfoAvail())
,lastColumnPosition_(optimizer.computeLastKeyColumnOfDisjunct(keyPredsByCol))
,firstColOverlaps_(FALSE)
,prefixSubsets_(csOne) // MDAM subsets
,cumulativePrefixSubsets_(csZero)
,prefixSubsetsAsSeeks_(csOne) // MDAM subsets for all probes
,prefixRows_(0)
,prefixRqsts_(csOne)
,prefixRqstsForSubsetBoundaries_(0)
,multiProbesDataRows_(0)
,prefixSeeks_(0)
,prefixKBRead_(0)
,prefixKeyPreds_()
,keyPredsPtr2Apply2Histograms_(NULL)
,prefixColumnPosition_(0)
,curPredIsEqual_(FALSE)
,prevPredIsEqual_(FALSE)
// The uec is used as a multiplier. Intialize the ones
// for the column previous to the very first column
,uecForPreviousCol_(csOne)
,uecForPreviousColBeforeAppPreds_(csOne)
,uecForPrevColForSeeks_(csOne)
,firstRound_(TRUE)
,crossProductApplied_(FALSE)
,prevColChosen_(FALSE)
,sumOfUecs_(csOne)
,sumOfUecsSoFar_(csOne)
,blocksToReadPerUec_(0)
,pMinCost_(NULL)
,rcAfterApplyFirstKeyPreds_(0)
{}
MDAMOptimalDisjunctPrefixWA::~MDAMOptimalDisjunctPrefixWA()
{
if(pMinCost_)
delete pMinCost_;
}
// LCOV_EXCL_START :cnu
// This method find if there are any intervenning missing key column present
NABoolean MDAMOptimalDisjunctPrefixWA::missingKeyColumnExists() const
{
KeyColumns::KeyColumn::KeyColumnType typeOfRange = KeyColumns::KeyColumn::EMPTY;
CollIndex index = 0;
for (index = 0; index < keyPredsByCol_.entries(); index++)
{
if (keyPredsByCol_.getPredicateExpressionPtr(index) != NULL)
typeOfRange = keyPredsByCol_.getPredicateExpressionPtr(index)->getType();
else
typeOfRange= KeyColumns::KeyColumn::EMPTY;
if(typeOfRange == KeyColumns::KeyColumn::EMPTY )
break;
}
if( index < (lastColumnPosition_ - 1))
{
return TRUE;
}
return FALSE;
}
// LCOV_EXCL_STOP
// This function computes the optimal prefix of the disjunct
void MDAMOptimalDisjunctPrefixWA::compute()
{
CostScalar uniqueProbes = csOne; // dummy var
// compute the number of failed probes for the disjunct
computeProbesDisjunct();
// compute the sum of uecs for all columns
const ColStatDescList & primaryTableCSDL =
optimizer_.getIndexDesc()->getPrimaryTableDesc()->getTableColStats();
const ValueIdList & keyColumns = optimizer_.getIndexDesc()->getIndexKey();
for (CollIndex colNum = 0; colNum < keyColumns.entries(); colNum++)
{
CollIndex indexInList;
primaryTableCSDL.
getColStatDescIndexForColumn(indexInList, keyColumns[colNum]);
sumOfUecs_ += primaryTableCSDL[indexInList]->getColStats()->
getTotalUec().getCeiling();
}
processLeadingColumns();
prefixColumnPosition_++;
while (prefixColumnPosition_ < lastColumnPosition_)
{
processNonLeadingColumn();
prefixColumnPosition_++;
}
}
// This function compute the number of failed probes.
// It has side effects on member failedProbes_
void MDAMOptimalDisjunctPrefixWA::
computeProbesDisjunct()
{
if (isMultipleProbes_)
{
CostScalar
disSuccProbes
,disUniSucPrbs /* dummy */
,disDupSucPrbs /* dummy */
,disFldPrbs /* dummy */
,disUniFailedProbes; /* dummy */
// Compute the RC by applying 1st non-empty key predicate.
// Any columns before the key column with the key predicate
// should have no predicates. Do this only if the 1st key predicate
// is not the same as the current predicate (disjunctKeyPreds_) to process.
CollIndex keyLength =(optimizer_.getIndexDesc()->getIndexKey()).entries();
const ValueIdSet divisioningColumns =
optimizer_.getIndexDesc()->
getPrimaryTableDesc()->getDivisioningColumns();
NABoolean sameKeyPreds = FALSE;
if ( keyLength > 0 && divisioningColumns.entries() > 0 &&
CmpCommon::getDefault(MTD_GENERATE_CC_PREDS) == DF_ON )
{
const ValueIdSet *predsPtr = NULL;
for (CollIndex i=0; i<keyLength; i++) {
predsPtr = keyPredsByCol_[i];
sameKeyPreds = ( disjunctKeyPreds_ == *predsPtr );
if (predsPtr AND NOT predsPtr->isEmpty() && i>0 && !sameKeyPreds) {
optimizer_.categorizeProbes(disSuccProbes /* out */
,disUniSucPrbs /* out, not used */
,disDupSucPrbs /* out, not used */
,disFldPrbs /* out, not used */
,disUniFailedProbes /* out, not used */
,incomingProbes_
,*predsPtr
,outerHistograms_
,TRUE // this is MDAM!
,&rcAfterApplyFirstKeyPreds_ // cache the RC
);
break;
}
}
}
// do the real work to figure out # of probes
optimizer_.categorizeProbes(disSuccProbes /* out */
,disUniSucPrbs /* out, not used */
,disDupSucPrbs /* out, not used */
,disFldPrbs /* out, not used */
,disUniFailedProbes /* out, not used */
,incomingProbes_
,disjunctKeyPreds_
,outerHistograms_
,TRUE // this is MDAM!
,&multiProbesDataRows_
);
if ( sameKeyPreds )
rcAfterApplyFirstKeyPreds_ = multiProbesDataRows_;
MDAM_DEBUG1(MTL2, "Incoming Probes %f", incomingProbes_.value());
MDAM_DEBUGX(MTL2, outerHistograms_.print());
MDAM_DEBUGX(MTL2, disjunctKeyPreds_.display());
MDAM_DEBUG1(MTL2, "categorizeProbes returns rows: %f",
multiProbesDataRows_.value());
failedProbes_ = incomingProbes_ - disSuccProbes;
DCMPASSERT(failedProbes_ >= csZero);
}
}
// This function processes the leading columns and initialize the
// optimal prefix. If there is a leading single subset, we include
// the single subset in our optimal prefix.
// It updates the firstColumnHistogram_, computes firstColOverlaps_
// It initializes disjunctHistograms_, prefixKeyPreds_,
// and other memebers related to the current column.
void MDAMOptimalDisjunctPrefixWA::processLeadingColumns()
{
disjunctHistograms_.appendHistogramForColumnPosition(1);
// compute whether there is overlap on the first column
CostScalar firstColRowCntBefore =
firstColumnHistogram_.getColStatsForColumn
( optimizer_.getIndexDesc()->getIndexKey()[0]).getRowcount().getCeiling();
// Apply first col predicate of the disjunct to first col histogram
const ValueIdSet *predsPtr = keyPredsByCol_[0];
if (predsPtr AND NOT predsPtr->isEmpty())
{
prefixKeyPreds_.insert(*predsPtr);
const SelectivityHint * selHint = optimizer_.getIndexDesc()->getPrimaryTableDesc()->getSelectivityHint();
const CardinalityHint * cardHint = optimizer_.getIndexDesc()->getPrimaryTableDesc()->getCardinalityHint();
firstColumnHistogram_.applyPredicates(*predsPtr, (Scan &)optimizer_.getRelExpr(), selHint, cardHint, REL_SCAN);
// accumulate the number of partitioning key predicates
ValueId predId;
BiRelat * predIEptr;
for(predId = predsPtr->init();
predsPtr->next(predId);
predsPtr->advance(predId))
{
ValueIdSet vdset;
if (predId.getItemExpr()->getArity() == 2)
{
// making change for blocking ITM_OR
if (predId.getItemExpr()->getOperatorType() == ITM_OR)
{
predId.getItemExpr()->getLeafPredicates(vdset);
for (ValueId predId = vdset.init();
vdset.next(predId);
vdset.advance(predId) )
{
predIEptr=(BiRelat *)predId.getItemExpr();
if (predIEptr->isaPartKeyPred())
{
numLeadingPartPreds_++;
}
}
}
else
{
predIEptr=(BiRelat *)predId.getItemExpr();
if (predIEptr->isaPartKeyPred())
{
numLeadingPartPreds_++;
break;
}
}//else
}
}
}
CostScalar firstColRowCntAfter =
firstColumnHistogram_.getColStatsForColumn
(optimizer_.getIndexDesc()->getIndexKey()[0]).getRowcount().getCeiling();
processSingleSubsetPrefix();
keyPredsPtr2Apply2Histograms_ = &prefixKeyPreds_;
applyPredsToHistogram(); // modify prefixRows_
if (predsPtr==NULL OR predsPtr->isEmpty()) {
// Adjust prefixRows_ based on the rowcount as a result of applying
// the first non-empty key predicate (if any). The value has been
// computed by callling ScanOptimizer::categorizeProbes(). The result is
// stored in ScanOptimizer::rcAfterApplyFirstKeyPreds_;
CostScalar rc = rcAfterApplyFirstKeyPreds();
if ( rc.isGreaterThanZero() )
prefixRows_ = rc;
}
firstColOverlaps_ =
( (firstColRowCntBefore == firstColRowCntAfter)
AND
(disjunctIndex_ > 0)
AND
(firstColRowCntAfter == prefixRows_) );
// firstColOverlaps_ = FALSE;
computeDensityOfColumn();
updatePositions(); // may better be decomposed on whether column == 0
updateStatistics();
updateMinPrefix();
}
// This function includes the leading single subset to the optimal prefix
// It has side effects on disjunctHistograms_, prefixKeyPreds_,
// prefixSubsets_, disjunctSubSetsAsSeeks_,
// prefixRqstsForSubsetBoundaries_, prefixColumnPosition_,
void MDAMOptimalDisjunctPrefixWA::processSingleSubsetPrefix()
{
// hasSingleSubset is FALSE iff there is a IN pred in the first column.
// The subset order is the last column position(0-based) for the subset
// i.e., consider a table t(A,B,C,D) with pk (A,B,C)
// for A=1, the order is 0
// for A=1 AND B=2 AND C=3, the order is 2.
CollIndex subsetOrder;
NABoolean hasSingleSubsetPrefix;
if (CmpCommon::getDefault(RANGESPEC_TRANSFORMATION) == DF_ON )
hasSingleSubsetPrefix = keyPredsByCol_.getSingleSubsetOrderForMdam(subsetOrder /*out ref*/);
else
hasSingleSubsetPrefix = keyPredsByCol_.getSingleSubsetOrder(subsetOrder /*out ref*/);
if(NOT hasSingleSubsetPrefix)
return;
// The loops below starts at 1 because the 0th column has already been added
for (CollIndex singleSubsetColPosition=1;
singleSubsetColPosition <= subsetOrder;
singleSubsetColPosition++)
{
disjunctHistograms_.appendHistogramForColumnPosition(singleSubsetColPosition+1);
const ValueIdSet *predsPtr = keyPredsByCol_[singleSubsetColPosition];
if (predsPtr AND NOT predsPtr->isEmpty())
prefixKeyPreds_.insert(*predsPtr);
}
// A single subset prefix has only one positioning by definition:
// These are the subsets that each probe is going to read
// The subsets cannot be higher that the number of rows in the raw table
prefixSubsets_ = MINOF(1.0, innerRowsUpperBound_.getValue());
prefixSubsetsAsSeeks_ = prefixSubsets_;
// Since this is a single subset, no boundary probes are necessary:
prefixRqstsForSubsetBoundaries_ = csZero;
prefixColumnPosition_ = subsetOrder;
}
// This function is called in a loop to scan each column for the optimal
// disjunct prefix. Assume processLeadingColumn() is called before to
// initialize the optimal prefix.
// It has side effects on disjunctHistograms_, dijunctKeyPreds_,
// and other members that keeps track of the current column.
void MDAMOptimalDisjunctPrefixWA::processNonLeadingColumn()
{
// Because of a VEG predicate can contain more than one
// predicate encoded, add histograms incrementally so that
// the reduction of a VEG predicate for a later column
// than the current column position does not affect the
// distribution of the current column.
// Note that the append method receives a one-based column position,
// so add one to the prefix because the prefix is zero based:
disjunctHistograms_.
appendHistogramForColumnPosition(prefixColumnPosition_+1);
const ValueIdSet *predsPtr = keyPredsByCol_[prefixColumnPosition_];
keyPredsPtr2Apply2Histograms_ = predsPtr;
if( predsPtr AND NOT predsPtr->isEmpty() ) {
// accumulate them in the pred set:
prefixKeyPreds_.insert(*predsPtr);
}
// $$$ if the column is of type IN list, then
// $$$ the uec should be equal to the IN list entries
// $$$ times the previous UEC
// $$$ If the hists can handle IN lists then
// $$$ the next line is correct.
MDAM_DEBUG1(MTL2, "processNonLeadingColumn(), prefixCOlumnPosition_-1: %d:", prefixColumnPosition_-1);
const ColStats& colStats = disjunctHistograms_.getColStatsForColumn
( optimizer_.getIndexDesc()->getIndexKey()[prefixColumnPosition_-1] );
MDAM_DEBUGX(MTL2, colStats.print());
uecForPreviousCol_ = colStats.getTotalUec().getCeiling();
MDAM_DEBUG1(MTL2, "processNonLeadingColumn(), total UEC, uecForPreviousCol_: %f:", uecForPreviousCol_.value());
CostScalar estimatedUec = csOne;
if(multiColUecInfoAvail_ AND
uecForPreviousCol_.isGreaterThanOne() AND
prefixColumnPosition_ > 1 AND
disjunctHistograms_.
estimateUecUsingMultiColUec(keyPredsByCol_, /*in*/
prefixColumnPosition_ - 1, /*in*/
estimatedUec /*out*/))
{
uecForPreviousCol_ = MIN_ONE(
(uecForPreviousCol_ / uecForPreviousColBeforeAppPreds_)
*estimatedUec);
MDAM_DEBUG1(MTL2, "processNonLeadingColumn(), MC uec : %f:", estimatedUec.value());
MDAM_DEBUG1(MTL2, "processNonLeadingColumn(), modify by MC uec, uecForPreviousCol_: %f:", uecForPreviousCol_.value());
uecForPreviousColBeforeAppPreds_ = estimatedUec;
}
sumOfUecsSoFar_ += uecForPreviousColBeforeAppPreds_;
uecForPrevColForSeeks_ = uecForPreviousCol_;
// compute number of blocks per uec of previous columns. to be used when "if" condition just below
// is true
CostScalar uecPerBlock = uecForPreviousColBeforeAppPreds_
/ blocksToReadPerUec_;
// what is this formula?
// The following formula was added so that we do not count every
// subset as a seek. For example let's say that so far we have
// computed 200 blocks for 20 subsets then so far each subset
// would access 10 blocks. Now if the next column has 20 uecs
// a naive algorithm would increase the subset by a factor of
// 20 and we would have 400 subsets equivalent to 400 seeks.
// But in reality these 20 uecs are going to create seeks in the
// 10 blocks so we know that it cannot create more than 10 seeks
// we further more reduce it by the location of the column and how
// "together" each of these seeks are.
/*****
if( uecForPrevColForSeeks_.isGreaterThanOne() AND
uecPerBlock.isGreaterThanOne())
{
// This limits the multiplication of uec due to the new column to be no
// more than the number of blocks per uec of previous columns.
uecForPrevColForSeeks_ = uecPerBlock;
// This further reduces the multiplication of uec considering the caching effects
// of DP2 read-ahead. The higher is the column, the more effects is there.
CostScalar reductionFactor =
MAXOF( (sumOfUecs_ - sumOfUecsSoFar_)/sumOfUecs_,
1.0/CURRSTMT_OPTDEFAULTS->readAheadMaxBlocks()
);
// don't consider read ahead cache benefit, it unduly favor mdam plans
//uecForPrevColForSeeks_ = MIN_ONE( uecForPrevColForSeeks_ * reductionFactor);
}
else if (uecForPrevColForSeeks_.isGreaterThanOne())
{
CostScalar reductionFactor =
MAXOF( (sumOfUecs_ - sumOfUecsSoFar_)/sumOfUecs_,
1.0/CURRSTMT_OPTDEFAULTS->readAheadMaxBlocks()
);
// don't consider read ahead cache benefit, it unduly favor mdam plans
//uecForPrevColForSeeks_ = MIN_ONE( uecForPrevColForSeeks_ * reductionFactor);
}
*****/
applyPredsToHistogram();
computeDensityOfColumn();
updatePositions();
updateStatistics();
updateMinPrefix();
}
// This function applies predicates to the disjunctHistograms_
// member. It has side effects on members
// crossProductApplied_, prefixRows_, disjunctHistograms_,
// prevPredIsEqual_, currPredIsEqual_
void MDAMOptimalDisjunctPrefixWA::applyPredsToHistogram()
{
NABoolean getRowCount = TRUE;
uecForPreviousColBeforeAppPreds_ = disjunctHistograms_.
getColStatsForColumn(optimizer_.getIndexDesc()->
getIndexKey()[prefixColumnPosition_]).
getTotalUec().getCeiling();
const ValueIdSet * predsPtr = keyPredsPtr2Apply2Histograms_;
if ( predsPtr AND
(NOT predsPtr->isEmpty())
)
{
const SelectivityHint * selHint = optimizer_.getIndexDesc()->getPrimaryTableDesc()->getSelectivityHint();
const CardinalityHint * cardHint = optimizer_.getIndexDesc()->getPrimaryTableDesc()->getCardinalityHint();
MDAM_DEBUG0(MTL2, "Applying predicates to disjunct histograms");
MDAM_DEBUGX(MTL2, predsPtr->print());
if (NOT crossProductApplied_
AND
isMultipleProbes_)
{
MDAM_DEBUG0(MTL2, "Applying cross product");
// If this is multiple probes, we need to use joined histograms to
// estimate the cost factors (rows, uecs)
// Therefore, apply the cross product to the disjunctHistograms only once.
// Use the disjunctHistograms as normal after cross product is applied.
// ??? It seems that we need do the cross product even though there
// ??? is no pred on the current column. This is not being done?
crossProductApplied_ = TRUE;
if(prefixColumnPosition_ == lastColumnPosition_ - 1)
{
// If the column is the last key column, we can skip this
// by using the data rows already computed.
// ??? uec would not be affected?
// ??? if the join is on a non-key column, and there is no correlation
// ??? from the join column to the current column, this would be true
// ??? What about other cases.
getRowCount = FALSE;
prefixRows_ = multiProbesDataRows_;
MDAM_DEBUG1(MTL2, "prefixRows_ comes from outerHist: %f",
prefixRows_.value());
}else
{
disjunctHistograms_.
applyPredicatesWhenMultipleProbes(
*predsPtr
,*(optimizer_.getContext().getInputLogProp())
,optimizer_.getRelExpr().getGroupAttr()->
getCharacteristicInputs()
,TRUE // doing MDAM!
,selHint
,cardHint
,NULL
,REL_SCAN);
}
}
else
disjunctHistograms_.applyPredicates(*predsPtr, (Scan &)optimizer_.getRelExpr(), selHint, cardHint, REL_SCAN);
}
prevPredIsEqual_ = curPredIsEqual_;
// compute curPredIsEqual_ for the current column
if (keyPredsByCol_.getPredicateExpressionPtr(prefixColumnPosition_) AND
(keyPredsByCol_.getPredicateExpressionPtr(prefixColumnPosition_)->
getType()==KeyColumns::KeyColumn::CONFLICT_EQUALS OR
keyPredsByCol_.getPredicateExpressionPtr(prefixColumnPosition_)->
getType()== KeyColumns::KeyColumn::EQUAL))
{
curPredIsEqual_ = TRUE;
}
else{
curPredIsEqual_ = FALSE;
}
if(getRowCount){
prefixRows_ = disjunctHistograms_.getRowCount();
MDAM_DEBUG1(MTL2, "prefixRows_ comes from disjunctHist: %f", prefixRows_.value());
}
}
// This function computes whether the column being scaned is dense or
// sparse. It has side effects on memebers *mdamKeyPtr_
void MDAMOptimalDisjunctPrefixWA::computeDensityOfColumn()
{
// Sparse dense force flags:
NABoolean sparseForced = FALSE;
NABoolean denseForced = FALSE;
// Check if scan is being forced
// if so check if density is forced too
if (scanForcePtr_ && mdamForced_)
{
sparseForced =
((scanForcePtr_->getEnumAlgorithmForColumn(prefixColumnPosition_)
== ScanForceWildCard::COLUMN_SPARSE)
? TRUE : FALSE);
denseForced =
((scanForcePtr_->getEnumAlgorithmForColumn(prefixColumnPosition_)
== ScanForceWildCard::COLUMN_DENSE)
? TRUE : FALSE);
}
if (sparseForced OR denseForced)
{
if (sparseForced)
mdamKeyPtr_->setColumnToSparse(prefixColumnPosition_);
else if (denseForced)
mdamKeyPtr_->setColumnToDense(prefixColumnPosition_);
}
else
{
// Sparse/dense was not forced, calculate it from histograms
// With single col. histograms we can only do
// a good job estimating this for the first column
if (prefixColumnPosition_ == 0)
{
// why not use firstColHistogram ?
const ColStats & firstColumnColStats =
disjunctHistograms_.
getColStatsForColumn(optimizer_.getIndexDesc()->getIndexKey()[0]);
// may want to put the threshold in the defaults table:
const CostScalar DENSE_THRESHOLD = 0.90;
const CostScalar density =
(firstColumnColStats.getTotalUec().getCeiling()) /
( firstColumnColStats.getMaxValue().getDblValue()
- firstColumnColStats.getMinValue().getDblValue()
+ 1.0 );
if ( density > DENSE_THRESHOLD )
// It is dense!!!
mdamKeyPtr_->setColumnToDense(prefixColumnPosition_);
else
// It is sparse!!!
mdamKeyPtr_->setColumnToSparse(prefixColumnPosition_);
} // if order == 0
else
// order > 0, always sparse
mdamKeyPtr_->setColumnToSparse(prefixColumnPosition_);
} // dense/sparse not forced
}
// This function updates the subset and seek counts cost factors
// for the current prefix. It has side effects on members prefixSubsets_,
// prefixSubsetsAsSeeks_, prefixRqstsForSubsetBoundaries_
void MDAMOptimalDisjunctPrefixWA::updatePositions()
{
// Note that there cannot be more positionings and more
// subsets than there are rows in the table
// The positionings include probing for the next subset
if (prefixColumnPosition_ == 0)
{
prefixSubsets_ =
MINOF(innerRowsUpperBound_.getValue(),
uecForPreviousCol_.getValue());
prefixSubsetsAsSeeks_ = prefixSubsets_;
prefixRqstsForSubsetBoundaries_ = csZero;
}
else
{
// Do not add subsets for the first column
// (i.e. we are in order 1) if we already added them
// in a previous subset:
if ( (prefixColumnPosition_ == 1 AND NOT firstColOverlaps_ )
OR
prefixColumnPosition_ > 1)
{
// the UEC for the previous column
// may be zero (if the table was empty
// or all the rows are eliminated after
// preds were applied.) In this
// case, don't multiply:
if (uecForPreviousCol_.isGreaterThanZero())
{
MDAM_DEBUG1(MTL2, "updatePositions(), prefixSubsets_: %f:", prefixSubsets_.value());
MDAM_DEBUG1(MTL2, "updatePositions(), uecForPreviousCol_: %f:", uecForPreviousCol_.value());
prefixSubsets_ *= uecForPreviousCol_;
MDAM_DEBUG1(MTL2, "updatePositions(), updated prefixSubsets_: %f:", prefixSubsets_.value());
prefixSubsets_ =
MINOF(innerRowsUpperBound_.getValue(),
prefixSubsets_.getValue());
MDAM_DEBUG1(MTL2, "updatePositions(), updated prefixSubsets_ bounded: %f:", prefixSubsets_.value());
}
if( uecForPrevColForSeeks_.isGreaterThanZero())
{
prefixSubsetsAsSeeks_ *= uecForPrevColForSeeks_;
prefixSubsetsAsSeeks_ =
MINOF(innerRowsUpperBound_.getValue(),
prefixSubsetsAsSeeks_.getValue());
}
// If the previous column is sparse, then for each subset
// we need to make an extra probe to find the subset
// boundary IF we are not in the second column
// and the first column overlaps:
if (mdamKeyPtr_->isColumnSparse(prefixColumnPosition_-1))
{
if (NOT prefixRqstsForSubsetBoundaries_.isGreaterThanZero() )
{
prefixRqstsForSubsetBoundaries_ = csOne;
}
prefixRqstsForSubsetBoundaries_ *= uecForPreviousCol_;
prefixRqstsForSubsetBoundaries_ =
MINOF(innerRowsUpperBound_.getValue(),
prefixRqstsForSubsetBoundaries_.getValue());
}
} // non-overlapping
} // prefixColumnPosition > 0
}
// This function updates cost factors for the current prefix
// It has side effects on members prefixRqsts_,
// blocksToReadPerUec_, prefixSeeks_, prefixKBRead_
void MDAMOptimalDisjunctPrefixWA::updateStatistics()
{
// the subsets requests are issued for every probe:
CostScalar effectiveProbes =
incomingProbes_ - failedProbes_;
prefixRqsts_ = prefixSubsets_ * effectiveProbes;
// --------------------------------------------------------------
// A successful request is equivalent to a successful probe
// in the SearchKey case (single subset), thus we need
// the rows per successful request, which we compute
// below.
// --------------------------------------------------------------
CostScalar subsetsPerBlock = csOne;
CostScalar rowsPerSubset = csZero;
if( NOT prefixRqsts_.isLessThanOne() AND
prefixRows_.isGreaterThanZero())
{
rowsPerSubset = prefixRows_ / prefixRqsts_;
subsetsPerBlock = estimatedRecordsPerBlock_ / rowsPerSubset;
} // if condition is FALSE we don't change these values
// ------------------------------------------------------------
// Compute seqKBRead and seeks for this disjunct
// The i/o is computed in a per-probe basis (thus the
// use of disjunctSubsets instead of disjunctRequests).
// It will be scaled up to number of probes below.
// -------------------------------------------------------------
// Get the blocks read per probe:
CostScalar disjunctBlocksToRead;
computeTotalBlocksLowerBound(disjunctBlocksToRead /*out*/
,prefixSubsetsAsSeeks_
,rowsPerSubset
,estimatedRecordsPerBlock_
,innerBlocksUpperBound_);
// prefixSubsets_ is the uec
blocksToReadPerUec_ = MIN_ONE(disjunctBlocksToRead / prefixSubsets_);
CostScalar beginBlocksLowerBound;
computeBeginBlocksLowerBound(beginBlocksLowerBound /*out*/
,MINOF( (prefixSubsetsAsSeeks_ /subsetsPerBlock).getCeiling(),
prefixSubsetsAsSeeks_.getValue() )
,innerBlocksUpperBound_);
// disjunctSubsets + prefixRqstsForSubsetBoundaries are basically
// the number of requests to all the appropriate values.
// Thus can be used to compute the number of index blocks
// that will be used in for MDAM access.
CostScalar indexBlocksLowerBound = optimizer_.getIndexDesc()->
getEstimatedIndexBlocksLowerBound(
MINOF( ( (prefixSubsets_ + prefixRqstsForSubsetBoundaries_)
/subsetsPerBlock).getCeiling(),
prefixSubsets_.getValue() ) );
// Assume that every disjunct does not overlap
// with the previous. Since we computed all
// rows to read (and not a lower bound),
// the following routine will compute
// the correct cost, despite its name.
optimizer_.computeIOForFullCacheBenefit(prefixSeeks_ /* out */
,prefixKBRead_ /* out */
,beginBlocksLowerBound
,disjunctBlocksToRead
,indexBlocksLowerBound);
// -------------------------------------------------------------
// The total cost for the disjunct is the cost of all
// the probes times the cost for this disjunct:
// -------------------------------------------------------------
NABoolean changeBack = FALSE;
if((prefixSeeks_ - beginBlocksLowerBound).getValue()>=5)
{
changeBack = TRUE;
prefixSeeks_ -= 5;
prefixKBRead_ = prefixKBRead_ - CostScalar(5)
* optimizer_.getIndexDesc()->getBlockSizeInKb();
}
prefixSeeks_ *= effectiveProbes;
prefixKBRead_ *= effectiveProbes;
if(changeBack)
{
prefixSeeks_ += 5;
prefixKBRead_ += CostScalar(5)
* optimizer_.getIndexDesc()->getBlockSizeInKb();
}
}
// This function updatess the optimal prefix cost found so far
// It has side effects on members optRows_, optRqsts_,
// optRqstsForSubsetBoundaries_, optSeeks_, optSeqKBRead_, optKeyPreds_,
// stopColumn_, prevColChosen_, noExePreds_, prevPredIsEqual, curPredIsEqual_
void MDAMOptimalDisjunctPrefixWA::updateMinPrefix()
{
SimpleCostVector prefixFR, prefixLR;
CostScalar seqKBytesPerScan;
Cost *scmCost = NULL;
cumulativePrefixSubsets_ += prefixSubsets_;
MDAM_DEBUG2(MTL2, "Disjunct: %d, Prefix Column: %d", disjunctIndex_, prefixColumnPosition_);
MDAM_DEBUG1(MTL2, "Incoming Probes: %f:", incomingProbes_.value());
MDAM_DEBUG1(MTL2, "Disjunct Failed Probes: %f:", failedProbes_.value());
MDAM_DEBUG1(MTL2, "Prefix Subsets: %f:", prefixSubsets_.value());
MDAM_DEBUG1(MTL2, "Cumulative Prefix Subsets: %f:", cumulativePrefixSubsets_.value());
MDAM_DEBUG1(MTL2, "Prefix Requests (probes * Subsets): %f:", prefixRqsts_.value());
MDAM_DEBUG1(MTL2, "Prefix Rows: %f:", prefixRows_.value());
MDAM_DEBUG1(MTL2, "Prefix Seeks %f:", prefixSeeks_.value());
MDAM_DEBUG1(MTL2, "Prefix KB Read: %f:", prefixKBRead_.value());
if (CmpCommon::getDefault(SIMPLE_COST_MODEL) == DF_ON)
{
// Factor in row sizes.
CostScalar rowSize = optimizer_.getIndexDesc()->getRecordSizeInKb() * csOneKiloBytes;
CostScalar outputRowSize = optimizer_.getRelExpr().getGroupAttr()->getRecordLength();
CostScalar rowSizeFactor = optimizer_.scmRowSizeFactor(rowSize);
CostScalar outputRowSizeFactor = optimizer_.scmRowSizeFactor(outputRowSize);
// adding cumulativePrefixSubsets_ represents the row handling costs of the probes of
// the MDAM algorithm as it traverses over key columns; the algorithm is recursive
// and thus has cumulative costs
CostScalar scmPrefixRows = (prefixRows_ + cumulativePrefixSubsets_) * rowSizeFactor;
CostScalar scmPrefixOutputRows = prefixRows_ * outputRowSizeFactor;
CostScalar rowSizeFactorSeqIO = optimizer_.scmRowSizeFactor(rowSize,
ScanOptimizer::SEQ_IO_ROWSIZE_FACTOR);
CostScalar rowSizeFactorRandIO = optimizer_.scmRowSizeFactor(rowSize,
ScanOptimizer::RAND_IO_ROWSIZE_FACTOR);
CostScalar scmPrefixIOSeq = (prefixKBRead_/optimizer_.getIndexDesc()->getBlockSizeInKb()).getCeiling() * rowSizeFactorSeqIO;
CostScalar scmPrefixIORand = prefixSeeks_ * rowSizeFactorRandIO;
// factor in the # of partitions (scm compares at the
// per-partition base)
CostScalar numActivePartitions;
if (CmpCommon::getDefault(NCM_HBASE_COSTING) == DF_ON)
numActivePartitions =
optimizer_.getEstNumActivePartitionsAtRuntimeForHbaseRegions();
else
numActivePartitions = optimizer_.getEstNumActivePartitionsAtRuntime();
scmPrefixRows /= numActivePartitions;
scmPrefixOutputRows /= numActivePartitions;
scmPrefixIORand /= numActivePartitions;
scmPrefixIOSeq /= numActivePartitions;
scmCost =
optimizer_.scmCost(scmPrefixRows, scmPrefixOutputRows, csZero, scmPrefixIORand,
scmPrefixIOSeq, incomingProbes_, rowSize, csZero, outputRowSize, csZero);
MDAM_DEBUG1(MTL2, "NCM cost for prefix: %f:", scmCost->convertToElapsedTime(NULL).value());
}
else
{
optimizer_.computeCostVectorsForMultipleSubset
(prefixFR /*out*/
,prefixLR /*out*/
,seqKBytesPerScan /*out unused*/
,prefixRows_
,prefixRqsts_ + prefixRqstsForSubsetBoundaries_
,failedProbes_
,prefixSeeks_
,prefixKBRead_
,prefixKeyPreds_
,exePreds_
,incomingProbes_
,CostScalar(prefixKeyPreds_.entries())
);
}
// Does the prefix exceeds the minimum prefix cost:
// If MDAM is forced then the user can specify
// up to which key column predicates will be included
// in the mdam key. If the user does not specify the
// column and MDAM is forced, all of the key predicates
// in the disjunct are included in the mdam key.
NABoolean newMinimumFound = FALSE;
NABoolean proceedViaCosting = TRUE;
if (scanForcePtr_ && mdamForced_)
{
proceedViaCosting = FALSE;
if(noExePreds_ AND
scanForcePtr_->getNumMdamColumns() < lastColumnPosition_)
noExePreds_ = FALSE;
if (prefixColumnPosition_ < scanForcePtr_->getNumMdamColumns())
{
// The user wants this column as part of the mdam key,
// so pretend that the cost is lower when the column is added
newMinimumFound = TRUE;
}
else
{
if (scanForcePtr_->getMdamColumnsStatus()
== ScanForceWildCard::MDAM_COLUMNS_ALL)
{
// Unconditionally force all of the columns:
newMinimumFound = TRUE;
}
else if (scanForcePtr_->getMdamColumnsStatus()
== ScanForceWildCard::MDAM_COLUMNS_NO_MORE)
{
// Unconditionally reject this and later columns:
newMinimumFound = FALSE;
prevPredIsEqual_ = FALSE; // ?
curPredIsEqual_ = FALSE; // ?
}
else if (scanForcePtr_->getMdamColumnsStatus()
==
ScanForceWildCard::MDAM_COLUMNS_REST_BY_SYSTEM)
{
// Let the system decide based on costing:
proceedViaCosting = TRUE;
}
}
} // if scanForcePtr
MDAM_DEBUGX(MTL2,
MdamTrace::printBasicCost(&optimizer_, prefixFR, prefixLR, "Cost for prefix in updateMinPrefix():"));
if (proceedViaCosting)
{
// Mdam has not been forced, or forced but with the choice
// of system decision for MDAM column. proceed with costing:
if (CmpCommon::getDefault(SIMPLE_COST_MODEL) == DF_ON)
{
DCMPASSERT(scmCost != NULL);
// When RangeSpec feture transforms old mdam disjuncts in to "Range in"
// types, we apply all disjuncts at one shot. The old way of taking Min(prefixCost) does
// not make sense and leads to gross underestimation of MDAM plan.
// For example, consider a predicate like this "A in (10, 20) and B in (100, 200)"
// without RangeSpec: we have 4 disjuncts Like this:
// dis0 : "A = 10 and B = 100", cost C1 = MIN (CostA, CostB) for values (10,100)
// dis1 : "A = 10 and B = 200", cost C2 = MIN (CostA, CostB) for values (10,200)
// dis2 : "A = 20 and B = 100", cost C3 = MIN (CostA, CostB) for values (20,100)
// dis3 : "A = 20 and B = 200", cost C4 = MIN (CostA, CostB) for values (20,200)
// Total Mdam Cost = C1 + C2 + C3 + C4
// with RangeSpec: we have 1 disjunct like this:
// dis0 : "A = 10 or A = 20" and "B = 100 or B = 200"
// we should not take MIN (CostA, CostB) for the second case, it should be the cost to apply
// last key predicate.
// Total Mdam Cost = CostB
//
// This is a heuristics in that we unconditionally include the last key column
// with IN list (OR preds) predicate without going through the cost comparison
// step.
//
// Updated comments: The commentary above is incorrect but I don't know quite
// what to do with it yet. MDAM at run time is a recursive algorithm. In the
// example above, it will materialize values in the A column, and for each one,
// do a subset access on the second column. So the cost is a sum of the
// materialization cost on the first column and the subset access on the second.
// If there is a third key column C with no predicates on it, it would be
// inefficient to go MDAM to the last column position; rather it would be better
// to use B as the stop column. That is, do subsets on each distinct value of (A,B),
// rather than do subsets on each distinct (A,B,C). The larger the UEC of C, the
// more gross the inefficiency. Unfortunately, the code below will cause us to
// go MDAM to column C. In reference to the comments above, we need to devise
// a better way to estimate cost in the presence of RangeSpecs.
if ( (CmpCommon::getDefault(RANGESPEC_TRANSFORMATION) == DF_ON ) &&
optimizer_.getDisjuncts().containsOrPredsInRanges() &&
prefixColumnPosition_ == (lastColumnPosition_ - 1)
) {
newMinimumFound = TRUE;
} else
newMinimumFound = (pMinCost_ == NULL) ? TRUE :
(scmCost->scmCompareCosts(*pMinCost_) == LESS);
}
else
{
newMinimumFound =
NOT optimizer_.exceedsBound(pMinCost_,
prefixFR
,prefixLR);
}
}
// If it is the first time we go around this
// loop update the minimum.
// Also updated if the current minimum exceeded
// the current prefix:
// If the previous column is chosen and the predicate
// on the previous column is equal predicate and there is a predicate
// on the current column, unconditionally include the current column
// because it is going to reduce the amount of data accessed.
if (firstRound_
OR newMinimumFound
OR (prevColChosen_
AND keyPredsByCol_[prefixColumnPosition_]
AND NOT prefixKeyPreds_.isEmpty()
AND prevPredIsEqual_))
{
// We found a new minimum, initialize its data:
firstRound_ = FALSE;
optRows_ = prefixRows_;
optRqsts_ = prefixRqsts_;
MDAM_DEBUG1(MTL2, "<<<<<Update optRqsts_ as %f\n", prefixRqsts_.value());
MDAM_DEBUG1(MTL2, "prefixColumnPosition_ =%d\n", prefixColumnPosition_);
MDAM_DEBUG1(MTL2, "newMinimumFound=%d\n", newMinimumFound);
optRqstsForSubsetBoundaries_ = prefixRqstsForSubsetBoundaries_;
optSeeks_ = prefixSeeks_;
optSeqKBRead_ = prefixKBRead_;
optKeyPreds_.insert(prefixKeyPreds_); // is a copy more efficient?
// Note: Formerly there was code here that would set stopColumn_
// to the last column position if the CQD RANGESPEC_TRANSFORMATION
// was on. This is incorrect; it would cause us to use MDAM to
// traverse through all columns always, even though it may be
// grossly inefficient to do so. (See commentary earlier in this
// method.) As it stands now, so long as there are no RangeSpec
// key predicates, this code will correctly pick the stop column.
// If there are RangeSpec predicates, code earlier in this method
// may cause us to only consider MDAM traversing on all columns.
// We can improve this later by improving how RangeSpec predicates
// are costed for MDAM.
stopColumn_ = prefixColumnPosition_;
prevColChosen_ = TRUE;
delete pMinCost_;
if (CmpCommon::getDefault(SIMPLE_COST_MODEL) == DF_ON)
pMinCost_ = scmCost;
else
pMinCost_ = optimizer_.computeCostObject(prefixFR,prefixLR);
DCMPASSERT(pMinCost_ != NULL);
} // update minimum prefix
else if (proceedViaCosting AND NOT newMinimumFound) {
if(prefixColumnPosition_ == lastColumnPosition_ - 1)
{//we have reached the lastcolumnPosition and its cost
//was exceeded for the lastcolumn so
//there will be additional exepreds
noExePreds_ = FALSE;
}
else // This is not the last column and it wasn't chosen
prevColChosen_ = FALSE;
}
}
CollIndex MDAMOptimalDisjunctPrefixWA::getStopColumn() const
{ return stopColumn_; }
CollIndex MDAMOptimalDisjunctPrefixWA::getNumLeadingPartPreds() const
{ return numLeadingPartPreds_; }
const CostScalar & MDAMOptimalDisjunctPrefixWA::getOptRows() const
{ return optRows_; }
const CostScalar & MDAMOptimalDisjunctPrefixWA::getOptRqsts() const
{ return optRqsts_; }
const CostScalar & MDAMOptimalDisjunctPrefixWA::getFailedProbes() const
{ return failedProbes_; }
const CostScalar &
MDAMOptimalDisjunctPrefixWA::getOptProbesForSubsetBoundaries() const
{ return optRqstsForSubsetBoundaries_; }
const CostScalar & MDAMOptimalDisjunctPrefixWA::getOptSeeks() const
{ return optSeeks_; }
const CostScalar & MDAMOptimalDisjunctPrefixWA::getOptSeqKBRead() const
{ return optSeqKBRead_; }
const ValueIdSet & MDAMOptimalDisjunctPrefixWA::getOptKeyPreds() const
{ return optKeyPreds_; }
// LCOV_EXCL_START :cnu
// return true if has resuable shared basic cost for this mdam
NABoolean
FileScanOptimizer::getSharedCost(FileScanBasicCost * &fileScanBasicCostPtr /*out, never NULL*/
,NABoolean & hasLostBefore /*out*/
,SimpleCostVector * &disjunctsFRPtr /*out never NULL*/
,SimpleCostVector * &disjunctsLRPtr /*out never NULL*/
,CostScalar & numKBytes /*out*/
,MdamKey* & sharedMdamKeyPtr /*out*/
,NABoolean mdamTypeIsCommon /*in*/)
{
NABoolean sharedCostFound = FALSE;
fileScanBasicCostPtr = shareBasicCost(sharedCostFound);
if ( mdamTypeIsCommon )
{
hasLostBefore = fileScanBasicCostPtr->hasMdamCommonLost();
disjunctsFRPtr = &(fileScanBasicCostPtr->getFRBasicCostMdamCommon());
disjunctsLRPtr = &(fileScanBasicCostPtr->getLRBasicCostMdamCommon());
numKBytes = fileScanBasicCostPtr->getMdamCommonNumKBytes();
}
else
{
hasLostBefore = fileScanBasicCostPtr->hasMdamDisjunctsLost();
disjunctsFRPtr = &(fileScanBasicCostPtr->getFRBasicCostMdamDisjuncts());
disjunctsLRPtr = &(fileScanBasicCostPtr->getLRBasicCostMdamDisjuncts());
numKBytes = fileScanBasicCostPtr->getMdamDisjunctsNumKBytes();
}
sharedMdamKeyPtr = fileScanBasicCostPtr->getMdamKeyPtr(mdamTypeIsCommon);
return ( sharedCostFound AND
sharedMdamKeyPtr AND
// disjunctsFRPtr->getCPUTime() > csZero AND
// disjunctsLRPtr->getCPUTime() > csZero AND
CURRSTMT_OPTDEFAULTS->reuseBasicCost() );
}
// LCOV_EXCL_STOP
// LCOV_EXCL_START :cnu
Cost* FileScanOptimizer::newComputeCostForMultipleSubset
( MdamKey* mdamKeyPtr,
const Cost * costBoundPtr,
NABoolean mdamForced,
CostScalar & numKBytes,
ValueIdSet exePreds,
NABoolean checkExePreds,
NABoolean mdamTypeIsCommon,
MdamKey *&sharedMdamKeyPtr )
{
MDAM_DEBUG0(MTL2,"BEGIN MDAM Costing --------");
MDAM_DEBUGX(MTL2, MdamTrace::printCostObject(this,
costBoundPtr, "Cost Bound"));
// MDAM only works for key sequenced files.
DCMPASSERT(getIndexDesc()->getNAFileSet()->isKeySequenced());
DCMPASSERT(getIndexDesc()->getIndexKey().entries() > 0);
FileScanBasicCost *fileScanBasicCostPtr;
SimpleCostVector *disjunctsFRPtr;
SimpleCostVector *disjunctsLRPtr;
NABoolean hasLostBefore = FALSE;
NABoolean shareCost = getSharedCost(fileScanBasicCostPtr,
hasLostBefore,
disjunctsFRPtr,
disjunctsLRPtr,
numKBytes,
sharedMdamKeyPtr,
mdamTypeIsCommon);
SimpleCostVector & disjunctsFR = *disjunctsFRPtr;
SimpleCostVector & disjunctsLR = *disjunctsLRPtr;
if(shareCost){
MDAM_DEBUG0(MTL2, "Use cached MDAM cost");
if(hasLostBefore){
MDAM_DEBUG0(MTL2, "MDAM Costing returning NULL cost");
MDAM_DEBUG0(MTL2, "END MDAM Costing --------\n");
return NULL;
}
else {
Cost * costPtr = computeCostObject(disjunctsFR, disjunctsLR);
// if ( costBoundPtr != NULL )
// {
// COMPARE_RESULT result =
// costPtr->compareCosts(*costBoundPtr,
// getContext().getReqdPhysicalProperty());
// if ( result == MORE OR result == SAME )
// {
// delete costPtr;
// MDAM_DEBUG0(MTL2, "MDAM Costing returning NULL cost");
// MDAM_DEBUG0(MTL2, "END MDAM Costing --------\n");
// return NULL;
// }
// }
// use cached MDAM Cost
mdamKeyPtr->reuseMdamKeyInfo(sharedMdamKeyPtr);
MDAM_DEBUGX(MTL2, MdamTrace::printCostObject(this, costPtr,
"Returning cached MDAM Cost"));
MDAM_DEBUG0(MTL2, "END MDAM Costing --------\n");
return costPtr;
}
}
// proceed with MDAM costing
disjunctsFR.reset();
disjunctsLR.reset();
sharedMdamKeyPtr = mdamKeyPtr;
fileScanBasicCostPtr->setMdamKeyPtr(mdamKeyPtr,mdamTypeIsCommon);
MDAMCostWA costWA(*this,
mdamForced,
mdamKeyPtr,
costBoundPtr,
exePreds,
disjunctsFR,
disjunctsLR);
costWA.compute();
NABoolean isMdamWon = costWA.isMdamWon();
if(NOT isMdamWon) {
MDAM_DEBUG0(MTL2, "MDAM Costing returning NULL cost");
MDAM_DEBUG0(MTL2, "END MDAM Costing --------\n");
if(mdamTypeIsCommon)
fileScanBasicCostPtr->setMdamCommonLost(TRUE);
else
fileScanBasicCostPtr->setMdamDisjunctsLost(TRUE);
return NULL;
}
// MDAM won! create the cost vector:
Cost *costPtr = computeCostObject(disjunctsFR
,disjunctsLR);
NABoolean noExePreds = costWA.hasNoExePreds();
//noExePreds is true, great set the flag in MDAM
if ( CmpCommon::getDefault(RANGESPEC_TRANSFORMATION) == DF_ON )
{
if(!exePreds.entries())
noExePreds = TRUE;
}
if(noExePreds AND checkExePreds)
{
mdamKeyPtr->setNoExePred();
}
numKBytes = costWA.getNumKBytes();
if ( checkExePreds )
fileScanBasicCostPtr->setMdamDisjunctsNumKBytes(numKBytes);
else
fileScanBasicCostPtr->setMdamCommonNumKBytes(numKBytes);
MDAM_DEBUGX(MTL2, MdamTrace::printCostObject(this, costPtr,
"Returning MDAM Cost"));
MDAM_DEBUG0(MTL2, "END MDAM Costing --------\n");
return costPtr;
} // newComputeCostForMultipleSubset(...)
// LCOV_EXCL_STOP
Cost*
FileScanOptimizer::scmComputeCostForMultipleSubset(MdamKey* mdamKeyPtr,
const Cost * costBoundPtr,
NABoolean mdamForced,
CostScalar & numKBytes,
ValueIdSet exePreds,
NABoolean checkExePreds,
NABoolean mdamTypeIsCommon,
MdamKey *&sharedMdamKeyPtr )
{
// MDAM only works for key sequenced files.
DCMPASSERT(getIndexDesc()->getNAFileSet()->isKeySequenced());
DCMPASSERT(getIndexDesc()->getIndexKey().entries() > 0);
sharedMdamKeyPtr = mdamKeyPtr;
SimpleCostVector a(0,0,0,0,0,0,0,0,0,0);
SimpleCostVector b(0,0,0,0,0,0,0,0,0,0);
MDAMCostWA costWA(*this,
mdamForced,
mdamKeyPtr,
costBoundPtr,
exePreds,
a,
b);
costWA.compute();
NABoolean isMdamWon = costWA.isMdamWon();
if(NOT isMdamWon)
return NULL;
NABoolean noExePreds = costWA.hasNoExePreds();
//noExePreds is true, great set the flag in MDAM
if(noExePreds AND checkExePreds)
{
mdamKeyPtr->setNoExePred();
}
numKBytes = costWA.getNumKBytes();
// MDAM won! create the cost vector:
return costWA.getScmCost();
} // scmComputeCostForMultipleSubset(...)
NABoolean FileScanOptimizer::isMultipleProbes() const
{
CostScalar repeatCount = getContext().getPlan()->getPhysicalProperty()->
getDP2CostThatDependsOnSPP()->getRepeatCountForOperatorsInDP2() ;
CollIndex numCols = getContext().getInputLogProp()->getColStats().entries();
return (repeatCount.isGreaterThanOne()) OR (numCols > 0);
}
// return the forced scan wild card if exists
// otherwise return NULL
const ScanForceWildCard* FileScanOptimizer::findScanForceWildCard() const
{
const ScanForceWildCard* scanForcePtr = NULL;
const ReqdPhysicalProperty* propertyPtr =
getContext().getReqdPhysicalProperty();
if ( propertyPtr
&& propertyPtr->getMustMatch()
&& (propertyPtr->getMustMatch()->getOperatorType()
== REL_FORCE_ANY_SCAN))
{
scanForcePtr =
(const ScanForceWildCard*)propertyPtr->getMustMatch();
}
return scanForcePtr;
}
// return the last key column in a disjunct, one based
CollIndex FileScanOptimizer::computeLastKeyColumnOfDisjunct
(const ColumnOrderList & keyPredsByCol)
{
// The number of entries in keyPredsByCol should be equal
// to the number columns in the key which should be greater than zero.
Lng32 lastColumnPosition = (Lng32)(keyPredsByCol.entries()) - 1;
DCMPASSERT(lastColumnPosition >= 0);
// Find the last column position:
// The order (i.e., column position) must be varied
// up to the last column position that references
// key predicates. Find the lastColumnPosition:
// walk from the last key column until you find
// a column position that references a key pred:
ValueId keyCol;
NABoolean foundLastColumn = FALSE;
while( NOT foundLastColumn && lastColumnPosition >=0 )
{
//following is guard against the situation where we can have
//key columns(a,b,a) and predicate a=3 we would chose second
//'a' as last coulumn whereas we know that we would never skip
//the first 'a' and apply the predicate to the second 'a'
NABoolean duplicateFound=FALSE;
keyCol = getIndexDesc()->getIndexKey()[lastColumnPosition];
for( Lng32 preColumn = lastColumnPosition - 1;
preColumn >= 0; preColumn--)
{
if(keyCol==getIndexDesc()->getIndexKey()[preColumn])
{
duplicateFound=TRUE;
break;
}
}
if ( NOT duplicateFound ) {
// any predicate in the set may refer to the key column:
const ValueIdSet *predsPtr = keyPredsByCol[lastColumnPosition];
if(predsPtr && NOT predsPtr->isEmpty())
{
foundLastColumn = TRUE;
}
}
if( NOT foundLastColumn ){
lastColumnPosition--;
}
}
// if not found last column (e.g. MDAM is forced, but there is no key pred)
// set it to the first column
if( NOT foundLastColumn ){
lastColumnPosition = 0;
}
// make column position start from one:
lastColumnPosition++;
return lastColumnPosition;
}
const CostScalar FileScanOptimizer::getIncomingProbes() const
{
// repeat count is the actual receiving probes.
const CostScalar repeatCount =
getContext().getPlan()->getPhysicalProperty()->
getDP2CostThatDependsOnSPP()->getRepeatCountForOperatorsInDP2();
const CostScalar incomingProbes = repeatCount * getNumActivePartitions();
DCMPASSERT(incomingProbes >= repeatCount);
return incomingProbes;
}
NABoolean FileScanBasicCost::hasMdamCommonLost() const
{
return mdamCommonLost_;
}
NABoolean FileScanBasicCost::hasMdamDisjunctsLost() const
{
return mdamDisjunctsLost_;
}
void FileScanBasicCost::setMdamCommonLost(NABoolean mdamCommonLost)
{
mdamCommonLost_ = mdamCommonLost;
}
void FileScanBasicCost::setMdamDisjunctsLost(NABoolean mdamDisjunctsLost)
{
mdamDisjunctsLost_ = mdamDisjunctsLost;
}