Search before asking
Description
ZoneMap::has_nan is optional bool = 8 in ZoneMapPB (gensrc/proto/segment_v2.proto), added by
#55633 together with the NaN handling in the zone map writer. ZoneMap::from_proto
(be/src/storage/index/zone_map/zone_map_index.cpp) reads it unconditionally, so a zone map
serialized before that field existed deserializes with has_nan == false. Nothing checks
has_has_nan(), and there is no segment-version gate.
Every consumer that treats has_nan == false as "no NaN in this zone" therefore trusts bounds that
may omit one. range_stats_usable_for_zonemap (be/src/exprs/expr_zonemap_filter.cpp) is one;
#55633 also taught the older olap predicate path to trust the same flag, so this is not limited to
expression zone maps.
Concretely: a FLOAT page written before #55633 holding {1.0, NaN, 2.0} stores bounds [1.0, 2.0]
with the field absent, because the generic min/max path never selects a NaN. After an upgrade,
WHERE d > 3.0 prunes the page, but Doris orders NaN above every other value, so that row satisfies
the predicate and should have been returned.
What I have not established is whether any deployment still reads pre-#55633 segments. That decides
the severity, and it is not something the code can answer.
Solution
Treat a FLOAT or DOUBLE ZoneMapPB with field 8 absent as unusable for range pruning, distinguishing
"no NaN" from "unknown", the same way the Parquet readers already distinguish those two with
floating_nan_count_unknown. The fix belongs at the from_proto or index-meta layer so that every
consumer inherits it.
Are you willing to submit PR?
Search before asking
Description
ZoneMap::has_nanisoptional bool = 8inZoneMapPB(gensrc/proto/segment_v2.proto), added by#55633 together with the NaN handling in the zone map writer.
ZoneMap::from_proto(
be/src/storage/index/zone_map/zone_map_index.cpp) reads it unconditionally, so a zone mapserialized before that field existed deserializes with
has_nan == false. Nothing checkshas_has_nan(), and there is no segment-version gate.Every consumer that treats
has_nan == falseas "no NaN in this zone" therefore trusts bounds thatmay omit one.
range_stats_usable_for_zonemap(be/src/exprs/expr_zonemap_filter.cpp) is one;#55633 also taught the older olap predicate path to trust the same flag, so this is not limited to
expression zone maps.
Concretely: a FLOAT page written before #55633 holding
{1.0, NaN, 2.0}stores bounds[1.0, 2.0]with the field absent, because the generic min/max path never selects a NaN. After an upgrade,
WHERE d > 3.0prunes the page, but Doris orders NaN above every other value, so that row satisfiesthe predicate and should have been returned.
What I have not established is whether any deployment still reads pre-#55633 segments. That decides
the severity, and it is not something the code can answer.
Solution
Treat a FLOAT or DOUBLE
ZoneMapPBwith field 8 absent as unusable for range pruning, distinguishing"no NaN" from "unknown", the same way the Parquet readers already distinguish those two with
floating_nan_count_unknown. The fix belongs at thefrom_protoor index-meta layer so that everyconsumer inherits it.
Are you willing to submit PR?