abelianbee opened a new pull request, #51299:
URL: https://github.com/apache/arrow/pull/51299

   ### Rationale for this change
   
   `NormalizeTime` is documented as preserving order. For signed types it 
doesn't, because the bias is applied after the value has already been widened:
   
   ```c++
   uint64_t bias =
       std::is_signed<T>::value ? static_cast<uint64_t>(1) << (8 * sizeof(T) - 
1) : 0;
   return t < 0 ? static_cast<uint64_t>(t + bias) : static_cast<uint64_t>(t);
   ```
   
   A negative `t` converts to `uint64_t` first and sign-extends to `2^64 + t`, 
so adding `2^(W-1)` wraps back into `[0, 2^(W-1))`, where the non-negative 
branch already maps. That folds the domain 2-to-1: `NormalizeTime(t) == 
NormalizeTime(t + 2^(W-1))` for every negative `t`. Over the full int16 domain 
only 32768 of 65536 outputs are distinct, and order inverts once, at `-1 -> 0`. 
`NormalizeTime(INT64_MIN)` and `NormalizeTime(int64_t{0})` are both 0.
   
   `GetTime` routes TIMESTAMP, DATE64 and TIME64 through int64 and 
DATE32/TIME32 through int32, and asof_join_node and sorted_merge_node both key 
rows on it, so any pre-epoch timestamp hits this.
   
   Two symptoms. The one in the issue is the out-of-order error, from input 
that is correctly sorted, such as `[-1000, 0, 1000]`:
   
   ```
   Invalid: AsofJoin does not allow out-of-order on-key values
     asof_join_node.cc:693  Advance()
   ```
   
   The other is silent. `TolType::Accepts` compares differences of normalized 
values, and those are exact only when both operands have the same sign, so an 
asof join whose tolerance window straddles the epoch drops matches that are 
inside the window without raising anything. A left row at t=30 and a right row 
at t=-30 with a backward tolerance of 60 returns null.
   
   ### What changes are included in this PR?
   
   Flip the sign bit inside `T`'s own width, then zero-extend. That is a 
strictly increasing bijection onto the same-width unsigned type, and 
zero-extension preserves order. Unsigned `T` stays the identity.
   
   The definition moves into the header. It was declared there and defined in 
the `.cc` with no explicit instantiation, so it only linked because `GetTime` 
instantiates it in that same TU, and no test TU could instantiate it at all. 
Explicit instantiations would work too if you would rather it stayed put.
   
   ### Are these changes tested?
   
   New `time_series_util_test.cc` on the existing `util_test` target: 
exhaustive monotonicity and injectivity over int8, uint8, int16 and uint16, 
boundary sweeps for the wider types, `INT64_MIN -> 0` and `INT64_MAX -> 
UINT64_MAX`, and exactness of differences spanning zero. Plus 
`TimesStraddlingEpochAreOrdered` and `ToleranceWindowStraddlingEpoch` in 
asof_join_node_test.
   
   All six fail without the fix. With it, util_test 18 pass, 
asof_join_node_test 154 pass with the pre-existing BackpressureWithBatchesGen 
skip, sorted_merge_node_test 1 pass.
   
   ### Are there any user-facing changes?
   
   Asof joins and sorted merges over pre-epoch timestamps stop erroring and 
stop dropping rows. Normalized values are an internal key encoding, so no API 
change.
   
   **This PR contains a "Critical Fix".** Asof joins over pre-epoch timestamps 
could silently return wrong results.
   


-- 
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]

Reply via email to