adriangb commented on PR #25161:
URL: https://github.com/apache/datafusion/pull/25161#issuecomment-5626553910

   **Self-review. This is my own PR, so this is a QA pass on my own work, not 
an independent review.** Its value is the list of things I ran, and the risk 
that remains after them.
   
   I built `main` (`da89c7c85b`, the exact merge base) and this branch 
(`e8cb6a69`) as two `datafusion-cli` binaries, and I compared them directly. 
Everything below comes from a run, not from a read.
   
   ---
   
   ## Findings
   
   ### 1. The description states "No previously-successful query changes 
behaviour". That is false.
   
   Two queries succeed on `main` and fail on this branch:
   
   ```sql
   SELECT to_unixtime(from_unixtime(-8334601211039, 'America/New_York'));
   -- main:   -8334601211039
   -- branch: Execution error: Cannot convert -8334601211039 to a timestamp in 
timezone
   --         "America/New_York" for function from_unixtime: ...
   
   SELECT from_unixtime(-8334601211039, 'America/New_York')
        > from_unixtime(0, 'America/New_York');
   -- main:   false
   -- branch: Execution error: ... (same)
   ```
   
   The reason is a real asymmetry. On `main` the value is only fatal when a 
formatter renders it. A query that computes with the value but never renders it 
works today. The new guard rejects the value earlier, at production time, so 
those queries now fail.
   
   The guard is still the right trade: the alternative is a process panic. But 
the sentence must go. A correct statement is: *no query that produced a 
rendered result changes behaviour; a query that consumed an out-of-range value 
without rendering it now returns an error.*
   
   A `count(*)` over the same expression still works on both binaries, because 
the projection is pruned before `invoke_with_args` runs.
   
   ### 2. `to_timestamp_seconds` still panics on the same input, on this branch.
   
   ```sql
   SET datafusion.execution.time_zone = 'America/New_York';
   SELECT to_timestamp_seconds(-8334601211039);
   -- main:   thread 'main' panicked ... Local time out of range for 
`NaiveDateTime`
   -- branch: thread 'main' panicked ... Local time out of range for 
`NaiveDateTime`
   ```
   
   This is a sibling in the same module. It returns `Timestamp(Second, <session 
tz>)`, which is the exact shape the guard protects in `from_unixtime`. It is 
pre-existing and this PR does not make it worse, but the description names only 
`arrow_cast` as the residual path. `to_timestamp_seconds` is a much more likely 
route for a user, so it belongs in the residual-risk list. The "removes the 
panic reproducer in #16594" line reads wider than the change is.
   
   ### 3. `cargo-semver-checks` is red on this PR and the description does not 
say so.
   
   The bot reports `function_parameter_count_changed` for 
`datafusion_functions::datetime::from_unixtime` and 
`type_method_marked_deprecated` for `FromUnixtimeFunc::new`. The description 
argues the convention (`now` and the five `to_timestamp*` factories already 
take `&ConfigOptions`), which is correct, but a reviewer must not discover the 
red bot on their own.
   
   ### 4. The `expr_fn` doc string keeps the wrong text that commit `bda12f1` 
fixed elsewhere.
   
   `datafusion/functions/src/datetime/mod.rs:82` still reads:
   
   ```rust
   from_unixtime,
   "converts an integer to RFC3339 timestamp format string",
   @config unixtime
   ```
   
   That is the same claim the `user_doc` change removed. A one-line follow-up.
   
   ### 5. Nit: the `#[deprecated]` attribute sits above the `///` block on 
`new()`.
   
   Convention in this repo puts the doc comment first and the attribute after 
it.
   
   ---
   
   ## What I verified, and how
   
   ### The `86400` over-bound is safe, with margin
   
   I enumerated all 598 IANA zones and took the UTC offset at year 1 (the local 
mean time era, before any transition) and at year 9999 (the POSIX footer rule). 
The two extremes are:
   
   - `Asia/Manila`, LMT `-15:56:08` = `-57368` s
   - `America/Metlakatla`, LMT `+15:13:42` = `+54822` s
   
   Both are inside `86400`, so the fast path leaves about 29000 s of headroom 
for named zones. I then confirmed `chrono-tz` agrees with that data at the 
exact boundary:
   
   ```sql
   SELECT from_unixtime(-8334601171432, 'Asia/Manila');  -- 
-262143-01-01T00:00:00-15:56
   SELECT from_unixtime(-8334601171433, 'Asia/Manila');  -- Execution error, as 
intended
   ```
   
   Fixed offsets are the tighter case. `+23:59` is `86340` s, which passes:
   
   ```sql
   SET datafusion.execution.time_zone = '+23:59';
   SELECT from_unixtime(8210266790459);  -- +262142-12-31T23:59:59+23:59
   ```
   
   `+24:00` and `+99:00` never reach the guard, because Arrow's `Tz` parser 
rejects them first (`Invalid timezone "+24:00": failed to parse timezone`). So 
`86400` is a true over-bound for every zone the engine can hold.
   
   ### The guard's boundary is exact, and the tests are real
   
   The counterfactual in the description is "removing the bounds check makes 
all five of them fail". I did not take that on trust. I ran the same values 
through `arrow_cast`, which is the unguarded path, in the same binary:
   
   ```sql
   SELECT arrow_cast(8210266844399, 'Timestamp(Second, Some("Asia/Tokyo"))');  
-- renders
   SELECT arrow_cast(8210266844400, 'Timestamp(Second, Some("Asia/Tokyo"))');  
-- panics
   ```
   
   The guard errors at exactly `8210266844400` and passes `8210266844399`. So 
the bound is neither loose nor tight by one second.
   
   On the "do the tests render?" question: yes. The unit tests call 
`display()`, which builds an `arrow::util::display::ArrayFormatter`. The 
sqllogictest cases use `query P`, which renders the value. A test that only 
computed the value would prove nothing here, and neither kind of test does that.
   
   ### The two-argument form is unchanged
   
   I ran 140 pairs — 20 values by 7 zones — through both binaries and diffed 
the output. The zones include `Australia/Lord_Howe` (a 30-minute DST step) and 
`Pacific/Chatham` (`+12:45`). The values include `0`, `±1`, the `NaiveDateTime` 
extremes, and 16 random values across `±4e12`. Result: byte-identical.
   
   ### The default (unset) session zone is byte-identical to `main`
   
   Eleven probes, including the error paths, all byte-identical:
   
   | Probe | Result |
   | --- | --- |
   | `arrow_typeof` + value, scalar | identical |
   | `NULL` input | identical |
   | Two-argument form | identical |
   | Both `NaiveDateTime` extremes | identical |
   | Array (not constant folded) input | identical |
   | `to_unixtime(from_unixtime(...))` | identical |
   | Empty timezone string `''` | identical |
   | Invalid zone `'Not/AZone'` | identical |
   | Wrong argument type | identical |
   | Equality against an explicit `'UTC'` | identical |
   | `arrow_cast`-wrapped input | identical |
   
   ### The documentation examples reproduce exactly
   
   Both blocks in `scalar_functions.md` match the real output character for 
character, offsets included.
   
   ### The interaction with #25175 holds
   
   I replayed SECTION 10's two `from_unixtime` assertions from 
https://github.com/apache/datafusion/pull/25175 on this branch, with the 
session zone unset:
   
   ```
   Timestamp(s) 2024-07-01T00:00:00
   Timestamp(s, "America/Denver") 2024-06-30T18:00:00-06:00
   ```
   
   Both still pass. Only the comment above them goes stale, exactly as #25175 
says.
   
   ### The guard costs nothing measurable
   
   `SELECT count(from_unixtime(v)) FROM generate_series(1, 100000000) t(v)`, 
three runs each, `ci` profile:
   
   - session zone unset (guard short-circuits): 3.95 s, 3.65 s, 3.07 s
   - session zone `America/Denver` (guard runs `min` and `max`): 3.09 s, 3.78 
s, 3.33 s
   
   The two extra kernel passes sit inside the run-to-run noise. The "the common 
case is free" claim holds at this scale.
   
   ### Local checks
   
   - `cargo test -p datafusion-functions --lib datetime::from_unixtime` — 10 
tests pass
   - `cargo test -p datafusion-sqllogictest --test sqllogictests` — 506 files 
pass
   - `cargo clippy -p datafusion-functions -p datafusion-sql --all-targets -- 
-D warnings` — clean
   - `cargo fmt --all --check` — clean
   - `./ci/scripts/doc_prettier_check.sh` — clean
   
   ---
   
   ## What I did not verify
   
   - Serialization. `FromUnixtimeFunc` now carries state, so a plan that a 
session with a zone builds, and a session without a zone decodes, resolves the 
function by name and picks up the second session's zone. `now` and 
`to_timestamp*` behave the same way on `main`, so this is not new, but I did 
not run a proto round trip.
   - Behaviour across a `SET` inside a prepared statement or a view.
   - Any engine other than `datafusion-cli`.
   


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