Skip to content

Read integer time columns in their declared unit in temporal queries - #205

Open
Vedaant-J wants to merge 11 commits into
mainfrom
vedaant/temporal-int-time-unit
Open

Vedaant-J wants to merge 11 commits into
mainfrom
vedaant/temporal-int-time-unit

Conversation

@Vedaant-J

@Vedaant-J Vedaant-J commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Temporal queries (SamplerEngine.query_temporal and query_temporal_lengths) read an integer time column as epoch seconds, even when the graph config declares it in another unit (timestamp_unit: millis, micros, nanos) or as a date format (timestamp_format, e.g. %Y%m%d). Ingestion honours those hints, including when it writes the edge sort keys, so the two halves of a query disagreed.

Example: a seconds clock, an int64 event_time declared as milliseconds, and an edge sort key on that column. For a window holding two events, query_temporal_lengths returned [2] (from the sort key) while query_temporal returned no rows (the rowpack check compared raw milliseconds with a seconds cutoff). Without the sort key both returned nothing, and returned timestamps were 1000 times too large.

The fix:

  • When SamplerEngine opens an index, it reads the hints of integer columns declared with a time unit or format from the persisted diskgraph_config.json. Only those columns' time fields are parsed strictly, so unrelated config fields from another version are ignored. Indexes without the file keep reading epoch seconds; a file that cannot be read makes temporal queries fail with DiskgraphConfigError (sampling is unaffected).
  • The temporal query decodes those columns through the same RowAccess timestamp path ingestion uses (bp_col_stats::row_access_hints, RowAccess::get_timestamp). This covers the window filter, the rowpack fallback in lengths, sort order, returned timestamps and the recency reduction.
  • Group stages carry the hint forward for keys and for min, max and mode outputs, so recency after a group reads them correctly too. Those reductions and projected columns still return the stored values.

Breaking changes: none. Results change only for integer time columns declared in a unit other than seconds or with a format, which were wrong before.

Not changed here: the sort key window on index clocks other than seconds is also unchanged; #165 covers that.

This is independent of #165, which rewrites much of temporal_query.rs for non-second index clocks. The diff here is kept small: a hint lookup plus one decoding helper used at the three places that read time. If both land, whichever merges second rebases; the conflicts are limited to those call sites.

Change map

#205 (comment)

Testing Done

  • New test_temporal_int_time_units.py:
    • int64 and uint64 time in seconds, millis, micros, nanos and %Y%m%d, each with and without the column as the sort key. It asserts the retrieved rows, the lengths, which path the lengths used, the returned timestamps and recency.
    • millisecond values 400 ms either side of both window bounds, on both paths, rounding up as ingestion does.
    • recency over a hinted group key, over one and two stages of max, and after a key is overwritten by a count; max returns the stored value.
    • an unreadable config fails the query; unknown config fields outside integer time columns are ignored.
    • On main every case with a non-second unit or format fails; all pass with the fix.
  • Existing temporal and clock suites (test_temporal_query_shapes.py, test_temporal_sequence_limit.py, test_temporal_tie_order.py, test_index_clocks.py) pass, 389 tests including the new ones.
  • cargo test -p diskgraph-pybind --lib temporal_query and cargo clippy -p diskgraph-pybind --all-targets -- -D warnings pass.

Hotfix Context

Not a hotfix or backport.

Checklist

  • I listed all the breaking changes introduced by the PR: none.
  • I made sure to update examples and documentation with my changes: diskgraph-pybind/README.md states how integer time columns are read.
  • I have added and modified unit tests required to cover these changes.

🤖 Generated with Claude Code

Vedaant-J and others added 4 commits November 4, 2026 22:11
An integer time column ingested with a timestamp_unit or timestamp_format
was read as epoch seconds by the temporal query, while its edge sort key
used the declared unit. Decode it with the same RowAccess path ingestion
uses, for the window filter, lengths, returned timestamps and recency.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
recency over a group key, or over a min, max or mode of a hinted integer
column, read the raw integer as seconds after a group stage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@copy-pr-bot

copy-pr-bot Bot commented Nov 4, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@Vedaant-J
Vedaant-J temporarily deployed to gha-kumo-workflow-tests November 4, 2026 22:25 — with GitHub Actions Inactive
@Vedaant-J

Vedaant-J commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor Author

Change map

Observed failure: an integer time column declared with timestamp_unit (millis, micros, nanos) or timestamp_format was read as epoch seconds by query_temporal and query_temporal_lengths. Its edge sort key used the declared unit, so lengths and retrieval disagreed ([2] against no rows), and returned timestamps were 1000 times too large.

Existing behavior: ingestion and sort keys decode these columns with RowAccess::timestamp_in. The temporal query decoded rowpack time values with scalar_at, which knows only Arrow timestamp and date types.

Smallest sufficient change: load the column hints from the persisted graph config when SamplerEngine opens (parsing only integer columns' time fields; an unreadable file fails temporal queries instead of silently reading seconds) and decode hinted integer columns with the same RowAccess path wherever the query reads time: window filter, lengths fallback, returned timestamps and recency (carried through group stages).

Focused verification: new tests over five units/formats on both the sort key and rowpack paths, sub-second bounds, and grouped recency; they fail on main and pass here. Existing temporal and clock suites, the temporal_query Rust tests and clippy pass.

Stopping condition: both paths agree with ingestion for hinted integer columns on a seconds clock. Non-second index clocks (#165) and indexes without a persisted config are unchanged.

File Function / scope What changed Why
packages/diskgraph/diskgraph-pybind/src/temporal_query.rs time_hints, epoch_seconds, materialize_frame, count_frame, reduce, group_rows, group_output_hints, aggregate_result, execute, execute_lengths Load hints of integer time columns (lenient except for those columns; unreadable config is an error) and decode them through RowAccess; pass hints to the time filter, lengths fallback, returned timestamps and recency, and carry them through group stages. Read integer times in the unit ingestion and the sort keys used.
packages/diskgraph/diskgraph-pybind/src/lib.rs SamplerEngine::new, query_temporal, query_temporal_lengths Hold the hints built from reader.time_columns(), or the config error, at open and pass them to both queries. Read the config once per engine; report a bad config only from temporal queries.
packages/diskgraph/diskgraph-index/src/graph.rs GraphIndexReader (time_columns, load_graph_config, parse_time_columns) Keep the raw columns that declare a timestamp_unit or timestamp_format from the existing config pass; an unreadable file is kept as an error. Parse diskgraph_config.json once at open.
packages/diskgraph/diskgraph-pybind/src/bp_col_stats.rs row_access_hints Make it crate-visible. Reuse the existing column-to-hint conversion.
packages/diskgraph/diskgraph-pybind/README.md Temporal query semantics State how integer time columns and recency are read, and that an unreadable config fails the query. Document the contract.
packages/diskgraph/diskgraph-pybind/python/tests/test_temporal_int_time_units.py New tests Cover signed and unsigned units, %Y%m%d, both paths, sub-second bounds, grouped recency, and unreadable or newer configs. Pin agreement between retrieval, lengths and ingestion.

@coderabbitai

coderabbitai Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Explain Change →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

Run configuration

Commits

Reviewing files that changed from the base of the PR and between 5ce51c6 and c624a90.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
Walkthrough

Temporal queries now use persisted timestamp units or formats to interpret integer time columns during frame selection and bounded length checks. The same hints apply to recency reductions and propagate through group keys and min, max, and mode results. Missing configuration yields empty hints. Configuration read or parse errors cause temporal queries to fail. Tests cover timestamp declarations, storage paths, boundary rounding, grouped reductions, and configuration handling.

Sequence Diagram(s)

sequenceDiagram
  participant GraphIndexReader
  participant SamplerEngine
  participant TemporalQuery
  participant FrameMaterialization
  GraphIndexReader->>SamplerEngine: Provide timestamp declarations or configuration error
  SamplerEngine->>TemporalQuery: Pass time hints to temporal execution
  TemporalQuery->>FrameMaterialization: Decode frame timestamps with column hints
Loading
Merge Risk: Moderate · previous review 5ce51

A timestamp column with an invalid persisted dtype can produce incorrect temporal results. Reject that configuration before merging.

Pre-merge checks: Passed 3

Check name Status Explanation
Change Map Design Review Passed PASS — The supplied change-map summary identifies the observed rowpack-versus-sort-key failure, the existing ingestion timestamp path, the focused fix, verification, and scope boundary. The diff suppo…
Pr Size Passed The pull request changes 6 files. The threshold is 40 files, so the PR passes this check.
Public Resource Access Passed The pull request adds or modifies no external URL, repository location, or package source location. The only resource-like reference added is the local runtime file diskgraph_config.json; it is not …
Full details: Change Map Design Review

Explanation

PASS — The supplied change-map summary identifies the observed rowpack-versus-sort-key failure, the existing ingestion timestamp path, the focused fix, verification, and scope boundary. The diff supports that design: GraphIndexReader reads persisted time-column metadata once; SamplerEngine caches hint errors and passes hints only to temporal queries; temporal_query.rs reuses row_access_hints for frame filtering, lengths, recency, and grouped min/max/mode outputs. The malformed-config path now propagates an error, while absent configs retain legacy behavior. The added tests cover units, formats, signed and unsigned integers, both query paths, grouping, rounding, and config failures. The change stays within the temporal-query and config-reader layers and does not alter unrelated reductions or index-clock behavior.

Full details: Public Resource Access

Explanation

The pull request adds or modifies no external URL, repository location, or package source location. The only resource-like reference added is the local runtime file diskgraph_config.json; it is not an external resource.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 6aafd69 and 46067f7.

Files selected for processing: 5
  • packages/diskgraph/diskgraph-pybind/README.md
  • packages/diskgraph/diskgraph-pybind/python/tests/test_temporal_int_time_units.py
  • packages/diskgraph/diskgraph-pybind/src/bp_col_stats.rs
  • packages/diskgraph/diskgraph-pybind/src/lib.rs
  • packages/diskgraph/diskgraph-pybind/src/temporal_query.rs

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 11 remain after this review.

Falling back to epoch seconds would silently return wrong rows for
hinted integer time columns. Only hinted columns are parsed strictly,
so config fields from another version fail nothing else, and sampling
still opens the index.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Vedaant-J
Vedaant-J temporarily deployed to gha-kumo-workflow-tests November 4, 2026 23:07 — with GitHub Actions Inactive
Vedaant-J and others added 3 commits November 5, 2026 18:09
A recency over a hinted integer column built a new reader for every
anchor and every group. Build one per reduction when the aggregate is
planned and reuse it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SamplerEngine parsed diskgraph_config.json a second time at open for
temporal time hints. The index reader already parses it for semantic
types, so it now also keeps the columns that declare a timestamp_unit
or timestamp_format, and an unreadable config still fails temporal
queries only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Duplicate tables and the first reported config error then follow the
file, as before the single parse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Vedaant-J and others added 2 commits November 5, 2026 18:53
This reverts commit d291634. It gave no measurable gain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the seconds clock a timestamp_unit of seconds reads every value
unchanged, yet it sent recency through the RowAccess decoder per anchor:
115 ms against 63 ms on main for 4,096 anchors. Keep hints only for a
format or a finer unit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Vedaant-J
Vedaant-J temporarily deployed to gha-kumo-workflow-tests November 5, 2026 19:29 — with GitHub Actions Inactive

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

Major · Reject an unknown dtype on a timestamp-declared column. · temporal_query.rs:563-580

packages/diskgraph/diskgraph-pybind/src/temporal_query.rs:563-580
Major · Data Integrity & Integration · Quick win

Reject an unknown dtype on a timestamp-declared column.

If ingested_dtype contains an unknown value, this filter skips the column instead of returning a config error. The graph parser has already retained the timestamp declaration. A query can then read that column’s integer values as epoch seconds and return incorrect temporal results. Return a parse error for an invalid dtype; continue to skip valid non-integer dtypes. This is another path to the malformed-config outcome flagged in the previous review. (raw.githubusercontent.com)

Review info

Run configuration

Commits

Reviewing files that changed from the base of the PR and between cdb5d6e and 5ce51c6.

Files selected for processing: 1

  • packages/diskgraph/diskgraph-pybind/src/temporal_query.rs

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 9 remain after this review.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Vedaant-J
Vedaant-J temporarily deployed to gha-kumo-workflow-tests November 6, 2026 01:08 — with GitHub Actions Inactive

This branch was previously deployed

1 inactive deployment
gha-kumo-workflow-tests — c624a902 Deployed Nov 6, 2026 by Vedaant-J via import #1622
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