Skip to content

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

Closed
wants to merge 2 commits into from

Conversation

fishcakez
Copy link
Member

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

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
@josevalim
Copy link
Member

josevalim commented Jul 5, 2017

Do we need all 8 new functions? I assume the last three are for convenience?

after
checkin(conn, opts)
end
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New line missing?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks.

@doc """
Acquire a lock on a connection.

Returns `{:ok, conn}` on success or `{;error, exception}` if there is an
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

;error

Returns `{:ok, conn}` on success or `{;error, exception}` if there is an
error.

To use the locked connection call requests with the connection
Copy link
Member

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rephrased


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.
Copy link
Member

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?

Copy link
Member

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?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made clearer (and elsewhere)

@doc """
Begin a transaction.

Return `{:ok, result}` on sucess or `{:error, exception}` if there was an
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

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?

Copy link
Member

@michalmuskala michalmuskala Jul 5, 2017

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.

Copy link
Member Author

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.

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.
Copy link
Member

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
Copy link
Member

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?

Copy link
Member Author

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
Copy link
Member

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}

Copy link
Member Author

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)
Copy link
Member

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?

Copy link
Member Author

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)
Copy link
Member Author

@fishcakez fishcakez Jul 6, 2017

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}.

Copy link
Member

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.

Copy link
Member Author

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).

Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

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.

@fishcakez
Copy link
Member Author

Closed in a favour of #88 #89 #90

@fishcakez fishcakez closed this Jul 11, 2017
@josevalim josevalim deleted the jf-no-fun-transaction branch September 24, 2018 08:20
@florinpatrascu florinpatrascu mentioned this pull request Oct 20, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants