Reapply iterative Visitor refactor (#36852) with SQL-332 fix#36903
Merged
Conversation
`first_value(mz_now()) OVER ()` (and `last_value`, aggregate window functions) returned the wrong result because the query was not recognized as time-dependent: `contains_temporal()` failed to find the `mz_now()` call. The iterative `Visit` traversal introduced in #36852 walks expressions via `VisitChildren::children()`. For `ValueWindowExpr` and `AggregateWindowExpr` the new `children()`/`children_mut()` impls descended *into* the argument expression and yielded its children, rather than yielding the argument expression itself (as `visit_children` does, and as the impl doc comments promise). When the argument is a leaf like `mz_now()` it has no children, so it was never visited and temporal detection missed it. Yield the argument expression itself, matching `visit_children`. https://claude.ai/code/session_017Xe6H3p7ZETe4LW5bqPiWZ
a0b4de0 to
f4ffcfb
Compare
Visitor refactor (#36852) with SQL-332 fix
Contributor
|
Triggered a Nightly subset: https://buildkite.com/materialize/nightly/builds/16708 |
ggevay
approved these changes
Jun 8, 2026
ggevay
left a comment
Contributor
There was a problem hiding this comment.
LGTM if Nightly is green, just minor comments.
| /// (default is to visit all children). | ||
| #[deprecated = "Use `visit_mut_pre_post` instead."] | ||
| fn visit_mut_pre_post_nolimit<F1, F2>(&mut self, pre: &mut F1, post: &mut F2) | ||
| /// It is improtant for safety that `pre` is (a) safe code and (b) returns children only. |
| { | ||
| f(&mut self.args) | ||
| } | ||
|
|
Contributor
There was a problem hiding this comment.
Could the above visit_children, visit_mut_children, try_visit_children, try_visit_mut_children be deleted? Their trait defaults call children/children_mut, which should have the same behavior.
And the same applies for impl VisitChildren<HirScalarExpr> for AggregateWindowExpr {.
(But this doesn't apply to other things, e.g., impl VisitChildren<HirScalarExpr> for WindowExprType {, because there the explicit implementations are saving Vec allocations.)
- Fix "improtant" typo in expr/visit.rs. - Drop the now-redundant explicit `visit_children`/`try_visit_*` impls for `ValueWindowExpr` and `AggregateWindowExpr`; the trait defaults delegate to the (now-correct) `children`/`children_mut`. `WindowExprType` keeps its explicit impls since those avoid Vec allocations. https://claude.ai/code/session_017Xe6H3p7ZETe4LW5bqPiWZ
Member
Author
|
Thanks for the review! Let's try again. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
The iterative, child-based
Visitorrefactor (#36852) was reverted in #36905 because it introduced a regression: SQL-332, wheremz_now()used as a direct argument to a value window function (e.g.first_value,last_value) or an aggregate window function (e.g.max) was no longer recognized as temporal, so the query failed to receive a timestamp near the current time. For example,SELECT first_value(mz_now()) OVER () < '3000-01-01'returnedfalseinstead oftrue.This PR re-lands the refactor with that regression fixed, so it doesn't ship again.
Description
This PR consists of two commits:
Reapply "refactor: Change all
Visitors to be iterative, child-based" (refactor: Change allVisitors to be iterative, child-based #36852) — reverts the revert (Revert "refactor: Change allVisitors to be iterative, child-based" #36905), restoring the iterative traversal.Fix temporal detection (SQL-332) — the root cause of the regression. The iterative
Visittraversals walk expressions via the newVisitChildren::children()/children_mut()iterators. ForValueWindowExprandAggregateWindowExpr, the newchildren()impls descended into the argument expression and yielded its children, rather than yielding the argument expression itself — which is what the canonicalvisit_childrendoes, and what the impl-level doc comments promise ("Yieldsargs; does not descend into it").contains_temporal()runs over this traversal (viavisit_post). When the argument is a leaf likemz_now()(aCallUnmaterializablewith no children), descending skipped it entirely, so themz_now()node was never visited and the query was planned as a constant.The fix yields the argument expression itself via
std::iter::once(...), matchingvisit_children.Verification
Green CI. Added regression tests in
test/sqllogictest/temporal.sltcoveringmz_now()as the direct argument offirst_value,last_value, andmaxwindow functions — each asserts the value is< '3000-01-01', confirming the query receives a current timestamp rather thanu64::MAX.https://claude.ai/code/session_017Xe6H3p7ZETe4LW5bqPiWZ