-
Notifications
You must be signed in to change notification settings - Fork 115
Switch to non-closure based transactions #87
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
Requiring a fun for a transaction and non-local return for a rollback is a very limiting API. With this breaking change the API becomes more flexible, in particular it allows transaction to continue over multiple OTP behaviour callbacks and use of (potentially nested) savepoints. Removes: * DBConnection.transaction/3 * DBConnection.rollback/2 Adds: * DBConnection.checkout/2 * DBConnection.checkin/2 * DBConnection.begin/2 * DBConnection.commit/2 * DBConnection.rollback/2 * DBConnection.checkout_begin/2 * DBConnection.commit_checkin/2 * DBConnection.rollback_checkin/2
Do we need all 8 new functions? I assume the last three are for convenience? |
after | ||
checkin(conn, opts) | ||
end | ||
end |
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.
New line missing?
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.
lib/db_connection.ex
Outdated
@doc """ | ||
Acquire a lock on a connection. | ||
|
||
Returns `{:ok, conn}` on success or `{;error, exception}` if there is an |
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.
;error
lib/db_connection.ex
Outdated
Returns `{:ok, conn}` on success or `{;error, exception}` if there is an | ||
error. | ||
|
||
To use the locked connection call requests with the 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.
I had a hard time understanding this first sentence.
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.
Rephrased
lib/db_connection.ex
Outdated
|
||
To use the locked connection call requests with the connection | ||
reference returned. If the connection disconnects all future calls using that | ||
connection reference will fail. |
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.
Can we be clearer on the meaning of fail? Is it raise
or an error tuple?
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 happens if I call it multiple times from the same process? Error? I get multiple connections? Other?
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.
Made clearer (and elsewhere)
lib/db_connection.ex
Outdated
@doc """ | ||
Begin a transaction. | ||
|
||
Return `{:ok, result}` on sucess or `{:error, exception}` if there was an |
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 about :aborted_transaction
and :already_in_transaction
? What happens if it is invoked multiple times?
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 approach doesn't work nicely with logging, because these requests will be logged when no query is occurring, and if either of those are returned it will crash loggers.
There are also issues with some DBs. PostgreSQL ignores (but warns on) multiple start transactions, whereas MySQL will do an implicit commit and start a fresh transaction. Also PostgreSQL will implicitly turn committing transaction into rolling back transaction in an aborted state. This means providing the safe/ecto style is also very opinionated, and doesn't fit any database behaviour.
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, so we need a mechanism to get the transaction status. And if we don't need those operations, maybe there is little to no purpose in providing begin,rollback,commit in isolation? Shouldn't we just use the checkout_begin and variants instead? Shouldn't we aim to have the smallest possible API?
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 about using db_connection with databases that don't have transactions? checkout
makes sense in that case, but checkout_begin
does not.
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 think we can definitely ditch commit_checkin
and rollback_checkin
because the semantics are not 100% clear. There is checkout
and checkin
for non-transaction dbs.
I think that begin
on its own can be useful for mode: :savepoint
- i believe we will need this feature in the sandbox.
lib/db_connection.ex
Outdated
reference passed as the single argument to the `fun`. If the | ||
connection disconnects all future calls using that connection | ||
reference will fail. | ||
Returns `:ok` on success, otherwise `{:error, exception}` on error. |
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 happens if the connection was already checked in? Or if it was disconnected between the last command and checking in?
disconnects all future calls using that connection reference will return (or | ||
raise if bang(!) variant) a `%DBConnection.ConnectionError{}` error. | ||
|
||
Use `checkin/2`, `commit_checkin/2` or `rollback_checkin/2` to release the |
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 will happen in case of plain checkin/2
?
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.
Nothing, but note we are going to have an option to force rollback
on checkin (inside the pool).
@doc """ | ||
Acquire a lock on a connection and begin a transaction. | ||
|
||
Return `{:ok, conn, result}` on success or raise an exception if there was |
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.
The return is {conn, result}
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.
return = | ||
with {:ok, result, meter} | ||
<- run(conn, &run_commit/4, meter(opts), opts) do | ||
checkin(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.
Can checkin/2
fail 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.
Define fail? checkin/2
will not raise, but it may return an {:error, %DBConnection.Error{}}
if the socket is disconnected. For the socket to be disconnect at this moment the commit must have disconnected, and for that happen the with
pattern doesn't match and checkin/2
isn't called. We are removing this function though because it is not easy to understand.
def begin(conn, opts \\ []) do | ||
conn | ||
|> run(&run_begin/4, meter(opts), opts) | ||
|> log(:begin, :begin, nil) |
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.
As this function would never be used with a pool (unless crazy) we could merge it with checkout_begin
, such that if the pool is used it performs the current checkout_begin
. The pool
version implemented here could be emulated by immediately checking in. This would mean begin(conn, opts) :: {:ok, t, result}
.
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 about a case where you check out a connection and do multiple transactions using the same connection? I could imagine this could be useful if you know you need to do couple transactions one after another.
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.
That would be begin(t, opts):: {:ok, t, result}
which is a subset of begin(conn, opts) :: {:ok, t, result}
. The merging means that if conn
is a GenServer.server
(aka a pool) it performs a checkout and a begin and if conn
is DBConnection.t
(aka connection reference) it performs a begin. This supports both nested and multiple serial transactions (should a database support 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.
What in the case of non-transactional dbs. Do they also use begin
?
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 have checkout(pool, opts) :: {:ok, t}
and checkin(t) :: :ok
.
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.
Ah, so it's only checkout_begin
we're getting rid of. Then 👍. I understood it in a way that the only think we're going to leave is begin
.
Requiring a fun for a transaction and non-local return for a rollback
is a very limiting API. With this breaking change the API becomes more
flexible, in particular it allows transaction to continue over multiple
OTP behaviour callbacks and use of (potentially nested) savepoints.
Removes:
Adds: