Skip to content

Nested transactions. #506

@jonathanstowe

Description

@jonathanstowe

The SQL Standards are a bit handwavey about nested transactions and different DB backends handle it different, but for our current purposes both Pg and SQLite basically ignore any subsequent BEGIN before a COMMIT or ROLLBACK :

WARNING:  there is no transaction in progress
COMMIT
hes_test=> BEGIN;
BEGIN
hes_test=> BEGIN;
WARNING:  there is already a transaction in progress
BEGIN
hes_test=> COMMIT;
COMMIT
hes_test=> ROLLBACK;
WARNING:  there is no transaction in progress
ROLLBACK
hes_test=> \q
sqlite> create table bar (zub text);
sqlite> begin;
sqlite> insert into bar value ("foo");
SQL error: near "value": syntax error
sqlite> insert into bar values ("foo");
sqlite> begin;
SQL error: cannot start a transaction within a transaction
sqlite> commit;
sqlite> select * from bar;
foo
sqlite> rollback;
SQL error: cannot rollback - no transaction is active

This has implications for code like:

red-do {
   my $foo = Foo.^create: @bars => [{ name => 'bumble' });
   Zub.^create: foo-id => $foo.id;
   True;
}, :transaction;

Where the implicit transaction formed ( after the #505 ,) by the first statement in the red-do will create a warning about the nested transaction, but crucially the COMMIT of that implicit transaction will silently end the transaction formed by the red-do so a failure in the second statement will not rollback any changes from the first, the subsequent ROLLBACK emitting a warning.

An inadvertent side effect of the #504 is that a failure in the nested transaction will lead to a rollback of the outer transaction, however because they are in effect separate transactions (on different client connections,) a failure in the second transaction will have no effect on the first. Prior to #504 the behaviour would have depended on the number of concurrent clients of the driver (so essentially non-deterministic.)

So what I think needs to happen is that when the new-connection is called to get a new driver for begin then an e.g. transaction-depth should be set (on the driver,) then any subsequent begin should check this and if it is set should increment the value and do nothing else and for any commit or rollback should check the value and if it is not the original value then should should do nothing but decrement the value, if it is the original value set by the first begin then the commit or rollback should be performed (and the transaction-depth unset though it is assumed that this instance of the driver will go out of scope at this point.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions