Skip to content

Commit 5f3a4f9

Browse files
authored
Remove calls to Statement.getUpdateCount (#1147)
Remove calls to getUpdateCount
1 parent 110da2a commit 5f3a4f9

File tree

7 files changed

+26
-206
lines changed

7 files changed

+26
-206
lines changed

CHANGELOG.asciidoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ This is not a breaking change, so former
4343
* Fix breakdown metrics span sub-types {pull}1113[#1113]
4444
* Fix flaky gRPC server instrumentation {pull}1122[#1122]
4545
* Fix side effect of calling `Statement.getUpdateCount` more than once {pull}1139[#1139]
46+
* Stop capturing JDBC affected rows count using `Statement.getUpdateCount` to prevent unreliable side-effects {pull}1147[#1147]
4647
4748
4849
[[release-notes-1.x]]

apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/StatementInstrumentation.java

Lines changed: 8 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -126,19 +126,6 @@ public static void onAfterExecute(@Advice.This Statement statement,
126126
return;
127127
}
128128

129-
if (t == null && jdbcHelperManager != null) {
130-
JdbcHelper helper = jdbcHelperManager.getForClassLoaderOfClass(Statement.class);
131-
if (helper != null) {
132-
int count = helper.getAndStoreUpdateCount(statement);
133-
if (count != Integer.MIN_VALUE) {
134-
span.getContext()
135-
.getDb()
136-
.withAffectedRowsCount(count);
137-
}
138-
}
139-
140-
}
141-
142129
span.captureException(t)
143130
.deactivate()
144131
.end();
@@ -237,10 +224,10 @@ public static void storeSql(@Advice.This Statement statement, @Advice.Argument(0
237224

238225
/**
239226
* Instruments:
240-
* <ul>
241-
* <li>{@link Statement#executeBatch()} </li>
242-
* <li>{@link Statement#executeLargeBatch()} (java8)</li>
243-
* </ul>
227+
* <ul>
228+
* <li>{@link Statement#executeBatch()} </li>
229+
* <li>{@link Statement#executeLargeBatch()} (java8)</li>
230+
* </ul>
244231
*/
245232
public static class ExecuteBatchInstrumentation extends StatementInstrumentation {
246233
public ExecuteBatchInstrumentation(ElasticApmTracer tracer) {
@@ -306,10 +293,10 @@ public static void onAfterExecute(@Advice.Enter @Nullable Span span,
306293

307294
/**
308295
* Instruments:
309-
* <ul>
310-
* <li>{@link PreparedStatement#executeUpdate()} </li>
311-
* <li>{@link PreparedStatement#executeLargeUpdate()} ()} (java8)</li>
312-
* </ul>
296+
* <ul>
297+
* <li>{@link PreparedStatement#executeUpdate()} </li>
298+
* <li>{@link PreparedStatement#executeLargeUpdate()} ()} (java8)</li>
299+
* </ul>
313300
*/
314301
public static class ExecuteUpdateNoQueryInstrumentation extends StatementInstrumentation {
315302
public ExecuteUpdateNoQueryInstrumentation(ElasticApmTracer tracer) {
@@ -396,59 +383,11 @@ public static void onAfterExecute(@Advice.This Statement statement,
396383
return;
397384
}
398385

399-
if (t == null && jdbcHelperManager != null) {
400-
JdbcHelper jdbcHelper = jdbcHelperManager.getForClassLoaderOfClass(Statement.class);
401-
if (jdbcHelper != null) {
402-
span.getContext()
403-
.getDb()
404-
.withAffectedRowsCount(jdbcHelper.getAndStoreUpdateCount(statement));
405-
}
406-
}
407-
408386
span.captureException(t)
409387
.deactivate()
410388
.end();
411389
}
412390

413391
}
414392

415-
416-
/**
417-
* Instruments:
418-
* <ul>
419-
* <li>{@link Statement#getUpdateCount()}</li>
420-
* </ul>
421-
*/
422-
public static class GetUpdateCountInstrumentation extends StatementInstrumentation {
423-
424-
public GetUpdateCountInstrumentation(ElasticApmTracer tracer) {
425-
super(tracer,
426-
named("getUpdateCount")
427-
.and(takesArguments(0))
428-
.and(isPublic())
429-
);
430-
}
431-
432-
@Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
433-
private static void onExit(@Advice.This Statement statement,
434-
@Advice.Thrown @Nullable Throwable thrown,
435-
@Advice.Return(readOnly = false) int returnValue) {
436-
437-
if (tracer == null || jdbcHelperManager == null) {
438-
return;
439-
}
440-
441-
JdbcHelper helperImpl = jdbcHelperManager.getForClassLoaderOfClass(Statement.class);
442-
if (helperImpl == null) {
443-
return;
444-
}
445-
446-
int storedValue = helperImpl.getAndClearStoredUpdateCount(statement);
447-
448-
if (thrown == null && storedValue != Integer.MIN_VALUE) {
449-
returnValue = storedValue;
450-
}
451-
452-
}
453-
}
454393
}

apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/helper/JdbcHelper.java

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -69,24 +69,4 @@ public String retrieveSqlForStatement(Object statement) {
6969
@Nullable
7070
public abstract Span createJdbcSpan(@Nullable String sql, Object statement, @Nullable TraceContextHolder<?> parent, boolean preparedStatement);
7171

72-
/**
73-
* Safely wraps calls to {@link java.sql.Statement#getUpdateCount()} and stores last result.
74-
* <p>
75-
* getUpdateCount javadoc indicates that this method should be called only once.
76-
* In practice, adding this extra call seem to not have noticeable side effects on most databases but Oracle.
77-
* </p>
78-
*
79-
* @param statement {@code java.sql.Statement} instance
80-
* @return {@link Integer#MIN_VALUE} if statement does not support this feature, returned value otherwise
81-
*/
82-
public abstract int getAndStoreUpdateCount(Object statement);
83-
84-
/**
85-
* Get and clears stored update count (if any) for a given statement.
86-
*
87-
* @param statement {@code java.sql.Statement} instance
88-
* @return {@link Integer#MIN_VALUE} if there is no stored value for this statement
89-
*/
90-
public abstract int getAndClearStoredUpdateCount(Object statement);
91-
9272
}

apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/helper/JdbcHelperImpl.java

Lines changed: 0 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,9 @@ public class JdbcHelperImpl extends JdbcHelper {
5252
// have the expected behavior, thus, any direct reference to `JdbcHelperImpl` should only be obtained from the
5353
// HelperClassManager<JdbcHelper> instance.
5454
private final WeakConcurrentMap<Connection, ConnectionMetaData> metaDataMap = DataStructures.createWeakConcurrentMapWithCleanerThread();
55-
private final WeakConcurrentMap<Class<?>, Boolean> updateCountSupported = new WeakConcurrentMap.WithInlinedExpunction<Class<?>, Boolean>();
5655
private final WeakConcurrentMap<Class<?>, Boolean> metadataSupported = new WeakConcurrentMap.WithInlinedExpunction<Class<?>, Boolean>();
5756
private final WeakConcurrentMap<Class<?>, Boolean> connectionSupported = new WeakConcurrentMap.WithInlinedExpunction<Class<?>, Boolean>();
5857

59-
// keeps track of non-idempotent implementations of getUpdateCount
60-
private final WeakConcurrentMap<Class<?>, Boolean> idempotentGetUpdateCount = new WeakConcurrentMap.WithInlinedExpunction<Class<?>, Boolean>();
61-
62-
// stores statements update count to avoid side-effects of calling Statement.getUpdateCount()
63-
private final WeakConcurrentMap<Object, Integer> statementsUpdateCount = new WeakConcurrentMap.WithInlinedExpunction<>();
64-
6558
@VisibleForAdvice
6659
public final ThreadLocal<SignatureParser> SIGNATURE_PARSER_THREAD_LOCAL = new ThreadLocal<SignatureParser>() {
6760
@Override
@@ -73,10 +66,8 @@ protected SignatureParser initialValue() {
7366
@Override
7467
public void clearInternalStorage() {
7568
metaDataMap.clear();
76-
updateCountSupported.clear();
7769
metadataSupported.clear();
7870
connectionSupported.clear();
79-
statementsUpdateCount.clear();
8071
}
8172

8273
@Override
@@ -189,55 +180,6 @@ private Connection safeGetConnection(Statement statement) {
189180
return connection;
190181
}
191182

192-
193-
@Override
194-
public int getAndStoreUpdateCount(Object statement) {
195-
int result = Integer.MIN_VALUE;
196-
if (!(statement instanceof Statement)) {
197-
return result;
198-
}
199-
200-
Class<?> type = statement.getClass();
201-
Boolean supported = isSupported(updateCountSupported, type);
202-
if (supported == Boolean.FALSE) {
203-
return result;
204-
}
205-
206-
try {
207-
result = ((Statement) statement).getUpdateCount();
208-
if (supported == null) {
209-
markSupported(updateCountSupported, type);
210-
}
211-
212-
// in order to minimize allocation, we keep track of implementation classes that are not idempotent,
213-
// that allows avoiding extra work for the most common idempotent case.
214-
Boolean isIdempotent = isSupported(idempotentGetUpdateCount, type);
215-
if (isIdempotent == null) {
216-
int secondResult = ((Statement) statement).getUpdateCount();
217-
isIdempotent = (secondResult == result) ? Boolean.TRUE : Boolean.FALSE;
218-
idempotentGetUpdateCount.put(type, isIdempotent);
219-
}
220-
221-
if (isIdempotent != Boolean.TRUE) {
222-
// save the value to be restored for the next call to getUpdateCount()
223-
statementsUpdateCount.put(statement, result);
224-
}
225-
226-
} catch (SQLException e) {
227-
markNotSupported(updateCountSupported, type, e);
228-
}
229-
return result;
230-
}
231-
232-
@Override
233-
public int getAndClearStoredUpdateCount(Object statement) {
234-
Integer value = null;
235-
if (Boolean.FALSE == idempotentGetUpdateCount.get(statement.getClass())) {
236-
value = statementsUpdateCount.remove(statement);
237-
}
238-
return value != null ? value : Integer.MIN_VALUE;
239-
}
240-
241183
@Nullable
242184
private static Boolean isSupported(WeakConcurrentMap<Class<?>, Boolean> featureMap, Class<?> type) {
243185
return featureMap.get(type);

apm-agent-plugins/apm-jdbc-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.bci.ElasticApmInstrumentation

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,3 @@ co.elastic.apm.agent.jdbc.StatementInstrumentation$ExecuteUpdateNoQueryInstrumen
55
co.elastic.apm.agent.jdbc.StatementInstrumentation$AddBatchInstrumentation
66
co.elastic.apm.agent.jdbc.StatementInstrumentation$ExecuteBatchInstrumentation
77
co.elastic.apm.agent.jdbc.StatementInstrumentation$ExecutePreparedStatementInstrumentation
8-
co.elastic.apm.agent.jdbc.StatementInstrumentation$GetUpdateCountInstrumentation

apm-agent-plugins/apm-jdbc-plugin/src/test/java/co/elastic/apm/agent/jdbc/AbstractJdbcInstrumentationTest.java

Lines changed: 17 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ public void tearDown() throws SQLException {
114114
@Test
115115
public void test() throws SQLException {
116116
executeTest(this::testStatement);
117-
executeTest(this::testUpdateStatement);
118-
executeTest(this::testStatementNotSupportingUpdateCount);
117+
executeTest(() -> testUpdateStatement(false));
118+
executeTest(() -> testUpdateStatement(true));
119119
executeTest(this::testStatementNotSupportingConnection);
120120
executeTest(this::testStatementWithoutConnectionMetadata);
121121

@@ -179,43 +179,28 @@ private void testStatement() throws SQLException {
179179
Statement statement = connection.createStatement();
180180
ResultSet resultSet = statement.executeQuery(sql);
181181

182-
checkUpdateCount(statement, -1);
183-
184182
assertThat(resultSet.next()).isTrue();
185183
assertThat(resultSet.getInt("foo")).isEqualTo(1);
186184
assertThat(resultSet.getString("BAR")).isEqualTo("APM");
187185

188186
assertSpanRecorded(sql, false, -1);
189187
}
190188

191-
private void testUpdateStatement() throws SQLException {
189+
private void testUpdateStatement(boolean executeUpdate) throws SQLException {
192190
final String sql = "UPDATE ELASTIC_APM SET BAR='AFTER' WHERE FOO=11";
193191
Statement statement = connection.createStatement();
194-
boolean isResultSet = statement.execute(sql);
195-
assertThat(isResultSet).isFalse();
196-
197-
checkUpdateCount(statement, 1);
198-
199-
assertSpanRecorded(sql, false, 1);
200-
}
201-
202-
private void testStatementNotSupportingUpdateCount() throws SQLException {
203-
final String sql = "UPDATE ELASTIC_APM SET BAR='AFTER1' WHERE FOO=11";
204-
TestStatement statement = new TestStatement(connection.createStatement());
205-
statement.setGetUpdateCountSupported(false);
206-
assertThat(statement.getUnsupportedThrownCount()).isZero();
207-
208-
boolean isResultSet = statement.execute(sql);
209-
assertThat(statement.getUnsupportedThrownCount()).isEqualTo(1);
210-
assertThat(isResultSet).isFalse();
211-
212-
assertSpanRecorded(sql, false, -1);
192+
int expectedAffectedRows;
193+
if (executeUpdate) {
194+
statement.executeUpdate(sql);
195+
// executeUpdate allows to capture affected rows without side-effects
196+
expectedAffectedRows = 1;
197+
} else {
198+
boolean isResultSet = statement.execute(sql);
199+
assertThat(isResultSet).isFalse();
200+
expectedAffectedRows = -1;
201+
}
213202

214-
// try to execute statement again, should not throw any exception
215-
statement.execute(sql);
216-
assertThat(statement.getUnsupportedThrownCount())
217-
.describedAs("unsupported exception should only be thrown once")
218-
.isEqualTo(1);
203+
assertSpanRecorded(sql, false, expectedAffectedRows);
219204
}
220205

221206
private void testStatementNotSupportingConnection() throws SQLException {
@@ -235,7 +220,7 @@ private void testStatementWithoutConnectionMetadata() throws SQLException {
235220
checkWithoutConnectionMetadata(statement, testConnection::getUnsupportedThrownCount);
236221
}
237222

238-
private interface ThrownCountCheck{
223+
private interface ThrownCountCheck {
239224
int getThrownCount();
240225
}
241226

@@ -247,7 +232,7 @@ private void checkWithoutConnectionMetadata(TestStatement statement, ThrownCount
247232
assertThat(check.getThrownCount()).isEqualTo(1);
248233
assertThat(isResultSet).isFalse();
249234

250-
assertSpanRecordedWithoutConnection(sql, false, 1);
235+
assertSpanRecordedWithoutConnection(sql, false, -1);
251236

252237
// try to execute statement again, should not throw again
253238
statement.execute(sql);
@@ -349,7 +334,7 @@ private void testUpdatePreparedStatement() throws SQLException {
349334
if (updatePreparedStatement != null) {
350335
updatePreparedStatement.setString(1, "UPDATED1");
351336
updatePreparedStatement.execute();
352-
assertSpanRecorded(UPDATE_PREPARED_STATEMENT_SQL, true, 1);
337+
assertSpanRecorded(UPDATE_PREPARED_STATEMENT_SQL, true, -1);
353338
}
354339
}
355340

@@ -399,11 +384,6 @@ private void testMultipleRowsModifiedStatement() throws SQLException {
399384
assertThat(reporter.getSpans()).hasSize(1);
400385
Db db = reporter.getFirstSpan().getContext().getDb();
401386
assertThat(db.getStatement()).isEqualTo(insert);
402-
if (!isKnownDatabase("Oracle", "")) {
403-
assertThat(db.getAffectedRowsCount())
404-
.isEqualTo(statement.getUpdateCount())
405-
.isEqualTo(1);
406-
}
407387
}
408388

409389
private void assertQuerySucceededAndSpanRecorded(ResultSet resultSet, String rawSql, boolean preparedStatement) throws SQLException {
@@ -517,17 +497,4 @@ private static boolean executePotentiallyUnsupportedFeature(JdbcTask task) throw
517497
return true;
518498
}
519499

520-
/**
521-
* Calls and asserts returned value of {@link Statement#getUpdateCount()}.
522-
*
523-
* @param statement statement
524-
* @param expectedValue expected value
525-
* @throws SQLException if something bad happens
526-
*/
527-
private static void checkUpdateCount(Statement statement, int expectedValue) throws SQLException {
528-
int updateCount = statement.getUpdateCount();
529-
assertThat(updateCount)
530-
.describedAs("unexpected update count value")
531-
.isEqualTo(expectedValue);
532-
}
533500
}

apm-agent-plugins/apm-jdbc-plugin/src/test/java/co/elastic/apm/agent/jdbc/TestStatement.java

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ class TestStatement implements Statement {
3434
private final Statement delegate;
3535

3636
private boolean isGetConnectionSupported;
37-
private boolean isGetUpdateCountSupported;
3837
private int unsupportedThrownCount;
3938

4039
private Connection connection;
@@ -43,7 +42,6 @@ public TestStatement(Statement delegate) {
4342
this.delegate = delegate;
4443
this.unsupportedThrownCount = 0;
4544
this.isGetConnectionSupported = true;
46-
this.isGetUpdateCountSupported = true;
4745
}
4846

4947
private void unsupportedCheck(boolean isFeatureSupported) throws SQLException {
@@ -57,10 +55,6 @@ public void setGetConnectionSupported(boolean supported) {
5755
this.isGetConnectionSupported = supported;
5856
}
5957

60-
public void setGetUpdateCountSupported(boolean supported) {
61-
this.isGetUpdateCountSupported = supported;
62-
}
63-
6458
int getUnsupportedThrownCount(){
6559
return unsupportedThrownCount;
6660
}
@@ -69,7 +63,6 @@ void setConnection(Connection connection) {
6963
this.connection = connection;
7064
}
7165

72-
7366
public ResultSet executeQuery(String sql) throws SQLException {
7467
return delegate.executeQuery(sql);
7568
}
@@ -135,7 +128,6 @@ public ResultSet getResultSet() throws SQLException {
135128
}
136129

137130
public int getUpdateCount() throws SQLException {
138-
unsupportedCheck(isGetUpdateCountSupported);
139131
return delegate.getUpdateCount();
140132
}
141133

0 commit comments

Comments
 (0)