DBIx might_have 具有自定义条件,对象总是 undef

DBIx might_have with custom conditions, object always undef

更新 我通常在 stachexchange 的乳胶部分漫游,因此我们必须提供一个完整的最小示例来重现问题。

所以这是一个完整的细分。我的问题的原始描述如下。

测试数据库设置,我们使用 SQLite,create.sql:

PRAGMA foreign_keys = ON;

DROP TABLE IF EXISTS `member`; 
DROP TABLE IF EXISTS `address`; 

create table `member` (
       `uid`    VARCHAR(30)  NOT NULL,
       `name`   VARCHAR(255) DEFAULT '',
       CONSTRAINT `pk_uid` PRIMARY KEY(`uid`)
);

INSERT INTO `member` VALUES ('m1','Test 1'),('m2','Test 2');

create table `address` (
       `uid`   VARCHAR(30)  NOT NULL,
       `address_type`  VARCHAR(30)  NOT NULL, -- will either be work or home
       `text`  TEXT         DEFAULT '',
       CONSTRAINT `pk_uid_type` UNIQUE(`uid`,`address_type`)
       CONSTRAINT `fk_uid`
         FOREIGN KEY(uid)
     REFERENCES member(uid)
         ON DELETE CASCADE
);

INSERT INTO `address` VALUES
('m1','home','home address'),
('m1','work','work address'),
('m2','home','home address');

通过

加载到test.db
sqlite3 test.db < create.sql

从测试数据可以看出,m1address中有两个条目,而m2有一个。

接下来是 DBIx 设置(我不知道如何将它合并到一个文件中,欢迎提出想法,因为它会使测试更容易)。这些是通过 dbicdump 自动生成的,这里我删除了所有评论。

Schema.pm:

use utf8;
package Schema;
use strict;
use warnings;
use base 'DBIx::Class::Schema';
__PACKAGE__->load_namespaces;
1;

Schema/Result/Member.pm:

use utf8;
package Schema::Result::Member;
use strict;
use warnings;
use base 'DBIx::Class::Core';
__PACKAGE__->table("member");
__PACKAGE__->add_columns(
  "uid",
  { data_type => "varchar", is_nullable => 0, size => 30 },
  "name",
  { data_type => "varchar", default_value => "", is_nullable => 1, size => 255 },
);
__PACKAGE__->set_primary_key("uid");
__PACKAGE__->has_many(
  "addresses",
  "Schema::Result::Address",
  { "foreign.uid" => "self.uid" },
  { cascade_copy => 0, cascade_delete => 0 },
);

# I added

__PACKAGE__->might_have(
    "home_address" =>  "Schema::Result::Address",
    #{ 'foreign.uid' => 'self.uid'},
    sub {
    my $args = shift;
    return {
        "$args->{foreign_alias}.uid" => "$args->{self_alias}.uid",
        "$args->{foreign_alias}.address_type"   => 'home',
      }
    },
    {  cascade_copy => 0, cascade_delete => 0 },
);

__PACKAGE__->might_have(
    "home_address_alt" =>  "Schema::Result::Address",
    { 'foreign.uid' => 'self.uid'},
    {  cascade_copy => 0, cascade_delete => 0 },
);

__PACKAGE__->might_have(
    "work_address" =>  "Schema::Result::Address",
    sub {
    my $args = shift;
    return {
        "$args->{foreign_alias}.uid" => "$args->{self_alias}.uid",
        "$args->{foreign_alias}.address_type"   => 'work',
      }
    },
    {  cascade_copy => 0, cascade_delete => 0 },
);

1;

Schema/Result/Address.pm:

use utf8;
package Schema::Result::Address;
use strict;
use warnings;
use base 'DBIx::Class::Core';
__PACKAGE__->table("address");
__PACKAGE__->add_columns(
  "uid",
  { data_type => "varchar", is_foreign_key => 1, is_nullable => 0, size => 30 },
  "address_type",
  { data_type => "varchar", is_nullable => 0, size => 30 },
  "text",
  { data_type => "text", default_value => "", is_nullable => 1 },
);
__PACKAGE__->add_unique_constraint("uid_address_type_unique", ["uid", "address_type"]);
__PACKAGE__->belongs_to(
  "u",
  "Schema::Result::Member",
  { uid => "uid" },
  { is_deferrable => 0, on_delete => "CASCADE", on_update => "NO ACTION" },
);

1;

我的测试脚本:

#!/usr/bin/perl

use strict;
use warnings;
use utf8;
use open qw/:std :utf8/;
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
$Data::Dumper::Maxdepth = 0;
use Modern::Perl;
use lib qw(.);
use Schema;

BEGIN {
    $ENV{DBIC_TRACE} = 1;
}


my $schema = Schema->connect(
  'dbi:SQLite:dbname=test.db',
  '',
  '',
  {
      on_connect_do => 'PRAGMA foreign_keys = ON',
      sqlite_unicode =>  1,
      RaiseError => 1,
  }
); 

my $row = $schema->resultset('Member')->find({ uid => 'm1'},
                         {
                         prefetch => ['home_address','work_address'],
                         }
                     );
# these are both undef
print Dumper $row->home_address;
print Dumper $row->work_address;

# using
$row = $schema->resultset('Member')->find({ uid => 'm1'},
                      {
                          prefetch => ['home_address','work_address'],
                          result_class => 'DBIx::Class::ResultClass::HashRefInflator',
                      }
                      );

# then
print Dumper $row;
# gives
# $VAR1 = {
#           'home_address' => undef,
#           'name' => 'Test 1',
#           'uid' => 'm1',
#           'work_address' => undef
#         };


# using the "normal might_have home_address_alt in Member on m2

$row = $schema->resultset('Member')->find({ uid => 'm2'},
                      {
                          prefetch => ['home_address_alt'],
                          result_class => 'DBIx::Class::ResultClass::HashRefInflator',
                      }
                      );

say Dumper $row;
# does work, but only because m2 only have a single entry in Address whereas m1 has two

$row = $schema->resultset('Member')->find({ uid => 'm1'},
                      {
                          prefetch => ['home_address_alt'],
                          result_class => 'DBIx::Class::ResultClass::HashRefInflator',
                      }
                      );

say Dumper $row;

# which gives this warning: DBIx::Class::Storage::DBI::select_single(): Query returned more than one row.  SQL that returns multiple rows is DEPRECATED for ->find and ->single and returns the first found.

DBIC_TRACE给出

SELECT me.uid, me.name, home_address.uid, home_address.address_type, home_address.text, work_address.uid, work_address.address_type, work_address.text FROM member me LEFT JOIN address home_address ON ( home_address.address_type = ? AND home_address.uid = ? ) LEFT JOIN address work_address ON ( work_address.address_type = ? AND work_address.uid = ? ) WHERE ( me.uid = ? ): 'home', 'me.uid', 'work', 'me.uid', 'm1'

如果你 运行 它手动反对 test.db 给出

m1|Test 1|m1|home|home address|m1|work|work address

所以 SQL 能够产生正确的输出。但是 accessors/objects 不管你怎么称呼他们,一直是空的。我想知道为什么?


我原来的问题:

我的数据我有成员,他们每个人最多可以有两个地址(家庭和工作)都存储在同一个 table

所以我有类似的东西

Member; primary key(uid)
Address; unique(uid,address_type) # the latter is work or home
 

当我抓住一个成员时,我想使用 might_have 关系预取最多两个地址。所以在 Schema::Result::Member 我有

__PACKAGE__->might_have(
    "home_address" =>  "Schema::Result::Address",
    sub {
    my $args = shift;
    return {
        "$args->{foreign_alias}.uid" => "$args->{self_alias}.uid",
        "$args->{foreign_alias}.address_type"   => 'home',
      }
    },
    {  cascade_copy => 0, cascade_delete => 0 },
);
__PACKAGE__->might_have(
    "work_address" =>  "Schema::Result::Address",
    sub {
    my $args = shift;
    return {
        "$args->{foreign_alias}.uid" => "$args->{self_alias}.uid",
        "$args->{foreign_alias}.address_type"   => 'work',
      }
    },
    {  cascade_copy => 0, cascade_delete => 0 },
);

我通过

调用它
my $row = $self->schema->resultset('Member')
    ->find({uid => $uid},
           {
           prefetch => [qw/home_address work_address/],
           });

据我所知 DBIC_TRACE 生成的 SQL 是正确的

... LEFT JOIN address home_address ON ( home_address.address_type = ? AND home_address.uid = ? ) LEFT JOIN address work_address ON ( work_address.address_type = ? AND work_address.uid = ? ) WHERE ( me.uid = ? ): 'home', 'me.uid', 'work', 'me.uid', '120969'

但是 $row->home_address 总是只是 undef,我不明白为什么。

我也试过了

__PACKAGE__->might_have(
  "home_address" => "Schema::Result::Address",
  { 'foreign.uid' => 'self.uid' },
  { where => { 'address_type' => 'home' } , cascade_copy => 0, cascade_delete => 0 },
);
__PACKAGE__->might_have(
  "work_address" =>  "Schema::Result::Address",
  { 'foreign.uid' => 'self.uid' },
  { where => { 'address_type' => 'work' } , cascade_copy => 0, cascade_delete => 0 },
);

但是 where 部分从来不是 DBIC_TRACE 的一部分。

关于我在这里遗漏了什么有什么想法吗?

DBIx::Class 文档有一个在 rel 的远程端具有固定值的自定义关系示例:https://metacpan.org/pod/DBIx::Class::Relationship::Base#Custom-join-conditions.

您错过的部分是 -ident,因此 DBIC 可以区分固定值和相关列。 因此,查询以一个绑定变量结束,该变量在执行时传递了文字字符串 'me.uid'。