[Dbix-class] DBIx::Class::Schema::Loader Can't use string error

Dave Howorth dhoworth at mrc-lmb.cam.ac.uk
Mon Nov 1 15:49:08 GMT 2010


Dave Howorth wrote:
> I can also confirm that it really is a problem in
> DBIx::Class::Schema::Loader or one of its dependencies.
[snip]
> Unfortunately, I haven't yet reproduced it with a reasonably small
> schema that makes it clear exactly where the problem is. A minimal
> example doesn't show the problem. It's something to do with mishandling
> of accessor name clashes but I'm not certain about the details yet.

OK, I've now got a failing test case and I understand the cause of the
crash in DBIx::Class::Schema::Loader (DCSL).

The test case is inline below. Note that the order of defining columns
in the database is critical to provoking the bug, which is why my
simplest cases didn't show it.

The data that causes DCSL to crash is a schema that contains a foreign
key  column definition 'belongs_to' (it predates DBIC). The same table
also has another column that is a foreign key. DCSL emits code that
injects a belongs_to relationship called 'belongs_to' into the package
and then tries to execute it as though it were the built-in method of
the same name.

DCSL::Base has a method called _resolve_col_accessor_collisions that
notices naming conflicts. But DCSL::RelBuilder doesn't make use of that
information. A possible fix would be to make it do so or otherwise make
an equivalent check before generating code.

But in my view a better solution would be for DCSL to emit warnings
about name clashes and not generate code at all. Quietly generating code
that isn't going to work even if it doesn't immediately crash, as it
does in most cases at present, is not very helpful to the user.


Another question is how to produce a functioning Result class, once one
is made aware of the need for customisation. I have found
custom_column_info but its POD says "Hook for adding *extra* attributes
to the column_info for a column" (my emphasis). I have found that I can
use it to change existing attributes, specifically 'accessor', so code
like that below appears to generate usable classes, even though the
replaced accessor name doesn't appear in the generated relationship
definition:

use DBIx::Class::Schema::Loader qw/ make_schema_at /;

sub my_custom_column_info
{
    my ($table_name, $column_name, $column_info) = @_;

    if ($column_name eq 'belongs_to')
    {
        return { accessor => 'belong_him' };
    }
}


make_schema_at( 'NameClash2::Schema',
                {
                        debug => 1,
                        dump_directory => '.',
                        custom_column_info => \&my_custom_column_info,
                },
                [
                        "dbi:SQLite:dbname=$file",
                        '',
                        '',
                ],
);


It's then possible in a script using the generated classes to say
$t2_obj->belong_him and get a T1 object in return.


Is that a good way to change accessor names or is there a better way? I
couldn't find any documentation and am concerned whether it works in all
cases and will keep working in the future?


Cheers, Dave


======== name-clash-test.pl =========

#!/usr/bin/perl
use strict;
use warnings;

=head1 NAME

name-clash-test.pl - crash DBIx::Class::Schema::Loader when there is a
column name that collides with a built-in method name

2010-10-28

=cut

my $file = 'name-clash-test.dbfile';

my $ddl1 = '
CREATE TABLE t1 (
    id         INTEGER PRIMARY KEY,
    noise      TEXT
);';

my $ddl2 = '
CREATE TABLE t2 (
    id         INTEGER PRIMARY KEY,
    second_foreign_key INTEGER,
    belongs_to INTEGER,
    FOREIGN KEY (second_foreign_key) REFERENCES t1(id),
    FOREIGN KEY (belongs_to) REFERENCES t1(id)
);';

my $sql1 = 'INSERT INTO t1 (noise) VALUES ("art of");';
my $sql2 = 'INSERT INTO t2 (belongs_to) VALUES (1);';

unlink $file if -e $file;

use DBI;

# DBI->trace(1);

my $dbh = DBI->connect("dbi:SQLite:dbname=$file", '', '') or die;
$dbh->do($ddl1) or die;
$dbh->do($ddl2) or die;
$dbh->do($sql1) or die;
$dbh->do($sql2) or die;
$dbh->disconnect or die;


use DBIx::Class::Schema::Loader qw/ make_schema_at /;

make_schema_at( 'NameClash::Schema',
                {
                        debug => 1,
                        dump_directory => '.',
                },
                [
                        "dbi:SQLite:dbname=$file",
                        '',
                        '',
                ],
);



More information about the DBIx-Class mailing list