Skip to content

fix(sql): return correct precision from arg_max/arg_min on TIMESTAMP_NS columns#7078

Merged
bluestreak01 merged 4 commits into
masterfrom
fix-arg-max-min-nano-timestamp
May 10, 2026
Merged

fix(sql): return correct precision from arg_max/arg_min on TIMESTAMP_NS columns#7078
bluestreak01 merged 4 commits into
masterfrom
fix-arg-max-min-nano-timestamp

Conversation

@bluestreak01
Copy link
Copy Markdown
Member

Fixes #7076.

Summary

When arg_max or arg_min was applied to a TIMESTAMP_NS column, the
returned value was rendered with microsecond precision, producing dates
thousands of years in the future (e.g. 54977-04-25T06:29:47.654321Z
instead of 2023-01-03T12:34:56.987654321Z). The underlying long was a
nanosecond value but the function reported its result type as
TIMESTAMP (microseconds), so the formatter scaled it incorrectly.

Root cause

The six arg_max/arg_min variants whose first argument is a
TIMESTAMP hardcoded super(ColumnType.TIMESTAMP) in the function
constructor:

  • arg_max(ND), arg_max(NL), arg_max(NZ)
  • arg_min(ND), arg_min(NL), arg_min(NZ)

Comparable functions such as max(N) and first(N) already derive the
type from the input via ColumnType.getTimestampType(args.getQuick(0).getType()).

Change

Each affected function now accepts the timestamp type as a constructor
parameter. The factory passes
ColumnType.getTimestampType(valueArg.getType()), mirroring the
existing pattern in MaxTimestampGroupByFunctionFactory and
FirstTimestampGroupByFunctionFactory. The internal storage layout is
unchanged (long); only the reported column type and resulting display
formatting differ.

There is no behavioural change for TIMESTAMP (micros) inputs. There
are no performance implications: the type is resolved once at
factory time.

Test plan

  • 18 new test methods spread across the six existing
    Arg{Max,Min}Timestamp{Double,Long,Uuid}GroupByFunctionFactoryTest
    classes, each covering:
    • end-to-end correctness with a timestamp_ns value column,
      asserting the full nine-digit nano string
    • typeOf(arg_max(...)) returns TIMESTAMP_NS
    • GROUP BY over a timestamp_ns value column
  • ArgMaxTimestampDoubleGroupByFunctionFactoryTest additionally
    exercises the parallel worker-pool merge path with typeOf assertion
  • All 317 arg_max/arg_min factory tests pass; all 220 timestamp
    group-by factory tests pass
  • Reverted one constructor to confirm the new tests catch the bug
    (off-by-1000 timestamp display and wrong reported type)

🤖 Generated with Claude Code

Fixes #7076.

The six arg_max/arg_min variants whose first argument is a TIMESTAMP
hardcoded their result type to ColumnType.TIMESTAMP (microseconds) in
the function constructor. When the input was a TIMESTAMP_NS column, the
underlying long was a nanosecond value but the output formatter treated
it as microseconds, producing dates thousands of years in the future.

This change makes each function accept the timestamp type as a
constructor parameter. The factory derives it from the value argument
via ColumnType.getTimestampType(), the same pattern used by
MaxTimestampGroupByFunction and FirstTimestampGroupByFunction. The
result column now reports TIMESTAMP_NS when the input is TIMESTAMP_NS
and TIMESTAMP otherwise.

Affected variants: arg_max(ND), arg_max(NL), arg_max(NZ), arg_min(ND),
arg_min(NL), arg_min(NZ).

Each of the six test classes gains three new methods covering: end-to-end
correctness with a timestamp_ns column, the typeOf() of the result, and
a GROUP BY case. The Double variant also gains a parallel test that
exercises the worker-pool merge path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bluestreak01 bluestreak01 added Bug Incorrect or unexpected behavior SQL Issues or changes relating to SQL execution labels May 8, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5fda5a14-55e6-41db-b280-26fa345a316a

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
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-arg-max-min-nano-timestamp

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Wrap the new TIMESTAMP_NS test methods in assertMemoryLeak() so any
native allocations leaked during a regression are caught immediately.
Switch the simple cases from assertSql() to assertQueryNoLeakCheck(),
which additionally validates supportsRandomAccess and expectSize on
the resulting factory.

Replace the parallel test's self-comparing assertSqlCursors(sql, sql)
- which only catches non-determinism - with a deterministic 100k-row
dataset and an exact (sym, type, max-value) row assertion. The
expected nanosecond values would shift by 1000x under any wrong-
precision regression, so this catches the original bug end to end
through the WorkerPool merge path.

Restore the trailing whitespace on the empty line in the
testArgMaxWithNullValue text block; the IntelliJ formatter step
in CI rewrites it back, so leaving it stripped breaks the
"git diff --exit-code" lint check.
Copy link
Copy Markdown
Contributor

@jovfer jovfer left a comment

Choose a reason for hiding this comment

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

LGTM: visual check + run new tests on master and got fails as expected.

@mtopolnik
Copy link
Copy Markdown
Contributor

[PR Coverage check]

😍 pass : 18 / 18 (100.00%)

file detail

path covered line new line coverage
🔵 io/questdb/griffin/engine/functions/groupby/ArgMinTimestampUuidGroupByFunctionFactory.java 2 2 100.00%
🔵 io/questdb/griffin/engine/functions/groupby/ArgMaxTimestampDoubleGroupByFunction.java 1 1 100.00%
🔵 io/questdb/griffin/engine/functions/groupby/ArgMaxTimestampLongGroupByFunctionFactory.java 2 2 100.00%
🔵 io/questdb/griffin/engine/functions/groupby/ArgMaxTimestampUuidGroupByFunctionFactory.java 2 2 100.00%
🔵 io/questdb/griffin/engine/functions/groupby/ArgMaxTimestampDoubleGroupByFunctionFactory.java 2 2 100.00%
🔵 io/questdb/griffin/engine/functions/groupby/ArgMinTimestampDoubleGroupByFunctionFactory.java 2 2 100.00%
🔵 io/questdb/griffin/engine/functions/groupby/ArgMinTimestampLongGroupByFunctionFactory.java 2 2 100.00%
🔵 io/questdb/griffin/engine/functions/groupby/ArgMinTimestampLongGroupByFunction.java 1 1 100.00%
🔵 io/questdb/griffin/engine/functions/groupby/ArgMaxTimestampLongGroupByFunction.java 1 1 100.00%
🔵 io/questdb/griffin/engine/functions/groupby/ArgMinTimestampUuidGroupByFunction.java 1 1 100.00%
🔵 io/questdb/griffin/engine/functions/groupby/ArgMinTimestampDoubleGroupByFunction.java 1 1 100.00%
🔵 io/questdb/griffin/engine/functions/groupby/ArgMaxTimestampUuidGroupByFunction.java 1 1 100.00%

@bluestreak01 bluestreak01 merged commit 9109faf into master May 10, 2026
51 checks passed
@bluestreak01 bluestreak01 deleted the fix-arg-max-min-nano-timestamp branch May 10, 2026 03:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Incorrect or unexpected behavior SQL Issues or changes relating to SQL execution

Projects

None yet

Development

Successfully merging this pull request may close these issues.

arg_max and arg_min break with nanoseconds

3 participants