Skip to content

Commit 6833696

Browse files
authored
feat: support Savepoint (#1212)
* feat: support Savepoint Add support for emulated Savepoints that are now supported in the client library. * fix: clirr check
1 parent b48ff42 commit 6833696

File tree

7 files changed

+885
-22
lines changed

7 files changed

+885
-22
lines changed

clirr-ignored-differences.xml

+10
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,14 @@
66
<className>com/google/cloud/spanner/jdbc/CloudSpannerJdbcConnection</className>
77
<method>com.google.cloud.spanner.Dialect getDialect()</method>
88
</difference>
9+
<difference>
10+
<differenceType>7012</differenceType>
11+
<className>com/google/cloud/spanner/jdbc/CloudSpannerJdbcConnection</className>
12+
<method>com.google.cloud.spanner.connection.SavepointSupport getSavepointSupport()</method>
13+
</difference>
14+
<difference>
15+
<differenceType>7012</differenceType>
16+
<className>com/google/cloud/spanner/jdbc/CloudSpannerJdbcConnection</className>
17+
<method>void setSavepointSupport(com.google.cloud.spanner.connection.SavepointSupport)</method>
18+
</difference>
919
</differences>

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

-22
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import java.sql.SQLException;
2929
import java.sql.SQLWarning;
3030
import java.sql.SQLXML;
31-
import java.sql.Savepoint;
3231
import java.sql.Struct;
3332
import java.util.Properties;
3433
import java.util.concurrent.Executor;
@@ -42,7 +41,6 @@ abstract class AbstractJdbcConnection extends AbstractJdbcWrapper
4241
"Only isolation level TRANSACTION_SERIALIZABLE is supported";
4342
private static final String ONLY_CLOSE_ALLOWED =
4443
"Only holdability CLOSE_CURSORS_AT_COMMIT is supported";
45-
private static final String SAVEPOINTS_UNSUPPORTED = "Savepoints are not supported";
4644
private static final String SQLXML_UNSUPPORTED = "SQLXML is not supported";
4745
private static final String STRUCTS_UNSUPPORTED = "Structs are not supported";
4846
private static final String ABORT_UNSUPPORTED = "Abort is not supported";
@@ -163,26 +161,6 @@ public void clearWarnings() throws SQLException {
163161
lastWarning = null;
164162
}
165163

166-
@Override
167-
public Savepoint setSavepoint() throws SQLException {
168-
return checkClosedAndThrowUnsupported(SAVEPOINTS_UNSUPPORTED);
169-
}
170-
171-
@Override
172-
public Savepoint setSavepoint(String name) throws SQLException {
173-
return checkClosedAndThrowUnsupported(SAVEPOINTS_UNSUPPORTED);
174-
}
175-
176-
@Override
177-
public void rollback(Savepoint savepoint) throws SQLException {
178-
checkClosedAndThrowUnsupported(SAVEPOINTS_UNSUPPORTED);
179-
}
180-
181-
@Override
182-
public void releaseSavepoint(Savepoint savepoint) throws SQLException {
183-
checkClosedAndThrowUnsupported(SAVEPOINTS_UNSUPPORTED);
184-
}
185-
186164
@Override
187165
public SQLXML createSQLXML() throws SQLException {
188166
return checkClosedAndThrowUnsupported(SQLXML_UNSUPPORTED);

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

+7
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.google.cloud.spanner.ResultSet;
2626
import com.google.cloud.spanner.TimestampBound;
2727
import com.google.cloud.spanner.connection.AutocommitDmlMode;
28+
import com.google.cloud.spanner.connection.SavepointSupport;
2829
import com.google.cloud.spanner.connection.TransactionMode;
2930
import java.sql.Connection;
3031
import java.sql.SQLException;
@@ -256,6 +257,12 @@ default String getStatementTag() throws SQLException {
256257
*/
257258
void setRetryAbortsInternally(boolean retryAbortsInternally) throws SQLException;
258259

260+
/** Returns the current savepoint support for this connection. */
261+
SavepointSupport getSavepointSupport() throws SQLException;
262+
263+
/** Sets how savepoints should be supported on this connection. */
264+
void setSavepointSupport(SavepointSupport savepointSupport) throws SQLException;
265+
259266
/**
260267
* Writes the specified mutation directly to the database and commits the change. The value is
261268
* readable after the successful completion of this method. Writing multiple mutations to a

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

+66
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.google.cloud.spanner.TimestampBound;
2424
import com.google.cloud.spanner.connection.AutocommitDmlMode;
2525
import com.google.cloud.spanner.connection.ConnectionOptions;
26+
import com.google.cloud.spanner.connection.SavepointSupport;
2627
import com.google.cloud.spanner.connection.TransactionMode;
2728
import com.google.common.collect.Iterators;
2829
import java.sql.Array;
@@ -33,6 +34,7 @@
3334
import java.sql.PreparedStatement;
3435
import java.sql.ResultSet;
3536
import java.sql.SQLException;
37+
import java.sql.Savepoint;
3638
import java.sql.Statement;
3739
import java.sql.Timestamp;
3840
import java.util.HashMap;
@@ -402,6 +404,70 @@ public String getSchema() throws SQLException {
402404
return "";
403405
}
404406

407+
@Override
408+
public SavepointSupport getSavepointSupport() throws SQLException {
409+
checkClosed();
410+
return getSpannerConnection().getSavepointSupport();
411+
}
412+
413+
@Override
414+
public void setSavepointSupport(SavepointSupport savepointSupport) throws SQLException {
415+
checkClosed();
416+
try {
417+
getSpannerConnection().setSavepointSupport(savepointSupport);
418+
} catch (SpannerException e) {
419+
throw JdbcSqlExceptionFactory.of(e);
420+
}
421+
}
422+
423+
@Override
424+
public Savepoint setSavepoint() throws SQLException {
425+
checkClosed();
426+
try {
427+
JdbcSavepoint savepoint = JdbcSavepoint.unnamed();
428+
getSpannerConnection().savepoint(savepoint.internalGetSavepointName());
429+
return savepoint;
430+
} catch (SpannerException e) {
431+
throw JdbcSqlExceptionFactory.of(e);
432+
}
433+
}
434+
435+
@Override
436+
public Savepoint setSavepoint(String name) throws SQLException {
437+
checkClosed();
438+
try {
439+
JdbcSavepoint savepoint = JdbcSavepoint.named(name);
440+
getSpannerConnection().savepoint(savepoint.internalGetSavepointName());
441+
return savepoint;
442+
} catch (SpannerException e) {
443+
throw JdbcSqlExceptionFactory.of(e);
444+
}
445+
}
446+
447+
@Override
448+
public void rollback(Savepoint savepoint) throws SQLException {
449+
checkClosed();
450+
JdbcPreconditions.checkArgument(savepoint instanceof JdbcSavepoint, savepoint);
451+
JdbcSavepoint jdbcSavepoint = (JdbcSavepoint) savepoint;
452+
try {
453+
getSpannerConnection().rollbackToSavepoint(jdbcSavepoint.internalGetSavepointName());
454+
} catch (SpannerException e) {
455+
throw JdbcSqlExceptionFactory.of(e);
456+
}
457+
}
458+
459+
@Override
460+
public void releaseSavepoint(Savepoint savepoint) throws SQLException {
461+
checkClosed();
462+
JdbcPreconditions.checkArgument(savepoint instanceof JdbcSavepoint, savepoint);
463+
JdbcSavepoint jdbcSavepoint = (JdbcSavepoint) savepoint;
464+
try {
465+
getSpannerConnection().releaseSavepoint(jdbcSavepoint.internalGetSavepointName());
466+
} catch (SpannerException e) {
467+
throw JdbcSqlExceptionFactory.of(e);
468+
}
469+
}
470+
405471
@Override
406472
public Timestamp getCommitTimestamp() throws SQLException {
407473
checkClosed();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2023 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 java.sql.SQLException;
20+
import java.sql.Savepoint;
21+
import java.util.concurrent.atomic.AtomicInteger;
22+
23+
class JdbcSavepoint implements Savepoint {
24+
private static final AtomicInteger COUNTER = new AtomicInteger();
25+
26+
static JdbcSavepoint named(String name) {
27+
return new JdbcSavepoint(-1, name);
28+
}
29+
30+
static JdbcSavepoint unnamed() {
31+
int id = COUNTER.incrementAndGet();
32+
return new JdbcSavepoint(id, String.format("s_%d", id));
33+
}
34+
35+
private final int id;
36+
private final String name;
37+
38+
private JdbcSavepoint(int id, String name) {
39+
this.id = id;
40+
this.name = name;
41+
}
42+
43+
@Override
44+
public int getSavepointId() throws SQLException {
45+
JdbcPreconditions.checkState(this.id >= 0, "This is a named savepoint");
46+
return id;
47+
}
48+
49+
@Override
50+
public String getSavepointName() throws SQLException {
51+
JdbcPreconditions.checkState(this.id < 0, "This is an unnamed savepoint");
52+
return name;
53+
}
54+
55+
String internalGetSavepointName() {
56+
return name;
57+
}
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2023 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 org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.assertThrows;
21+
import static org.junit.Assert.assertTrue;
22+
23+
import java.sql.SQLException;
24+
import java.sql.Savepoint;
25+
import org.junit.Test;
26+
import org.junit.runner.RunWith;
27+
import org.junit.runners.JUnit4;
28+
29+
@RunWith(JUnit4.class)
30+
public class JdbcSavepointTest {
31+
32+
@Test
33+
public void testNamed() throws SQLException {
34+
Savepoint savepoint = JdbcSavepoint.named("test");
35+
assertEquals("test", savepoint.getSavepointName());
36+
assertThrows(SQLException.class, savepoint::getSavepointId);
37+
}
38+
39+
@Test
40+
public void testUnnamed() throws SQLException {
41+
Savepoint savepoint = JdbcSavepoint.unnamed();
42+
assertTrue(
43+
String.format("Savepoint id: %d", savepoint.getSavepointId()),
44+
savepoint.getSavepointId() > 0);
45+
assertThrows(SQLException.class, savepoint::getSavepointName);
46+
}
47+
}

0 commit comments

Comments
 (0)