Repository navigation
fix: reject statistics whose column count does not match the schema - #25694
jackylee-ch wants to merge 14 commits into
Conversation
count's statistics fast path indexed column_statistics[col.index()] directly, so a TableProvider reporting fewer column statistics than its schema made SELECT count(col) panic. Use .get(idx)? in both branches, matching the guard already in sum.rs.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #25694 +/- ##
=======================================
Coverage 82.87% 82.87%
=======================================
Files 1153 1153
Lines 457043 457104 +61
Branches 457043 457104 +61
=======================================
+ Hits 378777 378836 +59
+ Misses 55268 55265 -3
- Partials 22998 23003 +5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
kosiew
left a comment
There was a problem hiding this comment.
Thanks for fixing this. Both COUNT and COUNT(DISTINCT) now handle missing column statistics safely, and the new regression tests cover both paths. The change looks good to me. I have one optional suggestion for additional end-to-end coverage.
| Ok(()) | ||
| } | ||
|
|
||
| #[test] |
There was a problem hiding this comment.
It would be useful to add an end-to-end regression test with incomplete column statistics, NULLs, and duplicates to verify that aggregation is preserved and returns the correct counts. Please test COUNT(b) and COUNT(DISTINCT b) separately, since the optimizer stops at the first unresolved expression, and make sure the column index remains out of bounds after any projection or remapping. This is optional since the existing unit tests already cover both guarded accesses.
There was a problem hiding this comment.
Edit: superseded by d032a22. Short column statistics now fail planning with an error naming the provider, which also covers COUNT(DISTINCT b) (rewritten into a GROUP BY), and the end-to-end test short_column_statistics_are_reported checks that error for count(b) and a plain scan. An earlier version of this reply said test_count_distinct_optimization gained a short-statistics case; that was wrong: the case passed without the guard and was never pushed. The count unit test covers both branches.
A statistics provider that drops the trailing column entry keeps the aggregated column's index out of bounds whatever the projection, so `count`'s statistics fast path is reached from SQL. The query panics on main and returns the correct count with the guard in place.
alamb
left a comment
There was a problem hiding this comment.
Thank you for this PR @jackylee-ch and for the review @kosiew
I feel like this is just papering over a bug and by sliently ignoring the problem we will make debugging harder
Changing the code so it doesn't panic seems fine, but if the table provider returns the wrong number of statistics I think that should return a clear error
|
Agreed — it should be an error. Two candidates: the provider/source boundary, or One thing first: planning-time rules compute statistics ( |
I don't understand what you are asking me What is the "broad gate"? |
`Statistics::column_statistics` must hold one entry per schema field, and consumers index it by column. Check that where `StatisticsContext` receives statistics from an operator or a statistics provider, and fail with an error naming the culprit instead of panicking in a consumer.
`value_from_stats` is public, so a direct caller can still pass short statistics; fold the two `count` cases into one loop. Link `ColumnStatistics` in the `StatisticsProvider` docs.
`min` and `max` index `column_statistics` like `count` did, and `value_from_stats` is public, so use `.get(idx)?` there as well. The end-to-end test now also checks that a plain scan fails planning.
Extra entries never indexed out of bounds; only missing ones did.
`FilterExec` indexes its input's column statistics when it is built, so a table provider returning too few entries also panicked on `WHERE`.
A statistics provider returning the statistics of the whole table for a scan of only `b` used to make `min(b)` and `max(b)` return the minimum and maximum of `a`. Check end to end that planning fails instead, and that both statistics sources reject one entry too many as well as too few. Also pin what file scans accept: with a projection, as all built-in sources have, table and file group statistics of any length, missing trailing entries being unknown and extra ones ignored; without one, exactly one entry per table column. Say so in the upgrade guide.
`FileScanConfig` pads table and file group statistics it is given and ignores extra entries when its source has a projection, so a missing or extra entry before the last column shifts the statistics of the columns after it. Describe that as unchecked rather than as accepted.
`FileScanConfig` looks up the table and file group statistics it is given by column position, so an extra entry anywhere but at the end shifted the statistics of every later column by one, and `min`/`max` could be answered from another column's values. Return an internal error naming the statistics instead. Missing trailing entries are still unknown, which virtual columns and some table providers rely on.
|
Sorry, that was unclear. "Broad gate" meant one check in Too few entries can make filters, joins, |
|
I am sorry -- I still don't get it. How about we start with a clear and concise description of what problem you are solving (from the end user API perspective, not a code description) Describe:
It seems like you are trying to add better error messages when a table provider provides incorrect statistics ? But that is not clear |
Which issue does this PR close?
Rationale for this change
Too few column statistics can make filters, joins,
GROUP BY,count/min/maxpanic; too many can givemin/maxanother column's values.What changes are included in this PR?
Unless operators and providers return one
ColumnStatisticsper field,StatisticsContextfails planning naming them;FileScanConfigrejects more than one per column.count/min/maxuse.get(idx)?likesum:value_from_statscannot return an error.Not covered: file scans pad short statistics (virtual columns #22604, delta-io/delta-rs#4823 need it).
What is the testing strategy for this PR?
Unit tests per source, file scans,
count/min/max; e2e tests.compute_statisticsbench up to 10% slower; TPC-H/DS planning in noise.Are there any user-facing changes?
Breaking for extensions (please add
api change): such plans fail, plain scans too; see the upgrade guide.