Skip to content

fix: translate common incremental-model timestamp idioms - #96

Closed
kvlonge wants to merge 15 commits into
mainfrom
fix/incremental-idioms
Closed

kvlonge wants to merge 15 commits into
mainfrom
fix/incremental-idioms

Conversation

@kvlonge

@kvlonge kvlonge commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Why

Incremental models commonly filter with idioms that SQLQuack refused: a TIMESTAMP_NTZ column compared with a DATE or with CURRENT_TIMESTAMP(), DATEADD/DATEDIFF over CURRENT_TIMESTAMP(), and COALESCE(MAX(ts), '1900-01-01') or a '9999-12-31' sentinel beside a timestamp.

Changes

  • A text literal beside a TIMESTAMP_NTZ branch in COALESCE, NVL, IFF or CASE is read as a timestamp, as Snowflake coerces it.
  • DATEADD and DATEDIFF over TIMESTAMP_LTZ use the UTC wall-clock time and require the session_timezone_utc policy entry.
  • TIMESTAMP_NTZ compared with DATE or TIMESTAMP_LTZ converts the other side to a naive timestamp.
  • A TIMESTAMP_NTZ(9) written as text truncates its digits past the millisecond, as new receipts show: Snowflake never rounds or carries. This removes the timestamp_text_rounding guard and divergence. With CURRENT_TIMESTAMP, that guard made a converted case raise depending on the current time.
  • New recorded cases: text_literal_timestamps.toml, naive_beside_local.toml and timestamp_text_digits.toml.
  • Translation baseline: 7 new statements match. asof_join_date_against_timestamp, current_timestamp_shape and three timestamp-to-text cases move to matching, and timestamp_ltz_column_eq_ntz becomes a declared divergence. Nothing regressed.

Verification

  • Recorded replay: 8,477 cases, 0 failed, 0 pending.
  • The translation measure holds the new baseline with no regressions.
  • make check-merge runs on the exact head before landing.
  • Hygiene scan clean.

COALESCE(MAX(updated_at), '1900-01-01') is the incremental high-water
mark idiom. A string literal beside a TIMESTAMP_NTZ(9) branch in
COALESCE, IFNULL, NVL, IFF or CASE is now read as the timestamp it
spells, checked when converting: text outside DuckDB's nanosecond range
or in an unproven form still refuses.
…side dates

Staging models filter with occurred_at >= DATEADD(day, -3,
CURRENT_TIMESTAMP()) and BETWEEN DATEADD(day, -7, CURRENT_DATE()) AND
CURRENT_DATE(). DATEADD and DATEDIFF of a TIMESTAMP_LTZ now compute on
its UTC wall time, relying on session_timezone_utc. A TIMESTAMP_NTZ
compared with a DATE reads the DATE as its midnight, and compared with a
TIMESTAMP_LTZ reads it at its UTC wall time.
Snowflake results for the five cases in cases/conditional/text_literal_timestamps.toml and cases/temporal/naive_beside_local.toml at 8b779bd.
…d with a naive timestamp

The comparison now converts, reading the TIMESTAMP_LTZ at its UTC wall
time, so the case's nanosecond value raises the declared guard like the
other zoned comparisons of that column.
Five new recorded statements now match, two existing cases move from
refused to matching, and the LTZ-equals-NTZ comparison becomes a declared
divergence. Nothing regressed.
Snowflake results for the two cases in cases/temporal/timestamp_text_digits.toml at 0d86922.
…iting it as text

The recorded receipts show Snowflake truncates TIMESTAMP_NTZ(9) text to
milliseconds and never rounds or carries, through TO_VARCHAR and casts
alike. The runtime guard that raised where truncating and rounding
differ, and its timestamp_text_rounding divergence, are removed; the
cases that expected it now compare values.
Three timestamp-to-text cases that declared timestamp_text_rounding now
match, and the two new receipt cases match. Nothing regressed.

kvlonge commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

Gate (D94) on 1f78b8f, which is 8190cd7 merged with main 0f0a26a:

  • make check-merge RECORDER=0: exit 0
  • make verify-recorded: 8,477 cases, 6,792 passed, 992 diverged as expected, 657 refused, 36 typed, 0 pending, 0 unproven, 0 failed; 220 of 220 equivalences and rules verified
  • syntax coverage against the target: 0 regressions, baseline holds
  • hygiene scan: clean
    GitHub Actions is unavailable (billing), so this local gate is the evidence.

kvlonge commented Nov 8, 2026

Copy link
Copy Markdown
Contributor Author

Closing: SQLQuack lands through local-gate trains, not PR merges (D94), so open PRs only add clutter (D628/D630). The branch is kept. Any work here that isn't on main yet is tracked by the train owner and lands through a train.

@kvlonge kvlonge closed this Nov 8, 2026
@kvlonge
kvlonge deleted the fix/incremental-idioms branch November 9, 2026 21:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant