Skip to content

Commit

Permalink
feat: support Savepoint (#1212)
Browse files Browse the repository at this point in the history
* feat: support Savepoint

Add support for emulated Savepoints that are now supported in the client
library.

* fix: clirr check
  • Loading branch information
olavloite committed May 26, 2023
1 parent b48ff42 commit 6833696
Show file tree
Hide file tree
Showing 7 changed files with 885 additions and 22 deletions.
10 changes: 10 additions & 0 deletions clirr-ignored-differences.xml
Expand Up @@ -6,4 +6,14 @@
<className>com/google/cloud/spanner/jdbc/CloudSpannerJdbcConnection</className>
<method>com.google.cloud.spanner.Dialect getDialect()</method>
</difference>
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/jdbc/CloudSpannerJdbcConnection</className>
<method>com.google.cloud.spanner.connection.SavepointSupport getSavepointSupport()</method>
</difference>
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/jdbc/CloudSpannerJdbcConnection</className>
<method>void setSavepointSupport(com.google.cloud.spanner.connection.SavepointSupport)</method>
</difference>
</differences>
Expand Up @@ -28,7 +28,6 @@
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Struct;
import java.util.Properties;
import java.util.concurrent.Executor;
Expand All @@ -42,7 +41,6 @@ abstract class AbstractJdbcConnection extends AbstractJdbcWrapper
"Only isolation level TRANSACTION_SERIALIZABLE is supported";
private static final String ONLY_CLOSE_ALLOWED =
"Only holdability CLOSE_CURSORS_AT_COMMIT is supported";
private static final String SAVEPOINTS_UNSUPPORTED = "Savepoints are not supported";
private static final String SQLXML_UNSUPPORTED = "SQLXML is not supported";
private static final String STRUCTS_UNSUPPORTED = "Structs are not supported";
private static final String ABORT_UNSUPPORTED = "Abort is not supported";
Expand Down Expand Up @@ -163,26 +161,6 @@ public void clearWarnings() throws SQLException {
lastWarning = null;
}

@Override
public Savepoint setSavepoint() throws SQLException {
return checkClosedAndThrowUnsupported(SAVEPOINTS_UNSUPPORTED);
}

@Override
public Savepoint setSavepoint(String name) throws SQLException {
return checkClosedAndThrowUnsupported(SAVEPOINTS_UNSUPPORTED);
}

@Override
public void rollback(Savepoint savepoint) throws SQLException {
checkClosedAndThrowUnsupported(SAVEPOINTS_UNSUPPORTED);
}

@Override
public void releaseSavepoint(Savepoint savepoint) throws SQLException {
checkClosedAndThrowUnsupported(SAVEPOINTS_UNSUPPORTED);
}

@Override
public SQLXML createSQLXML() throws SQLException {
return checkClosedAndThrowUnsupported(SQLXML_UNSUPPORTED);
Expand Down
Expand Up @@ -25,6 +25,7 @@
import com.google.cloud.spanner.ResultSet;
import com.google.cloud.spanner.TimestampBound;
import com.google.cloud.spanner.connection.AutocommitDmlMode;
import com.google.cloud.spanner.connection.SavepointSupport;
import com.google.cloud.spanner.connection.TransactionMode;
import java.sql.Connection;
import java.sql.SQLException;
Expand Down Expand Up @@ -256,6 +257,12 @@ default String getStatementTag() throws SQLException {
*/
void setRetryAbortsInternally(boolean retryAbortsInternally) throws SQLException;

/** Returns the current savepoint support for this connection. */
SavepointSupport getSavepointSupport() throws SQLException;

/** Sets how savepoints should be supported on this connection. */
void setSavepointSupport(SavepointSupport savepointSupport) throws SQLException;

/**
* Writes the specified mutation directly to the database and commits the change. The value is
* readable after the successful completion of this method. Writing multiple mutations to a
Expand Down
66 changes: 66 additions & 0 deletions src/main/java/com/google/cloud/spanner/jdbc/JdbcConnection.java
Expand Up @@ -23,6 +23,7 @@
import com.google.cloud.spanner.TimestampBound;
import com.google.cloud.spanner.connection.AutocommitDmlMode;
import com.google.cloud.spanner.connection.ConnectionOptions;
import com.google.cloud.spanner.connection.SavepointSupport;
import com.google.cloud.spanner.connection.TransactionMode;
import com.google.common.collect.Iterators;
import java.sql.Array;
Expand All @@ -33,6 +34,7 @@
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.HashMap;
Expand Down Expand Up @@ -402,6 +404,70 @@ public String getSchema() throws SQLException {
return "";
}

@Override
public SavepointSupport getSavepointSupport() throws SQLException {
checkClosed();
return getSpannerConnection().getSavepointSupport();
}

@Override
public void setSavepointSupport(SavepointSupport savepointSupport) throws SQLException {
checkClosed();
try {
getSpannerConnection().setSavepointSupport(savepointSupport);
} catch (SpannerException e) {
throw JdbcSqlExceptionFactory.of(e);
}
}

@Override
public Savepoint setSavepoint() throws SQLException {
checkClosed();
try {
JdbcSavepoint savepoint = JdbcSavepoint.unnamed();
getSpannerConnection().savepoint(savepoint.internalGetSavepointName());
return savepoint;
} catch (SpannerException e) {
throw JdbcSqlExceptionFactory.of(e);
}
}

@Override
public Savepoint setSavepoint(String name) throws SQLException {
checkClosed();
try {
JdbcSavepoint savepoint = JdbcSavepoint.named(name);
getSpannerConnection().savepoint(savepoint.internalGetSavepointName());
return savepoint;
} catch (SpannerException e) {
throw JdbcSqlExceptionFactory.of(e);
}
}

@Override
public void rollback(Savepoint savepoint) throws SQLException {
checkClosed();
JdbcPreconditions.checkArgument(savepoint instanceof JdbcSavepoint, savepoint);
JdbcSavepoint jdbcSavepoint = (JdbcSavepoint) savepoint;
try {
getSpannerConnection().rollbackToSavepoint(jdbcSavepoint.internalGetSavepointName());
} catch (SpannerException e) {
throw JdbcSqlExceptionFactory.of(e);
}
}

@Override
public void releaseSavepoint(Savepoint savepoint) throws SQLException {
checkClosed();
JdbcPreconditions.checkArgument(savepoint instanceof JdbcSavepoint, savepoint);
JdbcSavepoint jdbcSavepoint = (JdbcSavepoint) savepoint;
try {
getSpannerConnection().releaseSavepoint(jdbcSavepoint.internalGetSavepointName());
} catch (SpannerException e) {
throw JdbcSqlExceptionFactory.of(e);
}
}

@Override
public Timestamp getCommitTimestamp() throws SQLException {
checkClosed();
Expand Down
58 changes: 58 additions & 0 deletions src/main/java/com/google/cloud/spanner/jdbc/JdbcSavepoint.java
@@ -0,0 +1,58 @@
/*
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.cloud.spanner.jdbc;

import java.sql.SQLException;
import java.sql.Savepoint;
import java.util.concurrent.atomic.AtomicInteger;

class JdbcSavepoint implements Savepoint {
private static final AtomicInteger COUNTER = new AtomicInteger();

static JdbcSavepoint named(String name) {
return new JdbcSavepoint(-1, name);
}

static JdbcSavepoint unnamed() {
int id = COUNTER.incrementAndGet();
return new JdbcSavepoint(id, String.format("s_%d", id));
}

private final int id;
private final String name;

private JdbcSavepoint(int id, String name) {
this.id = id;
this.name = name;
}

@Override
public int getSavepointId() throws SQLException {
JdbcPreconditions.checkState(this.id >= 0, "This is a named savepoint");
return id;
}

@Override
public String getSavepointName() throws SQLException {
JdbcPreconditions.checkState(this.id < 0, "This is an unnamed savepoint");
return name;
}

String internalGetSavepointName() {
return name;
}
}
47 changes: 47 additions & 0 deletions src/test/java/com/google/cloud/spanner/jdbc/JdbcSavepointTest.java
@@ -0,0 +1,47 @@
/*
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.cloud.spanner.jdbc;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;

import java.sql.SQLException;
import java.sql.Savepoint;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

@RunWith(JUnit4.class)
public class JdbcSavepointTest {

@Test
public void testNamed() throws SQLException {
Savepoint savepoint = JdbcSavepoint.named("test");
assertEquals("test", savepoint.getSavepointName());
assertThrows(SQLException.class, savepoint::getSavepointId);
}

@Test
public void testUnnamed() throws SQLException {
Savepoint savepoint = JdbcSavepoint.unnamed();
assertTrue(
String.format("Savepoint id: %d", savepoint.getSavepointId()),
savepoint.getSavepointId() > 0);
assertThrows(SQLException.class, savepoint::getSavepointName);
}
}

0 comments on commit 6833696

Please sign in to comment.