Revision history for DBIx::Class
+ - Allow a scalarref to be supplied to the 'from' resultset attribute
+ - Classes submitted as result_class for a resultsource are now
+ automatically loaded via ensure_loaded()
+ - 'result_class' resultset attribute, identical to result_class()
0.08099_05 2008-10-30 21:30:00 (UTC)
- Rewritte of Storage::DBI::connect_info(), extended with an
SQL
# Finally, register your new ResultSource with your Schema
- My::Schema->register_source( 'UserFriendsComplex' => $new_source );
+ My::Schema->register_extra_source( 'UserFriendsComplex' => $new_source );
Next, you can execute your complex query using bind parameters like this:
sub belongs_to {
my ($class, $rel, $f_class, $cond, $attrs) = @_;
+
+ # assume a foreign key contraint unless defined otherwise
+ $attrs->{is_foreign_key_constraint} = 1
+ if not exists $attrs->{is_foreign_key_constraint};
+
# no join condition or just a column name
if (!ref $cond) {
$class->ensure_class_loaded($f_class);
use Scalar::Util ();
use base qw/DBIx::Class/;
-__PACKAGE__->mk_group_accessors('simple' => qw/result_class _source_handle/);
+__PACKAGE__->mk_group_accessors('simple' => qw/_result_class _source_handle/);
=head1 NAME
# see https://bugzilla.redhat.com/show_bug.cgi?id=196836
my $self = {
_source_handle => $source,
- result_class => $attrs->{result_class} || $source->resolve->result_class,
cond => $attrs->{where},
count => undef,
pager => undef,
bless $self, $class;
+ $self->result_class(
+ $attrs->{result_class} || $source->resolve->result_class
+ );
+
return $self;
}
If your table does not have a primary key, you B<must> provide a value for the
C<key> attribute matching one of the unique constraints on the source.
+In addition to C<key>, L</find> recognizes and applies standard
+L<resultset attributes|/ATTRIBUTES> in the same way as L</search> does.
+
Note: If your query does not return only one row, a warning is generated:
Query returned more than one row
=cut
+sub result_class {
+ my ($self, $result_class) = @_;
+ if ($result_class) {
+ $self->ensure_class_loaded($result_class);
+ $self->_result_class($result_class);
+ }
+ $self->_result_class;
+}
=head2 count
# SELECT child.* FROM person child
# INNER JOIN person father ON child.father_id = father.id
+If you need to express really complex joins or you need a subselect, you
+can supply literal SQL to C<from> via a scalar reference. In this case
+the contents of the scalar will replace the table name asscoiated with the
+resultsource.
+
+WARNING: This technique might very well not work as expected on chained
+searches - you have been warned.
+
+ # Assuming the Event resultsource is defined as:
+
+ MySchema::Event->add_columns (
+ sequence => {
+ data_type => 'INT',
+ is_auto_increment => 1,
+ },
+ location => {
+ data_type => 'INT',
+ },
+ type => {
+ data_type => 'INT',
+ },
+ );
+ MySchema::Event->set_primary_key ('sequence');
+
+ # This will get back the latest event for every location. The column
+ # selector is still provided by DBIC, all we do is add a JOIN/WHERE
+ # combo to limit the resultset
+
+ $rs = $schema->resultset('Event');
+ $table = $rs->result_source->name;
+ $latest = $rs->search (
+ undef,
+ { from => \ "
+ (SELECT e1.* FROM $table e1
+ JOIN $table e2
+ ON e1.location = e2.location
+ AND e1.sequence < e2.sequence
+ WHERE e2.sequence is NULL
+ ) me",
+ },
+ );
+
+ # Equivalent SQL (with the DBIC chunks added):
+
+ SELECT me.sequence, me.location, me.type FROM
+ (SELECT e1.* FROM events e1
+ JOIN events e2
+ ON e1.location = e2.location
+ AND e1.sequence < e2.sequence
+ WHERE e2.sequence is NULL
+ ) me;
+
=head2 for
=over 4
my $attrs = $new_parent_rs->_resolved_attrs;
$new_parent_rs->{attrs}->{$_} = undef for qw(prefetch include_columns +select +as); # prefetch, include_columns, +select, +as cause additional columns to be fetched
+ # If $column can be found in the 'as' list of the parent resultset, use the
+ # corresponding element of its 'select' list (to keep any custom column
+ # definition set up with 'select' or '+select' attrs), otherwise use $column
+ # (to create a new column definition on-the-fly).
my $as_list = $attrs->{as} || [];
my $select_list = $attrs->{select} || [];
my $as_index = List::Util::first { ($as_list->[$_] || "") eq $column } 0..$#$as_list;
-
my $select = defined $as_index ? $select_list->[$as_index] : $column;
my $new = bless { _select => $select, _as => $column, _parent_resultset => $new_parent_rs }, $class;
#warn "$self $k $for $v";
unless ($for->has_column_loaded($v)) {
if ($for->in_storage) {
- $self->throw_exception("Column ${v} not loaded on ${for} trying to reolve relationship");
+ $self->throw_exception("Column ${v} not loaded on ${for} trying to resolve relationship");
}
return $UNRESOLVABLE_CONDITION;
}
sub select {
my ($self, $table, $fields, $where, $order, @rest) = @_;
- $table = $self->_quote($table) unless ref($table);
+ if (ref $table eq 'SCALAR') {
+ $table = $$table;
+ }
+ elsif (not ref $table) {
+ $table = $self->_quote($table);
+ }
local $self->{rownum_hack_count} = 1
if (defined $rest[0] && $self->{limit_dialect} eq 'RowNum');
@rest = (-1) unless defined $rest[0];
my $schema = DBICTest->init_schema();
-plan tests => 67;
+plan tests => 69;
# has_a test
my $cd = $schema->resultset("CD")->find(4);
is( $cd->title, 'Greatest Hits 2: Louder Than Ever', 'find_or_new_related new record ok' );
ok( ! $cd->in_storage, 'find_or_new_related on a new record: not in_storage' );
-# print STDERR Data::Dumper::Dumper($cd->get_columns);
-# $cd->result_source->schema->storage->debug(1);
$cd->artist(undef);
my $newartist = $cd->find_or_new_related( 'artist', {
name => 'Random Boy Band Two',
artistid => 200,
} );
-# $cd->result_source->schema->storage->debug(0);
is($newartist->name, 'Random Boy Band Two', 'find_or_new_related new artist record with id');
is($newartist->id, 200, 'find_or_new_related new artist id set');
is ($@, '', 'Staged insertion successful');
ok($new_artist->in_storage, 'artist inserted');
ok($new_related_cd->in_storage, 'new_related_cd inserted');
+
+# check if is_foreign_key_constraint attr is set
+my $rs_normal = $schema->source('Track');
+my $relinfo = $rs_normal->relationship_info ('cd');
+cmp_ok($relinfo->{attrs}{is_foreign_key_constraint}, '==', 1, "is_foreign_key_constraint defined for belongs_to relationships.");
+
+my $rs_overridden = $schema->source('ForceForeign');
+my $relinfo_with_attr = $rs_overridden->relationship_info ('cd_3');
+cmp_ok($relinfo_with_attr->{attrs}{is_foreign_key_constraint}, '==', 0, "is_foreign_key_constraint defined for belongs_to relationships with attr.");
use Test::More qw(no_plan);
use lib qw(t/lib);
use DBICTest;
-use DBIx::Class::ResultClass::HashRefInflator;
my $schema = DBICTest->init_schema();
$schema->resultset('CD')->create({ title => 'Silence is golden', artist => 3, year => 2006 });
# order_by to ensure both resultsets have the rows in the same order
+# also check result_class-as-an-attribute syntax
my $rs_dbic = $schema->resultset('CD')->search(undef,
{
prefetch => [ qw/ artist tracks / ],
{
prefetch => [ qw/ artist tracks / ],
order_by => [ 'me.cdid', 'tracks.position' ],
+ result_class => 'DBIx::Class::ResultClass::HashRefInflator',
}
);
-$rs_hashrefinf->result_class('DBIx::Class::ResultClass::HashRefInflator');
my @dbic = $rs_dbic->all;
my @hashrefinf = $rs_hashrefinf->all;
select => [qw/name tracks.title tracks.cd /],
as => [qw/name cds.tracks.title cds.tracks.cd /],
order_by => [qw/cds.cdid tracks.trackid/],
+ result_class => 'DBIx::Class::ResultClass::HashRefInflator',
});
-$rs_hashrefinf->result_class('DBIx::Class::ResultClass::HashRefInflator');
@dbic = map { $_->tracks->all } ($rs_dbic->first->cds->all);
@hashrefinf = $rs_hashrefinf->all;
$dbh->do("CREATE SEQUENCE pkid1_seq START WITH 1 MAXVALUE 999999 MINVALUE 0");
$dbh->do("CREATE SEQUENCE pkid2_seq START WITH 10 MAXVALUE 999999 MINVALUE 0");
$dbh->do("CREATE SEQUENCE nonpkid_seq START WITH 20 MAXVALUE 999999 MINVALUE 0");
-$dbh->do("CREATE TABLE artist (artistid NUMBER(12), name VARCHAR(255), rank NUMBER(38))");
+$dbh->do("CREATE TABLE artist (artistid NUMBER(12), name VARCHAR(255), rank NUMBER(38), charfield VARCHAR2(10))");
$dbh->do("CREATE TABLE sequence_test (pkid1 NUMBER(12), pkid2 NUMBER(12), nonpkid NUMBER(12), name VARCHAR(255))");
$dbh->do("CREATE TABLE cd (cdid NUMBER(12), artist NUMBER(12), title VARCHAR(255), year VARCHAR(4))");
$dbh->do("CREATE TABLE track (trackid NUMBER(12), cd NUMBER(12), position NUMBER(12), title VARCHAR(255), last_updated_on DATE)");
my $schema = DBICTest->init_schema();
-plan tests => 7;
+plan tests => 11;
my $rs = $schema->resultset('CD')->search({},
{
lives_ok(sub { $rs->first->get_column('count') }, '+select/+as chained search 1st rscolumn present');
lives_ok(sub { $rs->first->get_column('addedtitle') }, '+select/+as chained search 1st rscolumn present');
lives_ok(sub { $rs->first->get_column('addedtitle2') }, '+select/+as chained search 3rd rscolumn present');
+
+
+# test the from search attribute (gets between the FROM and WHERE keywords, allows arbitrary subselects)
+# also shows that outer select attributes are ok (i.e. order_by)
+#
+# from doesn't seem to be useful without using a scalarref - there were no initial tests >:(
+#
+my $cds = $schema->resultset ('CD')->search ({}, { order_by => 'me.cdid'}); # make sure order is consistent
+cmp_ok ($cds->count, '>', 2, 'Initially populated with more than 2 CDs');
+
+my $table = $cds->result_source->name;
+my $subsel = $cds->search ({}, {
+ columns => [qw/cdid title/],
+ from => \ "(SELECT cdid, title FROM $table LIMIT 2) me",
+});
+
+is ($subsel->count, 2, 'Subselect correctly limited the rs to 2 cds');
+is ($subsel->next->title, $cds->next->title, 'First CD title match');
+is ($subsel->next->title, $cds->next->title, 'Second CD title match');
# (the TODO block itself contains tests ensuring that the warns are removed)
TODO: {
local $TODO = 'Prefetch of multiple has_many rels at the same level (currently warn to protect the clueless git)';
- use DBIx::Class::ResultClass::HashRefInflator;
#( 1 -> M + M )
my $cd_rs = $schema->resultset('CD')->search ({ 'me.title' => 'Forkful of bees' });
use lib qw(t/lib);
use DBICTest;
-plan tests => 58;
+plan tests => 62;
my $schema = DBICTest->init_schema();
);
};
diag $@ if $@;
+
+## test might_have/has_many interactions
+my $ff = $schema->resultset('ForceForeign');
+
+my $thing = $ff->create(
+ {
+ artist_1 =>
+ {
+ name => 'Crazy Frog',
+ cds =>
+ [
+ {
+ title => 'CD1',
+ year => 2007,
+ artist => {
+ name => 'Artist 1',
+ }
+ },
+ {
+ title => 'CD2',
+ year => 2007,
+ artist => {
+ name => 'Artist 2',
+ }
+ },
+ ],
+ },
+ cd_1 => {
+ title => 'CD3',
+ year => 2008,
+ artist => {
+ name => 'Artist 3',
+ }
+ }
+ }
+ );
+
+isa_ok($thing->artist_1, 'DBICTest::Schema::Artist', 'created might_have artist');
+is($thing->artist_1->name, 'Crazy Frog');
+isa_ok($thing->artist_1->cds, 'DBIx::Class::ResultSet', 'created artists cds');
+is($thing->artist_1->cds->count, 2, 'created two cds for artist');
use warnings;
use Test::More;
+use Test::Exception;
use lib qw(t/lib);
use DBICTest;
my $schema = DBICTest->init_schema();
-plan tests => 9;
+plan tests => 12;
{
my $cd_rc = $schema->resultset("CD")->result_class;
+ throws_ok {
+ $schema->resultset("Artist")
+ ->search_rs({}, {result_class => "IWillExplode"})
+ } qr/Can't locate IWillExplode/, 'nonexistant result_class exception';
+
+# to make ensure_class_loaded happy, dies on inflate
+ eval 'package IWillExplode; sub dummy {}';
+
my $artist_rs = $schema->resultset("Artist")
->search_rs({}, {result_class => "IWillExplode"});
is($artist_rs->result_class, 'IWillExplode', 'Correct artist result_class');
-
+
+ throws_ok {
+ $artist_rs->result_class('mtfnpy')
+ } qr/Can't locate mtfnpy/,
+ 'nonexistant result_access exception (from accessor)';
+
+ throws_ok {
+ $artist_rs->first
+ } qr/Can't locate object method "inflate_result" via package "IWillExplode"/,
+ 'IWillExplode explodes on inflate';
+
my $cd_rs = $artist_rs->related_resultset('cds');
is($cd_rs->result_class, $cd_rc, 'Correct cd result_class');
},
);
+# Normally this would appear as a FK constraint
+__PACKAGE__->belongs_to(
+ 'cd_3', 'DBICTest::Schema::CD', {
+ 'foreign.cdid' => 'self.cd',
+ }, {
+ is_foreign_key_constraint => 0,
+ },
+);
+
1;
--
-- Created by SQL::Translator::Producer::SQLite
--- Created on Sun Nov 9 16:28:17 2008
+-- Created on Mon Nov 10 23:52:55 2008
--
BEGIN TRANSACTION;
notes varchar(100) NOT NULL
);
+CREATE INDEX liner_notes_idx_liner_id_liner ON liner_notes (liner_id);
--
-- Table: link