Skip to content

Commit eaac709

Browse files
authored
Update SQL span name for procedures (#7557)
This PR includes updates to the SQLSanitizer, DbClientSpanNameExtractor and SqlStatementInfo to name spans according to procedure name for CALL statements. The updates to the naming logic are in the SqlSanitizer and table has been renamed to identifier as using the table variable for the procedure name would not be idiomatic. SqlStatementInfo has been updated so that the db.sql.table attribute is not included for procedures.
1 parent 268165c commit eaac709

File tree

9 files changed

+92
-50
lines changed

9 files changed

+92
-50
lines changed

Diff for: instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/db/SqlStatementInfo.java

+3-8
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,14 @@
66
package io.opentelemetry.instrumentation.api.db;
77

88
import com.google.auto.value.AutoValue;
9-
import java.util.function.Function;
109
import javax.annotation.Nullable;
1110

1211
@AutoValue
1312
public abstract class SqlStatementInfo {
1413

1514
public static SqlStatementInfo create(
16-
@Nullable String fullStatement, @Nullable String operation, @Nullable String table) {
17-
return new AutoValue_SqlStatementInfo(fullStatement, operation, table);
18-
}
19-
20-
public SqlStatementInfo mapTable(Function<String, String> mapper) {
21-
return SqlStatementInfo.create(getFullStatement(), getOperation(), mapper.apply(getTable()));
15+
@Nullable String fullStatement, @Nullable String operation, @Nullable String identifier) {
16+
return new AutoValue_SqlStatementInfo(fullStatement, operation, identifier);
2217
}
2318

2419
@Nullable
@@ -28,5 +23,5 @@ public SqlStatementInfo mapTable(Function<String, String> mapper) {
2823
public abstract String getOperation();
2924

3025
@Nullable
31-
public abstract String getTable();
26+
public abstract String getMainIdentifier();
3227
}

Diff for: instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/db/DbClientSpanNameExtractor.java

+12-11
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,12 @@ public static <REQUEST> SpanNameExtractor<REQUEST> create(
2525

2626
/**
2727
* Returns a {@link SpanNameExtractor} that constructs the span name according to DB semantic
28-
* conventions: {@code <db.operation> <db.name>.<table>}.
28+
* conventions: {@code <db.operation> <db.name>.<identifier>}.
2929
*
3030
* @see SqlStatementInfo#getOperation() used to extract {@code <db.operation>}.
3131
* @see DbClientAttributesGetter#name(Object) used to extract {@code <db.name>}.
32-
* @see SqlStatementInfo#getTable() used to extract {@code <db.table>}.
32+
* @see SqlStatementInfo#getMainIdentifier() used to extract {@code <db.table>} or stored
33+
* procedure name.
3334
*/
3435
public static <REQUEST> SpanNameExtractor<REQUEST> create(
3536
SqlClientAttributesGetter<REQUEST> getter) {
@@ -40,24 +41,24 @@ public static <REQUEST> SpanNameExtractor<REQUEST> create(
4041

4142
private DbClientSpanNameExtractor() {}
4243

43-
protected String computeSpanName(String dbName, String operation, String table) {
44+
protected String computeSpanName(String dbName, String operation, String mainIdentifier) {
4445
if (operation == null) {
4546
return dbName == null ? DEFAULT_SPAN_NAME : dbName;
4647
}
4748

4849
StringBuilder name = new StringBuilder(operation);
49-
if (dbName != null || table != null) {
50+
if (dbName != null || mainIdentifier != null) {
5051
name.append(' ');
5152
}
52-
// skip db name if table already has a db name prefixed to it
53-
if (dbName != null && (table == null || table.indexOf('.') == -1)) {
53+
// skip db name if identifier already has a db name prefixed to it
54+
if (dbName != null && (mainIdentifier == null || mainIdentifier.indexOf('.') == -1)) {
5455
name.append(dbName);
55-
if (table != null) {
56+
if (mainIdentifier != null) {
5657
name.append('.');
5758
}
5859
}
59-
if (table != null) {
60-
name.append(table);
60+
if (mainIdentifier != null) {
61+
name.append(mainIdentifier);
6162
}
6263
return name.toString();
6364
}
@@ -82,7 +83,7 @@ public String extract(REQUEST request) {
8283
private static final class SqlClientSpanNameExtractor<REQUEST>
8384
extends DbClientSpanNameExtractor<REQUEST> {
8485

85-
// a dedicated sanitizer just for extracting the operation and table name
86+
// a dedicated sanitizer just for extracting the operation and identifier name
8687
private static final SqlStatementSanitizer sanitizer = SqlStatementSanitizer.create(true);
8788

8889
private final SqlClientAttributesGetter<REQUEST> getter;
@@ -96,7 +97,7 @@ public String extract(REQUEST request) {
9697
String dbName = getter.name(request);
9798
SqlStatementInfo sanitizedStatement = sanitizer.sanitize(getter.rawStatement(request));
9899
return computeSpanName(
99-
dbName, sanitizedStatement.getOperation(), sanitizedStatement.getTable());
100+
dbName, sanitizedStatement.getOperation(), sanitizedStatement.getMainIdentifier());
100101
}
101102
}
102103
}

Diff for: instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/db/SqlClientAttributesExtractor.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ public static <REQUEST, RESPONSE> SqlClientAttributesExtractorBuilder<REQUEST, R
4343
return new SqlClientAttributesExtractorBuilder<>(getter);
4444
}
4545

46+
private static final String SQL_CALL = "CALL";
47+
4648
private final AttributeKey<String> dbTableAttribute;
4749
private final SqlStatementSanitizer sanitizer;
4850

@@ -60,8 +62,11 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST
6062
super.onStart(attributes, parentContext, request);
6163

6264
SqlStatementInfo sanitizedStatement = sanitizer.sanitize(getter.rawStatement(request));
65+
String operation = sanitizedStatement.getOperation();
6366
internalSet(attributes, SemanticAttributes.DB_STATEMENT, sanitizedStatement.getFullStatement());
64-
internalSet(attributes, SemanticAttributes.DB_OPERATION, sanitizedStatement.getOperation());
65-
internalSet(attributes, dbTableAttribute, sanitizedStatement.getTable());
67+
internalSet(attributes, SemanticAttributes.DB_OPERATION, operation);
68+
if (!SQL_CALL.equals(operation)) {
69+
internalSet(attributes, dbTableAttribute, sanitizedStatement.getMainIdentifier());
70+
}
6671
}
6772
}

Diff for: instrumentation-api-semconv/src/main/jflex/SqlSanitizer.jflex

+50-20
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,13 @@ WHITESPACE = [ \t\r\n]+
6363
}
6464

6565
/** @return text matched by current token without enclosing double quotes or backticks */
66-
private String readTableName() {
67-
String tableName = yytext();
68-
if (tableName != null && ((tableName.startsWith("\"") && tableName.endsWith("\""))
69-
|| (tableName.startsWith("`") && tableName.endsWith("`")))) {
70-
tableName = tableName.substring(1, tableName.length() - 1);
66+
private String readIdentifierName() {
67+
String identifierName = yytext();
68+
if (identifierName != null && ((identifierName.startsWith("\"") && identifierName.endsWith("\""))
69+
|| (identifierName.startsWith("`") && identifierName.endsWith("`")))) {
70+
identifierName = identifierName.substring(1, identifierName.length() - 1);
7171
}
72-
return tableName;
72+
return identifierName;
7373
}
7474

7575
// you can reference a table in the FROM clause in one of the following ways:
@@ -92,7 +92,7 @@ WHITESPACE = [ \t\r\n]+
9292
}
9393

9494
private static abstract class Operation {
95-
String mainTable = null;
95+
String mainIdentifier = null;
9696

9797
/** @return true if all statement info is gathered */
9898
boolean handleFrom() {
@@ -119,8 +119,13 @@ WHITESPACE = [ \t\r\n]+
119119
return false;
120120
}
121121

122+
/** @return true if all statement info is gathered */
123+
boolean handleNext() {
124+
return false;
125+
}
126+
122127
SqlStatementInfo getResult(String fullStatement) {
123-
return SqlStatementInfo.create(fullStatement, getClass().getSimpleName().toUpperCase(java.util.Locale.ROOT), mainTable);
128+
return SqlStatementInfo.create(fullStatement, getClass().getSimpleName().toUpperCase(java.util.Locale.ROOT), mainIdentifier);
124129
}
125130
}
126131

@@ -152,13 +157,13 @@ WHITESPACE = [ \t\r\n]+
152157
}
153158

154159
// subquery in WITH or SELECT clause, before main FROM clause; skipping
155-
mainTable = null;
160+
mainIdentifier = null;
156161
return true;
157162
}
158163

159164
boolean handleJoin() {
160165
// for SELECT statements with joined tables there's no main table
161-
mainTable = null;
166+
mainIdentifier = null;
162167
return true;
163168
}
164169

@@ -173,17 +178,17 @@ WHITESPACE = [ \t\r\n]+
173178

174179
// SELECT FROM (subquery) case
175180
if (parenLevel != 0) {
176-
mainTable = null;
181+
mainIdentifier = null;
177182
return true;
178183
}
179184

180185
// whenever >1 table is used there is no main table (e.g. unions)
181186
if (mainTableSetAlready) {
182-
mainTable = null;
187+
mainIdentifier = null;
183188
return true;
184189
}
185190

186-
mainTable = readTableName();
191+
mainIdentifier = readIdentifierName();
187192
mainTableSetAlready = true;
188193
expectingTableName = false;
189194
// start counting identifiers after encountering main from clause
@@ -199,7 +204,7 @@ WHITESPACE = [ \t\r\n]+
199204
// any other list that can appear later needs at least 4 idents)
200205
if (identifiersAfterMainFromClause > 0
201206
&& identifiersAfterMainFromClause <= FROM_TABLE_REF_MAX_IDENTIFIERS) {
202-
mainTable = null;
207+
mainIdentifier = null;
203208
return true;
204209
}
205210
return false;
@@ -219,7 +224,7 @@ WHITESPACE = [ \t\r\n]+
219224
return false;
220225
}
221226

222-
mainTable = readTableName();
227+
mainIdentifier = readIdentifierName();
223228
return true;
224229
}
225230
}
@@ -237,21 +242,33 @@ WHITESPACE = [ \t\r\n]+
237242
return false;
238243
}
239244

240-
mainTable = readTableName();
245+
mainIdentifier = readIdentifierName();
241246
return true;
242247
}
243248
}
244249

245250
private class Update extends Operation {
246251
boolean handleIdentifier() {
247-
mainTable = readTableName();
252+
mainIdentifier = readIdentifierName();
253+
return true;
254+
}
255+
}
256+
257+
private class Call extends Operation {
258+
boolean handleIdentifier() {
259+
mainIdentifier = readIdentifierName();
260+
return true;
261+
}
262+
263+
boolean handleNext() {
264+
mainIdentifier = null;
248265
return true;
249266
}
250267
}
251268

252269
private class Merge extends Operation {
253270
boolean handleIdentifier() {
254-
mainTable = readTableName();
271+
mainIdentifier = readIdentifierName();
255272
return true;
256273
}
257274
}
@@ -298,14 +315,20 @@ WHITESPACE = [ \t\r\n]+
298315
appendCurrentFragment();
299316
if (isOverLimit()) return YYEOF;
300317
}
318+
"CALL" {
319+
if (!insideComment) {
320+
setOperation(new Call());
321+
}
322+
appendCurrentFragment();
323+
if (isOverLimit()) return YYEOF;
324+
}
301325
"MERGE" {
302326
if (!insideComment) {
303327
setOperation(new Merge());
304328
}
305329
appendCurrentFragment();
306330
if (isOverLimit()) return YYEOF;
307331
}
308-
309332
"FROM" {
310333
if (!insideComment && !extractionDone) {
311334
if (operation == NoOp.INSTANCE) {
@@ -332,6 +355,13 @@ WHITESPACE = [ \t\r\n]+
332355
appendCurrentFragment();
333356
if (isOverLimit()) return YYEOF;
334357
}
358+
"NEXT" {
359+
if (!insideComment && !extractionDone) {
360+
extractionDone = operation.handleNext();
361+
}
362+
appendCurrentFragment();
363+
if (isOverLimit()) return YYEOF;
364+
}
335365
{COMMA} {
336366
if (!insideComment && !extractionDone) {
337367
extractionDone = operation.handleComma();
@@ -407,4 +437,4 @@ WHITESPACE = [ \t\r\n]+
407437
appendCurrentFragment();
408438
if (isOverLimit()) return YYEOF;
409439
}
410-
}
440+
}

Diff for: instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/db/SqlStatementSanitizerTest.java

+12-4
Original file line numberDiff line numberDiff line change
@@ -232,12 +232,13 @@ public Stream<? extends Arguments> provideArguments(ExtensionContext context) th
232232

233233
static class SimplifyArgs implements ArgumentsProvider {
234234

235-
static Function<String, SqlStatementInfo> expect(String operation, String table) {
236-
return sql -> SqlStatementInfo.create(sql, operation, table);
235+
static Function<String, SqlStatementInfo> expect(String operation, String identifier) {
236+
return sql -> SqlStatementInfo.create(sql, operation, identifier);
237237
}
238238

239-
static Function<String, SqlStatementInfo> expect(String sql, String operation, String table) {
240-
return ignored -> SqlStatementInfo.create(sql, operation, table);
239+
static Function<String, SqlStatementInfo> expect(
240+
String sql, String operation, String identifier) {
241+
return ignored -> SqlStatementInfo.create(sql, operation, identifier);
241242
}
242243

243244
@Override
@@ -275,6 +276,7 @@ public Stream<? extends Arguments> provideArguments(ExtensionContext context) th
275276
Arguments.of("/* update comment */ select * from table1", expect("SELECT", "table1")),
276277
Arguments.of("select /*((*/abc from table", expect("SELECT", "table")),
277278
Arguments.of("SeLeCT * FrOm TAblE", expect("SELECT", "table")),
279+
Arguments.of("select next value in hibernate_sequence", expect("SELECT", null)),
278280

279281
// hibernate/jpa
280282
Arguments.of("FROM schema.table", expect("SELECT", "schema.table")),
@@ -308,6 +310,12 @@ public Stream<? extends Arguments> provideArguments(ExtensionContext context) th
308310
expect("update \"my table\" set answer=?", "UPDATE", "my table")),
309311
Arguments.of("update /*table", expect("UPDATE", null)),
310312

313+
// Call
314+
Arguments.of("call test_proc()", expect("CALL", "test_proc")),
315+
Arguments.of("call test_proc", expect("CALL", "test_proc")),
316+
Arguments.of("call next value in hibernate_sequence", expect("CALL", null)),
317+
Arguments.of("call db.test_proc", expect("CALL", "db.test_proc")),
318+
311319
// Merge
312320
Arguments.of("merge into table", expect("MERGE", "table")),
313321
Arguments.of("merge into `my table`", expect("MERGE", "my table")),

Diff for: instrumentation/hibernate/hibernate-4.0/javaagent/src/test/groovy/SpringJpaTest.groovy

+2-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ class SpringJpaTest extends AgentInstrumentationSpecification {
112112
}
113113
if (!isHibernate4) {
114114
span(2) {
115-
name "test"
115+
name "CALL test"
116116
kind CLIENT
117117
childOf span(1)
118118
attributes {
@@ -121,6 +121,7 @@ class SpringJpaTest extends AgentInstrumentationSpecification {
121121
"$SemanticAttributes.DB_USER" "sa"
122122
"$SemanticAttributes.DB_STATEMENT" "call next value for hibernate_sequence"
123123
"$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:"
124+
"$SemanticAttributes.DB_OPERATION" "CALL"
124125
}
125126
}
126127
span(3) {

Diff for: instrumentation/hibernate/hibernate-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/OperationNameUtil.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ public static String getOperationNameForQuery(String query) {
2222
SqlStatementInfo info = sanitizer.sanitize(query);
2323
if (info.getOperation() != null) {
2424
operation = info.getOperation();
25-
if (info.getTable() != null) {
26-
operation += " " + info.getTable();
25+
if (info.getMainIdentifier() != null) {
26+
operation += " " + info.getMainIdentifier();
2727
}
2828
}
2929
return operation;

Diff for: instrumentation/hibernate/hibernate-procedure-call-4.3/javaagent/src/test/groovy/ProcedureCallTest.groovy

+2-1
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ class ProcedureCallTest extends AgentInstrumentationSpecification {
104104
}
105105
}
106106
span(2) {
107-
name "test"
107+
name "CALL test.TEST_PROC"
108108
kind CLIENT
109109
childOf span(1)
110110
attributes {
@@ -113,6 +113,7 @@ class ProcedureCallTest extends AgentInstrumentationSpecification {
113113
"$SemanticAttributes.DB_USER" "sa"
114114
"$SemanticAttributes.DB_STATEMENT" "{call TEST_PROC()}"
115115
"$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:"
116+
"$SemanticAttributes.DB_OPERATION" "CALL"
116117
}
117118
}
118119
span(3) {

Diff for: instrumentation/spring/spring-data/spring-data-common/testing/src/main/groovy/AbstractSpringJpaTest.groovy

+2-1
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ abstract class AbstractSpringJpaTest<ENTITY, REPOSITORY extends JpaRepository<EN
111111
if (!isHibernate4) {
112112
offset = 1
113113
span(1) {
114-
name "test"
114+
name "CALL test"
115115
kind CLIENT
116116
childOf span(0)
117117
attributes {
@@ -120,6 +120,7 @@ abstract class AbstractSpringJpaTest<ENTITY, REPOSITORY extends JpaRepository<EN
120120
"$SemanticAttributes.DB_USER" "sa"
121121
"$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:"
122122
"$SemanticAttributes.DB_STATEMENT" ~/^call next value for /
123+
"$SemanticAttributes.DB_OPERATION" "CALL"
123124
}
124125
}
125126
}

0 commit comments

Comments
 (0)