jhthorsen / mojo-mysql

Mojolicious and Async MySQL

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Transactions between promises

yuriy-zhilovets2 opened this issue · comments

Transactions do not work if they are splitted between promises like this:

my $tx;

$tx = $db->begin;
$db->query_p("INSERT ...")->then(sub 
{
  $db->query_p("DELETE ...");
})->then(sub {
  $tx->commit;
});

Apparently, queries from then-blocks execute on different connections.

I think that documentation must reflect this limitation.

I don't believe your statement is correct. There's nothing that changes the dbh attribute on the database object. Please provide a complete failure test.

Hi, jhthorsen!
I wrote example, which is very similar to the case in a real project:

#!/usr/bin/perl

use warnings;
use strict;
use 5.010;
use utf8;

use Mojo::mysql;

my $mysql = Mojo::mysql->new({
  dsn      => 'dbi:mysql:host=localhost;database=foo',
  username => 'root',
  password => '123456',
  options  => { RaiseError => 1, PrintError => 0 }
});


my $tx = $mysql->db->begin;

$mysql->db->query_p('CREATE TABLE bar (value int(11))')

->then(sub {
  return $mysql->db->query_p('CREATE TABLE baz (value int(11))')
})

->then(sub {
  return fill(10, 20)
})

->then(sub {
  $tx->commit;
})

->catch(sub {
  undef $tx;
})

->wait;


sub fill {
  my $count = shift;
  my $value = shift;

  return $mysql->db->query_p('SELECT COUNT(*) AS total FROM bar WHERE value = ?', $value)

  ->then(sub {
    my $res   = shift;
    my $total = $res->hash->{total};
    $res->finish;

    if ($total < $count) {
      return $mysql->db->insert_p('bar', { value => $value })

      ->then(sub {
        return fill($count, $value)
      });
    } else {
      return $mysql->db->insert_p('bazz', { value => $value });
    }
  })
}

As you can see, I have specifically made a mistake in the table name (bazz instead of baz) and I expected a rollback of the transaction, but it didn't happen. After execution all tables are still alive and table 'bar' has all inserted rows.

I don't see how your example is valid, since every call to $mysql->db might create a new connection. If you want to reuse the same connection, then you have to store the value from $mysql->db. Like this:

my $db = $mysql->db;
my $tx = $db->begin;

$db->query_p('CREATE TABLE bar (value int(11))')->then(sub {
  return $db->query_p('CREATE TABLE baz (value int(11))')
})->then(sub {
  $tx->commit;
})->catch(sub {
  warn "ERR! $_[0]";
  undef $tx;
  undef $db;
})

Also, I'm not sure how good MySQL handles transactions when doing ALTER TABLE. I would recommend to double check the documentation for the MySQL server version you're using.

My bad. I stored database handle and now transaction works fine. Sorry and thanks a lot.