Skip to content

Commit 268c6e2

Browse files
committed
Add walker example
1 parent e5a953f commit 268c6e2

File tree

3 files changed

+500
-0
lines changed

3 files changed

+500
-0
lines changed

Diff for: example/walk.go

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package main
2+
3+
import (
4+
"log"
5+
6+
"github.com/auxten/postgresql-parser/pkg/walk"
7+
)
8+
9+
func main() {
10+
sql := `select marr
11+
from (select marr_stat_cd AS marr, label AS l
12+
from root_loan_mock_v4
13+
order by root_loan_mock_v4.age desc, l desc
14+
limit 5) as v4
15+
LIMIT 1;`
16+
w := &walk.AstWalker{
17+
Fn: func(ctx interface{}, node interface{}) (stop bool) {
18+
log.Printf("node type %T", node)
19+
return false
20+
},
21+
}
22+
_, _ = w.Walk(sql, nil)
23+
return
24+
}

Diff for: pkg/walk/walker.go

+265
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
package walk
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"sort"
7+
"strings"
8+
9+
"github.com/auxten/postgresql-parser/pkg/sql/parser"
10+
"github.com/auxten/postgresql-parser/pkg/sql/sem/tree"
11+
)
12+
13+
type AstWalker struct {
14+
unknownNodes []interface{}
15+
Fn func(ctx interface{}, node interface{}) (stop bool)
16+
}
17+
type ReferredCols map[string]int
18+
19+
func (rc ReferredCols) ToList() []string {
20+
cols := make([]string, len(rc))
21+
i := 0
22+
for k, _ := range rc {
23+
cols[i] = k
24+
i++
25+
}
26+
sort.Strings(cols)
27+
return cols
28+
}
29+
30+
func (w *AstWalker) Walk(sql string, ctx interface{}) (ok bool, err error) {
31+
stmts, err := parser.Parse(sql)
32+
if err != nil {
33+
return false, err
34+
}
35+
36+
w.unknownNodes = make([]interface{}, 0)
37+
asts := make([]tree.NodeFormatter, len(stmts))
38+
for si, stmt := range stmts {
39+
asts[si] = stmt.AST
40+
}
41+
42+
// nodeCount is incremented on each visited node per statement. It is
43+
// currently used to determine if walk is at the top-level statement
44+
// or not.
45+
var walk func(...interface{})
46+
walk = func(nodes ...interface{}) {
47+
for _, node := range nodes {
48+
if w.Fn != nil {
49+
if w.Fn(ctx, node) {
50+
break
51+
}
52+
}
53+
54+
if node == nil {
55+
continue
56+
}
57+
if _, ok := node.(tree.Datum); ok {
58+
continue
59+
}
60+
61+
switch node := node.(type) {
62+
case *tree.AliasedTableExpr:
63+
walk(node.Expr)
64+
case *tree.AndExpr:
65+
walk(node.Left, node.Right)
66+
case *tree.AnnotateTypeExpr:
67+
walk(node.Expr)
68+
case *tree.Array:
69+
walk(node.Exprs)
70+
case *tree.BinaryExpr:
71+
walk(node.Left, node.Right)
72+
case *tree.CaseExpr:
73+
walk(node.Expr, node.Else)
74+
for _, w := range node.Whens {
75+
walk(w.Cond, w.Val)
76+
}
77+
case *tree.RangeCond:
78+
walk(node.Left, node.From, node.To)
79+
case *tree.CastExpr:
80+
walk(node.Expr)
81+
case *tree.CoalesceExpr:
82+
for _, expr := range node.Exprs {
83+
walk(expr)
84+
}
85+
case *tree.ColumnTableDef:
86+
case *tree.ComparisonExpr:
87+
walk(node.Left, node.Right)
88+
case *tree.CreateTable:
89+
for _, def := range node.Defs {
90+
walk(def)
91+
}
92+
if node.AsSource != nil {
93+
walk(node.AsSource)
94+
}
95+
case *tree.CTE:
96+
walk(node.Stmt)
97+
case *tree.DBool:
98+
case tree.Exprs:
99+
for _, expr := range node {
100+
walk(expr)
101+
}
102+
case *tree.FamilyTableDef:
103+
case *tree.FuncExpr:
104+
if node.WindowDef != nil {
105+
walk(node.WindowDef)
106+
}
107+
walk(node.Exprs, node.Filter)
108+
case *tree.IndexTableDef:
109+
case *tree.JoinTableExpr:
110+
walk(node.Left, node.Right, node.Cond)
111+
case *tree.NotExpr:
112+
walk(node.Expr)
113+
case *tree.NumVal:
114+
case *tree.OnJoinCond:
115+
walk(node.Expr)
116+
case *tree.OrExpr:
117+
walk(node.Left, node.Right)
118+
case *tree.ParenExpr:
119+
walk(node.Expr)
120+
case *tree.ParenSelect:
121+
walk(node.Select)
122+
case *tree.RowsFromExpr:
123+
for _, expr := range node.Items {
124+
walk(expr)
125+
}
126+
case *tree.Select:
127+
if node.With != nil {
128+
walk(node.With)
129+
}
130+
walk(node.Select)
131+
if node.OrderBy != nil {
132+
for _, order := range node.OrderBy {
133+
walk(order)
134+
}
135+
}
136+
if node.Limit != nil {
137+
walk(node.Limit)
138+
}
139+
case *tree.Order:
140+
walk(node.Expr, node.Table)
141+
case *tree.Limit:
142+
walk(node.Count)
143+
case *tree.SelectClause:
144+
walk(node.Exprs)
145+
if node.Where != nil {
146+
walk(node.Where)
147+
}
148+
if node.Having != nil {
149+
walk(node.Having)
150+
}
151+
for _, table := range node.From.Tables {
152+
walk(table)
153+
}
154+
if node.DistinctOn != nil {
155+
for _, distinct := range node.DistinctOn {
156+
walk(distinct)
157+
}
158+
}
159+
if node.GroupBy != nil {
160+
for _, group := range node.GroupBy {
161+
walk(group)
162+
}
163+
}
164+
case tree.SelectExpr:
165+
walk(node.Expr)
166+
case tree.SelectExprs:
167+
for _, expr := range node {
168+
walk(expr)
169+
}
170+
case *tree.SetVar:
171+
for _, expr := range node.Values {
172+
walk(expr)
173+
}
174+
case *tree.StrVal:
175+
case *tree.Subquery:
176+
walk(node.Select)
177+
case *tree.TableName, tree.TableName:
178+
case *tree.Tuple:
179+
for _, expr := range node.Exprs {
180+
walk(expr)
181+
}
182+
case *tree.UnaryExpr:
183+
walk(node.Expr)
184+
case *tree.UniqueConstraintTableDef:
185+
case *tree.UnionClause:
186+
walk(node.Left, node.Right)
187+
case tree.UnqualifiedStar:
188+
case *tree.UnresolvedName:
189+
case *tree.ValuesClause:
190+
for _, row := range node.Rows {
191+
walk(row)
192+
}
193+
case *tree.Where:
194+
walk(node.Expr)
195+
case *tree.WindowDef:
196+
walk(node.Partitions)
197+
if node.Frame != nil {
198+
walk(node.Frame)
199+
}
200+
case *tree.WindowFrame:
201+
if node.Bounds.StartBound != nil {
202+
walk(node.Bounds.StartBound)
203+
}
204+
if node.Bounds.EndBound != nil {
205+
walk(node.Bounds.EndBound)
206+
}
207+
case *tree.WindowFrameBound:
208+
walk(node.OffsetExpr)
209+
case *tree.Window:
210+
case *tree.With:
211+
for _, expr := range node.CTEList {
212+
walk(expr)
213+
}
214+
default:
215+
w.unknownNodes = append(w.unknownNodes, node)
216+
}
217+
}
218+
}
219+
220+
for _, ast := range asts {
221+
walk(ast)
222+
}
223+
224+
return true, nil
225+
}
226+
227+
func isColumn(node interface{}) bool {
228+
switch node.(type) {
229+
// it's wired that the "Subquery" type is also "VariableExpr" type
230+
// we have to ignore that case.
231+
case *tree.Subquery:
232+
return false
233+
case tree.VariableExpr:
234+
return true
235+
}
236+
return false
237+
}
238+
239+
// ColNamesInSelect finds all referred variables in a Select Statement.
240+
// (variables = sub-expressions, placeholders, indexed vars, etc.)
241+
// Implementation limits:
242+
// 1. Table with AS is not normalized.
243+
// 2. Columns referred from outer query are not translated.
244+
func ColNamesInSelect(sql string) (referredCols ReferredCols, err error) {
245+
referredCols = make(ReferredCols, 0)
246+
247+
w := &AstWalker{
248+
Fn: func(ctx interface{}, node interface{}) (stop bool) {
249+
rCols := ctx.(ReferredCols)
250+
if isColumn(node) {
251+
nodeName := fmt.Sprint(node)
252+
// just drop the "table." part
253+
tableCols := strings.Split(nodeName, ".")
254+
colName := tableCols[len(tableCols)-1]
255+
rCols[colName] = 1
256+
}
257+
return false
258+
},
259+
}
260+
_, err = w.Walk(sql, referredCols)
261+
for _, col := range w.unknownNodes {
262+
log.Printf("unhandled column type %T", col)
263+
}
264+
return
265+
}

0 commit comments

Comments
 (0)