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