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
从测试数据可以看出,m1
在address
中有两个条目,而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'。
更新 我通常在 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
从测试数据可以看出,m1
在address
中有两个条目,而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'。