Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions compiler/dag/op.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,3 +410,32 @@ func WalkT[T any](v reflect.Value, post func(T) T) {
}
}
}

func WalkTWithError[T any](v reflect.Value, post func(T) (T, error)) error {

@mattnibs mattnibs Aug 26, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nwt I don't like adding this, would be better to have a single WalkT but there are places where optimizer returns errors during analysis. My intuition is that the optimizer should just panic when it encounters errors since these should have already been caught in the semantic pass- stuff like a length 0 sequential, an unexpected source operator, a failed pool lookup, etc.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think your intuition is probably right.

switch v.Kind() {
case reflect.Array, reflect.Slice:
for i := range v.Len() {
if err := WalkTWithError(v.Index(i), post); err != nil {
return err
}
}
case reflect.Interface, reflect.Pointer:
return WalkTWithError(v.Elem(), post)
case reflect.Struct:
for _, field := range v.Fields() {
if err := WalkTWithError(field, post); err != nil {
return err
}
}
}
if v.CanSet() {
if t, ok := v.Interface().(T); ok {
r, err := post(t)
if err != nil {
return err
}
v.Set(reflect.ValueOf(r))
}
}
return nil
}
3 changes: 2 additions & 1 deletion compiler/optimizer/optimizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ func (o *Optimizer) OptimizeDeleter(main *dag.Main, replicas int) error {
}

func (o *Optimizer) optimizeSourcePaths(seq dag.Seq) (dag.Seq, error) {
return walkEntries(seq, func(seq dag.Seq) (dag.Seq, error) {
err := dag.WalkTWithError(reflect.ValueOf(&seq), func(seq dag.Seq) (dag.Seq, error) {
if len(seq) == 0 {
return nil, errors.New("internal error: optimizer encountered empty sequential operator")
}
Expand Down Expand Up @@ -275,6 +275,7 @@ func (o *Optimizer) optimizeSourcePaths(seq dag.Seq) (dag.Seq, error) {
}
return seq, nil
})
return seq, err
}

func (o *Optimizer) SortKeys(seq dag.Seq) ([]order.SortKeys, error) {
Expand Down
14 changes: 14 additions & 0 deletions compiler/ztests/pruner.yaml
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
# Test pruner flips comparator with literal/path
script: |
super -f parquet -o test.parquet -c 'values {v:1}'
echo === Test pruner flips comparator with literal/path
super compile -O -C -c 'from test.parquet | where 1 <= v and 1 >= v'
echo === Test pruner optimization works with in subquery expressions
super compile -O -C -c 'values {x:(from test.parquet | where 1 <= v and 1 >= v)}'

outputs:
- name: stdout
data: |
=== Test pruner flips comparator with literal/path
file test.parquet format parquet filter (1<=v and 1>=v)
pruner (
expr compare(v.max, 1, true)>=0 and compare(v.min, 1, true)<=0
fields v.max,v.min
)
| where 1<=v and 1>=v
| output main
=== Test pruner optimization works with in subquery expressions
null
| values {x:(
file test.parquet format parquet filter (1<=v and 1>=v)
pruner (
expr compare(v.max, 1, true)>=0 and compare(v.min, 1, true)<=0
fields v.max,v.min
)
| where 1<=v and 1>=v)}
| output main
Loading