Fix node validation error for static pivot after extend+concatenate#4688
Fix node validation error for static pivot after extend+concatenate#4688rafaelbey wants to merge 4 commits intofinos:masterfrom
Conversation
When a static `->pivot()` is the outermost operation in a chain that advanced the tree cursor (e.g. `->extend(window)->concatenate()->pivot()`), plan generation failed with "NODE VALIDATION ERROR: currentTreeNode / root / DOESN'T CONTAIN: root". `processStaticPivot` builds a TdsSelectSqlQuery with a fresh RootJoinTreeNode and hands it to `isolateNonTerminalGroupByQueryWithEmptyGroupingColumns`. When that function's `functionExpressionStack > 1` guard is false (pivot is the outermost function), the else branch swapped `select` without remapping `currentTreeNode` into the new tree -- leaving the cursor pointing at a node that no longer belongs to `select.data`. The fix mirrors the remap pattern used by `isolateTdsSelect`: run `findNode` against the old and new trees, falling back to the new root when the cursor was buried by the isolation boundary. New PCT test `testStaticPivot_AfterExtendConcatenate` reproduces the failure on DuckDB. Stores that do not support pivot at the dialect / binding layer inherit the same expected-failure entry as the existing `testStaticPivot_AfterConcatenate` case.
…tenate Cloud PCT runs revealed that the new test trips different error paths on the listed stores than the existing testStaticPivot_AfterConcatenate: - MemSQL: test actually succeeds -- remove entry. - Spanner, SqlServer: hits "pivot is not supported" (not the CTE path). - Trino: hits "Column ''a__|__newval'' cannot be resolved". - Deephaven: hits "function not supported yet: pivot_...".
Explain in router-and-pure-to-sql.md and expected-failures-howto.md that CTE-related errors (`Common table expression not supported on DB <X>`) only fire in PCT-relational for tests whose Pure expression contains top-level `let` statements. The adapter wraps multi-statement bodies in `eval`, which routes through `processFunctionDefinition` and lifts each top-level `let` into a `CommonTableExpression`. Tests without `let` skip this path and surface store-specific errors directly, so expected-failure messages cannot be copied blindly between sibling tests.
706a6e6 to
56a7963
Compare
Test Results 1 099 files - 18 1 099 suites - 18 3h 50m 26s ⏱️ - 1h 25m 49s Results for commit 474d1e7. ± Comparison against base commit 7b71d58. ♻️ This comment has been updated with latest results. |
|
Context for The helper has multiple call sites (lines 6291, 6315, 6389, 6439, 6457, 6486, 6500, 6524). They split into two shapes:
Three observable options for the remap fallback (when
Precedent. The same pattern as Option A was introduced in #4683 for That PR established the convention "after a tree-replacing isolation step, the cursor lands at the new root if it cannot be remapped." The change in this PR applies the same convention to the second isolation helper for consistency. Alternative architecture. A different shape of fix would push the cursor-update responsibility back to the call sites that build fresh trees (Shape B), e.g. have
Happy to refactor in either direction — flagging the trade-off rather than asserting a preference. |
Per PR review feedback, push the cursor-update responsibility to the call site that owns the freshly-built tree (processStaticPivot) rather than handling it defensively inside the shared isolateNonTerminalGroupByQueryWithEmptyGroupingColumns helper. processStaticPivot now binds the new pivot RootJoinTreeNode to a let, threads it both into the new TdsSelectSqlQuery and into the input SelectWithCursor's currentTreeNode, then calls the helper. The helper's else branch reverts to the original `^$query(select=$select)` form, scoping the change's blast radius to the static pivot path.
7ff6cdb to
474d1e7
Compare
Summary
Fix
NODE VALIDATION ERROR: currentTreeNode / root / DOESN'T CONTAIN: rootraised during plan generation when a static->pivot()is the outermost operation in a chain that advanced the tree cursor, e.g.#TDS#->extend(window)->concatenate(#TDS#)->pivot(...).Root cause
processStaticPivotbuilds a freshTdsSelectSqlQuerywith a brand-newRootJoinTreeNodeand passes it toisolateNonTerminalGroupByQueryWithEmptyGroupingColumns. When$state.functionExpressionStack->size() > 1is false (pivot is the outermost function), the else branch swappedselectwithout remappingcurrentTreeNodeinto the new tree — leaving the cursor pointing at a node that no longer belonged toselect.data.validateNodethen fires becauseallNodes(computed from the new tree) does not contain the old cursor.Fix
Mirror the remap pattern introduced by #4683 in
isolateTdsSelect: runfindNodeagainst the old and new trees, falling back to the new root when the cursor was buried by the isolation boundary.Tests
testStaticPivot_AfterExtendConcatenateincomposition.purereproducing the failure on DuckDB.testStaticPivot_AfterConcatenatecase.Test plan
Test_Relational_DuckDB_RelationFunctions_PCT— 346/346 passing (new test passes empirically)Test_Relational_H2_RelationFunctions_PCT— 346/346 (pivot not supported; expected-failure entry added)Test_JAVA_RelationFunction_PCT— 346/346 (TDS translation unsupported; expected-failure entry added)