@@ -316,17 +316,7 @@ InversionCandidate* Retrieval::getInversion()
316316 invCandidate = FB_NEW_POOL (getPool ()) InversionCandidate (getPool ());
317317 }
318318
319- if (invCandidate->unique )
320- {
321- // Set up the unique retrieval cost to be fixed and not dependent on
322- // possibly outdated statistics. It includes N index scans plus one data page fetch.
323- invCandidate->cost = DEFAULT_INDEX_COST * invCandidate->indexes + 1 ;
324- }
325- else
326- {
327- // Add the records retrieval cost to the priorly calculated index scan cost
328- invCandidate->cost += cardinality * invCandidate->selectivity ;
329- }
319+ invCandidate->matchSelectivity = invCandidate->selectivity ;
330320
331321 // Adjust the effective selectivity by treating computable but unmatched conjunctions
332322 // as filters. But consider only those local to our stream.
@@ -336,12 +326,15 @@ InversionCandidate* Retrieval::getInversion()
336326 {
337327 if (!(iter & Optimizer::CONJUNCT_USED))
338328 {
339- const auto matched = invCandidate->matches .exist (iter);
329+ const auto matched = invCandidate->matches .exist (iter) ||
330+ (navigationCandidate && navigationCandidate->matches .exist (iter));
340331
341- if (setConjunctionsMatched && matched)
342- iter |= Optimizer::CONJUNCT_MATCHED;
343- else if (!setConjunctionsMatched && !matched &&
344- iter->computable (csb, stream, true ) &&
332+ if (matched)
333+ {
334+ if (setConjunctionsMatched)
335+ iter |= Optimizer::CONJUNCT_MATCHED;
336+ }
337+ else if (iter->computable (csb, stream, true ) &&
345338 iter->containsStream (stream))
346339 {
347340 selectivity *= Optimizer::getSelectivity (*iter);
@@ -357,6 +350,18 @@ InversionCandidate* Retrieval::getInversion()
357350
358351 Optimizer::adjustSelectivity (invCandidate->selectivity , selectivity);
359352
353+ // Apply navigational walk, if meaningful
354+ if (navigationCandidate)
355+ applyNavigation (invCandidate);
356+
357+ // Add the records retrieval cost to the priorly calculated index scan cost.
358+ // For unique retrievals, set up a fixed cost (independent from a possibly outdated statistics).
359+ // It includes DEFAULT_INDEX_COST index scans plus one data page fetch.
360+ if (invCandidate->unique )
361+ invCandidate->cost = DEFAULT_INDEX_COST * invCandidate->indexes + 1 ;
362+ else
363+ invCandidate->cost += cardinality * invCandidate->selectivity ;
364+
360365 // Add the streams where this stream is depending on
361366 for (auto match : invCandidate->matches )
362367 {
@@ -376,94 +381,16 @@ InversionCandidate* Retrieval::getInversion()
376381 return invCandidate;
377382}
378383
379- IndexTableScan* Retrieval::getNavigation (const InversionCandidate* candidate )
384+ IndexTableScan* Retrieval::getNavigation ()
380385{
381386 if (!navigationCandidate)
382387 return nullptr ;
383388
384- const auto scratch = navigationCandidate->scratch ;
385-
386- const auto streamCardinality = csb->csb_rpt [stream].csb_cardinality ;
387-
388- // If the table looks like empty during preparation time, we cannot be sure about
389- // its real cardinality during execution. So, unless we have some index-based
390- // filtering applied, let's better be pessimistic and avoid external sorting
391- // due to likely cardinality under-estimation.
392- const bool avoidSorting = (streamCardinality <= MINIMUM_CARDINALITY && !candidate->inversion );
393-
394- if (!(scratch->index ->idx_runtime_flags & idx_plan_navigate) && !avoidSorting)
395- {
396- // Check whether the navigational index scan is cheaper than the external sort
397- // and give up if it's not worth the efforts.
398- //
399- // We ignore candidate->cost in the calculations below as it belongs
400- // to both parts being compared.
401-
402- fb_assert (candidate);
403-
404- // Restore the original selectivity of the inversion,
405- // i.e. before the navigation candidate was accounted
406- auto selectivity = candidate->selectivity / navigationCandidate->selectivity ;
407-
408- // Non-indexed booleans are checked before sorting, so they improve the selectivity
409-
410- double factor = MAXIMUM_SELECTIVITY;
411- for (auto iter = optimizer->getConjuncts (outerFlag, innerFlag); iter.hasData (); ++iter)
412- {
413- if (!(iter & Optimizer::CONJUNCT_USED) &&
414- !candidate->matches .exist (iter) &&
415- iter->computable (csb, stream, true ) &&
416- iter->containsStream (stream))
417- {
418- factor *= Optimizer::getSelectivity (*iter);
419- }
420- }
421-
422- Optimizer::adjustSelectivity (selectivity, factor);
423-
424- // Don't consider external sorting if optimization for first rows is requested
425- // and we have no local filtering applied
426-
427- if (!optimizer->favorFirstRows () || selectivity < MAXIMUM_SELECTIVITY)
428- {
429- // Estimate amount of records to be sorted
430- const auto cardinality = streamCardinality * selectivity;
431-
432- // We optimistically assume that records will be cached during sorting
433- const auto sortCost =
434- // record copying (to the sort buffer and back)
435- cardinality * COST_FACTOR_MEMCOPY * 2 +
436- // quicksort algorithm is O(n*log(n)) in average
437- cardinality * log2 (cardinality) * COST_FACTOR_QUICKSORT;
438-
439- // During navigation we fetch an index leaf page per every record being returned,
440- // thus add the estimated cardinality to the cost
441- auto navigationCost = navigationCandidate->cost +
442- streamCardinality * candidate->selectivity ;
443-
444- if (optimizer->favorFirstRows ())
445- {
446- // Reset the cost to represent a single record retrieval
447- navigationCost = DEFAULT_INDEX_COST;
448-
449- // We know that some local filtering is applied, so we need
450- // to adjust the cost as we need to walk the index
451- // until the first matching record is found
452- const auto fullIndexCost = navigationCandidate->scratch ->cardinality ;
453- const auto ratio = MAXIMUM_SELECTIVITY / selectivity;
454- const auto fraction = ratio / streamCardinality;
455- const auto walkCost = fullIndexCost * fraction * navigationCandidate->selectivity ;
456- navigationCost += walkCost;
457- }
458-
459- if (sortCost < navigationCost)
460- return nullptr ;
461- }
462- }
463-
464389 // Looks like we can do a navigational walk. Flag that
465390 // we have used this index for navigation, and allocate
466391 // a navigational rsb for it.
392+
393+ const auto scratch = navigationCandidate->scratch ;
467394 scratch->index ->idx_runtime_flags |= idx_navigate;
468395
469396 const auto indexNode = makeIndexScanNode (scratch);
@@ -715,6 +642,103 @@ void Retrieval::analyzeNavigation(const InversionCandidateList& inversions)
715642 navigationCandidate = bestCandidate;
716643}
717644
645+ void Retrieval::applyNavigation (InversionCandidate* candidate)
646+ {
647+ fb_assert (navigationCandidate && candidate);
648+ fb_assert (navigationCandidate != candidate);
649+
650+ candidate->navigated = true ;
651+
652+ const auto scratch = navigationCandidate->scratch ;
653+ const auto streamCardinality = csb->csb_rpt [stream].csb_cardinality ;
654+
655+ // If the table looks like empty during preparation time, we cannot be sure about
656+ // its real cardinality during execution. So, unless we have some index-based
657+ // filtering applied, let's better be pessimistic and avoid external sorting
658+ // due to likely cardinality under-estimation.
659+ const bool avoidSorting =
660+ (streamCardinality <= MINIMUM_CARDINALITY && !candidate->inversion ) ||
661+ // also don't consider external sorting if optimization for first rows is requested
662+ // and we have no local filtering applied
663+ (optimizer->favorFirstRows () && candidate->selectivity == MAXIMUM_SELECTIVITY) ||
664+ // and finally, skip it if the explicit plan specifies this index as navigated
665+ (scratch->index ->idx_runtime_flags & idx_plan_navigate);
666+
667+ if (!avoidSorting)
668+ {
669+ // Check whether the navigational index scan is cheaper than the external sort
670+ // and give up if it's not worth the efforts.
671+ //
672+ // We ignore candidate->cost in the calculations below as it belongs
673+ // to both parts being compared.
674+
675+ // Estimate amount of records to be sorted
676+ const auto cardinality = streamCardinality * candidate->selectivity ;
677+ const auto matchCardinality = streamCardinality * candidate->matchSelectivity ;
678+
679+ // We optimistically assume that records will be cached during sorting
680+ const auto sortCost =
681+ // record copying (to the sort buffer and back)
682+ cardinality * COST_FACTOR_MEMCOPY * 2 +
683+ // quicksort algorithm is O(n*log(n)) in average
684+ cardinality * log2 (cardinality) * COST_FACTOR_QUICKSORT;
685+
686+ // During navigation we fetch an index leaf page per every record being returned,
687+ // thus add the estimated cardinality to the cost
688+ auto navigationCost = navigationCandidate->cost +
689+ matchCardinality * navigationCandidate->selectivity ;
690+
691+ if (optimizer->favorFirstRows ())
692+ {
693+ // Reset the cost to represent a single record retrieval
694+ navigationCost = DEFAULT_INDEX_COST;
695+
696+ // We know that some local filtering is applied, so we need
697+ // to adjust the cost as we need to walk the index
698+ // until the first matching record is found
699+ const auto fullIndexCost = navigationCandidate->scratch ->cardinality ;
700+ const auto ratio = MAXIMUM_SELECTIVITY / candidate->selectivity ;
701+ const auto fraction = ratio / streamCardinality;
702+ const auto walkCost = fullIndexCost * fraction * navigationCandidate->selectivity ;
703+ navigationCost += walkCost;
704+ }
705+
706+ if (sortCost < navigationCost)
707+ {
708+ candidate->navigated = false ;
709+ navigationCandidate->cost = 0 ;
710+ }
711+ }
712+
713+ // Update the final inversion candidate
714+
715+ candidate->unique = candidate->unique || navigationCandidate->unique ;
716+ candidate->selectivity *= navigationCandidate->selectivity ;
717+ candidate->cost += navigationCandidate->cost ;
718+ candidate->indexes += navigationCandidate->indexes ;
719+
720+ for (const auto navMatch : navigationCandidate->matches )
721+ {
722+ if (!candidate->matches .exist (navMatch))
723+ candidate->matches .add (navMatch);
724+ }
725+
726+ if (!candidate->navigated )
727+ {
728+ // If navigation is undesired, add its possible inversion to the final candidate
729+
730+ if (navigationCandidate->matches .hasData ())
731+ {
732+ const auto inversionNode = (!navigationCandidate->inversion && navigationCandidate->scratch ) ?
733+ makeIndexScanNode (navigationCandidate->scratch ) : navigationCandidate->inversion ;
734+ candidate->inversion = composeInversion (candidate->inversion ,
735+ inversionNode, InversionNode::TYPE_AND);
736+ }
737+
738+ navigationCandidate.reset ();
739+ }
740+ }
741+
718742bool Retrieval::betterInversion (const InversionCandidate* inv1,
719743 const InversionCandidate* inv2,
720744 bool navigation) const
@@ -1755,27 +1779,6 @@ InversionCandidate* Retrieval::makeInversion(InversionCandidateList& inversions)
17551779 }
17561780 }
17571781
1758- // If we have no index used for filtering, but there's a navigational walk,
1759- // set up the inversion candidate appropriately.
1760-
1761- if (navigationCandidate)
1762- {
1763- if (!invCandidate)
1764- invCandidate = FB_NEW_POOL (getPool ()) InversionCandidate (getPool ());
1765-
1766- invCandidate->unique = navigationCandidate->unique ;
1767- invCandidate->selectivity *= navigationCandidate->selectivity ;
1768- invCandidate->cost += navigationCandidate->cost ;
1769- ++invCandidate->indexes ;
1770- invCandidate->navigated = true ;
1771-
1772- for (const auto navMatch : navigationCandidate->matches )
1773- {
1774- if (!invCandidate->matches .exist (navMatch))
1775- invCandidate->matches .add (navMatch);
1776- }
1777- }
1778-
17791782 return invCandidate;
17801783}
17811784
0 commit comments