Boldra has asked for the wisdom of the Perl Monks concerning the following question:

Hi monnks

I'm trying to use populate to fill three tables at once, and getting invalid SQL. With two tables this works, but with three tables there's a missing IN. By adding some debug code to DBIx::Class::Storage::DBI I was able output the SQL, and this is what is being sent to _prepare_sth:

INSERT INTO scenes ( film, time, title) VALUES ( (SELECT me.title FROM movies me WHERE ( ( director (SELECT me.name FROM directors me WHERE ( name = ? )) AND title = ? ) )), ?, ? )
That looks like it should work if there were only an IN between the WHERE and director. Below is the perl I'm using to demonstrate the problem. Is this a known DBIC limitation, or am I creating the relations in the schema incorrectly?
package TestDB::Scene { use parent 'DBIx::Class::Core'; __PACKAGE__->table('scenes'); __PACKAGE__->add_columns(qw<title time description film>); __PACKAGE__->set_primary_key(qw<time>); }; package TestDB::Film { use parent 'DBIx::Class::Core'; __PACKAGE__->table('movies'); __PACKAGE__->add_columns(qw<title director>); __PACKAGE__->set_primary_key(qw<title>); __PACKAGE__->has_many( scenes => 'Scene' ); }; package TestDB::Director { use parent 'DBIx::Class::Core'; __PACKAGE__->table('directors'); __PACKAGE__->add_columns(qw<name>); __PACKAGE__->set_primary_key(qw<name>); __PACKAGE__->has_many( nasties => 'Film' ); }; package TestDB { use parent 'DBIx::Class::Schema'; __PACKAGE__->load_classes(qw<Film Director Scene>); }; package main { use Modern::Perl; main: { my $schema = deploy_new_to_temp_file(); my $rs = $schema->resultset('Director'); my @population = ( { name => "Lesly Tack", nasties => [ { title => 'Alligator', scenes => [ { title => '1st swamp pan', time => '05:36-12:01', }, ] } ] } ); $rs->populate( \@population ); } sub deploy_new_to_temp_file { my $schema = TestDB->connect('dbi:SQLite:dbname=test'); $schema->deploy( { add_drop_table => 1 } ); return $schema; } };

Thanks for any help



- Boldra

Replies are listed 'Best First'.
Re: SQL Syntax error from recursive DBIx::Class populate
by Anonymous Monk on Nov 22, 2016 at 17:17 UTC

    Hi Boldra,

    with some minor modifications your script works as intended:

    package TestDB::Scene { use parent 'DBIx::Class::Core'; __PACKAGE__->table('scenes'); __PACKAGE__->add_columns(qw<title time description film>); __PACKAGE__->set_primary_key(qw<time>); }; package TestDB::Film { use parent 'DBIx::Class::Core'; __PACKAGE__->table('movies'); __PACKAGE__->add_columns(qw<title director>); __PACKAGE__->set_primary_key(qw<title>); __PACKAGE__->has_many( scenes => 'TestDB::Scene', 'time' ); }; package TestDB::Director { use parent 'DBIx::Class::Core'; __PACKAGE__->table('directors'); __PACKAGE__->add_columns(qw<name>); __PACKAGE__->set_primary_key(qw<name>); __PACKAGE__->has_many( movies => 'TestDB::Film', 'title' ); }; package TestDB { use parent 'DBIx::Class::Schema'; __PACKAGE__->load_classes(qw<Film Director Scene>); }; package main { use Modern::Perl; main: { my $schema = deploy_new_to_temp_file(); my $rs = $schema->resultset('Director'); my @population = ( { name => "Lesly Tack", movies => [ { title => 'Alligator', director => '#Lesly Tack', scenes => [ { title => '1st swamp pan', time => '05:36-12:01', description => 'Murder in a Swamp base +d Collection', film => '#Alligator', }, ] } ] } ); my @sources = $schema->sources; # for my $source (@sources) { # print "Table: ", $source, "\n"; # print "Columns: ", (join ", ", $schema->source($source)- +>columns), "\n"; # print "Relationships: ", $schema->source($source)->relat +ionships, "\n"; # } $rs->populate( \@population ); } sub deploy_new_to_temp_file { my $schema = TestDB->connect('dbi:SQLite:dbname=test.db'); $schema->deploy( { add_drop_table => 1 } ); return $schema; } };

    But I think, you have to redesign your database a little, because the columns 'director' in table movies and 'film' in table scenes seem redundant.

    Cheers Brian

      Thanks for your ideas Brian,

      It looks like you're doing something quite different to what I intended. I need the foreign keys (film in scene and director in films) to make relationships, so they're not redundant. By providing the foreign keys explicitly, the relationships aren't inferred by their position in the hashref (which works with <3 levels), which would be fine, but I have to use the keys of the parent object, or the relationship doesn't exist.

      By the way, the strange data and relationship names are inspired by the cascading-delete unit test for DBIC: t/cdbi/23-cascade.t.



      - Boldra
Re: SQL Syntax error from recursive DBIx::Class populate
by ribasushi (Pilgrim) on Dec 05, 2016 at 08:12 UTC

    Hi Paul,

    This is an actual bug introduced by (I think) https://github.com/dbsrgits/dbix-class/commit/d0cefd99a. I will fix that shortly after the current permission dispute is resolved and I can get back to actually working on things.

    In the meantime - use create() instead, it will be a tad (not much) slower, and will DTRT.

    Minimal reproduction based on the DBIC test suite:

    ~$ perl -It/lib -MDBICTest -e ' DBICTest->init_schema->resultset("Artist")->populate([{ name => "beh", cds => [{ cdid => 666, year => 0, title => "buh", tracks => [{ title => "eeeh" }] }] }]) '

    The working example with create() instead:

    ~$ DBIC_TRACE_PROFILE=console perl -It/lib -MDBICTest -e ' my $s = DBICTest->init_schema; $s->storage->debug(1); $s->resultset("Artist")->create({ name => "beh", cds => [{ cdid => 666, year => 0, title => "buh", tracks => [{ title => "eeeh" }] }] }) ' BEGIN WORK INSERT INTO artist( name ) VALUES( 'beh' ) INSERT INTO cd( artist, cdid, title, year ) VALUES( '4', '666', 'buh', '0' ) SELECT me.position FROM track me WHERE cd = '666' ORDER BY position DESC LIMIT '1' INSERT INTO track( cd, position, title ) VALUES( '666', '1', 'eeeh' ) COMMIT
      Thanks Ribasushi! I've refactored to make two populate calls, and that works fine for me. I didn't measure the performance difference, but I can leave a note for future maintainers that if the bug is fixed they can use one populate call to improve performance.


      - Boldra