Skip to content
This repository was archived by the owner on Mar 26, 2026. It is now read-only.

Commit b32e7ae

Browse files
authored
feat: add support for CommitStats (#261)
* feat: add support for CommitStats * fix: remove overload delay * fix: gracefully close pool after each test * chore: update copyright year, comments and assertions * deps: use Spanner client lib 5.0.0 * fix: add new methods to clirr * test: add tests for SQLException
1 parent f0cdf11 commit b32e7ae

5 files changed

Lines changed: 281 additions & 0 deletions

File tree

clirr-ignored-differences.xml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,21 @@
9494
<className>com/google/cloud/spanner/jdbc/CloudSpannerJdbcConnection</className>
9595
<method>void setTransactionMode(com.google.cloud.spanner.connection.TransactionMode)</method>
9696
</difference>
97+
98+
<!-- Commit stats -->
99+
<difference>
100+
<differenceType>7012</differenceType>
101+
<className>com/google/cloud/spanner/jdbc/CloudSpannerJdbcConnection</className>
102+
<method>com.google.cloud.spanner.CommitResponse getCommitResponse()</method>
103+
</difference>
104+
<difference>
105+
<differenceType>7012</differenceType>
106+
<className>com/google/cloud/spanner/jdbc/CloudSpannerJdbcConnection</className>
107+
<method>boolean isReturnCommitStats()</method>
108+
</difference>
109+
<difference>
110+
<differenceType>7012</differenceType>
111+
<className>com/google/cloud/spanner/jdbc/CloudSpannerJdbcConnection</className>
112+
<method>void setReturnCommitStats(boolean)</method>
113+
</difference>
97114
</differences>

src/main/java/com/google/cloud/spanner/jdbc/CloudSpannerJdbcConnection.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package com.google.cloud.spanner.jdbc;
1818

1919
import com.google.cloud.spanner.AbortedException;
20+
import com.google.cloud.spanner.CommitResponse;
21+
import com.google.cloud.spanner.CommitStats;
2022
import com.google.cloud.spanner.Mutation;
2123
import com.google.cloud.spanner.ResultSet;
2224
import com.google.cloud.spanner.TimestampBound;
@@ -152,6 +154,24 @@ public interface CloudSpannerJdbcConnection extends Connection {
152154
*/
153155
Timestamp getCommitTimestamp() throws SQLException;
154156

157+
/**
158+
* @return the {@link CommitResponse} of the last read/write transaction. If the last transaction
159+
* was not a read/write transaction, or a read/write transaction that did not return a {@link
160+
* CommitResponse} because the transaction was not committed, the method will throw a {@link
161+
* SQLException}. The {@link CommitResponse} will include {@link CommitStats} if {@link
162+
* #isReturnCommitStats()} returns true.
163+
*/
164+
CommitResponse getCommitResponse() throws SQLException;
165+
166+
/**
167+
* Sets whether this connection should request commit statistics from Cloud Spanner for read/write
168+
* transactions and for DML statements in autocommit mode.
169+
*/
170+
void setReturnCommitStats(boolean returnCommitStats) throws SQLException;
171+
172+
/** @return true if this connection requests commit statistics from Cloud Spanner. */
173+
boolean isReturnCommitStats() throws SQLException;
174+
155175
/**
156176
* @return the read {@link Timestamp} of the last read-only transaction. If the last transaction
157177
* was not a read-only transaction, or a read-only transaction that did not return a read

src/main/java/com/google/cloud/spanner/jdbc/JdbcConnection.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.google.cloud.spanner.jdbc;
1818

1919
import com.google.api.client.util.Preconditions;
20+
import com.google.cloud.spanner.CommitResponse;
2021
import com.google.cloud.spanner.Mutation;
2122
import com.google.cloud.spanner.SpannerException;
2223
import com.google.cloud.spanner.TimestampBound;
@@ -381,6 +382,36 @@ public Timestamp getCommitTimestamp() throws SQLException {
381382
}
382383
}
383384

385+
@Override
386+
public CommitResponse getCommitResponse() throws SQLException {
387+
checkClosed();
388+
try {
389+
return getSpannerConnection().getCommitResponse();
390+
} catch (SpannerException e) {
391+
throw JdbcSqlExceptionFactory.of(e);
392+
}
393+
}
394+
395+
@Override
396+
public void setReturnCommitStats(boolean returnCommitStats) throws SQLException {
397+
checkClosed();
398+
try {
399+
getSpannerConnection().setReturnCommitStats(returnCommitStats);
400+
} catch (SpannerException e) {
401+
throw JdbcSqlExceptionFactory.of(e);
402+
}
403+
}
404+
405+
@Override
406+
public boolean isReturnCommitStats() throws SQLException {
407+
checkClosed();
408+
try {
409+
return getSpannerConnection().isReturnCommitStats();
410+
} catch (SpannerException e) {
411+
throw JdbcSqlExceptionFactory.of(e);
412+
}
413+
}
414+
384415
@Override
385416
public Timestamp getReadTimestamp() throws SQLException {
386417
checkClosed();
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* Copyright 2021 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.spanner.jdbc;
18+
19+
import static com.google.common.truth.Truth.assertThat;
20+
import static org.junit.Assert.assertFalse;
21+
import static org.junit.Assert.assertNotNull;
22+
import static org.junit.Assert.assertTrue;
23+
24+
import com.google.cloud.spanner.CommitResponse;
25+
import com.google.cloud.spanner.Mutation;
26+
import com.google.cloud.spanner.connection.AbstractMockServerTest;
27+
import com.google.cloud.spanner.connection.SpannerPool;
28+
import java.sql.DriverManager;
29+
import java.sql.ResultSet;
30+
import java.sql.SQLException;
31+
import org.junit.After;
32+
import org.junit.Test;
33+
import org.junit.runner.RunWith;
34+
import org.junit.runners.JUnit4;
35+
36+
@RunWith(JUnit4.class)
37+
public class JdbcCommitStatsTest extends AbstractMockServerTest {
38+
39+
@After
40+
public void closeSpannerPool() {
41+
SpannerPool.closeSpannerPool();
42+
}
43+
44+
@Test
45+
public void testDefaultReturnCommitStats() throws SQLException {
46+
try (java.sql.Connection connection = createJdbcConnection()) {
47+
try (java.sql.ResultSet rs =
48+
connection.createStatement().executeQuery("SHOW VARIABLE RETURN_COMMIT_STATS")) {
49+
assertTrue(rs.next());
50+
assertFalse(rs.getBoolean("RETURN_COMMIT_STATS"));
51+
assertFalse(rs.next());
52+
}
53+
}
54+
}
55+
56+
@Test
57+
public void testReturnCommitStatsInConnectionUrl() throws SQLException {
58+
try (java.sql.Connection connection =
59+
DriverManager.getConnection(
60+
String.format("jdbc:%s;returnCommitStats=true", getBaseUrl()))) {
61+
try (java.sql.ResultSet rs =
62+
connection.createStatement().executeQuery("SHOW VARIABLE RETURN_COMMIT_STATS")) {
63+
assertTrue(rs.next());
64+
assertTrue(rs.getBoolean("RETURN_COMMIT_STATS"));
65+
assertFalse(rs.next());
66+
}
67+
}
68+
}
69+
70+
@Test
71+
public void testSetReturnCommitStats() throws SQLException {
72+
try (java.sql.Connection connection = createJdbcConnection()) {
73+
connection.createStatement().execute("SET RETURN_COMMIT_STATS=true");
74+
try (java.sql.ResultSet rs =
75+
connection.createStatement().executeQuery("SHOW VARIABLE RETURN_COMMIT_STATS")) {
76+
assertTrue(rs.next());
77+
assertTrue(rs.getBoolean("RETURN_COMMIT_STATS"));
78+
assertFalse(rs.next());
79+
}
80+
connection.createStatement().execute("SET RETURN_COMMIT_STATS=false");
81+
try (java.sql.ResultSet rs =
82+
connection.createStatement().executeQuery("SHOW VARIABLE RETURN_COMMIT_STATS")) {
83+
assertTrue(rs.next());
84+
assertFalse(rs.getBoolean("RETURN_COMMIT_STATS"));
85+
assertFalse(rs.next());
86+
}
87+
}
88+
}
89+
90+
@Test
91+
public void testSetAndUseReturnCommitStats() throws SQLException {
92+
try (CloudSpannerJdbcConnection connection =
93+
createJdbcConnection().unwrap(CloudSpannerJdbcConnection.class)) {
94+
connection.setReturnCommitStats(true);
95+
connection.bufferedWrite(Mutation.newInsertBuilder("FOO").set("ID").to(1L).build());
96+
connection.commit();
97+
CommitResponse response = connection.getCommitResponse();
98+
assertNotNull(response);
99+
assertNotNull(response.getCommitStats());
100+
assertThat(response.getCommitStats().getMutationCount()).isAtLeast(1);
101+
}
102+
}
103+
104+
@Test
105+
public void testSetAndUseReturnCommitStatsUsingSql() throws SQLException {
106+
try (java.sql.Connection connection = createJdbcConnection()) {
107+
connection.createStatement().execute("SET RETURN_COMMIT_STATS=true");
108+
// Use a Mutation as the mock server only returns a non-zero mutation count for mutations, and
109+
// not for DML statements.
110+
connection
111+
.unwrap(CloudSpannerJdbcConnection.class)
112+
.bufferedWrite(Mutation.newInsertBuilder("FOO").set("ID").to(1L).build());
113+
connection.commit();
114+
try (ResultSet rs =
115+
connection.createStatement().executeQuery("SHOW VARIABLE COMMIT_RESPONSE")) {
116+
assertTrue(rs.next());
117+
assertNotNull(rs.getTimestamp("COMMIT_TIMESTAMP"));
118+
assertThat(rs.getLong("MUTATION_COUNT")).isAtLeast(1L);
119+
assertFalse(rs.next());
120+
}
121+
}
122+
}
123+
}

src/test/java/com/google/cloud/spanner/jdbc/JdbcConnectionTest.java

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@
1818

1919
import static com.google.common.truth.Truth.assertThat;
2020
import static com.google.common.truth.Truth.assertWithMessage;
21+
import static org.junit.Assert.assertEquals;
22+
import static org.junit.Assert.assertFalse;
23+
import static org.junit.Assert.assertTrue;
2124
import static org.junit.Assert.fail;
25+
import static org.mockito.Matchers.any;
2226
import static org.mockito.Mockito.mock;
2327
import static org.mockito.Mockito.when;
2428

@@ -51,6 +55,7 @@
5155
import org.junit.Test;
5256
import org.junit.runner.RunWith;
5357
import org.junit.runners.JUnit4;
58+
import org.mockito.Mockito;
5459

5560
@RunWith(JUnit4.class)
5661
public class JdbcConnectionTest {
@@ -277,6 +282,14 @@ public void testClosedJdbcConnection()
277282
"releaseSavepoint",
278283
new Class<?>[] {Savepoint.class},
279284
new Object[] {null});
285+
286+
testClosed(CloudSpannerJdbcConnection.class, "isReturnCommitStats");
287+
testClosed(
288+
CloudSpannerJdbcConnection.class,
289+
"setReturnCommitStats",
290+
new Class<?>[] {boolean.class},
291+
new Object[] {true});
292+
testClosed(CloudSpannerJdbcConnection.class, "getCommitResponse");
280293
}
281294

282295
private void testClosed(Class<? extends Connection> clazz, String name)
@@ -705,4 +718,81 @@ public void testSchema() throws SQLException {
705718
}
706719
}
707720
}
721+
722+
@Test
723+
public void testIsReturnCommitStats() throws SQLException {
724+
try (JdbcConnection connection = createConnection(mock(ConnectionOptions.class))) {
725+
assertFalse(connection.isReturnCommitStats());
726+
connection.setReturnCommitStats(true);
727+
assertTrue(connection.isReturnCommitStats());
728+
}
729+
}
730+
731+
@Test
732+
public void testIsReturnCommitStats_throwsSqlException() throws SQLException {
733+
ConnectionOptions options = mock(ConnectionOptions.class);
734+
com.google.cloud.spanner.connection.Connection spannerConnection =
735+
mock(com.google.cloud.spanner.connection.Connection.class);
736+
when(options.getConnection()).thenReturn(spannerConnection);
737+
when(spannerConnection.isReturnCommitStats())
738+
.thenThrow(
739+
SpannerExceptionFactory.newSpannerException(
740+
ErrorCode.FAILED_PRECONDITION, "test exception"));
741+
try (JdbcConnection connection =
742+
new JdbcConnection(
743+
"jdbc:cloudspanner://localhost/projects/project/instances/instance/databases/database;credentialsUrl=url",
744+
options)) {
745+
connection.isReturnCommitStats();
746+
fail("missing expected exception");
747+
} catch (SQLException e) {
748+
assertTrue(e instanceof JdbcSqlException);
749+
assertEquals(Code.FAILED_PRECONDITION, ((JdbcSqlException) e).getCode());
750+
}
751+
}
752+
753+
@Test
754+
public void testSetReturnCommitStats_throwsSqlException() throws SQLException {
755+
ConnectionOptions options = mock(ConnectionOptions.class);
756+
com.google.cloud.spanner.connection.Connection spannerConnection =
757+
mock(com.google.cloud.spanner.connection.Connection.class);
758+
when(options.getConnection()).thenReturn(spannerConnection);
759+
Mockito.doThrow(
760+
SpannerExceptionFactory.newSpannerException(
761+
ErrorCode.FAILED_PRECONDITION, "test exception"))
762+
.when(spannerConnection)
763+
.setReturnCommitStats(any(boolean.class));
764+
try (JdbcConnection connection =
765+
new JdbcConnection(
766+
"jdbc:cloudspanner://localhost/projects/project/instances/instance/databases/database;credentialsUrl=url",
767+
options)) {
768+
connection.setReturnCommitStats(true);
769+
fail("missing expected exception");
770+
} catch (SQLException e) {
771+
assertTrue(e instanceof JdbcSqlException);
772+
assertEquals(Code.FAILED_PRECONDITION, ((JdbcSqlException) e).getCode());
773+
}
774+
}
775+
776+
@Test
777+
public void testGetCommitResponse_throwsSqlException() throws SQLException {
778+
ConnectionOptions options = mock(ConnectionOptions.class);
779+
com.google.cloud.spanner.connection.Connection spannerConnection =
780+
mock(com.google.cloud.spanner.connection.Connection.class);
781+
when(options.getConnection()).thenReturn(spannerConnection);
782+
Mockito.doThrow(
783+
SpannerExceptionFactory.newSpannerException(
784+
ErrorCode.FAILED_PRECONDITION, "test exception"))
785+
.when(spannerConnection)
786+
.setReturnCommitStats(any(boolean.class));
787+
try (JdbcConnection connection =
788+
new JdbcConnection(
789+
"jdbc:cloudspanner://localhost/projects/project/instances/instance/databases/database;credentialsUrl=url",
790+
options)) {
791+
connection.setReturnCommitStats(true);
792+
fail("missing expected exception");
793+
} catch (SQLException e) {
794+
assertTrue(e instanceof JdbcSqlException);
795+
assertEquals(Code.FAILED_PRECONDITION, ((JdbcSqlException) e).getCode());
796+
}
797+
}
708798
}

0 commit comments

Comments
 (0)