24
24
*/
25
25
package co .elastic .apm .agent .jdbc .signature ;
26
26
27
+ import javax .annotation .Nullable ;
27
28
import java .util .concurrent .ConcurrentHashMap ;
28
29
import java .util .concurrent .ConcurrentMap ;
29
30
30
31
import static co .elastic .apm .agent .jdbc .signature .Scanner .Token .EOF ;
31
32
import static co .elastic .apm .agent .jdbc .signature .Scanner .Token .FROM ;
32
33
import static co .elastic .apm .agent .jdbc .signature .Scanner .Token .IDENT ;
34
+ import static co .elastic .apm .agent .jdbc .signature .Scanner .Token .INTO ;
33
35
import static co .elastic .apm .agent .jdbc .signature .Scanner .Token .LPAREN ;
34
36
import static co .elastic .apm .agent .jdbc .signature .Scanner .Token .RPAREN ;
35
37
@@ -54,55 +56,61 @@ public class SignatureParser {
54
56
* When relying on weak keys, we would not leverage any caching benefits if the query string is collected.
55
57
* That means that we are leaking Strings but as the size of the map is limited that should not be an issue.
56
58
*/
57
- private final static ConcurrentMap <String , String > signatureCache = new ConcurrentHashMap <String , String >(DISABLE_CACHE_THRESHOLD , 0.5f , Runtime .getRuntime ().availableProcessors ());
59
+ private final static ConcurrentMap <String , String []> signatureCache = new ConcurrentHashMap <String , String []>(DISABLE_CACHE_THRESHOLD ,
60
+ 0.5f , Runtime .getRuntime ().availableProcessors ());
58
61
59
62
private final Scanner scanner = new Scanner ();
60
63
61
64
public void querySignature (String query , StringBuilder signature , boolean preparedStatement ) {
65
+ querySignature (query , signature , null , preparedStatement );
66
+ }
62
67
68
+ public void querySignature (String query , StringBuilder signature , @ Nullable StringBuilder dbLink , boolean preparedStatement ) {
63
69
final boolean cacheable = preparedStatement // non-prepared statements are likely to be dynamic strings
64
70
&& QUERY_LENGTH_CACHE_LOWER_THRESHOLD < query .length ()
65
71
&& query .length () < QUERY_LENGTH_CACHE_UPPER_THRESHOLD ;
66
72
if (cacheable ) {
67
- final String cachedSignature = signatureCache .get (query );
73
+ final String [] cachedSignature = signatureCache .get (query );
68
74
if (cachedSignature != null ) {
69
- signature .append (cachedSignature );
75
+ signature .append (cachedSignature [0 ]);
76
+ if (dbLink != null ) {
77
+ dbLink .append (cachedSignature [1 ]);
78
+ }
70
79
return ;
71
80
}
72
81
}
73
82
74
83
scanner .setQuery (query );
75
- parse (query , signature );
84
+ parse (query , signature , dbLink );
76
85
77
86
if (cacheable && signatureCache .size () <= DISABLE_CACHE_THRESHOLD ) {
78
87
// we don't mind a small overshoot due to race conditions
79
- signatureCache .put (query , signature .toString ());
88
+ signatureCache .put (query , new String []{ signature .toString (), dbLink != null ? dbLink . toString () : "" } );
80
89
}
81
90
}
82
91
83
- private void parse (String query , StringBuilder signature ) {
92
+ private void parse (String query , StringBuilder signature , @ Nullable StringBuilder dbLink ) {
84
93
final Scanner .Token firstToken = scanner .scanWhile (Scanner .Token .COMMENT );
85
94
switch (firstToken ) {
86
95
case CALL :
87
96
signature .append ("CALL" );
88
97
if (scanner .scanUntil (Scanner .Token .IDENT )) {
89
- signature .append (' ' );
90
- scanner .appendCurrentTokenText (signature );
98
+ appendIdentifiers (signature , dbLink );
91
99
}
92
100
return ;
93
101
case DELETE :
94
102
signature .append ("DELETE" );
95
103
if (scanner .scanUntil (FROM ) && scanner .scanUntil (Scanner .Token .IDENT )) {
96
- signature .append (" FROM " );
97
- appendIdentifiers (signature );
104
+ signature .append (" FROM" );
105
+ appendIdentifiers (signature , dbLink );
98
106
}
99
107
return ;
100
108
case INSERT :
101
109
case REPLACE :
102
110
signature .append (firstToken .name ());
103
111
if (scanner .scanUntil (Scanner .Token .INTO ) && scanner .scanUntil (Scanner .Token .IDENT )) {
104
- signature .append (" INTO " );
105
- appendIdentifiers (signature );
112
+ signature .append (" INTO" );
113
+ appendIdentifiers (signature , dbLink );
106
114
}
107
115
return ;
108
116
case SELECT :
@@ -116,8 +124,8 @@ private void parse(String query, StringBuilder signature) {
116
124
} else if (t == FROM ) {
117
125
if (level == 0 ) {
118
126
if (scanner .scanToken (Scanner .Token .IDENT )) {
119
- signature .append (" FROM " );
120
- appendIdentifiers (signature );
127
+ signature .append (" FROM" );
128
+ appendIdentifiers (signature , dbLink );
121
129
} else {
122
130
return ;
123
131
}
@@ -128,7 +136,7 @@ private void parse(String query, StringBuilder signature) {
128
136
case UPDATE :
129
137
signature .append ("UPDATE" );
130
138
// Scan for the table name
131
- boolean hasPeriod = false , hasFirstPeriod = false ;
139
+ boolean hasPeriod = false , hasFirstPeriod = false , isDbLink = false ;
132
140
if (scanner .scanToken (IDENT )) {
133
141
signature .append (' ' );
134
142
scanner .appendCurrentTokenText (signature );
@@ -145,6 +153,11 @@ private void parse(String query, StringBuilder signature) {
145
153
signature .setLength (0 );
146
154
signature .append ("UPDATE " );
147
155
scanner .appendCurrentTokenText (signature );
156
+ } else if (isDbLink ) {
157
+ if (dbLink != null ) {
158
+ scanner .appendCurrentTokenText (dbLink );
159
+ }
160
+ isDbLink = false ;
148
161
}
149
162
// Two adjacent identifiers found after the first period.
150
163
// Ignore the secondary ones, in case they are unknown keywords.
@@ -155,23 +168,62 @@ private void parse(String query, StringBuilder signature) {
155
168
signature .append ('.' );
156
169
break ;
157
170
default :
158
- return ;
171
+ if ("@" .equals (scanner .text ())) {
172
+ isDbLink = true ;
173
+ break ;
174
+ } else {
175
+ return ;
176
+ }
159
177
}
160
178
}
161
179
}
162
180
return ;
181
+ case MERGE :
182
+ signature .append ("MERGE" );
183
+ if (scanner .scanToken (INTO ) && scanner .scanUntil (Scanner .Token .IDENT )) {
184
+ signature .append (" INTO" );
185
+ appendIdentifiers (signature , dbLink );
186
+ }
187
+ return ;
163
188
default :
164
189
query = query .trim ();
165
190
final int indexOfWhitespace = query .indexOf (' ' );
166
191
signature .append (query , 0 , indexOfWhitespace > 0 ? indexOfWhitespace : query .length ());
167
192
}
168
193
}
169
194
170
- private void appendIdentifiers (StringBuilder signature ) {
195
+ private void appendIdentifiers (StringBuilder signature , @ Nullable StringBuilder dbLink ) {
196
+ signature .append (' ' );
171
197
scanner .appendCurrentTokenText (signature );
172
- while (scanner .scanToken (Scanner .Token .PERIOD ) && scanner .scanToken (Scanner .Token .IDENT )) {
173
- signature .append ('.' );
174
- scanner .appendCurrentTokenText (signature );
198
+ boolean connectedIdents = false , isDbLink = false ;
199
+ for (Scanner .Token t = scanner .scan (); t != EOF ; t = scanner .scan ()) {
200
+ switch (t ) {
201
+ case IDENT :
202
+ // do not add tokens which are separated by a space
203
+ if (connectedIdents ) {
204
+ scanner .appendCurrentTokenText (signature );
205
+ connectedIdents = false ;
206
+ } else {
207
+ if (isDbLink ) {
208
+ if (dbLink != null ) {
209
+ scanner .appendCurrentTokenText (dbLink );
210
+ }
211
+ }
212
+ return ;
213
+ }
214
+ break ;
215
+ case PERIOD :
216
+ signature .append ('.' );
217
+ connectedIdents = true ;
218
+ break ;
219
+ case USING :
220
+ return ;
221
+ default :
222
+ if ("@" .equals (scanner .text ())) {
223
+ isDbLink = true ;
224
+ }
225
+ break ;
226
+ }
175
227
}
176
228
}
177
229
}
0 commit comments