-
Notifications
You must be signed in to change notification settings - Fork 50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: support Savepoint #1212
feat: support Savepoint #1212
Conversation
Add support for emulated Savepoints that are now supported in the client library.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
class JdbcSavepoint implements Savepoint { | ||
private static final AtomicInteger COUNTER = new AtomicInteger(); | ||
|
||
static JdbcSavepoint named(String name) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason we are preferring static methods for instantiation instead of adding constructors for creating named and unnamed instances? Wouldn't the constructor be a more natural way for creating instance instead of static methods?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This gives more readable code where the savepoints are created, especially as the behavior of named and unnamed savepoints are slightly different. Internally, we always need to create savepoints with a name, as that is the only thing that Cloud Spanner supports. This way we make clear that one is a named savepoint, and the other is unnamed, even though both internally have a name.
|
||
@Override | ||
public int getSavepointId() throws SQLException { | ||
JdbcPreconditions.checkState(this.id >= 0, "This is a named savepoint"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion : It might be more readable to track named vs un-named through an explicit boolean variable say isNamed
. Using the the ID's state to determine this seems like an over-use of ID's value beyond what its meant for. For example - If for any reason we change this ID's type from integer
to String
, it ideally shouldn't impact the logic to determine named vs un-named. But in this case we will need to modify the logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't feel it's really necessary given the size of the class. Also; changing the type of ID from integer to anything else will not be possible, as the type is specified by the JDBC API (that is; getSavepointId
must return an int
).
@@ -402,6 +404,69 @@ public String getSchema() throws SQLException { | |||
return ""; | |||
} | |||
|
|||
@Override | |||
public SavepointSupport getSavepointSupport() { | |||
return getSpannerConnection().getSavepointSupport(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have we intentionally skipped checkClosed();
validation in this method? If yes, I didn't get why we won't need the check here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really intentionally. Added it to be consistent with the other methods.
return name; | ||
} | ||
|
||
String internalGetSavepointName() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Why is this method being added to an implementation when the interface
Savepoint
is not defining any such method? - Why are we prefixing this with
internal
? Wouldn't it be better to call thisgetName
which acts like a getter method for existing membername
? - Why are we adding this method when there is already getSavepointName() method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need a separate method from getSavepointName()
because:
- JDBC specifies that
getSavepointName()
must throw an error if it is called on an unnamed savepoint. - We internally need to get the name of a savepoint in order to set it on the underlying connection. For an unnamed savepoint, this will be a generated name that is only accessible internally in the package, and not to the outside world.
internalGetSavepointName()
is the internal (package-private) version of thegetSavepointName()
method that returns the name regardless whether the savepoint is named or unnamed. This is used internally to set the savepoint name on the underlying connection.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the explanation.
public Savepoint setSavepoint() throws SQLException { | ||
checkClosed(); | ||
try { | ||
JdbcSavepoint savepoint = JdbcSavepoint.unnamed(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's your take on inversion of control
? Are you intentionally using JdbcSavepoint
since you require the static methods and the internalGetSavepointName()
method?
Usually with interfaces we don't expose the implementation class at time of dependency injection. Over here it's not really dependency injection, but would it not be better to use just Savepoint
interface instead of referencing JdbcSavepoint
class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to use the JdbcSavepoint
class instead of the Savepoint
interface here, because we need access to the internalGetSavepointName()
method. We cannot use the Savepoint#getSavepointName()
method, because that method will throw an error for unnamed savepoints. The 'throw-an-error' behavior is part of the JDBC specification, so it is not something that we can choose to implement differently.
JdbcSavepoint jdbcSavepoint = (JdbcSavepoint) savepoint; | ||
try { | ||
getSpannerConnection().rollbackToSavepoint(jdbcSavepoint.internalGetSavepointName()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar comments on usage of JdbcSavepoint
class instead of Savepoint
interface as above methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above: We need access to the internalGetSavepointName()
method, as the Savepoint#getSavepointName()
could throw an error if the given savepoint is an unnamed savepoint.
Add support for emulated Savepoints that are now supported in the client library.