Skip to content

Rust panic on pushed TIMESTAMPTZ filter in read_vortex (jiff: value for seconds out of bounds) #9396

Description

@moshap-firebolt

Summary

Any query that pushes a filter on a TIMESTAMP WITH TIME ZONE (timestamptz) column into read_vortex panics and aborts the process (a Rust panic across the FFI boundary, not a catchable DuckDB error). Plain TIMESTAMP filters and unfiltered scans of the same column work fine.

This blocks Vortex-backed DuckLake in practice (every table with a timestamptz column becomes unqueryable once predicate pushdown kicks in), and blocks timestamptz predicate pushdown generally.

Minimal reproduction (stock extension, no DuckLake, no patches)

-- write a vortex file with a timestamptz column
COPY (SELECT i AS id, TIMESTAMPTZ '2024-01-01' + to_seconds(i) AS ts_tz
      FROM range(100000) t(i))
  TO 'tz.vortex' (FORMAT vortex);

-- read it back with a pushed timestamptz filter -> panic
SELECT count(*) FROM read_vortex('tz.vortex')
WHERE ts_tz >= TIMESTAMPTZ '2024-01-01 06:00:00';

Actual behavior

thread '<unnamed>' panicked at .../jiff-0.2.35/src/span.rs:830:35:
value for seconds is out of bounds: parameter 'seconds' is not in the
  required range of -631107417600..=631107417600
fatal runtime error: failed to initiate panic, error 5, aborting

Controls (isolates it to the pushed timestamptz filter)

case result
TIMESTAMPTZ column with pushed filter panic
same data as plain TIMESTAMP, with filter works
TIMESTAMPTZ column, no filter (SELECT count(*)) works

Likely root cause

In vortex-duckdb/src/convert/scalar.rs, the TemporalMetadata::Timestamp branch handles the tz case differently from the non-tz case:

if let Some(tz) = tz.as_ref() {            // TIMESTAMP_TZ
    ...
    return Ok(Value::new_timestamp_tz(value()?));   // <-- no TimeUnit passed
}
match unit {                               // plain TIMESTAMP
    TimeUnit::Microseconds => Value::new_timestamp_us(value()?),  // <-- unit-tagged
    ...
}

DuckDB's TIMESTAMP_TZ value is microseconds since epoch (and convert/dtype.rs correctly maps the type to Timestamp::new_with_tz(TimeUnit::Microseconds, "UTC", ...)). But the scalar branch calls new_timestamp_tz(value) without the microsecond unit, so the raw µs count (e.g. '2024-01-01 06:00:00' ~ 1.704e15) is later interpreted as seconds — overflowing jiff's +/-631_107_417_600s range and panicking. The non-tz path is correct because it tags the unit (new_timestamp_us).

Suggested fix: construct the tz value with the microsecond unit (mirror the non-tz branch), and add a filter-pushdown test over a timestamptz column.

Separately: scalar conversion for pushdown should ideally return a VortexError (→ fall back to no-pushdown) rather than panic, so a bad value can't abort the host process.

Environment

  • vortex rev 765489149270c9f2e82e742fe84d3f5575672f9d, jiff 0.2.35
  • duckdb-vortex extension, DuckDB v0.0.1 build, linux/amd64
  • Reproduced with the stock prebuilt duckdb+vortex shell (no DuckLake, no local patches)

Metadata

Metadata

Assignees

Labels

bugA bug issueext/duckdbRelates to the DuckDB integration

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions