[Dbix-class] $row->find_or_create_related does not find existing rows when $row is a new result

Robert Webb Robert.Webb at riverbed.com
Thu Mar 18 17:42:32 GMT 2010


Hi there,



We're currently upgrading our DBIC from v0.08012 to the latest version (v0.08120) on a project I'm working on.  Most things are going smoothly, but we've run into a use case for find_or_create_related that used to work in the older DBIC but no longer does.  We had some code that was calling $row->find_or_create_related('rel', { ... }) on a new row object that hasn't been inserted into the database yet.  This ought to work because the relationship is a belongs_to, so it doesn't need to know anything about $row.  In the new version, the related row is always created regardless of whether the related object already exists in the DB.



We can change the code easily to just call find_or_create() on the related table itself, but we were doing it this way because we wanted to find_or_create on the related table without having to know what class to search on (that's my guess, anyway - the original impetus for the way this was written is long gone now).  Having the new row object and the relationship names was an easy way to do this, and seems like a quirky yet valid use case, so I would argue that it ought to be supported.  Or at least it would be nice if there was some kind of error or warning or something, rather than the current behavior which is to silently create a new row in the related table even if the find() would have succeeded.



I'll provide more detail about my exact use case below, and I'm happy to provide anything else.









We have two related tables, build_data and build_urls, which are pasted below with the irrelevant columns and relationships removed:



=======================

=== BEGIN BuildData ===

=======================



package NBT::Schema::Icebox::BuildData;



# Created by DBIx::Class::Schema::Loader # DO NOT MODIFY THE FIRST PART OF THIS FILE



use strict;

use warnings;



use base 'DBIx::Class::Core';





=head1 NAME



NBT::Schema::Icebox::BuildData



=cut



__PACKAGE__->table("build_data");



=head1 ACCESSORS



=head2 id



  data_type: INT

  default_value: undef

  extra: HASH(0xa042260)

  is_auto_increment: 1

  is_nullable: 0

  size: 10



=head2 build_url



  data_type: INT

  default_value: undef

  extra: HASH(0xa047638)

  is_foreign_key: 1

  is_nullable: 1

  size: 10



=cut



__PACKAGE__->add_columns(

  "id",

  {

    data_type => "INT",

    default_value => undef,

    extra => { unsigned => 1 },

    is_auto_increment => 1,

    is_nullable => 0,

    size => 10,

  },

  "build_url",

  {

    data_type => "INT",

    default_value => undef,

    extra => { unsigned => 1 },

    is_foreign_key => 1,

    is_nullable => 1,

    size => 10,

  },

);

__PACKAGE__->set_primary_key("id");

__PACKAGE__->add_unique_constraint("build_pxe_branch", ["build_url", "pxe_kernel", "branch"]);



=head1 RELATIONS



=head2 build_url



Type: belongs_to



Related object: L<NBT::Schema::Icebox::BuildUrls>



=cut



__PACKAGE__->belongs_to(

  "build_url",

  "NBT::Schema::Icebox::BuildUrls",

  { id => "build_url" },

  { join_type => "LEFT" },

);









=======================

=== BEGIN BuildUrls ===

=======================





package NBT::Schema::Icebox::BuildUrls;



# Created by DBIx::Class::Schema::Loader # DO NOT MODIFY THE FIRST PART OF THIS FILE



use strict;

use warnings;



use base 'DBIx::Class::Core';





=head1 NAME



NBT::Schema::Icebox::BuildUrls



=cut



__PACKAGE__->table("build_urls");



=head1 ACCESSORS



=head2 id



  data_type: INT

  default_value: undef

  extra: HASH(0xa04789c)

  is_auto_increment: 1

  is_nullable: 0

  size: 10



=head2 name



  data_type: VARCHAR

  default_value: undef

  is_nullable: 0

  size: 255



=cut



__PACKAGE__->add_columns(

  "id",

  {

    data_type => "INT",

    default_value => undef,

    extra => { unsigned => 1 },

    is_auto_increment => 1,

    is_nullable => 0,

    size => 10,

  },

  "name",

  {

    data_type => "VARCHAR",

    default_value => undef,

    is_nullable => 0,

    size => 255,

  },

);

__PACKAGE__->set_primary_key("id");



=head1 RELATIONS



=head2 build_datas



Type: has_many



Related object: L<NBT::Schema::Icebox::BuildData>



=cut



__PACKAGE__->has_many(

  "build_datas",

  "NBT::Schema::Icebox::BuildData",

  { "foreign.build_url" => "self.id" },

);





# Created by DBIx::Class::Schema::Loader v0.05003 @ 2010-03-17 16:38:08 # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Ln4fU7T0Koi6pqWzS6APfw





===================

=== END PASTIES ===

===================







We have some code that does a find_or_create_related call like this:



    $build_datum = $schema->resultset("NBT::Schema::Icebox::BuildData")->new_result({});

    # actually we loop through a list of relationships next, but I'm keeping it simple for the sake of the example

    $build_datum->find_or_create_related("build_url", { name => '...' });



This produces some strange SQL that looks like this:



    SELECT me.id, me.name, me.modified_ts FROM build_urls me WHERE ( ( me.name = ? AND 1 = 0 ) ): '...'



The 'AND 1 = 0' condition makes the select impossible, so a related row is always created.  The impossible condition seems to come from DBIx::Class::ResultSource->_resolve_condition(), which decides that the relationship condition is unresolvable and returns \'1 = 0'.  The condition is 'unresolvable' because the build_url column is not in_storage on $build_datum, because $build_datum is a new_result.  If the 'AND 1 = 0' were left out, this would do the right thing, I think.  But maybe theres a good reason for its existence in this case and I'm just confused?



Thanks for your time,



Byron Young (original author, I'm sending this for him since he can't mail the list for some reason -Robert)

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.scsys.co.uk/pipermail/dbix-class/attachments/20100318/785b4aaa/attachment.htm


More information about the DBIx-Class mailing list