gortiz commented on code in PR #19108:
URL: https://github.com/apache/pinot/pull/19108#discussion_r4063535119
##########
pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/function/NotUdf.java:
##########
@@ -40,7 +40,7 @@ public class NotUdf extends Udf.FromAnnotatedMethod {
public NotUdf()
throws NoSuchMethodException {
- super(LogicalFunctions.class.getMethod("not", boolean.class));
+ super(LogicalFunctions.class.getMethod("not", Boolean.class));
Review Comment:
**Correct fix, and it explains why the whole `UdfTest` class died at
`setUp()`.**
`LogicalFunctions.not` was widened from `not(boolean)` to `@Nullable Boolean
not(@Nullable Boolean)` in #17189 (2025-12-07) for three-valued NULL logic,
while `NotUdf` has carried the primitive lookup since #16258 (2025-07-24). So
`getMethod("not", boolean.class)` throws `NoSuchMethodException` in the
constructor.
Why that is fatal rather than local: `UdfTestFramework.fromServiceLoader`
does `ServiceLoader.load(Udf.class).stream().map(Provider::get)`, so a single
provider whose constructor throws raises `ServiceConfigurationError` and *no*
UDF loads at all — `@BeforeClass` fails and every test in the class is skipped.
I checked the other 11 reflective lookups under `query/runtime/function/`
(`Acos`, `Degrees`, `Year`, `ToDateTime`, `Adler32`, `Array*`, `Uuid*`,
`BytesToUuid`): all still match their target signatures, so `NotUdf` is the
only stale one. The fix is complete.
##########
pinot-query-runtime/src/main/java/org/apache/pinot/query/runtime/function/NotUdf.java:
##########
@@ -40,7 +40,7 @@ public class NotUdf extends Udf.FromAnnotatedMethod {
public NotUdf()
throws NoSuchMethodException {
- super(LogicalFunctions.class.getMethod("not", boolean.class));
+ super(LogicalFunctions.class.getMethod("not", Boolean.class));
Review Comment:
**The CI gap is the part I would not defer** — there is now hard evidence
the snapshots have been maintained by hand for nine months.
Confirmed the claim in the description: the shard includes in
`pinot-integration-tests/pom.xml` are
`**/org/apache/pinot/integration/tests/<LETTER>*Test.java`, a single path
segment, so `tests/udf/UdfTest.java` matches neither `integration-tests-set-1`
nor `set-2`.
What that has cost, concretely: the framework has been unloadable since
#17189 (2025-12-07), yet `all-functions.yaml` was edited in #18171 (2026-04),
#18591 (2026-05), #19012 (2026-07), #19091 (2026-07) and #19223 (2026-08). None
of those could have run the generator, so the snapshot has been hand-maintained
throughout. That is the mechanism that let the `min`/`max` placeholders survive
(see the note on that hunk), and it means these snapshots have provided no
regression signal at all in that window.
```xml
<include>**/org/apache/pinot/integration/tests/udf/*Test.java</include>
```
in `set-2` closes it. If it is deferred for runtime reasons, please link the
follow-up issue rather than leaving it as prose.
Separately and minor: `NotUdf` extends `Udf.FromAnnotatedMethod` but
overrides `getMainName()` and returns `null` from `getScalarFunction()`, so the
reflected `Method` feeds only `getAllNames() -> Set.of("not")`. Its siblings
`AndUdf` and `OrUdf` are plain `Udf` stubs. The class pays the reflection-drift
cost for nothing — worth simplifying, not blocking.
##########
pinot-integration-tests/src/test/resources/udf-test-results/abs.yaml:
##########
@@ -146,7 +146,7 @@ MSE intermediate stage (with null handling):
'(arg0: big_decimal) -> big_decimal':
entries:
negative value_big_decimal:
- actualResult: "3.0"
+ actualResult: "3"
Review Comment:
**This is the one snapshot delta in the PR I could not clear** — it looks
like a real execution-result change, not a stale fixture.
Scope: only the two *MSE intermediate stage* sections moved. The
ingestion-transformer and SSE-projection sections still record `"3.0"`, so
`abs` itself is innocent — `AbsScalarFunction.bigDecimalAbs` is `value.abs()`,
which preserves scale (`new BigDecimal("-3.0").abs().toString()` is `"3.0"`).
What differs is the plan. `IntermediateUdfTestScenario` wraps the argument
in `coalesce(t1.col, t2.col)` across a self-join so the call lands after the
exchange. Something on that path — Calcite DECIMAL type inference on
`COALESCE`, or the MSE BigDecimal ser/de — now drops the scale. That is
user-visible: `CAST(abs(x) AS STRING)` yields `"3"` or `"3.0"` depending only
on whether the expression was pushed to the leaf.
Why it slipped through: the equivalence is `BIG_DECIMAL_AS_DOUBLE`, so the
assertion passes either way. And the window is wide — the last regeneration
that could have been real is #17140 (2025-11-04), before #17189 broke the
framework, so the change landed somewhere in the last ten months.
`UdfTest`'s own javadoc says a changed implementation must be called out as
a behaviour change in the PR and commit message. Could you identify the commit
responsible and confirm whether losing the scale is intended?
##########
pinot-integration-tests/src/test/resources/udf-test-results/abs.yaml:
##########
@@ -267,22 +267,22 @@ MSE intermediate stage (without null handling):
'(arg0: big_decimal) -> big_decimal':
entries:
negative value_big_decimal:
- actualResult: "3.0"
+ actualResult: "3"
Review Comment:
**The companion `.md` snapshot was not committed** — `abs.md` still says
`3.0`.
`generateSnapshots()` writes `<udf>.yaml` **and** `<udf>.md` in the same
loop, so a real regeneration also rewrites `abs.md`. On this branch
`abs.md:102-104` and `129-132` still read `3.0 (String)` for exactly the rows
changed here. So either the `.md` output was left out of the commit, or the
YAML was hand-edited from the test failure message. Please re-run the generator
and `git add` the `.md` files so the two stay consistent.
Separate and pre-existing: `getSnapshotFiles()` only deletes `*.yaml` before
regenerating, so orphan `.md` files accumulate (`asin.md`, `ceil.md`, `chr.md`
… have no matching `.yaml`, and `failWhenSnapshotNotUsed` only inspects
`.yaml`). Worth a cleanup, not in this PR.
##########
pinot-integration-tests/src/test/resources/udf-test-results/abs.yaml:
##########
@@ -267,22 +267,22 @@ MSE intermediate stage (without null handling):
'(arg0: big_decimal) -> big_decimal':
entries:
negative value_big_decimal:
- actualResult: "3.0"
+ actualResult: "3"
equivalence: "BIG_DECIMAL_AS_DOUBLE"
error: null
expectedResult: 3.0
null input_big_decimal:
- actualResult: "0.0"
+ actualResult: "0"
Review Comment:
Same scale loss on the null-default path: with null handling disabled the
NULL input materialises as the BIG_DECIMAL default and now reports `"0"`
instead of `"0.0"`.
Same root cause as the negative/positive rows; flagged separately only
because the default-value path is the one most likely to surface in a user's
`CAST(... AS STRING)` output.
##########
pinot-integration-tests/src/test/resources/udf-test-results/all-functions.yaml:
##########
@@ -34,6 +34,10 @@ acos:
scalar:
"ArgumentCountBasedScalarFunction{org.apache.pinot.common.function.scalar.TrigonometricFunctions.acos}"
transform:
"org.apache.pinot.core.operator.transform.function.TrigonometricTransformFunctions.AcosTransformFunction"
udf: "org.apache.pinot.query.runtime.function.AcosUdf"
+acosh:
+ scalar:
"ArgumentCountBasedScalarFunction{org.apache.pinot.common.function.scalar.TrigonometricFunctions.acosh}"
Review Comment:
**The branch is ~2 months behind master, so this regenerated file is already
stale again by 17 functions.**
The merge base is `a8b207e781` (2026-07-27). I diffed this file against the
live registry on current master (probe below):
```
in this file, absent from the registry: 0 <- nothing invented
value mismatches: 0 <- every value correct
registered but MISSING from this file: 17
bytestouuid, isuuid, touuid, uuidtostring, uuidtobytes,
uuidtimestamp, uuidv4, uuidv7, uuidversion,
jsonextractobject, jsonextractscalarfory, jsonpathdoublefory,
jsonpathlongfory, jsonpathstringfory,
isprivateip, overlay, translate
```
The first two lines are the good news — everything this PR wrote is faithful
to the registry. But those 17 landed after the fork point (#19091 UUID, #19223
Fory and friends), and master's copy of `all-functions.yaml` already lists
them, so this needs regenerating after a rebase or
`failWhenAllFunctionsYamlNotUpdated` will fail on merge.
##########
pinot-integration-tests/src/test/resources/udf-test-results/all-functions.yaml:
##########
@@ -1149,10 +1395,6 @@ mapvalue:
scalar: null
transform:
"org.apache.pinot.core.operator.transform.function.MapValueTransformFunction"
udf: null
-max:
Review Comment:
**Checked this one and it is correct** — `min`/`max` were hand-written
placeholders that the generator has never produced.
I ran the real registry code against `pinot-core`'s test classpath (same
shape as the integration-test env, test-classes included):
```
min scalarKey=false transformKey=false
max scalarKey=false transformKey=false
scalar null-valued keys: [] transform null-valued keys: []
(534 scalar, 134 transform)
```
And an all-null entry is structurally impossible for this generator, at any
commit:
* `FUNCTION_MAP = Map.copyOf(functionMap)` — `Map.copyOf` throws NPE on a
null value, and it has been `Map.copyOf` since #16258.
* `TRANSFORM_FUNCTION_MAP` only ever receives class literals in
`createRegistry()` and a non-null class in `init()`.
* Keys come from `Sets.union(scalarKeys, transformKeys)`, so every key
resolves to a non-null value in at least one map, and `getScalarFunctionId()` /
`getCanonicalName()` on a top-level class never return null.
`min`/`max` are `AggregationFunctionType` entries, not scalar or transform
functions, and no source declares them at either #16258 or #19223. Read with
the file's header — *"a list of UDFs we should create"* — they were typed in by
hand as to-do markers and the generator has been dropping them ever since.
No action needed; one line in the description would save the next reader the
dig.
##########
pinot-integration-tests/src/test/resources/udf-test-results/all-functions.yaml:
##########
@@ -1476,7 +1747,7 @@ sqrt:
transform:
"org.apache.pinot.core.operator.transform.function.SingleParamMathTransformFunction.SqrtTransformFunction"
udf: null
stageid:
- scalar:
"ArgumentCountBasedScalarFunction{org.apache.pinot.query.function.InternalMseFunctions.stageId}"
+ scalar:
"ArgumentCountBasedScalarFunction{org.apache.pinot.common.function.scalar.InternalFunctions.stageId}"
Review Comment:
Verified this move is real, not a generation artifact:
`org.apache.pinot.query.function.InternalMseFunctions` no longer exists
anywhere in the tree, and `stageId`/`workerId` now live in
`pinot-common/.../scalar/InternalFunctions` (lines 108 and 122). Same for
`workerid` further down.
No action — noting it so the delta does not have to be re-investigated at
merge time.
##########
pinot-integration-tests/src/test/resources/udf-test-results/all-functions.yaml:
##########
@@ -51,9 +55,13 @@ agomv:
transform: null
udf: null
and:
- scalar: null
+ scalar:
"ArgumentCountBasedScalarFunction{org.apache.pinot.common.function.scalar.LogicalFunctions.and}"
transform:
"org.apache.pinot.core.operator.transform.function.AndOperatorTransformFunction"
udf: "org.apache.pinot.query.runtime.function.AndUdf"
+appendtostringandreturn:
Review Comment:
**Test-only `@ScalarFunction` fixtures are being baked into the committed
snapshot.**
`MutableStringTestFunction` lives in
`pinot-common/src/test/java/org/apache/pinot/common/function/test/`. Same story
at `counttruebooleans` and `sumtimestampmillis`, both from
`pinot-core/src/test/.../ScalarTransformFunctionWrapperTest`. My local probe
registered all three as well, so this is deterministic rather than a one-off:
`pinot-integration-tests` depends on the `pinot-common` and `pinot-core`
test-jars, and `FunctionRegistry`'s classpath scan picks them up.
Two consequences:
1. The file's stated purpose in its own header — *"a list of UDFs we should
create"* — is now polluted with test fixtures nobody should write a UDF for.
2. Once `UdfTest` runs in CI, `failWhenAllFunctionsYamlNotUpdated` will fail
whenever someone adds an unrelated test-only `@ScalarFunction` anywhere in
`pinot-core` or `pinot-common` tests, which is a baffling failure to land on.
Not introduced by this PR — it falls out of any honest regeneration — but
better filtered at generation time (skip classes whose package is a test
package) than frozen in.
##########
pinot-integration-tests/src/test/resources/udf-test-results/all-functions.yaml:
##########
@@ -51,9 +55,13 @@ agomv:
transform: null
udf: null
and:
- scalar: null
+ scalar:
"ArgumentCountBasedScalarFunction{org.apache.pinot.common.function.scalar.LogicalFunctions.and}"
Review Comment:
Useful cross-check: `and`/`or` going from `scalar: null` to
`LogicalFunctions.and` / `LogicalFunctions.or` is the same #17189 that changed
`not(boolean)` to `not(Boolean)` and broke `NotUdf`. The snapshot is finally
catching up with that PR, which is good evidence the regeneration is faithful
rather than arbitrary.
Worth a line in the description, since it is a registry-visible change
rather than noise.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]