8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-23 02:03:04 +01:00

Some more cost based calculation. Fine-tunning will follow

This commit is contained in:
arnobrinkman 2005-08-08 22:17:40 +00:00
parent 374a9a33c7
commit c3f9c5bebc
3 changed files with 302 additions and 48 deletions

View File

@ -778,7 +778,8 @@ IndexScratchSegment::IndexScratchSegment(MemoryPool& p, IndexScratchSegment* seg
}
}
IndexScratch::IndexScratch(MemoryPool& p, index_desc* ix) :
IndexScratch::IndexScratch(MemoryPool& p, thread_db* tdbb, index_desc* ix,
CompilerScratch::csb_repeat* csb_tail) :
idx(ix), segments(p)
{
/**************************************
@ -800,10 +801,30 @@ IndexScratch::IndexScratch(MemoryPool& p, index_desc* ix) :
segments.grow(idx->idx_count);
int length = 0;
const Format* format = csb_tail->csb_format;
index_desc::idx_repeat* idx_desc = idx->idx_rpt;
IndexScratchSegment** segment = segments.begin();
for (int i = 0; i < segments.getCount(); i++) {
for (int i = 0; i < segments.getCount(); i++, idx_desc++) {
segment[i] = FB_NEW(p) IndexScratchSegment(p);
}
length += format->fmt_desc[idx_desc->idx_field].dsc_length;
}
// AB: Calculate the cardinality which should reflect the total number
// of index pages for this index.
// We assume that the average index-key can be compressed by a factor 0.5
// In the future the average key-length should be stored and retrieved
// from a system table (RDB$INDICES for example).
// Multipling the selectivity with this cardinality gives the estimated
// number of index pages that are read for the index retrieval.
double factor = 0.5;
if (segments.getCount() >= 2) {
// Compound indexes are generally less compressed.
factor = 0.7;
}
Database* dbb = tdbb->tdbb_database;
cardinality = (csb_tail->csb_cardinality * (2 + (length * factor))) /
(dbb->dbb_page_size - BTR_SIZE);
}
IndexScratch::IndexScratch(MemoryPool& p, IndexScratch* scratch) :
@ -819,6 +840,7 @@ IndexScratch::IndexScratch(MemoryPool& p, IndexScratch* scratch) :
*
**************************************/
selectivity = scratch->selectivity;
cardinality = scratch->cardinality;
candidate = scratch->candidate;
scopeCandidate = scratch->scopeCandidate;
lowerCount = scratch->lowerCount;
@ -866,6 +888,7 @@ InversionCandidate::InversionCandidate(MemoryPool& p) :
*
**************************************/
selectivity = MAXIMUM_SELECTIVITY;
cost = 0;
indexes = 0;
dependencies = 0;
nonFullMatchedSegments = MAX_INDEX_SEGMENTS + 1;
@ -913,7 +936,7 @@ OptimizerRetrieval::OptimizerRetrieval(MemoryPool& p, OptimizerBlk* opt,
IndexScratch** indexScratch = indexScratches.begin();
index_desc* idx = csb_tail->csb_idx->items;
for (int i = 0; i < csb_tail->csb_indices; ++i, ++idx) {
indexScratch[i] = FB_NEW(p) IndexScratch(p, idx);
indexScratch[i] = FB_NEW(p) IndexScratch(p, tdbb, idx, csb_tail);
}
inversionCandidates.shrink(0);
@ -1198,7 +1221,12 @@ InversionCandidate* OptimizerRetrieval::generateInversion(RecordSource** rsb)
}
}
invCandidate = makeInversion(&inversions);
#ifdef OPT_DEBUG_RETRIEVAL
// Debug
printCandidates(&inversions);
#endif
invCandidate = makeInversion(&inversions, true);
// Add the streams where this stream is depending on.
if (invCandidate) {
@ -1208,6 +1236,11 @@ InversionCandidate* OptimizerRetrieval::generateInversion(RecordSource** rsb)
}
}
#ifdef OPT_DEBUG_RETRIEVAL
// Debug
printFinalCandidate(invCandidate);
#endif
if (invCandidate && setConjunctionsMatched) {
Firebird::SortedArray<jrd_nod*> matches;
// AB: Putting a unsorted array in a sorted array directly by join isn't
@ -1409,10 +1442,11 @@ InversionCandidate* OptimizerRetrieval::getCost()
return inversion;
}
else {
// No index will be used
// No index will be used, thus
InversionCandidate* invCandidate = FB_NEW(pool) InversionCandidate(pool);
invCandidate->indexes = 0;
invCandidate->selectivity = MAXIMUM_SELECTIVITY;
invCandidate->cost = csb->csb_rpt[stream].csb_cardinality;
return invCandidate;
}
}
@ -1443,6 +1477,7 @@ InversionCandidate* OptimizerRetrieval::getInversion(RecordSource** rsb)
InversionCandidate* invCandidate = FB_NEW(pool) InversionCandidate(pool);
invCandidate->indexes = 0;
invCandidate->selectivity = MAXIMUM_SELECTIVITY;
invCandidate->cost = csb->csb_rpt[stream].csb_cardinality;
return invCandidate;
}
}
@ -1583,6 +1618,9 @@ bool OptimizerRetrieval::getInversionCandidates(InversionCandidateList* inversio
FB_NEW(pool) InversionCandidate(pool);
invCandidate->unique = unique;
invCandidate->selectivity = scratch[i]->selectivity;
// Calculate the cost (only index pages) for this index.
// The constant 3 is an avergae for the rootpage and non-leaf pages.
invCandidate->cost = 3 + (scratch[i]->selectivity * scratch[i]->cardinality);
invCandidate->nonFullMatchedSegments =
scratch[i]->nonFullMatchedSegments;
invCandidate->matchedSegments =
@ -1791,8 +1829,8 @@ jrd_nod* OptimizerRetrieval::makeIndexScanNode(IndexScratch* indexScratch) const
return node;
}
InversionCandidate* OptimizerRetrieval::makeInversion(InversionCandidateList* inversions)
const
InversionCandidate* OptimizerRetrieval::makeInversion(InversionCandidateList* inversions,
bool top) const
{
/**************************************
*
@ -1800,7 +1838,11 @@ InversionCandidate* OptimizerRetrieval::makeInversion(InversionCandidateList* in
*
**************************************
*
* Functional description
* Select best available inversion candidates
* and compose them to 1 inversion.
* If top is true the datapages-cost is
* also used in the calculation (only needed
* for top InversionNode generation).
*
**************************************/
@ -1810,9 +1852,15 @@ InversionCandidate* OptimizerRetrieval::makeInversion(InversionCandidateList* in
const jrd_nod* const plan = csb->csb_rpt[stream].csb_plan;
InversionCandidate* invCandidate = NULL;
int i = 0;
double totalSelectivity = MAXIMUM_SELECTIVITY; // worst selectivity
double totalCost = 0;
double totalIndexCost = 0;
const double maximumCost = csb->csb_rpt[stream].csb_cardinality * 0.95;
const double minimumSelectivity = 1 / csb->csb_rpt[stream].csb_cardinality;
double previousTotalCost = maximumCost;
int i = 0;
InversionCandidate* invCandidate = NULL;
InversionCandidate** inversion = inversions->begin();
for (i = 0; i < inversions->getCount(); i++) {
inversion[i]->used = false;
@ -1852,6 +1900,7 @@ InversionCandidate* OptimizerRetrieval::makeInversion(InversionCandidateList* in
}
invCandidate->unique = inversion[currentPosition]->unique;
invCandidate->selectivity = inversion[currentPosition]->selectivity;
invCandidate->cost = inversion[currentPosition]->cost;
invCandidate->indexes = inversion[currentPosition]->indexes;
invCandidate->nonFullMatchedSegments = 0;
invCandidate->matchedSegments = inversion[currentPosition]->matchedSegments;
@ -1964,29 +2013,62 @@ InversionCandidate* OptimizerRetrieval::makeInversion(InversionCandidateList* in
// If we have a candidate which is interesting build the inversion
// else we're done.
if (bestCandidate) {
if (plan || bestCandidate->selectivity < (totalSelectivity * SELECTIVITY_THRESHOLD_FACTOR_ADD)) {
// Estimate new selectivity
// Assign selectivity for the formula
double bestSel = bestCandidate->selectivity;
double worstSel = totalSelectivity;
if (bestCandidate->selectivity > totalSelectivity) {
worstSel = bestCandidate->selectivity;
bestSel = totalSelectivity;
}
if (bestCandidate)
{
// AB: Here we test if our new candidate is interesting enough to be added for
// index retrieval.
if (bestSel >= 1) {
totalSelectivity = 1;
}
else if (bestSel == 0) {
totalSelectivity = 0;
}
else {
totalSelectivity = (bestSel + (((worstSel - bestSel) / (1 - bestSel)) * bestSel)) / 2;
}
// AB: For now i'll use the calculation that's often used for and-ing selectivities (S1 * S2).
// I think this calculation is not right for many cases.
// For example two "good" selectivities will result in a very good selectivity, but
// mostly a filter is made by adding criteria's where every criteria is an extra filter
// compared to the previous one. Thus with the second criteria in _most_ cases still
// records are returned. (Think also on the segment-selectivity in compound indexes)
// Assume a table with 100000 records and two selectivities of 0.001 (100 records) which
// are both AND-ed (with S1 * S2 => 0.001 * 0.001 = 0.000001 => 0.1 record).
//
// A better formula could be where the result is between "Sbest" and "Sbest * factor"
// The reducing factor should be between 0 and 1 (Sbest = best selectivity)
//
// Example:
/*
double newTotalSelectivity = 0;
double bestSel = bestCandidate->selectivity;
double worstSel = totalSelectivity;
if (bestCandidate->selectivity > totalSelectivity) {
worstSel = bestCandidate->selectivity;
bestSel = totalSelectivity;
}
if (bestSel >= 1) {
newTotalSelectivity = 1;
}
else if (bestSel == 0) {
newTotalSelectivity = 0;
}
else {
newTotalSelectivity = bestSel - ((1 - worstSel) * (bestSel - (bestSel * 0.01)));
}
*/
double newTotalSelectivity = bestCandidate->selectivity * totalSelectivity;
totalIndexCost += bestCandidate->cost;
totalCost = totalIndexCost;
if (top) {
totalCost += (newTotalSelectivity * csb->csb_rpt[stream].csb_cardinality);
}
// Test if the new totalCost will be higher as the maximumCost or previous totalCost
// and if the current selectivty (without the bestCandidate) is already good enough.
//if (plan || bestCandidate->selectivity < (totalSelectivity * SELECTIVITY_THRESHOLD_FACTOR_ADD)) {
if (plan || ((totalCost < maximumCost) && (totalCost < previousTotalCost) &&
(totalSelectivity > minimumSelectivity)))
{
// Exclude index from next pass
bestCandidate->used = true;
previousTotalCost = totalCost;
totalSelectivity = newTotalSelectivity;
if (!invCandidate) {
invCandidate = FB_NEW(pool) InversionCandidate(pool);
@ -1998,6 +2080,7 @@ InversionCandidate* OptimizerRetrieval::makeInversion(InversionCandidateList* in
}
invCandidate->unique = bestCandidate->unique;
invCandidate->selectivity = bestCandidate->selectivity;
invCandidate->cost = totalCost;
invCandidate->indexes = bestCandidate->indexes;
invCandidate->nonFullMatchedSegments = 0;
invCandidate->matchedSegments = bestCandidate->matchedSegments;
@ -2026,6 +2109,7 @@ InversionCandidate* OptimizerRetrieval::makeInversion(InversionCandidateList* in
}
invCandidate->unique = (invCandidate->unique || bestCandidate->unique);
invCandidate->selectivity = totalSelectivity;
invCandidate->cost = totalCost;
invCandidate->indexes += bestCandidate->indexes;
invCandidate->nonFullMatchedSegments = 0;
invCandidate->matchedSegments =
@ -2364,6 +2448,7 @@ InversionCandidate* OptimizerRetrieval::matchOnIndexes(
composeInversion(invCandidate1->inversion, invCandidate2->inversion, nod_bit_or);
invCandidate->unique = (invCandidate1->unique && invCandidate2->unique);
invCandidate->selectivity = invCandidate1->selectivity + invCandidate2->selectivity;
invCandidate->cost = invCandidate1->cost + invCandidate2->cost;
invCandidate->indexes = invCandidate1->indexes + invCandidate2->indexes;
invCandidate->nonFullMatchedSegments = 0;
invCandidate->matchedSegments =
@ -2423,6 +2508,86 @@ InversionCandidate* OptimizerRetrieval::matchOnIndexes(
}
#ifdef OPT_DEBUG_RETRIEVAL
void OptimizerRetrieval::printCandidate(InversionCandidate* candidate) const
{
/**************************************
*
* p r i n t C a n d i d a t e
*
**************************************
*
* Functional description
*
**************************************/
FILE *opt_debug_file = fopen(OPTIMIZER_DEBUG_FILE, "a");
fprintf(opt_debug_file, " cost(%1.2f), selectivity(%1.10f), indexes(%d), matched(%d, %d)",
candidate->cost, candidate->selectivity, candidate->indexes, candidate->matchedSegments,
candidate->nonFullMatchedSegments);
if (candidate->unique) {
fprintf(opt_debug_file, ", unique");
}
int depFromCount = candidate->dependentFromStreams.getCount();
if (depFromCount >= 1) {
fprintf(opt_debug_file, ", dependent from ");
for (int i = 0; i < depFromCount; i++) {
if (i == 0) {
fprintf(opt_debug_file, "%d", candidate->dependentFromStreams[i]);
}
else {
fprintf(opt_debug_file, ", %d", candidate->dependentFromStreams[i]);
}
}
}
fprintf(opt_debug_file, "\n");
fclose(opt_debug_file);
}
void OptimizerRetrieval::printCandidates(InversionCandidateList* inversions) const
{
/**************************************
*
* p r i n t C a n d i d a t e s
*
**************************************
*
* Functional description
*
**************************************/
FILE *opt_debug_file = fopen(OPTIMIZER_DEBUG_FILE, "a");
fprintf(opt_debug_file, " retrieval candidates:\n");
fclose(opt_debug_file);
InversionCandidate** inversion = inversions->begin();
for (int i = 0; i < inversions->getCount(); i++) {
InversionCandidate* candidate = inversion[i];
printCandidate(candidate);
}
}
void OptimizerRetrieval::printFinalCandidate(InversionCandidate* candidate) const
{
/**************************************
*
* p r i n t C a n d i d a t e
*
**************************************
*
* Functional description
*
**************************************/
if (candidate) {
FILE *opt_debug_file = fopen(OPTIMIZER_DEBUG_FILE, "a");
fprintf(opt_debug_file, " final candidate: ");
fclose(opt_debug_file);
printCandidate(candidate);
}
}
#endif
bool OptimizerRetrieval::validateStarts(IndexScratch* indexScratch,
jrd_nod* boolean, USHORT segment) const
{
@ -2604,7 +2769,8 @@ OptimizerInnerJoin::OptimizerInnerJoin(MemoryPool& p, OptimizerBlk* opt, UCHAR*
innerStream[i]->stream = streams[i + 1];
}
calculateCardinalities();
// Cardinalities are calculated in OPT_compile()
//calculateCardinalities();
calculateStreamInfo();
}
@ -2676,7 +2842,7 @@ void OptimizerInnerJoin::calculateStreamInfo()
OptimizerRetrieval* optimizerRetrieval = FB_NEW(pool)
OptimizerRetrieval(pool, optimizer, innerStreams[i]->stream, false, false, NULL);
InversionCandidate* candidate = optimizerRetrieval->getCost();
innerStreams[i]->baseCost = candidate->selectivity * csb_tail->csb_cardinality;
innerStreams[i]->baseCost = candidate->cost;//candidate->selectivity * csb_tail->csb_cardinality;
innerStreams[i]->baseIndexes = candidate->indexes;
innerStreams[i]->baseUnique = candidate->unique;
innerStreams[i]->baseConjunctionMatches = candidate->matches.getCount();
@ -2795,12 +2961,13 @@ bool OptimizerInnerJoin::estimateCost(USHORT stream, double *cost,
// Based on the page-size we make an estimated number of keys per index leaf page.
// This is really a wild estimated number because it depends on key size and how good
// the prefix compression does its work.
const double nodesPerPage = ((double)database->dbb_page_size / 10);
//const double nodesPerPage = ((double)database->dbb_page_size / 10);
// The estimated index cost reflects the number of pages fetched for this index read.
// The number of pages is an index pointer page + the B-Tree level - 1 (leaf page) +
// index leaf pages to be read.
const double indexCost = 2 + ((cardinality * selectivity) / nodesPerPage);
*cost = (cardinality * selectivity) + (candidate->indexes * indexCost);
//const double indexCost = 2 + ((cardinality * selectivity) / nodesPerPage);
//*cost = (cardinality * selectivity) + (candidate->indexes * indexCost);
*cost = candidate->cost;
}
else {
// No indexes are used, this meant for every record a data-page is read.
@ -2843,6 +3010,11 @@ int OptimizerInnerJoin::findJoinOrder()
optimizer->opt_best_count = 0;
#ifdef OPT_DEBUG
// Debug
printStartOrder();
#endif
int i = 0;
remainingStreams = 0;
for (i = 0; i < innerStreams.getCount(); i++) {
@ -2881,6 +3053,11 @@ int OptimizerInnerJoin::findJoinOrder()
streamInfo->used = true;
}
#ifdef OPT_DEBUG
// Debug
printBestOrder();
#endif
return optimizer->opt_best_count;
}
@ -2943,7 +3120,7 @@ void OptimizerInnerJoin::findBestOrder(int position, InnerJoinStreamInfo* stream
#ifdef OPT_DEBUG
// Debug information
printFoundOrder(position, new_cost, new_cardinality);
printFoundOrder(position, position_cost, position_cardinality, new_cost, new_cardinality);
#endif
// mark this stream as "used" in the sense that it is already included
@ -3058,7 +3235,7 @@ void OptimizerInnerJoin::getIndexedRelationship(InnerJoinStreamInfo* baseStream,
OptimizerRetrieval* optimizerRetrieval = FB_NEW(pool)
OptimizerRetrieval(pool, optimizer, testStream->stream, false, false, NULL);
InversionCandidate* candidate = optimizerRetrieval->getCost();
double cost = candidate->selectivity * csb_tail->csb_cardinality;
double cost = candidate->cost;// candidate->selectivity * csb_tail->csb_cardinality;
if (candidate->unique) {
// If we've an unique index retrieval the cost is equal to 1
// The cost calculation can be far away from the real cost value if there
@ -3120,7 +3297,34 @@ InnerJoinStreamInfo* OptimizerInnerJoin::getStreamInfo(int stream)
}
#ifdef OPT_DEBUG
void OptimizerInnerJoin::printFoundOrder(int position, double cost, double cardinality) const
void OptimizerInnerJoin::printBestOrder() const
{
/**************************************
*
* p r i n t B e s t O r d e r
*
**************************************
*
* Dump finally selected stream order.
*
**************************************/
FILE *opt_debug_file = fopen(OPTIMIZER_DEBUG_FILE, "a");
fprintf(opt_debug_file, " best order, streams: ");
for (int i = 0; i < optimizer->opt_best_count; i++) {
if (i == 0) {
fprintf(opt_debug_file, "%d", optimizer->opt_streams[i].opt_best_stream);
}
else {
fprintf(opt_debug_file, ", %d", optimizer->opt_streams[i].opt_best_stream);
}
}
fprintf(opt_debug_file, "\n");
fclose(opt_debug_file);
}
void OptimizerInnerJoin::printFoundOrder(int position, double positionCost,
double positionCardinality, double cost, double cardinality) const
{
/**************************************
*
@ -3134,13 +3338,21 @@ void OptimizerInnerJoin::printFoundOrder(int position, double cost, double cardi
**************************************/
FILE *opt_debug_file = fopen(OPTIMIZER_DEBUG_FILE, "a");
fprintf(opt_debug_file, "order pos. %2.2d, streams: ", position);
fprintf(opt_debug_file, " position %2.2d:", position);
fprintf(opt_debug_file, " pos. cardinality(%10.2f) pos. cost(%10.2f)", positionCardinality, positionCost);
fprintf(opt_debug_file, " cardinality(%10.2f) cost(%10.2f)", cardinality, cost);
fprintf(opt_debug_file, ", streams: ", position);
const OptimizerBlk::opt_stream* tail = optimizer->opt_streams.begin();
const OptimizerBlk::opt_stream* const order_end = tail + position;
for (; tail < order_end; tail++) {
fprintf(opt_debug_file, "%2.2d - ", tail->opt_stream_number);
if (tail == optimizer->opt_streams.begin()) {
fprintf(opt_debug_file, "%d", tail->opt_stream_number);
}
else {
fprintf(opt_debug_file, ", %d", tail->opt_stream_number);
}
}
fprintf(opt_debug_file, "\tcardinality: %10.2f\tcost: %10.2f\n", cardinality, cost);
fprintf(opt_debug_file, "\n");
fclose(opt_debug_file);
}
@ -3158,11 +3370,36 @@ void OptimizerInnerJoin::printProcessList(const IndexedRelationships* processLis
**************************************/
FILE *opt_debug_file = fopen(OPTIMIZER_DEBUG_FILE, "a");
fprintf(opt_debug_file, "processlist, basestream %2.2d, relationships: \n", stream);
fprintf(opt_debug_file, " basestream %d, relationships: stream(cost)", stream);
const IndexRelationship* const* relationships = processList->begin();
for (int i = 0; i < processList->getCount(); i++) {
fprintf(opt_debug_file, "\t\t%2.2d (%10.2f)\n", relationships[i]->stream, relationships[i]->cost);
fprintf(opt_debug_file, ", %d (%1.2f)", relationships[i]->stream, relationships[i]->cost);
}
fprintf(opt_debug_file, "\n");
fclose(opt_debug_file);
}
void OptimizerInnerJoin::printStartOrder() const
{
/**************************************
*
* p r i n t B e s t O r d e r
*
**************************************
*
* Dump finally selected stream order.
*
**************************************/
FILE *opt_debug_file = fopen(OPTIMIZER_DEBUG_FILE, "a");
fprintf(opt_debug_file, "Start join order: with stream(baseCost)");
bool firstStream = true;
for (int i = 0; i < innerStreams.getCount(); i++) {
if (!innerStreams[i]->used) {
fprintf(opt_debug_file, ", %d (%1.2f)", innerStreams[i]->stream, innerStreams[i]->baseCost);
}
}
fprintf(opt_debug_file, "\n");
fclose(opt_debug_file);
}
#endif

View File

@ -30,10 +30,11 @@
#define OPTIMIZER_H
//#define OPT_DEBUG
//#define OPT_DEBUG_RETRIEVAL
#ifdef OPT_DEBUG
//#ifdef OPT_DEBUG
#define OPTIMIZER_DEBUG_FILE "opt_debug.out"
#endif
//#endif
#include "../common/classes/alloc.h"
@ -114,7 +115,7 @@ public:
class IndexScratch
{
public:
IndexScratch(MemoryPool& p, index_desc* idx);
IndexScratch(MemoryPool& p, thread_db* tdbb, index_desc* idx, CompilerScratch::csb_repeat* csb_tail);
IndexScratch(MemoryPool& p, IndexScratch* scratch);
~IndexScratch();
@ -125,6 +126,7 @@ public:
int lowerCount; //
int upperCount; //
int nonFullMatchedSegments; //
double cardinality; // Estimated cardinality when using the whole index
Firebird::Array<IndexScratchSegment*> segments;
};
@ -137,6 +139,7 @@ public:
InversionCandidate(MemoryPool& p);
double selectivity;
double cost;
USHORT nonFullMatchedSegments;
USHORT matchedSegments;
int indexes;
@ -174,10 +177,17 @@ protected:
IndexScratchList* indexScratches, USHORT scope) const;
jrd_nod* makeIndexNode(const index_desc* idx) const;
jrd_nod* makeIndexScanNode(IndexScratch* indexScratch) const;
InversionCandidate* makeInversion(InversionCandidateList* inversions) const;
InversionCandidate* makeInversion(InversionCandidateList* inversions, bool top = false) const;
bool matchBoolean(IndexScratch* indexScratch, jrd_nod* boolean, USHORT scope) const;
InversionCandidate* matchOnIndexes(IndexScratchList* indexScratches,
jrd_nod* boolean, USHORT scope) const;
#ifdef OPT_DEBUG_RETRIEVAL
void printCandidate(InversionCandidate* candidate) const;
void printCandidates(InversionCandidateList* inversions) const;
void printFinalCandidate(InversionCandidate* candidate) const;
#endif
bool validateStarts(IndexScratch* indexScratch, jrd_nod* boolean, USHORT segment) const;
private:
MemoryPool& pool;
@ -252,8 +262,11 @@ protected:
InnerJoinStreamInfo* testStream);
InnerJoinStreamInfo* getStreamInfo(int stream);
#ifdef OPT_DEBUG
void printFoundOrder(int position, double cost, double cardinality) const;
void printBestOrder() const;
void printFoundOrder(int position, double positionCost,
double positionCardinality, double cost, double cardinality) const;
void printProcessList(const IndexedRelationships* processList, int stream) const;
void printStartOrder() const;
#endif
private:

View File

@ -583,6 +583,10 @@ RecordSource* OPT_compile(thread_db* tdbb,
}
else
csb->csb_rpt[stream].csb_indices = 0;
const Format* format = CMP_format(tdbb, csb, stream);
csb->csb_rpt[stream].csb_cardinality =
getRelationCardinality(tdbb, relation, format);
}
}