@@ -28,32 +28,39 @@ type HintInjectionDonor struct {
2828 // in the same order. Some nodes are ignored, such as ParenExpr and nodes
2929 // after collapse of a Tuple or Array or ValuesClause.
3030 walk []any
31-
32- // collapsed is a stack indicating whether we've collapsed the rest of the
33- // current ValuesClause, Tuple, or Array list in the donor AST. Collapsed is
34- // only used during AST walks.
35- collapsed []bool
3631}
3732
3833// NewHintInjectionDonor creates a HintInjectionDonor from a parsed AST. The
3934// parsed donor statement could be a regular SQL statement or a statement
4035// fingerprint.
36+ //
37+ // After NewHintInjectionDonor returns a HintInjectionDonor, the donor becomes
38+ // read-only to allow concurrent use from multiple goroutines.
4139func NewHintInjectionDonor (ast Statement , fingerprintFlags FmtFlags ) (* HintInjectionDonor , error ) {
4240 hd := & HintInjectionDonor {
4341 ast : ast ,
4442 validationSQL : FormatStatementHideConstants (ast , fingerprintFlags , FmtHideHints ),
45- collapsed : []bool {false },
4643 }
47- WalkStmt (hd , ast )
44+ v := newDonorVisitor {hd : hd , collapsed : []bool {false }}
45+ WalkStmt (& v , ast )
4846 return hd , nil
4947}
5048
51- // The ExtendedVisitor methods of HintInjectionDonor are used to walk the donor
52- // AST and build the ahead-of-time pre-order traversal.
53- var _ ExtendedVisitor = & HintInjectionDonor {}
49+ // newDonorVisitor is an ExtendedVisitor used to walk the donor AST and build
50+ // the ahead-of-time pre-order traversal at donor creation time.
51+ type newDonorVisitor struct {
52+ // hd is the hint donor being initialized.
53+ hd * HintInjectionDonor
54+
55+ // collapsed is a stack indicating whether we've collapsed the rest of the
56+ // current ValuesClause, Tuple, or Array list in the donor AST.
57+ collapsed []bool
58+ }
59+
60+ var _ ExtendedVisitor = & newDonorVisitor {}
5461
55- func (hd * HintInjectionDonor ) VisitPre (expr Expr ) (recurse bool , newExpr Expr ) {
56- if hd .collapsed [len (hd .collapsed )- 1 ] {
62+ func (v * newDonorVisitor ) VisitPre (expr Expr ) (recurse bool , newExpr Expr ) {
63+ if v .collapsed [len (v .collapsed )- 1 ] {
5764 return false , expr
5865 }
5966 switch expr .(type ) {
@@ -65,78 +72,88 @@ func (hd *HintInjectionDonor) VisitPre(expr Expr) (recurse bool, newExpr Expr) {
6572 // If the donor was a statement fingerprint, we might have collapse a Tuple
6673 // or Array into __more__. Push on a boolean tracking whether we have
6774 // collapsed it.
68- hd .collapsed = append (hd .collapsed , false )
75+ v .collapsed = append (v .collapsed , false )
6976 case * UnresolvedName :
7077 if isArityIndicatorString (expr .String ()) {
7178 // If we find __more__ or another arity indicator, treat the rest of the
7279 // current Tuple or Array or ValuesClause as collapsed, and skip forward
7380 // until we VisitPost it.
74- hd .collapsed [len (hd .collapsed )- 1 ] = true
75- hd .walk = append (hd .walk , expr )
81+ v .collapsed [len (v .collapsed )- 1 ] = true
82+ v . hd .walk = append (v . hd .walk , expr )
7683 return false , expr
7784 }
7885 }
79- hd .walk = append (hd .walk , expr )
86+ v . hd .walk = append (v . hd .walk , expr )
8087 return true , expr
8188}
8289
83- func (hd * HintInjectionDonor ) VisitPost (expr Expr ) (newNode Expr ) {
90+ func (v * newDonorVisitor ) VisitPost (expr Expr ) (newNode Expr ) {
8491 switch expr .(type ) {
8592 case * Tuple , * Array :
8693 // Pop off the boolean tracking whether we collapsed the Tuple or Array.
87- hd .collapsed = hd .collapsed [:len (hd .collapsed )- 1 ]
94+ v .collapsed = v .collapsed [:len (v .collapsed )- 1 ]
8895 }
8996 return expr
9097}
9198
92- func (hd * HintInjectionDonor ) VisitTablePre (expr TableExpr ) (recurse bool , newExpr TableExpr ) {
93- if hd .collapsed [len (hd .collapsed )- 1 ] {
99+ func (v * newDonorVisitor ) VisitTablePre (expr TableExpr ) (recurse bool , newExpr TableExpr ) {
100+ if v .collapsed [len (v .collapsed )- 1 ] {
94101 return false , expr
95102 }
96- hd .walk = append (hd .walk , expr )
103+ v . hd .walk = append (v . hd .walk , expr )
97104 return true , expr
98105}
99106
100- func (hd * HintInjectionDonor ) VisitTablePost (expr TableExpr ) (newNode TableExpr ) {
107+ func (v * newDonorVisitor ) VisitTablePost (expr TableExpr ) (newNode TableExpr ) {
101108 return expr
102109}
103110
104- func (hd * HintInjectionDonor ) VisitStatementPre (expr Statement ) (recurse bool , newNode Statement ) {
105- if hd .collapsed [len (hd .collapsed )- 1 ] {
111+ func (v * newDonorVisitor ) VisitStatementPre (expr Statement ) (recurse bool , newNode Statement ) {
112+ if v .collapsed [len (v .collapsed )- 1 ] {
106113 return false , expr
107114 }
108115 switch expr .(type ) {
109116 case * ValuesClause :
110117 // If the donor was a statement fingerprint, we might have collapse a
111118 // ValuesClause into __more__. Push on a boolean tracking whether we have
112119 // collapsed it.
113- hd .collapsed = append (hd .collapsed , false )
120+ v .collapsed = append (v .collapsed , false )
114121 }
115- hd .walk = append (hd .walk , expr )
122+ v . hd .walk = append (v . hd .walk , expr )
116123 return true , expr
117124}
118125
119- func (hd * HintInjectionDonor ) VisitStatementPost (expr Statement ) (newNode Statement ) {
126+ func (v * newDonorVisitor ) VisitStatementPost (expr Statement ) (newNode Statement ) {
120127 switch expr .(type ) {
121128 case * ValuesClause :
122129 // Pop off the boolean tracking whether we collapsed the ValuesClause.
123- hd .collapsed = hd .collapsed [:len (hd .collapsed )- 1 ]
130+ v .collapsed = v .collapsed [:len (v .collapsed )- 1 ]
124131 }
125132 return expr
126133}
127134
128135// hintInjectionVisitor is an ExtendedVisitor used to walk the target AST at
129136// injection time. It performs the rewrite of the target AST.
130137type hintInjectionVisitor struct {
131- hd * HintInjectionDonor
138+ // hd is the hint donor. Note that multiple hintInjectionVisitors could share
139+ // the same donor concurrently from different sessions, so this is read-only.
140+ hd * HintInjectionDonor
141+
142+ // walkIdx is the current position within the donor AST walk.
132143 walkIdx int
133- err error
144+
145+ // collapsed is a stack indicating whether we've collapsed the rest of the
146+ // current ValuesClause, Tuple, or Array list in the donor AST.
147+ collapsed []bool
148+
149+ // err is set if we detect a mismatch while walking the target AST.
150+ err error
134151}
135152
136153var _ ExtendedVisitor = & hintInjectionVisitor {}
137154
138155func (v * hintInjectionVisitor ) VisitPre (expr Expr ) (recurse bool , newExpr Expr ) {
139- if v .err != nil || v .hd . collapsed [len (v . hd .collapsed )- 1 ] {
156+ if v .err != nil || v .collapsed [len (v .collapsed )- 1 ] {
140157 return false , expr
141158 }
142159
@@ -161,7 +178,7 @@ func (v *hintInjectionVisitor) VisitPre(expr Expr) (recurse bool, newExpr Expr)
161178 // If the donor was a statement fingerprint, we might have collapse a Tuple
162179 // or Array into __more__. Push on a boolean tracking whether we have
163180 // collapsed it.
164- v .hd . collapsed = append (v . hd .collapsed , false )
181+ v .collapsed = append (v .collapsed , false )
165182 case * UnresolvedName :
166183 // If the donor was a fingerprint, then we might not exactly match _ or
167184 // __more__ or __more10_100__ etc in the donor. Treat these as wildcards
@@ -172,7 +189,7 @@ func (v *hintInjectionVisitor) VisitPre(expr Expr) (recurse bool, newExpr Expr)
172189 if isArityIndicatorString (donor .String ()) {
173190 // And skip the rest of the current Tuple or Array or ValuesClause if it
174191 // has been collapsed to __more__ or one of the other arity indicators.
175- v .hd . collapsed [len (v . hd .collapsed )- 1 ] = true
192+ v .collapsed [len (v .collapsed )- 1 ] = true
176193 return false , expr
177194 }
178195 }
@@ -191,13 +208,13 @@ func (v *hintInjectionVisitor) VisitPost(expr Expr) Expr {
191208 switch expr .(type ) {
192209 case * Tuple , * Array :
193210 // Pop off the boolean tracking whether we collapsed the Tuple or Array.
194- v .hd . collapsed = v .hd . collapsed [:len (v . hd .collapsed )- 1 ]
211+ v .collapsed = v .collapsed [:len (v .collapsed )- 1 ]
195212 }
196213 return expr
197214}
198215
199216func (v * hintInjectionVisitor ) VisitTablePre (expr TableExpr ) (recurse bool , newExpr TableExpr ) {
200- if v .err != nil || v .hd . collapsed [len (v . hd .collapsed )- 1 ] {
217+ if v .err != nil || v .collapsed [len (v .collapsed )- 1 ] {
201218 return false , expr
202219 }
203220
@@ -266,7 +283,7 @@ func (v *hintInjectionVisitor) VisitTablePost(expr TableExpr) TableExpr {
266283}
267284
268285func (v * hintInjectionVisitor ) VisitStatementPre (expr Statement ) (recurse bool , newExpr Statement ) {
269- if v .err != nil || v .hd . collapsed [len (v . hd .collapsed )- 1 ] {
286+ if v .err != nil || v .collapsed [len (v .collapsed )- 1 ] {
270287 return false , expr
271288 }
272289
@@ -285,7 +302,7 @@ func (v *hintInjectionVisitor) VisitStatementPre(expr Statement) (recurse bool,
285302 // If the donor was a statement fingerprint, we might have collapse a
286303 // ValuesClause into __more__. Push on a boolean tracking whether we have
287304 // collapsed it.
288- v .hd . collapsed = append (v . hd .collapsed , false )
305+ v .collapsed = append (v .collapsed , false )
289306 }
290307
291308 // Check that the corresponding donor Expr matches this node.
@@ -302,13 +319,15 @@ func (v *hintInjectionVisitor) VisitStatementPost(expr Statement) Statement {
302319 switch expr .(type ) {
303320 case * ValuesClause :
304321 // Pop off the boolean tracking whether we collapsed the ValuesClause.
305- v .hd . collapsed = v .hd . collapsed [:len (v . hd .collapsed )- 1 ]
322+ v .collapsed = v .collapsed [:len (v .collapsed )- 1 ]
306323 }
307324 return expr
308325}
309326
310327// Validate checks that the target statement exactly matches the donor (except
311328// for hints).
329+ //
330+ // It is safe to call Validate concurrently from multiple goroutines.
312331func (hd * HintInjectionDonor ) Validate (stmt Statement , fingerprintFlags FmtFlags ) error {
313332 sql := FormatStatementHideConstants (stmt , fingerprintFlags , FmtHideHints )
314333 if sql != hd .validationSQL {
@@ -332,9 +351,11 @@ func (hd *HintInjectionDonor) Validate(stmt Statement, fingerprintFlags FmtFlags
332351//
333352// No semantic checks of the donor hints are performed. It is assumed that
334353// semantic checks happen elsewhere.
354+ //
355+ // It is safe to call InjectHints concurrently from multiple goroutines.
335356func (hd * HintInjectionDonor ) InjectHints (stmt Statement ) (Statement , bool , error ) {
336357 // Walk the given statement, copying hints from the hints donor.
337- v := hintInjectionVisitor {hd : hd }
358+ v := hintInjectionVisitor {hd : hd , collapsed : [] bool { false } }
338359 newStmt , changed := WalkStmt (& v , stmt )
339360 if v .err != nil {
340361 return stmt , false , v .err
0 commit comments