Repository navigation
fix(orca): merge colid->attno mapping in CStatistics::AppendStats to avoid extended-stats crash #1966
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
fix(orca): merge colid->attno mapping in CStatistics::AppendStats to avoid extended-stats crash #1966
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -375,7 +375,16 @@ CFilterStatsProcessor::MakeHistHashMapConjFilter( | |
| if (CStatsPred::EsptDisj != child_pred_stats->GetPredStatsType()) | ||
| { | ||
| GPOS_ASSERT(gpos::ulong_max != colid); | ||
| hist_before = result_histograms->Find(&colid)->CopyHistogram(); | ||
| const CHistogram *base_histogram = result_histograms->Find(&colid); | ||
| if (nullptr == base_histogram) | ||
| { | ||
| // no histogram for the filter column; estimate the clause | ||
| // with the default selectivity instead of dereferencing null | ||
| scale_factors->Append(GPOS_NEW(mp) CDouble( | ||
| 1 / CHistogram::DefaultSelectivity.Get())); | ||
| continue; | ||
| } | ||
| hist_before = base_histogram->CopyHistogram(); | ||
| GPOS_ASSERT(nullptr != hist_before); | ||
|
|
||
| CHistogram *result_histogram = nullptr; | ||
|
|
@@ -438,9 +447,15 @@ CFilterStatsProcessor::MakeHistHashMapConjFilter( | |
| { | ||
| CHistogram *result_histogram = | ||
| disjunctive_histograms_after->Find(&colid); | ||
| CStatisticsUtils::AddHistogram(mp, colid, result_histogram, | ||
| result_histograms, | ||
| true /* fReplaceOld */); | ||
| // the column may have no histogram at all (see | ||
| // MakeHistHashMapDisjFilter()); then there is nothing to | ||
| // replace and the scale factor alone carries the estimate | ||
| if (nullptr != result_histogram) | ||
| { | ||
| CStatisticsUtils::AddHistogram(mp, colid, result_histogram, | ||
| result_histograms, | ||
| true /* fReplaceOld */); | ||
| } | ||
| disjunctive_histograms_after->Release(); | ||
|
|
||
| last_scale_factor = | ||
|
|
@@ -525,6 +540,22 @@ CFilterStatsProcessor::MakeHistHashMapDisjFilter( | |
| continue; | ||
| } | ||
|
|
||
| BOOL is_pred_simple = | ||
| !CStatsPredUtils::IsConjOrDisjPred(child_pred_stats); | ||
| CHistogram *histogram = input_histograms->Find(&colid); | ||
| if (is_pred_simple && nullptr == histogram) | ||
| { | ||
| // no histogram for the filter column; estimate the clause with | ||
| // the default selectivity instead of dereferencing null. Do this | ||
| // before the per-column bookkeeping below, like the unsupported | ||
| // predicate case, so the previous column's intermediate result | ||
| // is not flushed twice. | ||
| scale_factors->Append(GPOS_NEW(mp) CDouble( | ||
| 1 / CHistogram::DefaultSelectivity.Get())); | ||
|
|
||
| continue; | ||
| } | ||
|
|
||
| if (IsNewStatsColumn(colid, previous_colid)) | ||
| { | ||
| scale_factors->Append(GPOS_NEW(mp) | ||
|
|
@@ -536,11 +567,8 @@ CFilterStatsProcessor::MakeHistHashMapDisjFilter( | |
| previous_histogram = nullptr; | ||
| } | ||
|
|
||
| CHistogram *histogram = input_histograms->Find(&colid); | ||
| CHistogram *disjunctive_child_col_histogram = nullptr; | ||
|
|
||
| BOOL is_pred_simple = | ||
| !CStatsPredUtils::IsConjOrDisjPred(child_pred_stats); | ||
| BOOL is_colid_present = (gpos::ulong_max != colid); | ||
| UlongToHistogramMap *child_histograms = nullptr; | ||
| CDouble child_scale_factor(1.0); | ||
|
|
@@ -574,8 +602,40 @@ CFilterStatsProcessor::MakeHistHashMapDisjFilter( | |
| if (is_colid_present) | ||
| { | ||
| // conjunction or disjunction uses only a single column | ||
| disjunctive_child_col_histogram = | ||
| child_histograms->Find(&colid)->CopyHistogram(); | ||
| const CHistogram *child_col_histogram = | ||
| child_histograms->Find(&colid); | ||
| if (nullptr != child_col_histogram) | ||
| { | ||
| disjunctive_child_col_histogram = | ||
| child_col_histogram->CopyHistogram(); | ||
| } | ||
| else | ||
| { | ||
| // the column has no histogram, so the child left none | ||
| // behind; treat the child like a multi-column predicate | ||
| // and only merge its histogram map and scale factor. | ||
| // | ||
| // The multi-column path below is otherwise only reached | ||
| // with colid == gpos::ulong_max, which makes | ||
| // IsNewStatsColumn() true at the top of the loop, so it | ||
| // assumes the pending per-column state has already been | ||
| // flushed. Flush it here and forget the column id too, | ||
| // otherwise the pending histogram would be dropped and | ||
| // a later child on the same column would overwrite this | ||
| // child's scale factor instead of accumulating it. | ||
| if (nullptr != previous_histogram) | ||
| { | ||
| scale_factors->Append( | ||
| GPOS_NEW(mp) CDouble(previous_scale_factor.Get())); | ||
| CStatisticsUtils::UpdateDisjStatistics( | ||
| mp, non_updatable_cols, input_rows, cumulative_rows, | ||
| previous_histogram, disjunctive_result_histograms, | ||
| previous_colid); | ||
| previous_histogram = nullptr; | ||
| } | ||
| is_colid_present = false; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch, and it turns out the consequence is worse than just the scale factor. The multi-column path at the bottom of the loop is otherwise only reached with
Fixed in a1af361 by flushing the pending histogram and scale factor in the fallback and then resetting if (nullptr != previous_histogram)
{
scale_factors->Append(
GPOS_NEW(mp) CDouble(previous_scale_factor.Get()));
CStatisticsUtils::UpdateDisjStatistics(
mp, non_updatable_cols, input_rows, cumulative_rows,
previous_histogram, disjunctive_result_histograms,
previous_colid);
previous_histogram = nullptr;
}
is_colid_present = false;
colid = gpos::ulong_max;The Verified on a cassert build (ORCA on): |
||
| colid = gpos::ulong_max; | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -552,6 +552,33 @@ CStatistics::AppendStats(CMemoryPool *mp, IStatistics *input_stats) | |
| CStatisticsUtils::AddWidthInfo(mp, stats->m_colid_width_mapping, | ||
| m_colid_width_mapping); | ||
| GPOS_CHECK_ABORT; | ||
|
|
||
| // Also merge the colid -> attno mapping so that every column that has a | ||
| // histogram keeps an attno entry; extended-stats estimation relies on | ||
| // this invariant. The mapping may be shared with other stats objects | ||
| // (see ScaleStats()), so merge copy-on-write instead of mutating it. | ||
| if (0 < stats->m_colid_to_attno_mapping->Size()) | ||
| { | ||
| UlongToIntMap *attnos_new = GPOS_NEW(mp) UlongToIntMap(mp); | ||
| AddAttnoInfo(mp, m_colid_to_attno_mapping, attnos_new); | ||
| AddAttnoInfo(mp, stats->m_colid_to_attno_mapping, attnos_new); | ||
| m_colid_to_attno_mapping->Release(); | ||
| m_colid_to_attno_mapping = attnos_new; | ||
| } | ||
|
|
||
| // Attnos are only meaningful relative to m_ext_stats' base relation, so | ||
| // merging is only valid for stats objects describing the same relation; | ||
| // all callers merge stats of the same memo group / expression today. | ||
| GPOS_ASSERT(nullptr == m_ext_stats || nullptr == stats->m_ext_stats || | ||
| m_ext_stats == stats->m_ext_stats); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would MDId()->Equals() be better?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I checked this, and switching to Equals() would be a style/robustness nit only, with no behavioral difference. |
||
|
|
||
| if (nullptr == m_ext_stats) | ||
| { | ||
| // non-owning pointer, kept alive by the MD cache (never released by | ||
| // ~CStatistics and never AddRef'd) | ||
| m_ext_stats = stats->m_ext_stats; | ||
| } | ||
| GPOS_CHECK_ABORT; | ||
| } | ||
|
|
||
| // copy statistics object | ||
|
|
@@ -776,6 +803,27 @@ CStatistics::AddAttnoInfoWithRemap(CMemoryPool *mp, UlongToIntMap *src_attno, | |
| } | ||
| } | ||
|
|
||
| // add attno information without remapping | ||
| void | ||
| CStatistics::AddAttnoInfo(CMemoryPool *mp, UlongToIntMap *src_attno, | ||
| UlongToIntMap *dest_attno) | ||
| { | ||
| UlongToIntMapIter col_attno_map_iterator(src_attno); | ||
| while (col_attno_map_iterator.Advance()) | ||
| { | ||
| ULONG colid = *(col_attno_map_iterator.Key()); | ||
|
|
||
| if (nullptr == dest_attno->Find(&colid)) | ||
| { | ||
| const INT *attno = col_attno_map_iterator.Value(); | ||
| INT *attno_copy = GPOS_NEW(mp) INT(*attno); | ||
| BOOL result GPOS_ASSERTS_ONLY = | ||
| dest_attno->Insert(GPOS_NEW(mp) ULONG(colid), attno_copy); | ||
| GPOS_ASSERT(result); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // return the index of the array of upper bound ndvs to which column reference belongs | ||
| ULONG | ||
| CStatistics::GetIndexUpperBoundNDVs(const CColRef *colref) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.