blob: 616b14dd04b7a6b80edfe85837a55203634ff269 [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: IndexDesc.C
* Description: Index descriptors
* Index descriptors contain the value ids, uniqueness, key
* and ordering information for a particular TableDesc
* Created: 4/21/95
* Language: C++
*
*
*****************************************************************************
*/
// -----------------------------------------------------------------------
#include "Sqlcomp.h"
#include "AllItemExpr.h"
#include "ValueDesc.h"
#include "PartFunc.h"
#include "IndexDesc.h"
#include "RelScan.h"
#include "CmpContext.h"
#include "CostScalar.h"
#include "ScanOptimizer.h"
#include "AppliedStatMan.h"
// -----------------------------------------------------------------------
// make an IndexDesc from an existing TableDesc and an NAFileSet
// -----------------------------------------------------------------------
IndexDesc::IndexDesc(TableDesc *tdesc,
NAFileSet *fileSet,
CmpContext* cmpContext)
: tableDesc_(tdesc), clusteringIndexFlag_(FALSE),
partFunc_(NULL),
fileSet_(fileSet), cmpContext_(cmpContext), scanBasicCosts_(NULL)
{
DCMPASSERT( tdesc != NULL AND fileSet != NULL );
Lng32 ixColNumber;
ValueId keyValueId;
ValueId baseValueId;
const NATable *naTable = tdesc->getNATable();
indexLevels_ = fileSet_->getIndexLevels();
// ---------------------------------------------------------------------
// Make the column list for the index or vertical partition.
// Any reference to index also holds for vertical partitions.
// ---------------------------------------------------------------------
const NAColumnArray & allColumns = fileSet_->getAllColumns();
// any index gets a new set of IndexColumn
// item expressions and new value ids
CollIndex i = 0;
for (i = 0; i < allColumns.entries(); i++)
{
ItemExpr *baseItemExpr = NULL;
// make a new IndexColumn item expression, indicate how it is
// defined (in terms of base table columns) and give a value
// id to the new IndexColumn expression
if (allColumns[i]->getPosition() >= 0)
{
baseValueId =
tdesc->getColumnList()[allColumns[i]->getPosition()];
baseItemExpr = baseValueId.getItemExpr();
}
else
{
// this column doesn't exist in the base table.
// This is the KEYTAG column of sql/mp indices.
ItemExpr * keytag = new(wHeap())
NATypeToItem((NAType *)(allColumns[i]->getType()));
keytag->synthTypeAndValueId();
baseValueId = keytag->getValueId();
baseItemExpr = NULL;
}
IndexColumn *ixcol = new(wHeap()) IndexColumn(fileSet_,i,baseValueId);
ixcol->synthTypeAndValueId();
// add the newly obtained value id to the index column list
indexColumns_.insert(ixcol->getValueId());
// if the index column is defined as a 1:1 copy of a base
// column, add it as an equivalent index column (EIC) to the
// base column item expression
if ((baseItemExpr) &&
(baseItemExpr->getOperatorType() == ITM_BASECOLUMN))
((BaseColumn *) baseItemExpr)->addEIC(ixcol->getValueId());
}
// ---------------------------------------------------------------------
// make the list of access key columns in the index and make a list
// of the order that the index provides
// ---------------------------------------------------------------------
const NAColumnArray & indexKeyColumns = fileSet_->getIndexKeyColumns();
for (i = 0; i < indexKeyColumns.entries(); i++)
{
// which column of the index is this (usually this will be == i)
if ( !naTable->isHbaseTable() )
ixColNumber = allColumns.index(indexKeyColumns[i]);
else {
// For Hbase tables, a new NAColumn is created for every column
// in an index. The above pointer-based lookup for the key column
// in base table will only find the index column itself. The
// fix is to lookup by the column name and type as is
// implemented by the getColumnPosition() method.
ixColNumber = allColumns.getColumnPosition(*indexKeyColumns[i]);
CMPASSERT(ixColNumber >= 0);
}
// insert the value id of the index column into the key column
// value id list
keyValueId = indexColumns_[ixColNumber];
indexKey_.insert(keyValueId);
// insert the same value id into the order list, if the column
// is in ascending order, otherwise insert the inverse of the
// column
if (indexKeyColumns.isAscending(i))
{
orderOfKeyValues_.insert(keyValueId);
}
else
{
InverseOrder *invExpr = new(wHeap())
InverseOrder(keyValueId.getItemExpr());
invExpr->synthTypeAndValueId();
orderOfKeyValues_.insert(invExpr->getValueId());
}
}
// ---------------------------------------------------------------------
// Find the clustering key columns in the index and store their value
// ids in clusteringKey_
// ---------------------------------------------------------------------
NABoolean found = TRUE;
const NAColumnArray & clustKeyColumns =
naTable->getClusteringIndex()->getIndexKeyColumns();
for (i = 0; i < clustKeyColumns.entries() AND found; i++)
{
// which column of the index is this?
ixColNumber = allColumns.index(clustKeyColumns[i]);
found = (ixColNumber != NULL_COLL_INDEX);
if (found)
{
// insert the value id of the index column into the clustering key
// value id list
keyValueId = indexColumns_[ixColNumber];
clusteringKey_.insert(keyValueId);
}
else
{
// clustering key isn't contained in this index, clear the
// list that is supposed to indicate the clustering key
clusteringKey_.clear();
}
}
// ---------------------------------------------------------------------
// make the list of partitioning key columns in the index and make a list
// of the order that the partitioning provides
// ---------------------------------------------------------------------
const NAColumnArray & partitioningKeyColumns
= fileSet_->getPartitioningKeyColumns();
for (i = 0; i < partitioningKeyColumns.entries(); i++)
{
// which column of the index is this
ixColNumber = allColumns.index(partitioningKeyColumns[i]);
// insert the value id of the index column into the partitioningkey column
// value id list
keyValueId = indexColumns_[ixColNumber];
partitioningKey_.insert(keyValueId);
// insert the same value id into the order list, if the column
// is in ascending order, otherwise insert the inverse of the
// column
if (partitioningKeyColumns.isAscending(i))
{
orderOfPartitioningKeyValues_.insert(keyValueId);
}
else
{
InverseOrder *invExpr = new(wHeap())
InverseOrder(keyValueId.getItemExpr());
invExpr->synthTypeAndValueId();
orderOfPartitioningKeyValues_.insert(invExpr->getValueId());
}
}
// ---------------------------------------------------------------------
// If this index is partitioned, find the partitioning key columns
// and build a partitioning function.
// ---------------------------------------------------------------------
if ((fileSet_->getCountOfFiles() > 1) ||
(fileSet_->getPartitioningFunction() &&
fileSet_->getPartitioningFunction()->
isARoundRobinPartitioningFunction()))
partFunc_ = fileSet_->getPartitioningFunction()->
createPartitioningFunctionForIndexDesc(this);
} // IndexDesc::IndexDesc()
const QualifiedName& IndexDesc::getIndexName() const
{
return fileSet_->getFileSetName();
} // IndexDesc::getIndexName()
const NAString& IndexDesc::getExtIndexName() const
{
return fileSet_->getExtFileSetName();
} // IndexDesc::getExtIndexName()
Lng32 IndexDesc::getRecordLength() const
{
return fileSet_->getRecordLength();
} // IndexDesc::getRecordLength()
Lng32 IndexDesc::getKeyLength() const
{
return fileSet_->getKeyLength();
} // IndexDesc::getKeyLength()
const NAColumnArray & IndexDesc::getAllColumns() const
{
return fileSet_->getAllColumns();
} // IndexDesc::getAllColumns()
// Partitioning information
NABoolean IndexDesc::isPartitioned() const
{
if (partFunc_ AND (partFunc_->getCountOfPartitions() > 1))
return TRUE;
else
return FALSE;
}
// Should the plan priority of this index adjusted, due to a hint?
// Returns:
// 0: if there are no hints or the index isn't mentioned in a hint
// otherwise: the value to add to the plan priority
// (a positive value indicates a more desirable plan)
int IndexDesc::indexHintPriorityDelta() const
{
if (tableDesc_->getHintIndexes().entries() == 0)
return 0;
CollIndex found = tableDesc_->getHintIndexes().index(this);
if (found == NULL_COLL_INDEX)
// no hints or index not listed in the hint
return 0;
else
// index mentioned in hint, all indexes get the same priority, so
// we select the best one based on cost
return INDEX_HINT_PRIORITY;
}
// Print function
void IndexDesc::print(FILE* ofd, const char* indent, const char* title)
{
BUMP_INDENT(indent);
cout << title << " " << this << " "
<< indexLevels_ << "," << clusteringIndexFlag_
<< " pf=" << partFunc_ << " fs=" << fileSet_
<< endl;
indexColumns_.print(ofd, indent, "IndexDesc::indexColumns_");
indexKey_.print(ofd, indent, "IndexDesc::indexKey_");
orderOfKeyValues_.print(ofd, indent, "IndexDesc::orderOfKeyValues_");
clusteringKey_.print(ofd, indent, "IndexDesc::clusteringKey_");
}
// Get the statement heap
CollHeap* IndexDesc::wHeap()
{
return (cmpContext_) ? cmpContext_->statementHeap() : 0;
}
CostScalar IndexDesc::getKbForLocalPred() const
{
AppliedStatMan * appStatMan = QueryAnalysis::ASM();
if ( !appStatMan )
return csMinusOne;
const TableAnalysis * tAnalysis = getPrimaryTableDesc()->getTableAnalysis();
if ( !tAnalysis )
return csMinusOne;
CANodeId tableId = tAnalysis->getNodeAnalysis()->getId();
const ValueIdList &keys = getIndexKey();
EstLogPropSharedPtr estLPPtr = appStatMan->
getStatsForLocalPredsOnPrefixOfColList(tableId, keys);
if ( ! estLPPtr->getColStats().containsAtLeastOneFake() )
return estLPPtr->getResultCardinality() * getRecordSizeInKb();
else
return csMinusOne;
}
CostScalar
IndexDesc::getKbPerVolume() const
{
return getRecordSizeInKb() * tableDesc_->getTableColStats()[0]->getColStats()->
getRowcount()/ (partFunc_?
((NodeMap *)(partFunc_->getNodeMap()))->getNumOfDP2Volumes()
:1);
}
CostScalar
IndexDesc::getRecordSizeInKb() const
{
// Get this info from the file set:
// record length is given in bytes, thus divide over
// 1024 to get Kb:
// Don't get the ceiling, we want a fraction if
CostScalar kbPerRow = getNAFileSet()->getRecordLength() / 1024.;
return kbPerRow;
}
CostScalar
IndexDesc::getBlockSizeInKb() const
{
// Get this info from the file set:
// block size is given in bytes, thus divide over
// 1024 to get Kb:
CostScalar blockSize = getNAFileSet()->getBlockSize() / 1024.;
return blockSize;
}
CostScalar IndexDesc::getEstimatedRecordsPerBlock() const
{
// We don't have "spanning rows" so the fractional part
// is "garbage" and wasted space, therefore
// we take the floor of the following measure since
// a fractional "rows per block" does not make sense:
const CostScalar blockSize = getNAFileSet()->getBlockSize();
const CostScalar recordLength = getNAFileSet()->getRecordLength();
const CostScalar recordsPerBlock = (blockSize / recordLength).getFloor();
if (NOT getPrimaryTableDesc()->getNATable()->isHbaseTable())
{
// A row can never be larger than a block:
CMPASSERT( recordsPerBlock > csZero );
}
else
{
// For an hbase table, there is no concept of a SQ like blocksize and
// the record size can be very large.
// For now, make recordsPerBlock as 1 if it becomes 0 or less.
// TBD TBD
if (recordsPerBlock <= csZero)
return (CostScalar)1;
}
return recordsPerBlock;
}
// -----------------------------------------------------------------------
// Input: probes, the number of data requests for a
// subset of data that a physical scan operator issues to
// DP2.
//
// Output: The estimated number of index blocks that all of the
// input requests touch in their way to the data leaves
// in the B-Tree.
//
// -----------------------------------------------------------------------
CostScalar
IndexDesc::getEstimatedIndexBlocksLowerBound(const CostScalar& probes) const
{
// -----------------------------------------------------------------------
// This method works by estimating how many blocks all probes
// touch in their way down in every level of the tree. We need
// to know the number of blocks in each level.
// At each level, the number of index blocks touched by all
// data requests is
// MINOF(index blocks in the b-tree at that level, number
// of data requests (i.e. probes)).
// To estimate the number of index blocks at a given level we
// use:
// The rule of thumb formula to
// estimate the number of index blocks in each level of the b-tree:
// One level: zero blocks (actually one block but i't in cache all the time)
// Two levels: 5 blocks
// Three levels: 200 blocks
// Four levels: 800 blocks
// T(0) = 0, empty table, no index blocks.
// T(1) = 0, only a few records, the root node costs 0.
// T(2) = 5
// T(n) = T(n-1)*40, n > 2
// -----------------------------------------------------------------------
Lng32 levels = getIndexLevels();
CostScalar indexBlocksLowerBound = csZero;
if ( levels == 0.0 OR levels == 1.0 )
{
// Index blocks touch by all probes for level zero and one
// (For indexes with one level we assume the blocks are zero
// because we assume that the root always stay in cache):
indexBlocksLowerBound = csZero;
}
else
{
CostScalar indexBlocks = 5.0;
// Index blocks touch by all probes for level two:
indexBlocksLowerBound = MINOF( indexBlocks, probes );
// Index blocks touch by all probes for level three and above:
for (CollIndex i=2; i < levels; i++)
{
indexBlocks = indexBlocks*40;
indexBlocksLowerBound += MINOF( indexBlocks, probes );
}
}
return indexBlocksLowerBound;
}
void
IndexDesc::getNonKeyColumnSet(ValueIdSet& nonKeyColumnSet) const
{
const ValueIdList
&indexColumns = getIndexColumns(),
&keyColumns = getIndexKey();
// clean up input:
nonKeyColumnSet.clear();
// Add all index columns
CollIndex i = 0;
for (i=0;
i < indexColumns.entries();
i++)
{
nonKeyColumnSet.insert(indexColumns[i]);
}
// And remove all key columns:
for (i=0;
i < keyColumns.entries();
i++)
{
nonKeyColumnSet.remove(keyColumns[i]);
// if this is a secondary index, the base column
// which is part of the index,
// may also be present, remove it:
const ItemExpr *colPtr = keyColumns[i].getItemExpr();
if (colPtr->getOperatorType()
==
ITM_INDEXCOLUMN)
{
const ValueId & colDef = ((IndexColumn *)(colPtr))->getDefinition();
nonKeyColumnSet.remove(colDef);
}
}
} // IndexDesc::getNonKeyColumnSet(ValueIdSet& nonKeyColumnSet) const
void
IndexDesc::getNonKeyColumnList(ValueIdList& nonKeyColumnList) const
{
const ValueIdList
&indexColumns = getIndexColumns(),
&keyColumns = getIndexKey();
// clean up input:
nonKeyColumnList.clear();
// Add all index columns
CollIndex i = 0;
for (i=0;
i < indexColumns.entries();
i++)
{
nonKeyColumnList.insert(indexColumns[i]);
}
// And remove all key columns:
for (i=0;
i < keyColumns.entries();
i++)
{
nonKeyColumnList.remove(keyColumns[i]);
// if this is a secondary index, the base column
// which is part of the index,
// may also be present, remove it:
const ItemExpr *colPtr = keyColumns[i].getItemExpr();
if (colPtr->getOperatorType()
==
ITM_INDEXCOLUMN)
{
const ValueId & colDef = ((IndexColumn *)(colPtr))->getDefinition();
nonKeyColumnList.remove(colDef);
}
}
} // IndexDesc::getNonKeyColumnList(ValueIdSet& nonKeyColumnSet) const
NABoolean IndexDesc::isUniqueIndex() const
{
return getNAFileSet()->uniqueIndex();
ValueIdList nonKeyColumnList;
getNonKeyColumnList(nonKeyColumnList);
// if there are some non-index-key columns(the key of base table),
// then this is a unique index. The primary key of base table is
// not needed to define the key of the index. It is, of course,
// needed to be present in the index as a non-key column.
if (nonKeyColumnList.entries() > 0)
return TRUE;
else
return FALSE;
}
/********************************************************************
* Input: Selection predicates for the scan node, boolean indicating if
* it is a indexOnlyIndex, reference parameter that will indicate if
* IndexJoin is viable or not, GroupAttributes for the group and characteristic
* inputs
* Output: MdamFlag indicating if the index key access is good enough for
* MDAM access (if a index does not have good MDAM access we have to
* scan the whole index because single subset also will not have any
* keys to apply)
* IndexJoin flag indicating if index join cost would exceed base table
* access or not.
********************************************************************/
MdamFlags IndexDesc::pruneMdam(const ValueIdSet& preds,
NABoolean indexOnlyIndex,
IndexJoinSelectivityEnum&
selectivityEnum /* out*/ ,
const GroupAttributes * groupAttr,
const ValueIdSet * inputValues) const
{
CollIndex numEmptyColumns=0;
CostScalar numSkips = csOne;
ValueIdSet emptyColumns;
ValueId vid;
if(indexOnlyIndex)
selectivityEnum = INDEX_ONLY_INDEX;
else
selectivityEnum = INDEX_JOIN_VIABLE;
if(preds.isEmpty()) return MDAM_OFF;
//calculate how many key columns don't have any predicates
for(CollIndex i=0;i<indexKey_.entries();i++)
{
if(preds.referencesTheGivenValue(indexKey_[i],vid))
break;
else
numEmptyColumns++;
}
//if we don't have any empty columns or we don't have to evaluate if index
//join is promising or not then just return
if(numEmptyColumns>=1 OR NOT indexOnlyIndex)
{
IndexDescHistograms ixHistogram(*this,
(indexOnlyIndex?numEmptyColumns:indexKey_.entries()));
NABoolean multiColUecAvail = ixHistogram.isMultiColUecInfoAvail();
ColumnOrderList keyPredsByCol(indexKey_);
for(CollIndex j=0;j<numEmptyColumns;j++)
{
emptyColumns.insert(indexKey_[j]);
if(j==0 OR multiColUecAvail == FALSE)
{
//no MCUec so just multiply the empty columns UEC count to
//calculate MDAM skips
numSkips *=(ixHistogram.getColStatsForColumn(indexKey_[j])).
getTotalUec().getCeiling();
}
else // otherwise try to use MCUec
{
NABoolean uecFound = FALSE;
CostScalar correctUec = csOne;
CostScalar combinedUECCount = csOne;
// first let's see if there is multiColUec count for the skipped columns
// so far. If there is that will be number of skips. If there isn't then
// get the best estimate of UEC count for the current column using MCUec
// if possible otherwise just using single column histograms.
combinedUECCount = ixHistogram.getUecCountForColumns(emptyColumns);
if(combinedUECCount >0)
{
numSkips = combinedUECCount;
}
else
{
uecFound = ixHistogram.estimateUecUsingMultiColUec(keyPredsByCol,j,correctUec);
if(uecFound==TRUE)
{
numSkips *= correctUec;
}
else
{
numSkips *=(ixHistogram.getColStatsForColumn(indexKey_[j])).
getTotalUec().getCeiling();
}
}
}
}
CostScalar rowCount = ixHistogram.getRowCount();
CostScalar numIndexBlocks = rowCount /getEstimatedRecordsPerBlock();
CostScalar numProbes = csOne;
CostScalar numBaseTableBlocks = csOne;
CostScalar inputProbes = csOne;
// Pass any selectivity hint provided by the user
const SelectivityHint * selHint = tableDesc_->getSelectivityHint();
const CardinalityHint * cardHint = tableDesc_->getCardinalityHint();
// If it is an index join then compute the number probes into the base
// table. If the alternate index is not selective enough, we will have
// lots of them making the index quite expensive.
if(NOT indexOnlyIndex)
{
if((groupAttr->getInputLogPropList()).entries() >0)
{
//if there are incoming probes to the index. i.e. if the index join
//is under another nested join or TSJ then compute result for all
//probes. We are using the initial inputEstLogProp to compute the
//resulting cardinality. It is possible that for the same group and
//different inputEstLogProp would provide less row count per probe.
//So in FileScanRule::nextSubstitute() we make sure that the context
//inputEstLogProp is in the error range of this inputEstLogProp.
// Ex. select * from lineitem, customer, nation
// where l_custkey < c_custkey and c_custkey = n_nationkey;
//Now if we were evaluating lineitem indexes where the outer was customer
//we would want to exclude alternate index on custkey whereas if nation got
//pushed below customer then range of values would be fewer and max value
//being less would make alternate index on custkey quite attractive.
ixHistogram.
applyPredicatesWhenMultipleProbes(preds,
*((groupAttr->getInputLogPropList())[0]),
*inputValues,
TRUE,
selHint,
cardHint,
NULL,
REL_SCAN);
inputProbes = MIN_ONE((groupAttr->getInputLogPropList())[0]->getResultCardinality());
}
else
{
RelExpr * dummyExpr = new (STMTHEAP) RelExpr(ITM_FIRST_ITEM_OP,
NULL,
NULL,
STMTHEAP);
ixHistogram.applyPredicates(preds, *dummyExpr, selHint, cardHint, REL_SCAN);
}
numProbes = ixHistogram.getRowCount();
numBaseTableBlocks = rowCount / tableDesc_->getClusteringIndex()->
getEstimatedRecordsPerBlock();
double readAhead = CURRSTMT_OPTDEFAULTS->readAheadMaxBlocks();
// although we compute cardinality from the index for all probes we
// do the comparison for per probe. The assumption is that per probe
// the upper bound of cost is scanning the whole base table.
if(numProbes/inputProbes + MINOF((numIndexBlocks / readAhead),numSkips)
> (numBaseTableBlocks/readAhead))
{
selectivityEnum = EXCEEDS_BT_SCAN;
}
}
//Does the number of skips exceed the cost of scanning the index.
if((indexOnlyIndex AND numSkips <=
(numIndexBlocks * CURRSTMT_OPTDEFAULTS->mdamSelectionDefault())) OR
(NOT indexOnlyIndex AND numSkips + numProbes/inputProbes <=
(numBaseTableBlocks * CURRSTMT_OPTDEFAULTS->mdamSelectionDefault())))
return MDAM_ON;
}
else
return MDAM_ON;
return MDAM_OFF;
}
void IndexProperty::updatePossibleIndexes(SET(IndexProperty *) & indexes, Scan *scan)
{
// We will compare the new possible index with each one already
// in a set. If the new index gives the SAME or LESS promise
// it will be ignored. If the new index is better than some of
// existing indexes those will be removed and the new one - added.
CollIndex numIndexes = indexes.entries(), i=0;
COMPARE_RESULT comp;
NABoolean addIndex = TRUE;
while (i<numIndexes AND addIndex)
{
comp = compareIndexPromise(indexes[i]);
switch(comp)
{
case LESS:
case SAME:
// new index doesn't have a better promise, ignore it.
addIndex = FALSE;
break;
case MORE:
// new index is more promising than the current one in indexes
// remove the current index.
indexes.remove(indexes[i]);
numIndexes--;
break;
case INCOMPATIBLE:
case UNDEFINED:
// couldn't decide at this moment, go to the next index
i++;
break;
default:
// the enumerated type COMPARE_RESULT can not retrun default;
// so CMPASSERT() is ok
CMPASSERT(0);
}
}
if ( addIndex )
indexes.insert(this);
return;
}
COMPARE_RESULT
IndexProperty::compareIndexPromise(const IndexProperty *ixProp) const
{
// currently it is the same for indexOnlyScans ans alternateIndexScans.
// If index key starts from the same (single column) then the smaller index
// (having smaller KbPerVolume attribute) is MORE promising, the bigger index
// is LESS promising, and the same index size has the SAME promise.
const IndexDesc * index = getIndexDesc();
const IndexDesc * otherIndex = ixProp->getIndexDesc();
// If the two indexes have differing leading columns, consider them incompatible.
// For this check, we ignore the "_SALT_" column if both are salted.
CollIndex columnToCheck = 0;
NABoolean done = FALSE;
while (!done)
{
if (columnToCheck >= index->getIndexKey().entries())
return INCOMPATIBLE; // must be one of the indexes is just "_SALT_" or "_DIVISION_1_" (seems unlikely actually)
else if (columnToCheck >= otherIndex->getIndexKey().entries())
return INCOMPATIBLE; // must be one of the indexes is just "_SALT_" or "_DIVISION_1_" (seems unlikely actually)
else
{
IndexColumn * indexCol = (IndexColumn *)(index->getIndexKey()[columnToCheck]).getItemExpr();
IndexColumn * otherIndexCol = (IndexColumn *)(otherIndex->getIndexKey()[columnToCheck]).getItemExpr();
if ( indexCol->getNAColumn()->isSaltColumn() &&
otherIndexCol->getNAColumn()->isSaltColumn() )
columnToCheck++;
else if ( indexCol->getNAColumn()->isDivisioningColumn() &&
otherIndexCol->getNAColumn()->isDivisioningColumn() )
columnToCheck++;
else if ( indexCol->getDefinition() != otherIndexCol->getDefinition() )
return INCOMPATIBLE;
else
done = TRUE; // leading column (ignoring salt, divisioning if both have it) is the same; do Kb checks
}
}
CostScalar myKbForLPred = index->getKbForLocalPred();
CostScalar othKbForLPred = otherIndex->getKbForLocalPred();
int myDelta = index->indexHintPriorityDelta();
int otherDelta = otherIndex->indexHintPriorityDelta();
if (myDelta != otherDelta)
{
// return a result based on index hints given
if (myDelta > otherDelta)
// only I am mentioned in the hints
return MORE;
return LESS;
}
// If stats is available for this and the other index, compare the
// amount of data accessed through the local predicate. The one
// that accesses less is more promising.
if ( myKbForLPred >= 0 && othKbForLPred >=0 )
{
if ( myKbForLPred < othKbForLPred )
return MORE; // more promising
else {
if( myKbForLPred > othKbForLPred )
return LESS;
else {
// When the amount of data to access is the same, prefer
// the index with less # of index key columns
CollIndex myNumKeyCols = index->getIndexKey().entries();
CollIndex otherNumKeyCols = otherIndex->getIndexKey().entries();
if ( myNumKeyCols < otherNumKeyCols )
return MORE; // more promissing
if ( myNumKeyCols > otherNumKeyCols )
return LESS;
else
return SAME;
}
}
}
return INCOMPATIBLE;
}
// eof