Skip to content

[AURON #1863] Support the zero-argument Flink UNIX_TIMESTAMP natively - #2465

Merged
Tartarus0zm merged 4 commits into
apache:masterfrom
weiqingy:AURON-1863-0arg
Aug 12, 2026
Merged

[AURON #1863] Support the zero-argument Flink UNIX_TIMESTAMP natively#2465
Tartarus0zm merged 4 commits into
apache:masterfrom
weiqingy:AURON-1863-0arg

Conversation

@weiqingy

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Part of #1863.

#1863 enumerates all three argument forms of UNIX_TIMESTAMP. The 1-argument and 2-argument string-parsing forms landed in #2409 and #2448, and this PR adds the remaining 0-argument form. #1863 stays open after this one merges, for the timezone configuration work tracked as sub-issue #2455.

Rationale for this change

UNIX_TIMESTAMP() with no arguments returns the current epoch seconds. The converter rejected it, so any Calc containing it fell back to Flink wholesale, taking every other expression in that Calc down with it.

Two things were believed to block it. Both turned out to be false, and I stated one of them myself earlier in this issue.

"A zero-argument ext function cannot size its output." The ext-function ABI drops the batch row count, so a 0-argument function appears to have no way to know how many values to return. That is true only for a function returning an Array. The length check in ScalarFunctionExpr::evaluate sits entirely inside the ColumnarValue::Array branch, so a function returning ColumnarValue::Scalar never reaches it, and the projection broadcasts the scalar to the batch width. No operand is needed, and no carrier column either.

"The 0-argument form needs the session time zone." It does not. The generated Flink operator holds a timeZone field, passes it to the 1-argument and 2-argument calls, and emits result$1 = DateTimeUtils.unixTimestamp() with no argument at all. The result is epoch seconds, which is zone-independent.

An approach was proposed on #1863 that works around the first premise: pass any input column to the Rust function purely to convey the batch length, ignore its data, and return that many timestamps. That would have worked, and it was a sound answer to the constraint as I had described it. This PR does not use it, because once the constraint turns out not to exist the carrier column has no job left. The difference is visible in the diff, so it is worth stating plainly: no column is passed, the Rust function reads no length, and the row count comes from the projection broadcasting a scalar. The observable behavior is the same either way.

What changes are included in this PR?

Three commits.

  1. A native zero-argument ext function Flink_UnixTimestampNow returning ColumnarValue::Scalar(Int64). The clock is read as timestamp_millis() / 1000, mirroring Flink's System.currentTimeMillis() / 1000 operator for operator.
  2. The converter admits the 0-argument form and emits that function with an empty operand list. The 0-argument arm sits above the session time zone check, since a zone the native side cannot resolve is no reason to fall back a query that never consults a zone. Class and method javadoc are corrected: they previously described the 0-argument form as falling back and described UNIX_TIMESTAMP as always mapping to Flink_UnixTimestamp with [value, chronoFormat, zoneId].
  3. End-to-end coverage in AuronFlinkCalcITCase.

No protobuf change, no Cargo.toml change, no pom.xml change. It is a separate native function rather than a fourth arity on Flink_UnixTimestamp, whose contract is "parse this string with this format in this zone" and which the 0-argument form shares none of.

Are there any user-facing changes?

Yes, and one of them is a deliberate semantic divergence worth reviewing on its own merits.

A query using UNIX_TIMESTAMP() that previously fell back to Flink now runs natively.

Flink evaluates the niladic form per record. This implementation reads the clock once per call site per evaluation and broadcasts it across the batch, so rows in one batch share a timestamp, and that timestamp is taken when the batch is evaluated rather than when each row arrived. Two UNIX_TIMESTAMP() calls in one projection are read independently, which matches Flink.

How large the skew can get depends on what closes the batch:

Path Bound
Calc with a declared WATERMARK FOR about 205 ms, the default autoWatermarkInterval
Calc with no watermark strategy 8192 rows only, so 8192 / rate seconds. About 82 s at 100 rec/s
Fused Kafka plan the native scan blocks until its buffer fills, default 3000, with no time flush

The unwatermarked case is the common shape for a processing-time query, so this is not a corner case. It was discussed and settled on the issue (#1863 (comment)): per-batch is acceptable, since the zero-argument form is non-idempotent anyway and what users generally want is to know roughly when a record was processed. It is also the finest granularity among comparable engines: StreamFusion stamps PROCTIME() once per operator compile, Flink's own batch mode folds CURRENT_TIMESTAMP to a query-start literal, DataFusion, ClickHouse and Velox are per query, DuckDB is per transaction, and ClickHouse ships nowInBlock() as a documented per-block clock. Flink also declares the niladic form only SqlMonotonicity.INCREASING, which a per-batch constant satisfies.

Gating it conditionally on a declared watermark was considered and rejected: the converter has no access to the ExecNode graph, the predicate misses DataStream-level watermarks, auto-watermark-interval=0 and idle partitions, it keys native support off an unrelated DDL clause, and it only shrinks the skew rather than removing it.

How was this patch tested?

Native unit tests for the scalar return and its broadcast to batch width, and for the value being a plausible epoch second rather than milliseconds.

Converter tests for the emitted node shape, and testUnixTimestampZeroArgSupportedWithFixedOffsetZone, which exists to catch the 0-argument arm being placed below the time zone check. It is a real discriminator: moving the arm below the check fails that test and no other. Two tests that asserted the form falls back are inverted.

An ITCase asserting one clock-bracketed row per input row and no recorded fallback. Both assertions are load-bearing and neither subsumes the other. A zero fallback count establishes that the Calc converted, not that it ran: a native library holding no registry arm for the function still converts at plan time and fails only during execution, where nothing records a fallback, leaving the counter at zero and the result set empty. The row count is what establishes the native plan executed.

The ITCase was checked to be non-vacuous by running rather than by inspection: reverting the converter gate fails it on the fallback count, and running against a library without the registry arm fails it on the row count.

Full module build green, 0 checkstyle violations, spotless clean.

Was this patch authored or co-authored using generative AI tooling?

  • Yes
  • No

Generated-by: Claude Code (Claude Opus 5)

…ction

Flink's UNIX_TIMESTAMP() with no arguments returns the current epoch
seconds. Add Flink_UnixTimestampNow, a zero-argument ext function that
returns it.

The function takes no operand. A ColumnarValue::Scalar return bypasses
the row-count check in ScalarFunctionExpr::evaluate, which applies only
to an Array return, and the projection broadcasts the scalar to the
batch width. So no argument is needed to size the output.

The clock is read as timestamp_millis() / 1000, mirroring Flink's
System.currentTimeMillis() / 1000 operator for operator.

The Java converter still rejects the zero-argument form, so this arm is
dormant until that gate opens, in the same way the arity-3 function
shipped ahead of its converter.
…tively

Admit UNIX_TIMESTAMP() in the converter and emit the native
Flink_UnixTimestampNow ext function with no operands.

The zero-argument arm sits above the session time zone check. Its result
is epoch seconds, which is zone-independent, so a session zone the native
side cannot resolve is no reason to fall back a query that never consults
a zone.

The class and method javadoc described the zero-argument form as falling
back and described UNIX_TIMESTAMP as always mapping to
Flink_UnixTimestamp with [value, chronoFormat, zoneId]. Both are now
scoped to the string-parsing forms, with the wall-clock form named
alongside them.

The clock is read once per evaluation and broadcast across the batch,
whereas Flink evaluates its niladic form per record.
Assert that SELECT UNIX_TIMESTAMP() returns one clock-bracketed row per
input row and that the Calc records no fallback.

Both assertions are load-bearing and neither subsumes the other. A zero
fallback count establishes that the Calc converted, not that it ran: a
native library holding no registry arm for the function still converts at
plan time and fails only during execution, where nothing records a
fallback. The row count is what establishes that the native plan
executed, since a vacuous pass over an empty result set would otherwise
read as success.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds native support for Flink SQL UNIX_TIMESTAMP() (zero-argument form) by introducing a dedicated ext-function that returns epoch seconds as a scalar (broadcast per batch) and updating the Flink Calc converter to emit it, with unit/integration coverage to ensure the Calc both converts and executes natively.

Changes:

  • Added and registered a new native ext-function Flink_UnixTimestampNow (0 args) returning ColumnarValue::Scalar(Int64) epoch seconds.
  • Updated RexCallConverter to accept the 0-arg UNIX_TIMESTAMP() and lower it to Flink_UnixTimestampNow without requiring a session time zone.
  • Expanded planner/unit tests and IT coverage to validate node shape, gating behavior, and native execution (non-fallback + non-empty results).

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
native-engine/datafusion-ext-functions/src/lib.rs Registers the new Flink_UnixTimestampNow ext-function in the native function factory.
native-engine/datafusion-ext-functions/src/flink_datetime.rs Implements flink_unix_timestamp_now and adds unit tests for scalar broadcasting and seconds-level output.
auron-flink-extension/auron-flink-planner/src/main/java/org/apache/auron/flink/table/planner/converter/RexCallConverter.java Lowers 0-arg UNIX_TIMESTAMP() to Flink_UnixTimestampNow and adjusts gating/docs around timezone requirements.
auron-flink-extension/auron-flink-planner/src/test/java/org/apache/flink/table/planner/plan/nodes/exec/stream/StreamExecCalcTest.java Updates the converter contract test to assert native conversion (no fallback) for 0-arg UNIX_TIMESTAMP().
auron-flink-extension/auron-flink-planner/src/test/java/org/apache/auron/flink/table/runtime/AuronFlinkCalcITCase.java Adds an end-to-end test that asserts no fallback and verifies results are non-vacuous and clock-bracketed.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +102 to +104
/// The clock is read as `timestamp_millis() / 1000` to mirror Flink's
/// `System.currentTimeMillis() / 1000` operator for operator, so the two
/// implementations can be compared side by side.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not a duplicated word. operator for operator was meant the way word for word is, that the arithmetic mirrors Flink's term by term. It does read as a typo though, and the sentence says the same thing without it, so I dropped the phrasing in 4b115097:

/// The clock is read as `timestamp_millis() / 1000` so the truncation matches
/// Flink's `System.currentTimeMillis() / 1000` exactly.

Comment on lines +105 to +108
pub fn flink_unix_timestamp_now(_args: &[ColumnarValue]) -> Result<ColumnarValue> {
let secs = Utc::now().timestamp_millis() / 1000;
Ok(ColumnarValue::Scalar(ScalarValue::Int64(Some(secs))))
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added in 4b115097. Nothing rejects a wrong-arity call before this point: planner.rs:1028 builds each ext function's UDF signature from the argument list the plan node already carries, so DataFusion never checks arity itself. The in-body check is the only gate, which is why flink_unix_timestamp checks its own at line 56.

One qualifier on the reasoning. Extra operands can't make the result wrong here, since the value is a clock read and does not depend on the arguments. So this buys early detection of a converter or plumbing mistake, not correctness. arity_mismatch_errors now covers the zero-argument form too.

Flink_UnixTimestampNow ignored any operands it was handed. Nothing rejects
a wrong-arity call before it: the planner builds each ext function's UDF
signature from the argument list the plan node already carries, so
DataFusion never checks arity, and the in-body check is the only gate.
That is the same reason Flink_UnixTimestamp checks its own arity.

Extra operands cannot make the result wrong here, since the value is a
clock read, so this buys early detection of a converter or plumbing
mistake rather than correctness.

Also drop the "operator for operator" phrasing from the clock doc, which
read as a duplicated word.
Copilot AI review requested due to automatic review settings August 12, 2026 02:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

@weiqingy

Copy link
Copy Markdown
Contributor Author

Hi @Tartarus0zm, could you please help review this PR when you get a chance? Thanks!

@Tartarus0zm
Tartarus0zm self-requested a review August 12, 2026 03:58

@Tartarus0zm Tartarus0zm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@weiqingy thanks for your contribution! LGTM

@Tartarus0zm Tartarus0zm linked an issue Aug 12, 2026 that may be closed by this pull request
@Tartarus0zm
Tartarus0zm merged commit 67cff18 into apache:master Aug 12, 2026
123 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support Native Flink UNIX_TIMESTAMP Function

3 participants