gortiz opened a new pull request, #19552:
URL: https://github.com/apache/pinot/pull/19552

   ## Problem
   
   `generateArray` resolves only in the single-stage engine, where 
`TransformFunctionFactory` special-cases it by name. The multi-stage engine 
offers the typed `generate{Int,Long,Float,Double}Array` scalars instead, but 
folding a call to any of them throws:
   
   ```
   ClassCastException: class [J cannot be cast to class [Ljava.lang.Object;
     at 
PinotEvaluateLiteralRule.evaluateLiteralOnlyFunction(PinotEvaluateLiteralRule.java:241)
   ```
   
   `PinotEvaluateLiteralRule` reads an array result by casting it to 
`Object[]`, with a special case for `double[]` only, so every other primitive 
array fails. `generateDoubleArray` is the only one of the four usable today.
   
   The generators also mishandle their own arguments. A literal call is 
evaluated on the broker while planning, so these are broker-side failures:
   
   | Call | Today |
   |---|---|
   | `generateLongArray(0, 10, 0)` | `ArithmeticException: / by zero` |
   | `generateLongArray(10, 0, 1)` | `NegativeArraySizeException: -9` |
   | `generateLongArray(0, Long.MAX_VALUE, 1)` | silently empty — the length is 
truncated by an `(int)` cast |
   | `generateLongArray(0, 1000000000, 1)` | 8 GB allocation, no bound of any 
kind |
   | `generateLongArray(7, 7, 1)` | rejected, though a zero span is a valid 
single element sequence |
   
   ## Changes
   
   **`PinotEvaluateLiteralRule`** — read array elements with 
`java.lang.reflect.Array`, which handles primitive and object arrays uniformly, 
replacing the `Object[]` cast and its `double[]` special case. This fixes 
constant folding for any scalar function returning a primitive array, not only 
these.
   
   **`GenerateArrayScalarFunction`** (new) — registers `generateArray` for the 
multi-stage engine as a `PinotScalarFunction`, which can dispatch on argument 
*types*; the method level `@ScalarFunction` annotation dispatches on argument 
*count* alone, so one name cannot cover four element types. The element type is 
the widest of the arguments:
   
   ```sql
   generateArray(0, 6, 2)                                -- ARRAY(0, 2, 4, 6)
   generateArray(1633078800000, 1633080600000, 1800000)  -- ARRAY(...:BIGINT, 
...:BIGINT)
   generateArray(0, 1, 0.5)                              -- ARRAY(0.0E0:DOUBLE, 
...)
   ```
   
   This follows the existing scalar-for-MSE / transform-for-SSE pattern 
(`ArrayLengthScalarFunction`). The single-stage transform function is kept, 
since it infers the element type from literals in a way the scalar registry 
cannot express, but it now delegates to `ArrayFunctions` so the two engines 
cannot disagree on the sequence or on which arguments are rejected.
   
   **`ArrayFunctions`** — validate the increment, bound the length at 100 000 
elements, add `(start, end)` overloads defaulting the increment to `1` (or `-1` 
when counting down), and index each element from `start` instead of 
accumulating, so a floating point sequence does not drift. The length is 
checked on the step count rather than the length so a span of `Long.MAX_VALUE` 
cannot overflow into a small positive size.
   
   ## Why
   
   A dense time grid to `UNNEST` and left join a sparse series onto is how gap 
filling is expressed relationally in the multi-stage engine. Without a series 
generator the grid has to be faked, typically with an unrelated table and 
`ROW_NUMBER`, which needs a table with enough rows and hides the bucket count 
in a `LIMIT` — get it wrong and the result is silently truncated rather than an 
error:
   
   ```sql
   -- before
   WITH someRows AS (SELECT 1 FROM some_other_table LIMIT 6),
        timeBuckets AS (SELECT ROW_NUMBER() OVER () * 1800000 + 1633078800000 - 
1800000 AS ts FROM someRows)
   ...
   
   -- after
   SELECT ts FROM UNNEST(generateArray(1633078800000, 1633089600000, 1800000)) 
AS grid(ts)
   ```
   
   ## Backward compatibility
   
   Single-stage behaviour changes in three ways, all of them fixes: a zero span 
now yields `[start]` instead of throwing; the two argument form now counts down 
when `end < start` instead of erroring; and a bad increment raises 
`IllegalArgumentException` rather than `IllegalStateException`, since 
validation moved into `ArrayFunctions`. The existing test is updated 
accordingly. The length bound is new — a call generating more than 100 000 
elements now fails with a clear message instead of allocating.
   
   ## Testing
   
   | Suite | Tests |
   |---|---|
   | `ResourceBasedQueriesTest`, full suite | 3710, 0 failures |
   | `QueryCompilationTest` + `UnnestSqlPlannerTest` | 257 |
   | `GenerateArrayTransformFunctionTest` + `ArrayFunctionsTest` (pinot-core) | 
52 |
   | `GenerateArrayFunctionsTest` + `ArrayFunctionsTest` (pinot-common) | 14 |
   
   New coverage:
   
   - `GenerateArrayFunctionsTest` — sequence semantics, every rejected argument 
shape, timestamps past 2^53, and the absence of floating point drift.
   - `QueryCompilationTest` — the folded array literal, the element type 
dispatch, and the planning time rejections.
   - `GenerateArray.json` — end to end through `ResourceBasedQueriesTest`, 
including the full gap fill query (grid × keys, `LEFT JOIN`, `LAST_VALUE ... 
IGNORE NULLS`) returning the carried forward rows.
   
   The new end-to-end cases set `ignoreLiteMode` and `ignoreV2Optimizer`: both 
planners reject `UNNEST` outright with `Unexpected relNode type: Uncollect`, 
independently of this change.
   
   ## Note on `all-functions.yaml`
   
   Only the five affected entries are updated. The snapshot cannot be 
regenerated wholesale at the moment: `NotUdf` looks up 
`LogicalFunctions.not(boolean)` while the method takes a `Boolean`, so 
`ServiceLoader.load(Udf.class)` throws and `UdfTest` cannot run at all. The 
file is already stale as a result — 488 entries against 564 registered 
functions, with entries such as `and.scalar` still `null`. The five entries 
here were rendered with the same mapper the generator uses and verified against 
the live registries. The `NotUdf` mismatch looks worth a separate fix.
   


-- 
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]

Reply via email to