Improvements to copy() and set_from_related() code
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / ResultSource.pm
CommitLineData
9c992ba1 1package DBIx::Class::ResultSource;
2
3use strict;
4use warnings;
5
6use DBIx::Class::ResultSet;
701da8c4 7use Carp::Clan qw/^DBIx::Class/;
9c992ba1 8
6da5894c 9use Storable;
1225fc4d 10use Scalar::Util qw/weaken/;
6da5894c 11
9c992ba1 12use base qw/DBIx::Class/;
13__PACKAGE__->load_components(qw/AccessorGroup/);
14
15__PACKAGE__->mk_group_accessors('simple' =>
fc969005 16 qw/_ordered_columns _columns _primaries _unique_constraints name resultset_attributes schema from _relationships/);
17__PACKAGE__->mk_group_accessors('component_class' => qw/resultset_class result_class/);
9c992ba1 18
19=head1 NAME
20
21DBIx::Class::ResultSource - Result source object
22
23=head1 SYNOPSIS
24
25=head1 DESCRIPTION
26
27A ResultSource is a component of a schema from which results can be directly
28retrieved, most usually a table (see L<DBIx::Class::ResultSource::Table>)
29
30=head1 METHODS
31
32=cut
33
34sub new {
35 my ($class, $attrs) = @_;
36 $class = ref $class if ref $class;
1225fc4d 37 my $new = bless({ %{$attrs || {}}, _resultset => undef }, $class);
9c992ba1 38 $new->{resultset_class} ||= 'DBIx::Class::ResultSet';
5ac6a044 39 $new->{resultset_attributes} = { %{$new->{resultset_attributes} || {}} };
6da5894c 40 $new->{_ordered_columns} = [ @{$new->{_ordered_columns}||[]}];
41 $new->{_columns} = { %{$new->{_columns}||{}} };
42 $new->{_relationships} = { %{$new->{_relationships}||{}} };
9c992ba1 43 $new->{name} ||= "!!NAME NOT SET!!";
5afa2a15 44 $new->{_columns_info_loaded} ||= 0;
9c992ba1 45 return $new;
46}
47
5ac6a044 48=head2 add_columns
49
50 $table->add_columns(qw/col1 col2 col3/);
51
52 $table->add_columns('col1' => \%col1_info, 'col2' => \%col2_info, ...);
53
54Adds columns to the result source. If supplied key => hashref pairs uses
55the hashref as the column_info for that column.
56
57=head2 add_column
58
59 $table->add_column('col' => \%info?);
60
61Convenience alias to add_columns
62
63=cut
64
9c992ba1 65sub add_columns {
66 my ($self, @cols) = @_;
571dced3 67 $self->_ordered_columns( \@cols )
68 if !$self->_ordered_columns;
20518cb4 69 my @added;
70 my $columns = $self->_columns;
9c992ba1 71 while (my $col = shift @cols) {
53509665 72
30126ac7 73 my $column_info = ref $cols[0] ? shift(@cols) : {};
53509665 74 # If next entry is { ... } use that for the column info, if not
75 # use an empty hashref
76
20518cb4 77 push(@added, $col) unless exists $columns->{$col};
78
79 $columns->{$col} = $column_info;
9c992ba1 80 }
20518cb4 81 push @{ $self->_ordered_columns }, @added;
30126ac7 82 return $self;
9c992ba1 83}
84
85*add_column = \&add_columns;
86
3842b955 87=head2 has_column
88
9c992ba1 89 if ($obj->has_column($col)) { ... }
90
91Returns 1 if the source has a column of this name, 0 otherwise.
92
93=cut
94
95sub has_column {
96 my ($self, $column) = @_;
97 return exists $self->_columns->{$column};
98}
99
100=head2 column_info
101
102 my $info = $obj->column_info($col);
103
104Returns the column metadata hashref for a column.
105
106=cut
107
108sub column_info {
109 my ($self, $column) = @_;
701da8c4 110 $self->throw_exception("No such column $column")
111 unless exists $self->_columns->{$column};
5afa2a15 112 #warn $self->{_columns_info_loaded}, "\n";
113 if ( ! $self->_columns->{$column}->{data_type}
114 && ! $self->{_columns_info_loaded}
a953d8d9 115 && $self->schema && $self->storage() ){
5afa2a15 116 $self->{_columns_info_loaded}++;
a953d8d9 117 my $info;
118############ eval for the case of storage without table
119 eval{
120 $info = $self->storage->columns_info_for ( $self->from() );
121 };
122 if ( ! $@ ){
123 for my $col ( keys %{$self->_columns} ){
124 for my $i ( keys %{$info->{$col}} ){
125 $self->_columns()->{$col}->{$i} = $info->{$col}->{$i};
126 }
127 }
128 }
129 }
9c992ba1 130 return $self->_columns->{$column};
131}
132
133=head2 columns
134
20518cb4 135 my @column_names = $obj->columns;
136
137Returns all column names in the order they were declared to add_columns
87f0da6a 138
139=cut
9c992ba1 140
141sub columns {
701da8c4 142 my $self=shift;
143 $self->throw_exception("columns() is a read-only accessor, did you mean add_columns()?") if (@_ > 1);
144 return @{$self->{_ordered_columns}||[]};
571dced3 145}
146
87f0da6a 147=head2 set_primary_key(@cols)
148
9c992ba1 149Defines one or more columns as primary key for this source. Should be
150called after C<add_columns>.
87f0da6a 151
152Additionally, defines a unique constraint named C<primary>.
153
154=cut
9c992ba1 155
156sub set_primary_key {
157 my ($self, @cols) = @_;
158 # check if primary key columns are valid columns
159 for (@cols) {
701da8c4 160 $self->throw_exception("No such column $_ on table ".$self->name)
9c992ba1 161 unless $self->has_column($_);
162 }
163 $self->_primaries(\@cols);
87f0da6a 164
165 $self->add_unique_constraint(primary => \@cols);
9c992ba1 166}
167
87f0da6a 168=head2 primary_columns
169
9c992ba1 170Read-only accessor which returns the list of primary keys.
30126ac7 171
87f0da6a 172=cut
9c992ba1 173
174sub primary_columns {
175 return @{shift->_primaries||[]};
176}
177
87f0da6a 178=head2 add_unique_constraint
179
180Declare a unique constraint on this source. Call once for each unique
181constraint.
182
183 # For e.g. UNIQUE (column1, column2)
184 __PACKAGE__->add_unique_constraint(constraint_name => [ qw/column1 column2/ ]);
185
186=cut
187
188sub add_unique_constraint {
189 my ($self, $name, $cols) = @_;
190
191 for (@$cols) {
701da8c4 192 $self->throw_exception("No such column $_ on table ".$self->name)
87f0da6a 193 unless $self->has_column($_);
194 }
195
196 my %unique_constraints = $self->unique_constraints;
197 $unique_constraints{$name} = $cols;
198 $self->_unique_constraints(\%unique_constraints);
199}
200
201=head2 unique_constraints
202
203Read-only accessor which returns the list of unique constraints on this source.
204
205=cut
206
207sub unique_constraints {
208 return %{shift->_unique_constraints||{}};
209}
210
9c992ba1 211=head2 from
212
213Returns an expression of the source to be supplied to storage to specify
214retrieval from this source; in the case of a database the required FROM clause
215contents.
216
217=cut
218
219=head2 storage
220
221Returns the storage handle for the current schema
222
223=cut
224
225sub storage { shift->schema->storage; }
226
8452e496 227=head2 add_relationship
228
229 $source->add_relationship('relname', 'related_source', $cond, $attrs);
230
231The relation name can be arbitrary, but must be unique for each relationship
232attached to this result source. 'related_source' should be the name with
233which the related result source was registered with the current schema
234(for simple schemas this is usally either Some::Namespace::Foo or just Foo)
235
236The condition needs to be an SQL::Abstract-style representation of the join
237between the tables. For example, if you're creating a rel from Foo to Bar,
238
239 { 'foreign.foo_id' => 'self.id' }
240
241will result in the JOIN clause
242
243 foo me JOIN bar bar ON bar.foo_id = me.id
244
245You can specify as many foreign => self mappings as necessary.
246
247Valid attributes are as follows:
248
249=over 4
250
251=item join_type
252
253Explicitly specifies the type of join to use in the relationship. Any SQL
254join type is valid, e.g. C<LEFT> or C<RIGHT>. It will be placed in the SQL
255command immediately before C<JOIN>.
256
257=item proxy
258
259An arrayref containing a list of accessors in the foreign class to proxy in
260the main class. If, for example, you do the following:
261
fc69fea6 262 __PACKAGE__->might_have(bar => 'Bar', undef, { proxy => [ qw/margle/ ] });
8452e496 263
264Then, assuming Bar has an accessor named margle, you can do:
265
266 my $obj = Foo->find(1);
267 $obj->margle(10); # set margle; Bar object is created if it doesn't exist
268
269=item accessor
270
271Specifies the type of accessor that should be created for the relationship.
272Valid values are C<single> (for when there is only a single related object),
273C<multi> (when there can be many), and C<filter> (for when there is a single
274related object, but you also want the relationship accessor to double as
275a column accessor). For C<multi> accessors, an add_to_* method is also
276created, which calls C<create_related> for the relationship.
277
278=back
279
280=cut
281
282sub add_relationship {
283 my ($self, $rel, $f_source_name, $cond, $attrs) = @_;
701da8c4 284 $self->throw_exception("Can't create relationship without join condition") unless $cond;
8452e496 285 $attrs ||= {};
87772e46 286
8452e496 287 my %rels = %{ $self->_relationships };
288 $rels{$rel} = { class => $f_source_name,
87772e46 289 source => $f_source_name,
8452e496 290 cond => $cond,
291 attrs => $attrs };
292 $self->_relationships(\%rels);
293
30126ac7 294 return $self;
87772e46 295
953a18ef 296 # XXX disabled. doesn't work properly currently. skip in tests.
297
8452e496 298 my $f_source = $self->schema->source($f_source_name);
299 unless ($f_source) {
300 eval "require $f_source_name;";
301 if ($@) {
302 die $@ unless $@ =~ /Can't locate/;
303 }
304 $f_source = $f_source_name->result_source;
87772e46 305 #my $s_class = ref($self->schema);
306 #$f_source_name =~ m/^${s_class}::(.*)$/;
307 #$self->schema->register_class(($1 || $f_source_name), $f_source_name);
308 #$f_source = $self->schema->source($f_source_name);
8452e496 309 }
310 return unless $f_source; # Can't test rel without f_source
311
312 eval { $self->resolve_join($rel, 'me') };
313
314 if ($@) { # If the resolve failed, back out and re-throw the error
315 delete $rels{$rel}; #
316 $self->_relationships(\%rels);
701da8c4 317 $self->throw_exception("Error creating relationship $rel: $@");
8452e496 318 }
319 1;
320}
321
322=head2 relationships()
323
324Returns all valid relationship names for this source
325
326=cut
327
328sub relationships {
329 return keys %{shift->_relationships};
330}
331
332=head2 relationship_info($relname)
333
334Returns the relationship information for the specified relationship name
335
336=cut
337
338sub relationship_info {
339 my ($self, $rel) = @_;
340 return $self->_relationships->{$rel};
341}
342
953a18ef 343=head2 has_relationship($rel)
344
345Returns 1 if the source has a relationship of this name, 0 otherwise.
346
347=cut
348
349sub has_relationship {
350 my ($self, $rel) = @_;
351 return exists $self->_relationships->{$rel};
352}
353
8452e496 354=head2 resolve_join($relation)
355
356Returns the join structure required for the related result source
357
358=cut
359
360sub resolve_join {
489709af 361 my ($self, $join, $alias, $seen) = @_;
362 $seen ||= {};
87772e46 363 if (ref $join eq 'ARRAY') {
489709af 364 return map { $self->resolve_join($_, $alias, $seen) } @$join;
87772e46 365 } elsif (ref $join eq 'HASH') {
489709af 366 return
887ce227 367 map {
368 my $as = ($seen->{$_} ? $_.'_'.($seen->{$_}+1) : $_);
369 ($self->resolve_join($_, $alias, $seen),
370 $self->related_source($_)->resolve_join($join->{$_}, $as, $seen));
371 } keys %$join;
87772e46 372 } elsif (ref $join) {
701da8c4 373 $self->throw_exception("No idea how to resolve join reftype ".ref $join);
87772e46 374 } else {
489709af 375 my $count = ++$seen->{$join};
376 #use Data::Dumper; warn Dumper($seen);
377 my $as = ($count > 1 ? "${join}_${count}" : $join);
3842b955 378 my $rel_info = $self->relationship_info($join);
701da8c4 379 $self->throw_exception("No such relationship ${join}") unless $rel_info;
3842b955 380 my $type = $rel_info->{attrs}{join_type} || '';
489709af 381 return [ { $as => $self->related_source($join)->from,
953a18ef 382 -join_type => $type },
489709af 383 $self->resolve_condition($rel_info->{cond}, $as, $alias) ];
953a18ef 384 }
385}
386
489709af 387=head2 resolve_condition($cond, $as, $alias|$object)
953a18ef 388
3842b955 389Resolves the passed condition to a concrete query fragment. If given an alias,
953a18ef 390returns a join condition; if given an object, inverts that object to produce
391a related conditional from that object.
392
393=cut
394
395sub resolve_condition {
489709af 396 my ($self, $cond, $as, $for) = @_;
953a18ef 397 #warn %$cond;
398 if (ref $cond eq 'HASH') {
399 my %ret;
400 while (my ($k, $v) = each %{$cond}) {
401 # XXX should probably check these are valid columns
701da8c4 402 $k =~ s/^foreign\.// || $self->throw_exception("Invalid rel cond key ${k}");
403 $v =~ s/^self\.// || $self->throw_exception("Invalid rel cond val ${v}");
953a18ef 404 if (ref $for) { # Object
3842b955 405 #warn "$self $k $for $v";
406 $ret{$k} = $for->get_column($v);
407 #warn %ret;
fde6e28e 408 } elsif (ref $as) { # reverse object
409 $ret{$v} = $as->get_column($k);
953a18ef 410 } else {
489709af 411 $ret{"${as}.${k}"} = "${for}.${v}";
953a18ef 412 }
953a18ef 413 }
414 return \%ret;
5efe4c79 415 } elsif (ref $cond eq 'ARRAY') {
489709af 416 return [ map { $self->resolve_condition($_, $as, $for) } @$cond ];
953a18ef 417 } else {
418 die("Can't handle this yet :(");
87772e46 419 }
420}
421
b3e8ac9b 422=head2 resolve_prefetch (hashref/arrayref/scalar)
423
424Accepts one or more relationships for the current source and returns an
425array of column names for each of those relationships. Column names are
426prefixed relative to the current source, in accordance with where they appear
427in the supplied relationships. Examples:
428
5ac6a044 429 my $source = $schema->resultset('Tag')->source;
b3e8ac9b 430 @columns = $source->resolve_prefetch( { cd => 'artist' } );
431
432 # @columns =
433 #(
434 # 'cd.cdid',
435 # 'cd.artist',
436 # 'cd.title',
437 # 'cd.year',
438 # 'cd.artist.artistid',
439 # 'cd.artist.name'
440 #)
441
442 @columns = $source->resolve_prefetch( qw[/ cd /] );
443
444 # @columns =
445 #(
446 # 'cd.cdid',
447 # 'cd.artist',
448 # 'cd.title',
449 # 'cd.year'
450 #)
451
452 $source = $schema->resultset('CD')->source;
453 @columns = $source->resolve_prefetch( qw[/ artist producer /] );
454
455 # @columns =
456 #(
457 # 'artist.artistid',
458 # 'artist.name',
459 # 'producer.producerid',
460 # 'producer.name'
461 #)
462
463=cut
464
465sub resolve_prefetch {
489709af 466 my ($self, $pre, $alias, $seen) = @_;
467 $seen ||= {};
b3e8ac9b 468 use Data::Dumper;
469 #$alias ||= $self->name;
470 #warn $alias, Dumper $pre;
471 if( ref $pre eq 'ARRAY' ) {
489709af 472 return map { $self->resolve_prefetch( $_, $alias, $seen ) } @$pre;
b3e8ac9b 473 }
474 elsif( ref $pre eq 'HASH' ) {
475 my @ret =
476 map {
489709af 477 $self->resolve_prefetch($_, $alias, $seen),
478 $self->related_source($_)->resolve_prefetch(
479 $pre->{$_}, "${alias}.$_", $seen)
480 } keys %$pre;
b3e8ac9b 481 #die Dumper \@ret;
482 return @ret;
483 }
484 elsif( ref $pre ) {
701da8c4 485 $self->throw_exception( "don't know how to resolve prefetch reftype " . ref $pre);
b3e8ac9b 486 }
487 else {
489709af 488 my $count = ++$seen->{$pre};
489 my $as = ($count > 1 ? "${pre}_${count}" : $pre);
b3e8ac9b 490 my $rel_info = $self->relationship_info( $pre );
701da8c4 491 $self->throw_exception( $self->name . " has no such relationship '$pre'" ) unless $rel_info;
489709af 492 my $as_prefix = ($alias =~ /^.*?\.(.*)$/ ? $1.'.' : '');
493 return map { [ "${as}.$_", "${as_prefix}${pre}.$_", ] }
494 $self->related_source($pre)->columns;
b3e8ac9b 495 #warn $alias, Dumper (\@ret);
489709af 496 #return @ret;
b3e8ac9b 497 }
498}
953a18ef 499
87772e46 500=head2 related_source($relname)
501
502Returns the result source for the given relationship
503
504=cut
505
506sub related_source {
507 my ($self, $rel) = @_;
aea52c85 508 if( !$self->has_relationship( $rel ) ) {
701da8c4 509 $self->throw_exception("No such relationship '$rel'");
aea52c85 510 }
87772e46 511 return $self->schema->source($self->relationship_info($rel)->{source});
8452e496 512}
513
5ac6a044 514=head2 resultset
515
516Returns a resultset for the given source created by calling
517
518$self->resultset_class->new($self, $self->resultset_attributes)
519
520=head2 resultset_class
521
522Simple accessor.
523
524=head2 resultset_attributes
525
526Simple accessor.
527
528=cut
529
530sub resultset {
531 my $self = shift;
1225fc4d 532 return $self->{_resultset} if ref $self->{_resultset} eq $self->resultset_class;
533 return $self->{_resultset} = do {
534 my $rs = $self->resultset_class->new($self, $self->{resultset_attributes});
535 weaken $rs->result_source;
536 $rs;
537 };
5ac6a044 538}
539
701da8c4 540=head2 throw_exception
541
542See schema's throw_exception
543
544=cut
545
546sub throw_exception {
547 my $self = shift;
548 if (defined $self->schema) {
549 $self->schema->throw_exception(@_);
550 } else {
551 croak(@_);
552 }
553}
554
555
9c992ba1 556=head1 AUTHORS
557
558Matt S. Trout <mst@shadowcatsystems.co.uk>
559
560=head1 LICENSE
561
562You may distribute this code under the same terms as Perl itself.
563
564=cut
565