Skip to content

Commit 25029b4

Browse files
committed
Add integration tests for in-memory jdbc drivers.
1 parent 14b834a commit 25029b4

File tree

5 files changed

+293
-13
lines changed

5 files changed

+293
-13
lines changed

dd-java-agent-ittests/dd-java-agent-ittests.gradle

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ dependencies {
2828
testCompile group: 'javax.jms', name: 'javax.jms-api', version: '2.0.1'
2929
testCompile group: 'org.apache.activemq.tooling', name: 'activemq-junit', version: '5.14.5'
3030
testCompile group: 'org.apache.activemq', name: 'activemq-broker', version: '5.14.5'
31+
32+
// JDBC tests:
33+
testCompile group: 'com.h2database', name: 'h2', version: '1.4.196'
34+
testCompile group: 'org.hsqldb', name: 'hsqldb', version: '2.4.0'
35+
testCompile group: 'org.apache.derby', name: 'derby', version: '10.14.1.0'
3136
}
3237

3338
configurations.all {
Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
package com.datadoghq.agent.integration.jdbc
2+
3+
import com.datadoghq.trace.DDTracer
4+
import com.datadoghq.trace.writer.ListWriter
5+
import io.opentracing.util.GlobalTracer
6+
import org.apache.derby.jdbc.EmbeddedDriver
7+
import org.h2.Driver
8+
import org.hsqldb.jdbc.JDBCDriver
9+
import spock.lang.Shared
10+
import spock.lang.Specification
11+
import spock.lang.Unroll
12+
13+
import java.lang.reflect.Field
14+
import java.sql.Connection
15+
import java.sql.PreparedStatement
16+
import java.sql.ResultSet
17+
import java.sql.Statement
18+
19+
class JDBCInstrumentationTest extends Specification {
20+
21+
ListWriter writer = new ListWriter()
22+
DDTracer tracer = new DDTracer(writer)
23+
24+
@Shared
25+
private Map<String, Connection> connections
26+
27+
def setupSpec() {
28+
Connection h2Connection = new Driver().connect("jdbc:h2:mem:integ-test", null)
29+
Connection hsqlConnection = new JDBCDriver().connect("jdbc:hsqldb:mem:integTest", null)
30+
Connection derbyConnection = new EmbeddedDriver().connect("jdbc:derby:memory:integTest;create=true", null)
31+
32+
connections = [
33+
h2 : h2Connection,
34+
derby : derbyConnection,
35+
hsqldb: hsqlConnection,
36+
]
37+
}
38+
39+
def cleanupSpec() {
40+
connections.values().each {
41+
it.close()
42+
}
43+
}
44+
45+
def setup() {
46+
try {
47+
GlobalTracer.register(tracer)
48+
} catch (final Exception e) {
49+
// Force it anyway using reflection
50+
final Field field = GlobalTracer.getDeclaredField("tracer")
51+
field.setAccessible(true)
52+
field.set(null, tracer)
53+
}
54+
writer.start()
55+
assert GlobalTracer.isRegistered()
56+
}
57+
58+
@Unroll
59+
def "basic statement on #driver generates spans"() {
60+
setup:
61+
Statement statement = connection.createStatement()
62+
ResultSet resultSet = statement.executeQuery(query)
63+
64+
expect:
65+
resultSet.next()
66+
resultSet.getInt(1) == 3
67+
writer.size() == 1
68+
69+
def trace = writer.firstTrace()
70+
trace.size() == 1
71+
def span = trace[0]
72+
73+
span.context().operationName == "${driver}.query"
74+
span.serviceName == driver
75+
span.resourceName == query
76+
span.type == "sql"
77+
!span.context().getErrorFlag()
78+
span.context().parentId == 0
79+
80+
81+
def tags = span.context().tags
82+
tags["db.type"] == driver
83+
tags["span.kind"] == "client"
84+
tags["component"] == "java-jdbc-statement"
85+
86+
tags["db.jdbc.url"].contains(driver)
87+
tags["span.origin.type"] != null
88+
89+
tags["thread.name"] != null
90+
tags["thread.id"] != null
91+
tags.size() == 7
92+
93+
cleanup:
94+
statement.close()
95+
96+
where:
97+
driver | connection | query
98+
"h2" | connections.get("h2") | "SELECT 3"
99+
"derby" | connections.get("derby") | "SELECT 3 FROM SYSIBM.SYSDUMMY1"
100+
"hsqldb" | connections.get("hsqldb") | "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS"
101+
}
102+
103+
@Unroll
104+
def "prepared statement execute on #driver generates a span"() {
105+
setup:
106+
PreparedStatement statement = connection.prepareStatement(query)
107+
assert statement.execute()
108+
ResultSet resultSet = statement.resultSet
109+
110+
expect:
111+
resultSet.next()
112+
resultSet.getInt(1) == 3
113+
writer.size() == 1
114+
115+
def trace = writer.firstTrace()
116+
trace.size() == 1
117+
def span = trace[0]
118+
119+
span.context().operationName == "${driver}.query"
120+
span.serviceName == driver
121+
span.resourceName == query
122+
span.type == "sql"
123+
!span.context().getErrorFlag()
124+
span.context().parentId == 0
125+
126+
127+
def tags = span.context().tags
128+
tags["db.type"] == driver
129+
tags["span.kind"] == "client"
130+
tags["component"] == "java-jdbc-prepared_statement"
131+
132+
tags["db.jdbc.url"].contains(driver)
133+
tags["span.origin.type"] != null
134+
135+
tags["thread.name"] != null
136+
tags["thread.id"] != null
137+
tags.size() == 7
138+
139+
cleanup:
140+
statement.close()
141+
142+
where:
143+
driver | connection | query
144+
"h2" | connections.get("h2") | "SELECT 3"
145+
"derby" | connections.get("derby") | "SELECT 3 FROM SYSIBM.SYSDUMMY1"
146+
"hsqldb" | connections.get("hsqldb") | "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS"
147+
}
148+
149+
@Unroll
150+
def "prepared statement query on #driver generates a span"() {
151+
setup:
152+
PreparedStatement statement = connection.prepareStatement(query)
153+
ResultSet resultSet = statement.executeQuery()
154+
155+
expect:
156+
resultSet.next()
157+
resultSet.getInt(1) == 3
158+
writer.size() == 1
159+
160+
def trace = writer.firstTrace()
161+
trace.size() == 1
162+
def span = trace[0]
163+
164+
span.context().operationName == "${driver}.query"
165+
span.serviceName == driver
166+
span.resourceName == query
167+
span.type == "sql"
168+
!span.context().getErrorFlag()
169+
span.context().parentId == 0
170+
171+
172+
def tags = span.context().tags
173+
tags["db.type"] == driver
174+
tags["span.kind"] == "client"
175+
tags["component"] == "java-jdbc-prepared_statement"
176+
177+
tags["db.jdbc.url"].contains(driver)
178+
tags["span.origin.type"] != null
179+
180+
tags["thread.name"] != null
181+
tags["thread.id"] != null
182+
tags.size() == 7
183+
184+
cleanup:
185+
statement.close()
186+
187+
where:
188+
driver | connection | query
189+
"h2" | connections.get("h2") | "SELECT 3"
190+
"derby" | connections.get("derby") | "SELECT 3 FROM SYSIBM.SYSDUMMY1"
191+
"hsqldb" | connections.get("hsqldb") | "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS"
192+
}
193+
194+
@Unroll
195+
def "statement update on #driver generates a span"() {
196+
setup:
197+
Statement statement = connection.createStatement()
198+
def sql = connection.nativeSQL(query)
199+
200+
expect:
201+
!statement.execute(sql)
202+
statement.updateCount == 0
203+
204+
writer.size() == 1
205+
206+
def trace = writer.firstTrace()
207+
trace.size() == 1
208+
def span = trace[0]
209+
210+
span.context().operationName == "${driver}.query"
211+
span.serviceName == driver
212+
span.resourceName == query
213+
span.type == "sql"
214+
!span.context().getErrorFlag()
215+
span.context().parentId == 0
216+
217+
218+
def tags = span.context().tags
219+
tags["db.type"] == driver
220+
tags["span.kind"] == "client"
221+
tags["component"] == "java-jdbc-statement"
222+
223+
tags["db.jdbc.url"].contains(driver)
224+
tags["span.origin.type"] != null
225+
226+
tags["thread.name"] != null
227+
tags["thread.id"] != null
228+
tags.size() == 7
229+
230+
cleanup:
231+
statement.close()
232+
233+
where:
234+
driver | connection | query
235+
"h2" | connections.get("h2") | "CREATE TABLE S_H2 (id INTEGER not NULL, PRIMARY KEY ( id ))"
236+
"derby" | connections.get("derby") | "CREATE TABLE S_DERBY (id INTEGER not NULL, PRIMARY KEY ( id ))"
237+
"hsqldb" | connections.get("hsqldb") | "CREATE TABLE PUBLIC.S_HSQLDB (id INTEGER not NULL, PRIMARY KEY ( id ))"
238+
}
239+
240+
@Unroll
241+
def "prepared statement update on #driver generates a span"() {
242+
setup:
243+
def sql = connection.nativeSQL(query)
244+
PreparedStatement statement = connection.prepareStatement(sql)
245+
246+
expect:
247+
statement.executeUpdate() == 0
248+
writer.size() == 1
249+
250+
def trace = writer.firstTrace()
251+
trace.size() == 1
252+
def span = trace[0]
253+
254+
span.context().operationName == "${driver}.query"
255+
span.serviceName == driver
256+
span.resourceName == query
257+
span.type == "sql"
258+
!span.context().getErrorFlag()
259+
span.context().parentId == 0
260+
261+
262+
def tags = span.context().tags
263+
tags["db.type"] == driver
264+
tags["span.kind"] == "client"
265+
tags["component"] == "java-jdbc-prepared_statement"
266+
267+
tags["db.jdbc.url"].contains(driver)
268+
tags["span.origin.type"] != null
269+
270+
tags["thread.name"] != null
271+
tags["thread.id"] != null
272+
tags.size() == 7
273+
274+
cleanup:
275+
statement.close()
276+
277+
where:
278+
driver | connection | query
279+
"h2" | connections.get("h2") | "CREATE TABLE PS_H2 (id INTEGER not NULL, PRIMARY KEY ( id ))"
280+
// Derby calls executeLargeUpdate from executeUpdate thus generating a nested span breaking this test.
281+
"hsqldb" | connections.get("hsqldb") | "CREATE TABLE PUBLIC.PS_HSQLDB (id INTEGER not NULL, PRIMARY KEY ( id ))"
282+
}
283+
}

dd-java-agent/src/main/java/com/datadoghq/agent/instrumentation/jdbc/DriverInstrumentation.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public static void addDBInfo(
4141
// Remove end of url to prevent passwords from leaking:
4242
final String sanitizedURL = url.replaceAll("[?;].*", "");
4343
final String type = url.split(":")[1];
44-
final String dbUser = info.getProperty("user");
44+
final String dbUser = info == null ? null : info.getProperty("user");
4545
connectionInfo.put(connection, new DBInfo(sanitizedURL, type, dbUser));
4646
}
4747
}

dd-java-agent/src/main/java/com/datadoghq/agent/instrumentation/jdbc/PreparedStatementInstrumentation.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
44
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
5+
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
56
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
67
import static net.bytebuddy.matcher.ElementMatchers.named;
78
import static net.bytebuddy.matcher.ElementMatchers.not;
@@ -30,7 +31,7 @@ public AgentBuilder instrument(final AgentBuilder agentBuilder) {
3031
.transform(
3132
new AgentBuilder.Transformer.ForAdvice()
3233
.advice(
33-
nameStartsWith("execute").and(takesArguments(0)),
34+
nameStartsWith("execute").and(takesArguments(0)).and(isPublic()),
3435
PreparedStatementAdvice.class.getName()))
3536
.asDecorator();
3637
}
@@ -62,11 +63,6 @@ public static ActiveSpan startSpan(@Advice.This final PreparedStatement statemen
6263
span.setTag(DDTags.SPAN_TYPE, "sql");
6364
span.setTag("span.origin.type", statement.getClass().getName());
6465
span.setTag("db.jdbc.url", dbInfo.getUrl());
65-
try {
66-
span.setTag("db.schema", connection.getSchema());
67-
} catch (final Throwable e) {
68-
// Ignore...
69-
}
7066

7167
if (dbInfo.getUser() != null) {
7268
Tags.DB_USER.set(span, dbInfo.getUser());

dd-java-agent/src/main/java/com/datadoghq/agent/instrumentation/jdbc/StatementInstrumentation.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
44
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
5+
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
56
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
67
import static net.bytebuddy.matcher.ElementMatchers.named;
78
import static net.bytebuddy.matcher.ElementMatchers.not;
@@ -30,7 +31,7 @@ public AgentBuilder instrument(final AgentBuilder agentBuilder) {
3031
.transform(
3132
new AgentBuilder.Transformer.ForAdvice()
3233
.advice(
33-
nameStartsWith("execute").and(takesArgument(0, String.class)),
34+
nameStartsWith("execute").and(takesArgument(0, String.class)).and(isPublic()),
3435
StatementAdvice.class.getName()))
3536
.asDecorator();
3637
}
@@ -62,11 +63,6 @@ public static ActiveSpan startSpan(
6263
span.setTag(DDTags.SPAN_TYPE, "sql");
6364
span.setTag("span.origin.type", statement.getClass().getName());
6465
span.setTag("db.jdbc.url", dbInfo.getUrl());
65-
try {
66-
span.setTag("db.schema", connection.getSchema());
67-
} catch (final Throwable e) {
68-
// Ignore...
69-
}
7066

7167
if (dbInfo.getUser() != null) {
7268
Tags.DB_USER.set(span, dbInfo.getUser());

0 commit comments

Comments
 (0)