2

I'm trying to put some DBIX transactions under an eval block to either make all transactions or none if something goes wrong in the middle.

I've got several transactions being done like this and none of them is giving me problems. None but one:

The way I build the transactions is rather tricky and, for what it's worth, these actually work. I can post that chunk of code if you want, but maybe the eval block will suffice:

    eval {
      for (my $sub = 0; $sub < $neuroexam_index; $sub++){
        $transactions{neuroexams}{$sub}->insert;
      }
    } or die $!;

The problem would be that the eval shows an error ([error] Caught exception in pbitdb::Controller::Subjects->add "Died at /home/lioneluranl/svn/pbitdb/pbitdb/script/../lib/pbitdb/Controller/Subjects.pm line 411.") in my console, but actually performs the insertions. Why would that be?

Leitouran
  • 578
  • 3
  • 16
  • 5
    *"to either make all transactions or none if something goes wrong in the middle"* An `eval` block won't act like a database transaction, if that's what you're hoping. Any calls to `insert` that didn't cause an exception won't be rolled back if a later one fails. Also, it looks like `$transactions{neuroexams}` should be an *array* reference rather than a hash reference. – Borodin Jan 27 '17 at 18:38
  • 1
    The problem with `eval { ... } or do { ... };` is that it may go to `do` because of the (possibly legitimate) false return from code in `eval`, what ikegami's post solves. The other way is to explicitely test for error, `eval { }; if ($@) { ... };`, in which case the return doesn't matter (it doesn't decide about it). – zdim Jan 27 '17 at 20:50
  • @borodin that's very interesting because it means I understood the whole eval thing wrong. I actually took the idea from an already working chunk of code in another catalyst app at work, and it was implemented like above and the documentation regarding that implementation explicitly said the expected behaviour was to create an "all or nothing" sort of thing. Would it be any different if I made the transactions in a subroutine and then eval the subroutine for a change? – Leitouran Jan 27 '17 at 22:49
  • 2
    @LionelUranLandaburu: There is nothing in the great majority of languages, including Perl, that allows you to automatically rewind all that has been done since a specific point. SQL has the notion of *transactions*, which does exactly this, but only in relation to the data in a database. Clearly the actions a Perl program may take can be much further reaching, including all of the systems on the world-wide internet, and the program may consist of multiple asynchronous threads, so it cannot be done in general. – Borodin Jan 27 '17 at 23:04
  • 1
    @LionelUranLandaburu: `eval` simply catches fatal errors that would otherwise terminate the program, and gives you the chance to write code which will handle the error. If you can write code to "undo" the actions of the `eval` block before it failed, then it can be executed in the case of an error. But nothing will happen automatically. – Borodin Jan 27 '17 at 23:09
  • Well thanks... this is great input. As a rather new bioinformatician coming from the wet lab (with close to none background in programming), there's clearly a lot to learn and understand yet. Thank you very much for the patience :) – Leitouran Jan 28 '17 at 06:55
  • The abbreviation for DBIx::Class is DBIC, DBIx is a whole namespace of DBI extensions. – Alexander Hartmaier Jan 28 '17 at 07:33

1 Answers1

10
eval {
  for (my $sub = 0; $sub < $neuroexam_index; $sub++){
    $transactions{neuroexams}{$sub}->insert;
  }
  1;  # No exception.
}
  or do {
    # ... Perform rollback here ...
    die("[error] Caught exception in pbitdb::Controller::Subjects->add: $@");
  };

or

if (!eval {
  for (my $sub = 0; $sub < $neuroexam_index; $sub++){
    $transactions{neuroexams}{$sub}->insert;
  }
  1;  # No exception.
}) {
  # ... Perform rollback here ...
  die("[error] Caught exception in pbitdb::Controller::Subjects->add: $@");
}

Three changes:

  • 1; was added to ensure a true value is returned when there is no exception to ensure the RHS of the or isn't evaluated.
  • The caught exception is found in $@, not $!. ($! maps to errno, the error code set by C library calls and by system calls.)
  • Catching the exception to simply rethrow it useless. You seem to want to catch the exception to wrap the exception message, but you weren't doing so. You also mention database transactions, so I presume you want to perform a rollback on exception.
ikegami
  • 367,544
  • 15
  • 269
  • 518
  • Thanks for the tip :) . Now, adding that "1" wouldn't force the eval to ignore whether or not an error occurred above? Just tested it, and now no exceptions are thrown with the 1, but removing it will cause the exception to be thrown again. Is this safe? – Leitouran Jan 27 '17 at 18:35
  • 5
    It won't reach the `1` if an exception occurred earlier in the block. – ikegami Jan 27 '17 at 18:40
  • @ikegami out of sheer curiosity, are there any benefits to doing this the `or do` way as opposed to the simple `if()`? – stevieb Jan 27 '17 at 20:10
  • 4
    @stevieb, They are equivalent. – ikegami Jan 27 '17 at 20:25
  • @stevieb Curious, I find `or do` to be the simplest and cleanest way. But then that's precisely why it needs that `1` .... eh – zdim Jan 28 '17 at 00:37
  • @zdim, No, `if (!eval { ... })` also needs the block to return true. – ikegami Jan 28 '17 at 00:54
  • @ikegami I meant that I prefer `or do` and that I'd be a happy camper if _that_ didn't need it. The `if` may have it :) – zdim Jan 28 '17 at 00:56
  • @zdim I've always preferred the `if()` way of doing things, as it flows better (imho). I haven't ever used `or do` before, so was asking to see whether there was a benefit to the latter from someone I've always looked up to ;) – stevieb Jan 28 '17 at 01:07
  • @ikegami I also meant that it needs it precisely for being so clean -- it merely tests the return value for truthiness. Then the same goes for `if`. – zdim Jan 28 '17 at 01:08
  • @stevieb By all means, I also cherish every word :). Was just commenting, taste is an interesting thing. The `if-else` part is exactly the same as `or`. But the block after `if` is not (exactly) the same as `do`. See docs for do. I like it and use it. – zdim Jan 28 '17 at 01:13
  • @zdim will `do`, so to speak – stevieb Jan 28 '17 at 01:19