LuciferYang opened a new pull request, #67779:
URL: https://github.com/apache/doris/pull/67779

   ### What problem does this PR solve?
   
   Issue Number: close #67773
   
   Problem Summary:
   
   `ZoneMapPB` fields 6 to 8 (`has_positive_inf`, `has_negative_inf`, 
`has_nan`) were all added in the same change that made float and double zone 
maps NaN-aware. `ZoneMap::from_proto` reads `zone_map.has_nan()` without 
consulting `has_has_nan()`, so a zone map serialized before those fields 
existed and one written by the current code for a genuinely NaN-free zone 
deserialize to the same in-memory state.
   
   That matters because the two are not the same. Float and double zone maps 
already existed before the flags did, and their bounds were computed by a 
comparison that never selects a NaN: a page holding `{1.0, NaN, 2.0}` stores 
`[1.0, 2.0]` and has nowhere to record the NaN. Doris orders NaN above every 
other value, so `WHERE d > 3.0` prunes that page even though the NaN row 
satisfies the predicate. The query loses a row and reports no error.
   
   The path is reachable on upgrade rather than only in theory. The 3.1 line 
has no `has_nan` field, the 4.x readers have the unguarded read, and an upgrade 
replaces binaries while leaving existing data files in place, so a 4.x BE can 
read pre-4.0 rowsets immediately. The reversed-bounds guard added in #67431 
does not cover this: it catches legacy pages whose bounds never moved, such as 
NaN-only pages, while a mixed finite and NaN page has ordinary ordered bounds.
   
   `from_proto` now treats an absent field 8 on a float or double column as 
"the NaN state is unknown" and marks the zone map `pass_all`, which is the same 
degradation the function already applies to a bound that fails to parse and to 
reversed bounds. Field presence is a better signal than a segment version gate: 
the writer calls `set_has_nan(false)` explicitly for a NaN-free zone, so 
presence separates current known-false metadata from legacy unknown metadata. 
Keeping this in the deserialization layer means segment pruning, page pruning, 
predicate elimination and expression zone map evaluation all inherit the 
conservative behaviour; a check in one consumer would leave the others exposed. 
`has_null` and `has_not_null` are untouched, so IS NULL and IS NOT NULL keep 
pruning.
   
   The cost is that range pruning stops for float and double columns in rowsets 
written before the flags existed, until compaction rewrites them. A finer 
version would keep pruning for the operators a hidden NaN cannot satisfy (`<`, 
`<=`) and disable it only for the ones it can (`>`, `>=`, `= NaN`, `!=` against 
a non-NaN literal), which is the distinction the Parquet readers already make 
with their own unknown-NaN-count flag. That needs a new state carried into both 
the expression zone map path and the older column predicate path, so it is a 
follow-up rather than part of a correctness fix that wants backporting.
   
   ### Release note
   
   Fixed a correctness bug where a query could silently drop rows containing 
NaN when reading float or double columns from rowsets written before NaN-aware 
zone maps.
   
   ### Check List (For Author)
   
   - Test <!-- At least one of them must be included. -->
       - [ ] Regression test
       - [X] Unit Test
       - [ ] Manual test (add detailed scripts or steps below)
       - [ ] No need to test or manual test. Explain why:
           - [ ] This is a refactor/code format and no logic has been changed.
           - [ ] Previous test can cover this change.
           - [ ] No code files have been changed.
           - [ ] Other reason <!-- Add your reason?  -->
   
   `LegacyFloatZoneMapWithoutHasNanDegradesToPassAll` covers FLOAT and DOUBLE, 
nullable and not: a `ZoneMapPB` with valid ordered finite bounds and no field 8 
comes back with `pass_all` set and its null flags intact, the same bounds with 
`set_has_nan(false)` stay usable, and an INT zone map without the field is 
unaffected.
   
   Two existing tests build a DOUBLE `ZoneMapPB` without calling `set_has_nan`, 
which now reads as legacy metadata and made their control cases fail. Their 
helpers set `set_has_nan(false)`, which is what the writer actually emits, so 
each test keeps testing what it was written for.
   
   A reader-level fixture driving page and segment pruning would be stronger 
than a `from_proto` unit test, but the honest version of it needs a segment 
artifact produced by a pre-flag build, which is not something this PR can 
generate.
   
   - Behavior changed:
       - [ ] No.
       - [X] Yes.
   
   Float and double columns in rowsets written before the NaN flags existed no 
longer participate in range pruning. Rowsets written by current code are 
unaffected, since their zone maps carry the flag.
   
   This is a candidate for backport to the maintained 4.0 and 4.1 lines, which 
are the readers that can encounter pre-4.0 rowsets.
   
   - Does this need documentation?
       - [X] No.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to