Skip to content

Commit 9522bea

Browse files
committed
chore: remove decorative aliasse and sort AND predicates
1 parent 991c4d3 commit 9522bea

4 files changed

Lines changed: 151 additions & 2 deletions

File tree

fingerprint.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ package qshape
33
import pg_query "github.com/pganalyze/pg_query_go/v6"
44

55
func Fingerprint(sql string) (string, error) {
6-
fp, err := pg_query.Fingerprint(sql)
6+
// Normalize first so ORM variants share a fingerprint
7+
canonical, err := Normalize(sql)
8+
if err != nil {
9+
return "", err
10+
}
11+
fp, err := pg_query.Fingerprint(canonical)
712
if err != nil {
813
return "", err
914
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/jackc/pgx/v5 v5.8.0
77
github.com/pganalyze/pg_query_go/v6 v6.1.0
88
github.com/spf13/cobra v1.10.2
9+
google.golang.org/protobuf v1.31.0
910
)
1011

1112
require (
@@ -14,5 +15,4 @@ require (
1415
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
1516
github.com/spf13/pflag v1.0.9 // indirect
1617
golang.org/x/text v0.29.0 // indirect
17-
google.golang.org/protobuf v1.31.0 // indirect
1818
)

normalize.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,8 @@ func Normalize(sql string) (string, error) {
77
if err != nil {
88
return "", err
99
}
10+
if err := reshape(tree); err != nil {
11+
return "", err
12+
}
1013
return pg_query.Deparse(tree)
1114
}

reshape.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package qshape
2+
3+
import (
4+
"sort"
5+
6+
pg_query "github.com/pganalyze/pg_query_go/v6"
7+
"google.golang.org/protobuf/proto"
8+
)
9+
10+
// reshape rewrites a parsed tree so that decorative table aliases are
11+
// stripped and sort AND conditions
12+
func reshape(tree *pg_query.ParseResult) error {
13+
if tree == nil {
14+
return nil
15+
}
16+
17+
for _, raw := range tree.Stmts {
18+
if raw == nil || raw.Stmt == nil {
19+
continue
20+
}
21+
if v, ok := raw.Stmt.Node.(*pg_query.Node_SelectStmt); ok {
22+
reshapeSelect(v.SelectStmt)
23+
}
24+
}
25+
26+
return nil
27+
}
28+
29+
func reshapeSelect(s *pg_query.SelectStmt) {
30+
if s == nil {
31+
return
32+
}
33+
34+
// Single-table FROM: strip a decorative table alias
35+
if len(s.FromClause) == 1 {
36+
if rv, ok := s.FromClause[0].Node.(*pg_query.Node_RangeVar); ok {
37+
if rv.RangeVar.Alias != nil && rv.RangeVar.Alias.Aliasname != "" &&
38+
len(rv.RangeVar.Alias.Colnames) == 0 {
39+
alias := rv.RangeVar.Alias.Aliasname
40+
rv.RangeVar.Alias = nil
41+
stripAliasInSelect(s, alias)
42+
}
43+
}
44+
}
45+
46+
sortAndTree(s.WhereClause)
47+
sortAndTree(s.HavingClause)
48+
}
49+
50+
func stripAliasInSelect(s *pg_query.SelectStmt, alias string) {
51+
for _, n := range s.TargetList {
52+
stripAliasInNode(n, alias)
53+
}
54+
55+
stripAliasInNode(s.WhereClause, alias)
56+
stripAliasInNode(s.HavingClause, alias)
57+
58+
for _, n := range s.GroupClause {
59+
stripAliasInNode(n, alias)
60+
}
61+
62+
for _, n := range s.SortClause {
63+
stripAliasInNode(n, alias)
64+
}
65+
66+
for _, n := range s.DistinctClause {
67+
stripAliasInNode(n, alias)
68+
}
69+
}
70+
71+
func stripAliasInNode(n *pg_query.Node, alias string) {
72+
if n == nil {
73+
return
74+
}
75+
76+
switch v := n.Node.(type) {
77+
case *pg_query.Node_ColumnRef:
78+
if len(v.ColumnRef.Fields) >= 2 {
79+
if s, ok := v.ColumnRef.Fields[0].Node.(*pg_query.Node_String_); ok && s.String_.Sval == alias {
80+
v.ColumnRef.Fields = v.ColumnRef.Fields[1:]
81+
}
82+
}
83+
case *pg_query.Node_AExpr:
84+
stripAliasInNode(v.AExpr.Lexpr, alias)
85+
stripAliasInNode(v.AExpr.Rexpr, alias)
86+
case *pg_query.Node_BoolExpr:
87+
for _, a := range v.BoolExpr.Args {
88+
stripAliasInNode(a, alias)
89+
}
90+
case *pg_query.Node_ResTarget:
91+
stripAliasInNode(v.ResTarget.Val, alias)
92+
case *pg_query.Node_FuncCall:
93+
for _, a := range v.FuncCall.Args {
94+
stripAliasInNode(a, alias)
95+
}
96+
case *pg_query.Node_List:
97+
for _, a := range v.List.Items {
98+
stripAliasInNode(a, alias)
99+
}
100+
case *pg_query.Node_SortBy:
101+
stripAliasInNode(v.SortBy.Node, alias)
102+
case *pg_query.Node_TypeCast:
103+
stripAliasInNode(v.TypeCast.Arg, alias)
104+
case *pg_query.Node_NullTest:
105+
stripAliasInNode(v.NullTest.Arg, alias)
106+
case *pg_query.Node_BooleanTest:
107+
stripAliasInNode(v.BooleanTest.Arg, alias)
108+
}
109+
}
110+
111+
func sortAndTree(n *pg_query.Node) {
112+
if n == nil {
113+
return
114+
}
115+
116+
switch v := n.Node.(type) {
117+
case *pg_query.Node_BoolExpr:
118+
for _, a := range v.BoolExpr.Args {
119+
sortAndTree(a)
120+
}
121+
if v.BoolExpr.Boolop == pg_query.BoolExprType_AND_EXPR {
122+
sort.SliceStable(v.BoolExpr.Args, func(i, j int) bool {
123+
return argSortKey(v.BoolExpr.Args[i]) < argSortKey(v.BoolExpr.Args[j])
124+
})
125+
}
126+
case *pg_query.Node_AExpr:
127+
sortAndTree(v.AExpr.Lexpr)
128+
sortAndTree(v.AExpr.Rexpr)
129+
}
130+
}
131+
132+
func argSortKey(n *pg_query.Node) string {
133+
if n == nil {
134+
return ""
135+
}
136+
b, err := proto.Marshal(n)
137+
if err != nil {
138+
return ""
139+
}
140+
return string(b)
141+
}

0 commit comments

Comments
 (0)