-
Notifications
You must be signed in to change notification settings - Fork 116
Introduce begin, rollback and commit #88
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
Conversation
* Add begin(!)/1,2 to checkout and begin * Add rollback(!)/1,2 to rollback and checkin * Add commit(!)/1,2 to commit (or rollback) and checkin rollback/2 is overloaded and has old behaviour inside transaction/3 for backwards compatibility. However transaction/3 is deprecated because is makes assumptions about database status that may not hold. New return values from handle_begin/2, handle_rollback/2 and handle_commit/2 allow to signal mistaken database status.
lib/db_connection.ex
Outdated
{:error, err, _meter} -> | ||
@spec rollback(conn, (opts :: Keyword.t) | reason :: term) :: | ||
{:ok, result} | {:error, Exception.t} | ||
def rollback(conn, opts \\ []) |
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.
Given the transaction/3
behaviour is this incorrect?
lib/db_connection.ex
Outdated
meter = event(meter, query) | ||
msg = "can not #{query} inside legacy transaction" | ||
err = DBConnection.ConnectionError.exception(msg) | ||
delete_disconnect(conn, conn_state, err, opts) |
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.
disconnect or just error? mark as failed transaction?
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 think we need to disconnect, do we?
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 don't need but I worry what might happen if anyone hits this path. If we fail the transaction I guess that stop any issue - i.e. force a rollback at end of transaction/3
.
lib/db_connection.ex
Outdated
and disconnect. | ||
to continue, `{:begin, state}` to open transaction before continuing, | ||
`{:rollback, state}` to rollback aborted transaction, | ||
`{:error, exception, state}` to abort the transaction and continue or |
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.
It might be that I'm not that familiar with db_connection, but what does "abort the transaction & continue" mean 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.
In this case it probably needs rewording. The error will cause {:error, exception}
to be returned by rollback/2
. For example this might occur if the driver can't do a side effect free transaction status check.
lib/db_connection.ex
Outdated
`{:disconnect, exception, state}` to abort the transaction and disconnect. | ||
|
||
A callback implementation should only return `:begin` or `:rollback` if it | ||
can determine the database's transaction status without side effect. |
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.
Maybe "without a roundtrip" would be clearer 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.
With a roundtrip is fine. We will need to do this for the sandbox.
lib/db_connection.ex
Outdated
DBConnection.execute!(conn, "SELECT * FROM table", []) | ||
DBConnection.commit(conn) | ||
after | ||
DBConnection.rollback(conn) |
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.
How does always calling rollback
work in this case? Should it only be called in case execute!
raises?
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 guess this is explained in rollback
with:
It is possible to issue multiple rollbacks requests without a begin (
begin/2
orcheckout_begin/2
). The semantics are left to the callback
implementation.
But it still seems a bit wired to me
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.
Should it only be called in case execute! raises?
Yes, this is simplest/safest way to handle a transaction.
Oops i missed removing checkout_begin
after copy and paste of docs.
But it still seems a bit wired to me
We are depending on the database's state, not our own, so the callback implementation will use information from the last request when available.
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.
Sorry to clarify this is safe to call because the connection is checked in by commit so rollback won't do anything if it doesn't raise. This is a standard approach and imo should be the encouraged way as it ensures safety with minimal fuss.
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.
Ok, sounds great. If it's an optimal approach maybe we should talk about it in the docs?
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.
Okay, i will add it the examples and mention in the docs for begin.
lib/db_connection.ex
Outdated
|
||
### Example | ||
|
||
{:ok, result} = DBConnection.begin(conn) |
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.
begin
returns a 3 tuple, doesn't it?
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.
yes, nice catch
end | ||
end | ||
defp checkin(pool, fun, meter, opts) do | ||
run(pool, fun, meter, opts) |
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'm not sure what will happen in this case. Are we going to check out a connection, call checkin
from the fun and then check the connection back in?
I'm not sure I understand the use case of calling checkin
with the pool & without an actual 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.
This is for API completeness. Also someone using the ownership pool may wish to commit/rollback their transaction before returning connection to the inner pool. I am happy to remove this option.
lib/db_connection.ex
Outdated
meter = event(meter, query) | ||
msg = "can not #{query} inside legacy transaction" | ||
err = DBConnection.ConnectionError.exception(msg) | ||
delete_disconnect(conn, conn_state, err, opts) |
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 think we need to disconnect, do we?
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.
Looks good to me. We just need to be careful with versioning here. If we plan this change to emit warnings, it will emit warnings for everyone using postgrex and mariaex today. So we probably want to soft-deprecate (no warnings) OR simply release 2.0 and remove the old chunks.
This change is backwards compatible, so the plan is to soft deprecated |
rollback/2 is overloaded and has old behaviour inside transaction/3 for
backwards compatibility. However transaction/3 is deprecated because is
makes assumptions about database status that may not hold.
New return values from handle_begin/2, handle_rollback/2 and
handle_commit/2 allow to signal mistaken database status.