test(promql): cover the multi-value timestamp refusal (#9886)

I hereby agree to the terms of the [GreptimeDB
CLA](https://github.com/GreptimeTeam/.github/blob/main/CLA.md).

## Refer to a related PR or issue link (optional)

Test-only follow-up to #9882. Merge #9882 into `narthex` first, then
merge this PR. The PR base remains `feat/narthex-instant-at` until its
parent lands.

## What's changed and what's your intention?

Add regression coverage for the existing native multi-field
`timestamp()` refusal. The net change against the refreshed #9882 is
exactly **one file, `src/query/src/promql/native_compile/tests.rs`, with
43 added lines and no production changes**.

- `timestamp(x)` over two fields reaches the raw-selector guard;
`timestamp(abs(x))` reaches the evaluated-vector guard. Both must return
`NotImplemented` naming `timestamp() over a multi-value input` as the
reason.
- The same two expressions over one field must compile. These positive
controls establish that the refusal is about field count rather than
blanket rejection of timestamp expressions.
- A small local multi-field schema fixture leaves the existing
single-field fixture unchanged.

The old branch history was not mechanically replayed: only this final
test delta is retained atop the new parent. Production refusal, public
single-field native timestamp admission and the paired public-planner
multi-field fallback test belong to #9882. This PR does not implement
multi-field timestamp results or add fallback machinery.

### Verification

Verified clean final HEAD
**`9a702209747646a68a67fc09a3d44a3c30cfc5cf`**, directly atop #9882
**`24e214b7d4b3d277ce04a7e40d3aaf3d7673c70a`** (rebased onto
`narthex=aa30968d43b3`, including #9936).

- `make fmt-check` and the exact-parent `git diff --check`: passed.
- `cargo check --locked -p query -p promql --all-targets`: passed.
- `cargo nextest run --locked -p query -p promql --retries 0
--no-fail-fast`: **1990 passed, 2 skipped**.
- `cargo clippy --locked -p query -p promql --all-targets --all-features
-- -D warnings`: passed.

Old `06695dcf4a5` CI results are not attributed to this rewritten HEAD.
The refreshed Checks, Rust CI and Integration CI results for this exact
immutable HEAD are required before merge; this PR still follows #9882.

The 43 added test lines and their context were compared with the
original `3e80f822595..93ad42b76d5` test-only delta. They are identical;
only Git blob IDs and hunk line numbers move because #9936 changed the
surrounding baseline. Parent standalone Native/Complete Aggregate
verification is reported in #9882 rather than attributed to this
test-only change.

## PR Checklist

- [x] I have written the necessary rustdoc comments.
- [x] I have added the necessary unit tests and integration tests.
- [ ] This PR requires documentation updates.
- [x] API changes are backward compatible.
- [x] Schema or data changes are backward compatible.
- [ ] This PR needs to be backported to release branches, and I have
added the `backport-<target>` labels (e.g. `backport-v1.3` targets
`release/v1.3`).

This test-only change alters no API, schema, stored data or wire format,
and requires no release backport.

Refreshed CI runs for this published immutable HEAD are pending:
[Checks](https://github.com/GreptimeTeam/greptimedb/actions/runs/40266409294),
[Rust
CI](https://github.com/GreptimeTeam/greptimedb/actions/runs/40266409205),
[Integration
CI](https://github.com/GreptimeTeam/greptimedb/actions/runs/40266409248).
No merge is performed until these new-HEAD checks pass.
This commit is contained in:
discord9
2026-10-26 12:43:46 +08:00
committed by GitHub
@@ -73,6 +73,19 @@ fn raw_schema() -> Arc<SeriesSchema> {
})
}
/// The same layout with `count` fields, for the shapes whose answer depends on how many a scan holds.
fn raw_schema_with_fields(count: usize) -> Arc<SeriesSchema> {
Arc::new(SeriesSchema {
fields: (0..count)
.map(|index| FieldSpec {
name: format!("value_{index}").into(),
})
.collect::<Vec<_>>()
.into(),
eval_grid: None,
})
}
/// A statement over 100 seconds in 5-second steps, which is 21 grid points.
fn eval_stmt(query: &str) -> EvalStmt {
eval_stmt_over(query, Duration::from_secs(100))
@@ -4537,6 +4550,36 @@ fn an_invalid_label_replace_expression_fails_the_statement() {
assert!(matches!(error, DataFusionError::Plan(_)), "{error}");
}
/// `timestamp` reports one value per input field, and the native node reports the time of the sample
/// each field was selected from. A table with several data fields would need that node to say which
/// field's sample it means, so the compiler declines the shape instead of answering with one
/// field's timestamps under every field's name.
#[test]
fn timestamp_multi_field_input_is_not_supported_natively() {
for query in ["timestamp(x)", "timestamp(abs(x))"] {
let error = compile(
&eval_stmt(query),
raw_schema_with_fields(2),
SeriesOrdering::KeyThenTime,
)
.unwrap_err();
assert!(
matches!(&error, DataFusionError::NotImplemented(_)),
"`{query}` is a shape this compiler declines: {error}"
);
assert!(
error
.to_string()
.contains("timestamp() over a multi-value input"),
"`{query}` did not name the field count as the reason: {error}"
);
}
// One field is the shape the node does answer, so the refusal is about the field count.
for query in ["timestamp(x)", "timestamp(abs(x))"] {
compile(&eval_stmt(query), raw_schema(), SeriesOrdering::KeyThenTime)
.unwrap_or_else(|error| panic!("`{query}` over one field was refused: {error}"));
}
}
#[test]
fn selector_requirements_capture_bounds_and_source_occurrence() {
let compiled = compile_selectors(