@@ -13,7 +13,8 @@ import (
1313)
1414
1515// HintInjectionDonor holds the donor statement that provides the hints to be
16- // injected into a target statement.
16+ // injected into a target statement. The donor statement could be a regular SQL
17+ // statement or a statement fingerprint.
1718type HintInjectionDonor struct {
1819 // ast is the donor statement AST after parsing.
1920 ast Statement
@@ -23,9 +24,16 @@ type HintInjectionDonor struct {
2324 // statements match.
2425 validationSQL string
2526
26- // walk is a pre-order traversal of the AST, holding Expr and TableExpr. As we
27- // walk the target AST we expect to find matching nodes in the same order.
27+ // walk is a pre-order traversal of the AST, holding Expr, TableExpr, and
28+ // Statement nodes. As we walk the target AST we expect to find matching nodes
29+ // in the same order. Some nodes are ignored, such as ParenExpr and nodes
30+ // after collapse of a Tuple or Array or ValuesClause.
2831 walk []any
32+
33+ // collapsed is a stack indicating whether we've collapsed the rest of the
34+ // current ValuesClause, Tuple, or Array list in the donor AST. Collapsed is
35+ // only used during AST walks.
36+ collapsed []bool
2937}
3038
3139// validationFmtFlags returns the FmtFlags used to check that the donor
@@ -37,49 +45,148 @@ func validationFmtFlags(sv *settings.Values) FmtFlags {
3745 return stmtFingerprintFmtMask | FmtHideHints
3846}
3947
40- // NewHintInjectionDonor creates a HintInjectionDonor from a parsed AST.
48+ // NewHintInjectionDonor creates a HintInjectionDonor from a parsed AST. The
49+ // parsed donor statement could be a regular SQL statement or a statement
50+ // fingerprint.
4151func NewHintInjectionDonor (ast Statement , sv * settings.Values ) (* HintInjectionDonor , error ) {
42- donor := & HintInjectionDonor {
52+ hd := & HintInjectionDonor {
4353 ast : ast ,
4454 validationSQL : AsStringWithFlags (ast , validationFmtFlags (sv )),
55+ collapsed : []bool {false },
56+ }
57+ WalkStmt (hd , ast )
58+ return hd , nil
59+ }
60+
61+ // The ExtendedVisitor methods of HintInjectionDonor are used to walk the donor
62+ // AST and build the ahead-of-time pre-order traversal.
63+ var _ ExtendedVisitor = & HintInjectionDonor {}
64+
65+ func (hd * HintInjectionDonor ) VisitPre (expr Expr ) (recurse bool , newExpr Expr ) {
66+ if hd .collapsed [len (hd .collapsed )- 1 ] {
67+ return false , expr
68+ }
69+ switch expr .(type ) {
70+ case * ParenExpr :
71+ // We sometimes wrap scalar expressions in extra ParenExpr when
72+ // printing. Skip over ParenExpr to match in more cases.
73+ return true , expr
74+ case * Tuple , * Array :
75+ // If the donor was a statement fingerprint, we might have collapse a Tuple
76+ // or Array into __more__. Push on a boolean tracking whether we have
77+ // collapsed it.
78+ hd .collapsed = append (hd .collapsed , false )
79+ case * UnresolvedName :
80+ if isArityIndicatorString (expr .String ()) {
81+ // If we find __more__ or another arity indicator, treat the rest of the
82+ // current Tuple or Array or ValuesClause as collapsed, and skip forward
83+ // until we VisitPost it.
84+ hd .collapsed [len (hd .collapsed )- 1 ] = true
85+ hd .walk = append (hd .walk , expr )
86+ return false , expr
87+ }
88+ }
89+ hd .walk = append (hd .walk , expr )
90+ return true , expr
91+ }
92+
93+ func (hd * HintInjectionDonor ) VisitPost (expr Expr ) (newNode Expr ) {
94+ switch expr .(type ) {
95+ case * Tuple , * Array :
96+ // Pop off the boolean tracking whether we collapsed the Tuple or Array.
97+ hd .collapsed = hd .collapsed [:len (hd .collapsed )- 1 ]
98+ }
99+ return expr
100+ }
101+
102+ func (hd * HintInjectionDonor ) VisitTablePre (expr TableExpr ) (recurse bool , newExpr TableExpr ) {
103+ if hd .collapsed [len (hd .collapsed )- 1 ] {
104+ return false , expr
105+ }
106+ hd .walk = append (hd .walk , expr )
107+ return true , expr
108+ }
109+
110+ func (hd * HintInjectionDonor ) VisitTablePost (expr TableExpr ) (newNode TableExpr ) {
111+ return expr
112+ }
113+
114+ func (hd * HintInjectionDonor ) VisitStatementPre (expr Statement ) (recurse bool , newNode Statement ) {
115+ if hd .collapsed [len (hd .collapsed )- 1 ] {
116+ return false , expr
117+ }
118+ switch expr .(type ) {
119+ case * ValuesClause :
120+ // If the donor was a statement fingerprint, we might have collapse a
121+ // ValuesClause into __more__. Push on a boolean tracking whether we have
122+ // collapsed it.
123+ hd .collapsed = append (hd .collapsed , false )
45124 }
46- if _ , err := ExtendedSimpleStmtVisit (
47- ast ,
48- func (expr Expr ) (bool , Expr , error ) {
49- donor .walk = append (donor .walk , expr )
50- return true , expr , nil
51- },
52- func (expr TableExpr ) (bool , TableExpr , error ) {
53- donor .walk = append (donor .walk , expr )
54- return true , expr , nil
55- },
56- ); err != nil {
57- return nil , err
125+ hd .walk = append (hd .walk , expr )
126+ return true , expr
127+ }
128+
129+ func (hd * HintInjectionDonor ) VisitStatementPost (expr Statement ) (newNode Statement ) {
130+ switch expr .(type ) {
131+ case * ValuesClause :
132+ // Pop off the boolean tracking whether we collapsed the ValuesClause.
133+ hd .collapsed = hd .collapsed [:len (hd .collapsed )- 1 ]
58134 }
59- return donor , nil
135+ return expr
60136}
61137
138+ // hintInjectionVisitor is an ExtendedVisitor used to walk the target AST at
139+ // injection time. It performs the rewrite of the target AST.
62140type hintInjectionVisitor struct {
63- donor * HintInjectionDonor
141+ hd * HintInjectionDonor
64142 walkIdx int
65143 err error
66144}
67145
146+ var _ ExtendedVisitor = & hintInjectionVisitor {}
147+
68148func (v * hintInjectionVisitor ) VisitPre (expr Expr ) (recurse bool , newExpr Expr ) {
69- if v .err != nil {
149+ if v .err != nil || v . hd . collapsed [ len ( v . hd . collapsed ) - 1 ] {
70150 return false , expr
71151 }
72152
73- if v .walkIdx >= len (v .donor .walk ) {
153+ switch expr .(type ) {
154+ case * ParenExpr :
155+ // Skip ParenExpr to match the donor walk.
156+ return true , expr
157+ }
158+
159+ if v .walkIdx >= len (v .hd .walk ) {
74160 v .err = errors .Newf (
75161 "hint injection donor statement missing AST node corresponding to AST node %v" , expr ,
76162 )
77163 return false , expr
78164 }
79165
80- donorExpr := v .donor .walk [v .walkIdx ]
166+ donorExpr := v .hd .walk [v .walkIdx ]
81167 v .walkIdx += 1
82168
169+ switch donor := donorExpr .(type ) {
170+ case * Tuple , * Array :
171+ // If the donor was a statement fingerprint, we might have collapse a Tuple
172+ // or Array into __more__. Push on a boolean tracking whether we have
173+ // collapsed it.
174+ v .hd .collapsed = append (v .hd .collapsed , false )
175+ case * UnresolvedName :
176+ // If the donor was a fingerprint, then we might not exactly match _ or
177+ // __more__ or __more10_100__ etc in the donor. Treat these as wildcards
178+ // matching any subtree and don't recurse any further into the target AST.
179+ if donor .String () == string (StmtFingerprintPlaceholder ) {
180+ return false , expr
181+ }
182+ if isArityIndicatorString (donor .String ()) {
183+ // And skip the rest of the current Tuple or Array or ValuesClause if it
184+ // has been collapsed to __more__ or one of the other arity indicators.
185+ v .hd .collapsed [len (v .hd .collapsed )- 1 ] = true
186+ return false , expr
187+ }
188+ }
189+
83190 // Check that the corresponding donor Expr matches this node.
84191 if reflect .TypeOf (expr ) != reflect .TypeOf (donorExpr ) {
85192 v .err = errors .Newf (
@@ -90,19 +197,28 @@ func (v *hintInjectionVisitor) VisitPre(expr Expr) (recurse bool, newExpr Expr)
90197 return true , expr
91198}
92199
200+ func (v * hintInjectionVisitor ) VisitPost (expr Expr ) Expr {
201+ switch expr .(type ) {
202+ case * Tuple , * Array :
203+ // Pop off the boolean tracking whether we collapsed the Tuple or Array.
204+ v .hd .collapsed = v .hd .collapsed [:len (v .hd .collapsed )- 1 ]
205+ }
206+ return expr
207+ }
208+
93209func (v * hintInjectionVisitor ) VisitTablePre (expr TableExpr ) (recurse bool , newExpr TableExpr ) {
94- if v .err != nil {
210+ if v .err != nil || v . hd . collapsed [ len ( v . hd . collapsed ) - 1 ] {
95211 return false , expr
96212 }
97213
98- if v .walkIdx >= len (v .donor .walk ) {
214+ if v .walkIdx >= len (v .hd .walk ) {
99215 v .err = errors .Newf (
100216 "hint injection donor statement missing AST node corresponding to AST node" , expr ,
101217 )
102218 return false , expr
103219 }
104220
105- donorExpr := v .donor .walk [v .walkIdx ]
221+ donorExpr := v .hd .walk [v .walkIdx ]
106222 v .walkIdx += 1
107223
108224 // Copy hints from the corresponding donor TableExpr.
@@ -132,10 +248,11 @@ func (v *hintInjectionVisitor) VisitTablePre(expr TableExpr) (recurse bool, newE
132248 case * JoinTableExpr :
133249 switch orig := expr .(type ) {
134250 case * JoinTableExpr :
135- if donor .Hint != "" {
251+ if donor .JoinType != orig . JoinType || donor . Hint != orig . Hint {
136252 // Create a new node with any pre-existing inline hints replaced with
137253 // hints from the donor.
138254 newNode := * orig
255+ newNode .JoinType = donor .JoinType
139256 newNode .Hint = donor .Hint
140257 return true , & newNode
141258 }
@@ -153,10 +270,51 @@ func (v *hintInjectionVisitor) VisitTablePre(expr TableExpr) (recurse bool, newE
153270 return true , expr
154271}
155272
156- func (v * hintInjectionVisitor ) VisitPost (expr Expr ) Expr { return expr }
157- func (v * hintInjectionVisitor ) VisitTablePost (expr TableExpr ) TableExpr { return expr }
273+ func (v * hintInjectionVisitor ) VisitTablePost (expr TableExpr ) TableExpr {
274+ return expr
275+ }
276+
277+ func (v * hintInjectionVisitor ) VisitStatementPre (expr Statement ) (recurse bool , newExpr Statement ) {
278+ if v .err != nil || v .hd .collapsed [len (v .hd .collapsed )- 1 ] {
279+ return false , expr
280+ }
158281
159- var _ ExtendedVisitor = & hintInjectionVisitor {}
282+ if v .walkIdx >= len (v .hd .walk ) {
283+ v .err = errors .Newf (
284+ "hint injection donor statement missing AST node corresponding to AST node %v" , expr ,
285+ )
286+ return false , expr
287+ }
288+
289+ donorExpr := v .hd .walk [v .walkIdx ]
290+ v .walkIdx += 1
291+
292+ switch donorExpr .(type ) {
293+ case * ValuesClause :
294+ // If the donor was a statement fingerprint, we might have collapse a
295+ // ValuesClause into __more__. Push on a boolean tracking whether we have
296+ // collapsed it.
297+ v .hd .collapsed = append (v .hd .collapsed , false )
298+ }
299+
300+ // Check that the corresponding donor Expr matches this node.
301+ if reflect .TypeOf (expr ) != reflect .TypeOf (donorExpr ) {
302+ v .err = errors .Newf (
303+ "hint injection donor statement AST node %v did not match AST node %v" , donorExpr , expr ,
304+ )
305+ return false , expr
306+ }
307+ return true , expr
308+ }
309+
310+ func (v * hintInjectionVisitor ) VisitStatementPost (expr Statement ) Statement {
311+ switch expr .(type ) {
312+ case * ValuesClause :
313+ // Pop off the boolean tracking whether we collapsed the ValuesClause.
314+ v .hd .collapsed = v .hd .collapsed [:len (v .hd .collapsed )- 1 ]
315+ }
316+ return expr
317+ }
160318
161319// Validate checks that the target statement exactly matches the donor (except
162320// for hints).
@@ -185,7 +343,7 @@ func (hd *HintInjectionDonor) Validate(stmt Statement, sv *settings.Values) erro
185343// semantic checks happen elsewhere.
186344func (hd * HintInjectionDonor ) InjectHints (stmt Statement ) (Statement , bool , error ) {
187345 // Walk the given statement, copying hints from the hints donor.
188- v := hintInjectionVisitor {donor : hd }
346+ v := hintInjectionVisitor {hd : hd }
189347 newStmt , changed := WalkStmt (& v , stmt )
190348 if v .err != nil {
191349 return stmt , false , v .err
0 commit comments