This is one of those issues you hope you don't encounter more than once a year...

Try this:

Setup a database with the following schema:
create table foo ( test1 int not null auto_increment, test2 int null, primary key (test1) );
Setup a CDBI class like this:
package Foo; use base 'Class::DBI'; __PACKAGE__->columns( All => qw/test1 test2/ ); __PACKAGE__->add_trigger(before_create => \&trigger_code); sub trigger_code { my $self = shift; $self->test2('123') unless $self->test2; }
i.e. we're setting a default value.

Now write a script to insert a row:
Foo->connection('dbi:mysql:dbname=testdb','root',''); Foo->create({});
If all went according to plan, you should have a row inserted into your DB with the 'test2' column set to the default value.

Here comes the weird part. Add the line use Test::MockObject at the top of your script. (You don't actually have to use the module in any way). If you're anything like me, you'll get the error:
Use of uninitialized value in split at /usr/local/share/perl/5.8.4/UNI +VERSAL/can.pm line 51. Called UNIVERSAL::can() as a function, not a method at /usr/local/shar +e/perl/5.8.4/Class/DBI.pm line 265 Can't call method "can" on an undefined value at /usr/local/share/perl +/5.8.4/UNIVERSAL/can.pm line 40.
Extremely strange.... and the sort of thing you can easily spend an afternoon chasing (guess who just did this...)

Now try one of the following: Anyone of these should solve the problem...

Hmm... anyone have any ideas why this might be the case? (Note, I haven't tried this on another db, because SQLite doesn't have auto_increment, and I don't have easy access to anything else that does. I guess it could be a bug with db's that use sequences as well.. or maybe that's nothing to do with it).

Versions used:

Replies are listed 'Best First'.
Re: Strange bug involving mysql, auto_increment, Class::DBI and Test::MockObject
by stvn (Monsignor) on Dec 13, 2005 at 22:05 UTC

    From the Test::MockObject ChangeLog file:

    - use UNIVERSAL::isa and UNIVERSAL::can modules
    These two module override &UNIVERAL::isa and &UNIVERSAL::can (obviously), and attempt to enforce their "proper" usage (called as a method, not as a function). Of course this is a subject not everyone agrees upon (just look at the CPAN reviews for the two UNIVERSAL:: modules to see this).

    From your error messages, I can deduce that Class::DBI does not use &UNIVERSAL::can "properly", so that is the source of your error. Althougth the two errors that it is sandwiched between are (I suspect) bugs in UNIVERSAL::can since it really should handle those edge cases correctly.

    UPDATE
    Upon closer inspection of the issue, I found this at line 265 of Class::DBI:

    line 255 > my @pk_values = $self->_attrs($self->primary_columns); line 265 > UNIVERSAL::can($_ => 'id') and $_ = $_->id for @pk_values;
    Then given one of your fixes:
    Hardcode the primary key value when you 'create' (i.e. set 'test1' to something)
    I would suspect that this is possibly a bug in Class::DBI and maybe line 265 should read:
    UNIVERSAL::can($_ => 'id') and $_ = $_->id for grep { defined $_ } @pk +_values;
    Or something similar.

    However, it could also be argued that it worked before, and the "bug" is only introduced by the addition of UNIVERSAL::can. So I guess it all comes down to which author you want to complain to, chromatic or TBOWDEN :)

    -stvn

      Any code that uses UNIVERSAL::can() or UNIVERSAL::isa() as functions breaks Test::MockObject and Class::Roles. I can't fix that.

      I can use UNIVERSAL::isa and UNIVERSAL::can in my code to warn people that things will break if they use them incorrectly and to try to route around the damage as much as possible.

      Still, I don't have any sympathy for people who write broken code and expect it to work. If you break my code, you get to keep both pieces. Don't save your receipt.

      Oh, and the Class::DBI code should likely be instead:

      eval { $_->can( 'id' ) } and $_ = $_->id for grep { defined $_ } @pk_values;
        I can use UNIVERSAL::isa and UNIVERSAL::can in my code to warn people that things will break if they use them incorrectly and to try to route around the damage as much as possible.

        I agree with you 100%, however, the problem is that UNIVERSAL::can actually dies, and not just warns in this particular case. The last error in the OPs post ...

        Can't call method "can" on an undefined value at /usr/local/share/perl/5.8.4/UNIVERSAL/can.pm line 40.
        is actually UNIVERSAL::can die-ing. This is is something too that (IMO) can be routed around fairly easily so I sent you a patch and test for this.

        -stvn
Re: Strange bug involving mysql, auto_increment, Class::DBI and Test::MockObject
by perrin (Chancellor) on Dec 13, 2005 at 19:17 UTC
    It probably involves the way Test::MockObject replaces UNIVERSAL::isa and UNIVERSAL::can with it's own stuff. You really should not use this module from a non-test class. Both Class::DBI and Test::MockObject do a lot of perilous things in their code, so you are likely to discover other compatibility problems after solving this one.
      So, how should one use Test::MockObject to test code that uses CDBI?

      My criteria for good software:
      1. Does it work?
      2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
        I would need some explanation of what is being attempted here before I could offer advice. Is it to test a mock of a CDBI class? To store some kind of T::MO state? I have no idea. In normal use, you call T::MO from a test script and use it to replace some other class.
Re: Strange bug involving mysql, auto_increment, Class::DBI and Test::MockObject
by chromatic (Archbishop) on Dec 14, 2005 at 01:54 UTC