Skip to content

Commit f0523ff

Browse files
Add Substrait Protobuf Visitor Framework
1 parent 121a143 commit f0523ff

File tree

5 files changed

+2156
-0
lines changed

5 files changed

+2156
-0
lines changed

traverse/multi_visitor.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
package traverse
3+
4+
import (
5+
proto "github.com/substrait-io/substrait-protobuf/go/substraitpb"
6+
)
7+
8+
// MultiVisitor combines multiple visitors into a single visitor.
9+
// All visitors are called for each node they support.
10+
type MultiVisitor struct {
11+
visitors []Visitor
12+
}
13+
14+
// NewMultiVisitor creates a visitor that delegates to multiple visitors.
15+
func NewMultiVisitor(visitors ...Visitor) *MultiVisitor {
16+
return &MultiVisitor{visitors: visitors}
17+
}
18+
19+
// VisitRel calls VisitRel on all visitors that implement RelVisitor.
20+
func (m *MultiVisitor) VisitRel(rel *proto.Rel) {
21+
for _, v := range m.visitors {
22+
if rv, ok := v.(RelVisitor); ok {
23+
rv.VisitRel(rel)
24+
}
25+
}
26+
}
27+
28+
// VisitExpr calls VisitExpr on all visitors that implement ExprVisitor.
29+
func (m *MultiVisitor) VisitExpr(expr *proto.Expression) {
30+
for _, v := range m.visitors {
31+
if ev, ok := v.(ExprVisitor); ok {
32+
ev.VisitExpr(expr)
33+
}
34+
}
35+
}

traverse/visitor.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
/*
3+
Package traverse provides a visitor framework for analyzing Substrait plans
4+
with multiple read-only operations in a single tree traversal.
5+
6+
## Key Features:
7+
- Interface-based visitor pattern
8+
- Single tree walk for multiple analyses
9+
- Sequential execution
10+
- Read-only operations - NO MUTATIONS
11+
- Zero allocations per node
12+
13+
## IMPORTANT: Read-Only Analysis Only
14+
This interface is designed for read-only analysis, and assumes the tree does not change
15+
as it is being walked. Any modification of the tree could disrupt the walking of the
16+
tree causing nodes to be missed, visited out of order, or cause panics.
17+
18+
## Basic Usage:
19+
20+
// Option 1: Simple usage with Visit (builds context automatically)
21+
visitor := NewMyVisitor()
22+
traverse.Visit(plan, visitor)
23+
24+
// Option 2: Explicit context for advanced usage
25+
ctx := traverse.NewPlanContext(plan)
26+
visitor := NewMyVisitor(ctx)
27+
traverse.Walk(plan.Relations, visitor)
28+
29+
// Option 3: With cycle detection for DAG enforcement
30+
visitor := NewMyVisitor()
31+
cycleDetector := traverse.NewCycleDetectingVisitor(visitor)
32+
traverse.Walk(rel, cycleDetector)
33+
34+
## Creating Custom Visitors:
35+
36+
// Visitor that only cares about relations
37+
type NodeCounter struct {
38+
count int
39+
}
40+
41+
func (v *NodeCounter) VisitRel(rel *proto.Rel) {
42+
v.count++
43+
}
44+
45+
// Visitor that only cares about expressions (e.g., finding all functions)
46+
type FunctionCollector struct {
47+
ctx *PlanContext
48+
functions []string
49+
}
50+
51+
func (v *FunctionCollector) VisitExpr(expr *proto.Expression) {
52+
// Collect function names
53+
}
54+
55+
// Visitor that visits both
56+
type FullAnalyzer struct{}
57+
58+
func (v *FullAnalyzer) VisitRel(rel *proto.Rel) {
59+
// Analyze relations
60+
}
61+
62+
func (v *FullAnalyzer) VisitExpr(expr *proto.Expression) {
63+
// Analyze expressions
64+
}
65+
*/
66+
package traverse
67+
68+
import (
69+
proto "github.com/substrait-io/substrait-protobuf/go/substraitpb"
70+
)
71+
72+
// Visitor is a marker interface for all visitors.
73+
// Visitors should implement one or more of the following methods:
74+
// - VisitRel(*proto.Rel) - called for each relation node
75+
// - VisitExpr(*proto.Expression) - called for each non-literal expression
76+
//
77+
// The traversal framework uses type assertions to determine which methods to call.
78+
type Visitor interface{}
79+
80+
// RelVisitor is implemented by visitors that want to visit relation nodes.
81+
type RelVisitor interface {
82+
VisitRel(rel *proto.Rel)
83+
}
84+
85+
// ExprVisitor is implemented by visitors that want to visit expression nodes.
86+
type ExprVisitor interface {
87+
VisitExpr(expr *proto.Expression)
88+
}
89+
90+
// Visit provides a convenient way to traverse a Substrait plan.
91+
// It walks all relations in the plan, calling the appropriate visitor methods
92+
// based on what interfaces the visitor implements.
93+
func Visit(plan *proto.Plan, visitor Visitor) {
94+
if visitor == nil || plan == nil {
95+
return
96+
}
97+
98+
// Walk all relations in the plan
99+
for _, rel := range plan.Relations {
100+
if root := rel.GetRoot(); root != nil {
101+
walk(root.Input, visitor)
102+
}
103+
}
104+
}
105+
106+
// Walk traverses a Substrait relation tree, applying the visitor to each node.
107+
// The visitor's methods are called based on which interfaces it implements.
108+
// This function guarantees zero allocations per node visited.
109+
//
110+
// If your visitor needs plan context (for extensions, etc.), construct it
111+
// with NewPlanContext and pass it to your visitor's constructor.
112+
func Walk(rel *proto.Rel, visitor Visitor) {
113+
if visitor == nil {
114+
return
115+
}
116+
walk(rel, visitor)
117+
}

0 commit comments

Comments
 (0)