X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FResultSource.pm;h=b4dbfd36aca3c18a9e8f5f4ff24f08685f23eb80;hb=39e54ad026643adc23699f2e187c0e71ec4b6532;hp=fc2c9293482ccc056845910239aa72484485dc02;hpb=87772e46b99bc03f36c5a82ec29282a3228c29c7;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/ResultSource.pm b/lib/DBIx/Class/ResultSource.pm index fc2c929..b4dbfd3 100644 --- a/lib/DBIx/Class/ResultSource.pm +++ b/lib/DBIx/Class/ResultSource.pm @@ -7,11 +7,13 @@ use DBIx::Class::ResultSet; use Carp qw/croak/; +use Storable; + use base qw/DBIx::Class/; __PACKAGE__->load_components(qw/AccessorGroup/); __PACKAGE__->mk_group_accessors('simple' => - qw/_ordered_columns _columns _primaries name resultset_class result_class schema from _relationships/); + qw/_ordered_columns _columns _primaries _unique_constraints name resultset_class result_class schema from _relationships/); =head1 NAME @@ -33,9 +35,9 @@ sub new { $class = ref $class if ref $class; my $new = bless({ %{$attrs || {}} }, $class); $new->{resultset_class} ||= 'DBIx::Class::ResultSet'; - $new->{_ordered_columns} ||= []; - $new->{_columns} ||= {}; - $new->{_relationships} ||= {}; + $new->{_ordered_columns} = [ @{$new->{_ordered_columns}||[]}]; + $new->{_columns} = { %{$new->{_columns}||{}} }; + $new->{_relationships} = { %{$new->{_relationships}||{}} }; $new->{name} ||= "!!NAME NOT SET!!"; return $new; } @@ -44,15 +46,20 @@ sub add_columns { my ($self, @cols) = @_; $self->_ordered_columns( \@cols ) if !$self->_ordered_columns; - push @{ $self->_ordered_columns }, @cols; + my @added; + my $columns = $self->_columns; while (my $col = shift @cols) { - my $column_info = ref $cols[0] ? shift : {}; + my $column_info = ref $cols[0] ? shift(@cols) : {}; # If next entry is { ... } use that for the column info, if not # use an empty hashref - $self->_columns->{$col} = $column_info; + push(@added, $col) unless exists $columns->{$col}; + + $columns->{$col} = $column_info; } + push @{ $self->_ordered_columns }, @added; + return $self; } *add_column = \&add_columns; @@ -79,8 +86,8 @@ sub resultset { return $self->resultset_class->new($self); } -=head2 has_column - +=head2 has_column + if ($obj->has_column($col)) { ... } Returns 1 if the source has a column of this name, 0 otherwise. @@ -103,39 +110,45 @@ Returns the column metadata hashref for a column. sub column_info { my ($self, $column) = @_; croak "No such column $column" unless exists $self->_columns->{$column}; + if ( (! $self->_columns->{$column}->{data_type}) + && $self->schema && $self->storage() ){ + my $info; +############ eval for the case of storage without table + eval{ + $info = $self->storage->columns_info_for ( $self->from() ); + }; + if ( ! $@ ){ + for my $col ( keys %{$self->_columns} ){ + for my $i ( keys %{$info->{$col}} ){ + $self->_columns()->{$col}->{$i} = $info->{$col}->{$i}; + } + } + } + } return $self->_columns->{$column}; } =head2 columns - my @column_names = $obj->columns; - -=cut + my @column_names = $obj->columns; -sub columns { - croak "columns() is a read-only accessor, did you mean add_columns()?" if (@_ > 1); - return keys %{shift->_columns}; -} - -=head2 ordered_columns - - my @column_names = $obj->ordered_columns; - -Like columns(), but returns column names using the order in which they were -originally supplied to add_columns(). +Returns all column names in the order they were declared to add_columns =cut -sub ordered_columns { +sub columns { + croak "columns() is a read-only accessor, did you mean add_columns()?" if (@_ > 1); return @{shift->{_ordered_columns}||[]}; } -=head2 set_primary_key(@cols) - +=head2 set_primary_key(@cols) + Defines one or more columns as primary key for this source. Should be called after C. - -=cut + +Additionally, defines a unique constraint named C. + +=cut sub set_primary_key { my ($self, @cols) = @_; @@ -145,18 +158,53 @@ sub set_primary_key { unless $self->has_column($_); } $self->_primaries(\@cols); + + $self->add_unique_constraint(primary => \@cols); } -=head2 primary_columns - +=head2 primary_columns + Read-only accessor which returns the list of primary keys. - -=cut + +=cut sub primary_columns { return @{shift->_primaries||[]}; } +=head2 add_unique_constraint + +Declare a unique constraint on this source. Call once for each unique +constraint. + + # For e.g. UNIQUE (column1, column2) + __PACKAGE__->add_unique_constraint(constraint_name => [ qw/column1 column2/ ]); + +=cut + +sub add_unique_constraint { + my ($self, $name, $cols) = @_; + + for (@$cols) { + $self->throw("No such column $_ on table ".$self->name) + unless $self->has_column($_); + } + + my %unique_constraints = $self->unique_constraints; + $unique_constraints{$name} = $cols; + $self->_unique_constraints(\%unique_constraints); +} + +=head2 unique_constraints + +Read-only accessor which returns the list of unique constraints on this source. + +=cut + +sub unique_constraints { + return %{shift->_unique_constraints||{}}; +} + =head2 from Returns an expression of the source to be supplied to storage to specify @@ -230,7 +278,7 @@ created, which calls C for the relationship. sub add_relationship { my ($self, $rel, $f_source_name, $cond, $attrs) = @_; - die "Can't create relationship without join condition" unless $cond; + croak "Can't create relationship without join condition" unless $cond; $attrs ||= {}; my %rels = %{ $self->_relationships }; @@ -240,7 +288,9 @@ sub add_relationship { attrs => $attrs }; $self->_relationships(\%rels); - return 1; + return $self; + + # XXX disabled. doesn't work properly currently. skip in tests. my $f_source = $self->schema->source($f_source_name); unless ($f_source) { @@ -261,7 +311,7 @@ sub add_relationship { if ($@) { # If the resolve failed, back out and re-throw the error delete $rels{$rel}; # $self->_relationships(\%rels); - die "Error creating relationship $rel: $@"; + croak "Error creating relationship $rel: $@"; } 1; } @@ -287,6 +337,17 @@ sub relationship_info { return $self->_relationships->{$rel}; } +=head2 has_relationship($rel) + +Returns 1 if the source has a relationship of this name, 0 otherwise. + +=cut + +sub has_relationship { + my ($self, $rel) = @_; + return exists $self->_relationships->{$rel}; +} + =head2 resolve_join($relation) Returns the join structure required for the related result source @@ -302,18 +363,121 @@ sub resolve_join { $self->related_source($_)->resolve_join($join->{$_}, $_) } keys %$join; } elsif (ref $join) { - die("No idea how to resolve join reftype ".ref $join); + croak ("No idea how to resolve join reftype ".ref $join); } else { - my $rel_obj = $self->relationship_info($join); - #use Data::Dumper; warn Dumper($class->result_source) unless $rel_obj; - die("No such relationship ${join}") unless $rel_obj; - my $j_class = $self->related_source($join)->result_class; - my %join = (_action => 'join', - _aliases => { 'self' => $alias, 'foreign' => $join }, - _classes => { $alias => $self->result_class, $join => $j_class }); - my $j_cond = $j_class->resolve_condition($rel_obj->{cond}, \%join); - return [ { $join => $j_class->_table_name, - -join_type => $rel_obj->{attrs}{join_type} || '' }, $j_cond ]; + my $rel_info = $self->relationship_info($join); + croak("No such relationship ${join}") unless $rel_info; + my $type = $rel_info->{attrs}{join_type} || ''; + return [ { $join => $self->related_source($join)->from, + -join_type => $type }, + $self->resolve_condition($rel_info->{cond}, $join, $alias) ]; + } +} + +=head2 resolve_condition($cond, $rel, $alias|$object) + +Resolves the passed condition to a concrete query fragment. If given an alias, +returns a join condition; if given an object, inverts that object to produce +a related conditional from that object. + +=cut + +sub resolve_condition { + my ($self, $cond, $rel, $for) = @_; + #warn %$cond; + if (ref $cond eq 'HASH') { + my %ret; + while (my ($k, $v) = each %{$cond}) { + # XXX should probably check these are valid columns + $k =~ s/^foreign\.// || croak "Invalid rel cond key ${k}"; + $v =~ s/^self\.// || croak "Invalid rel cond val ${v}"; + if (ref $for) { # Object + #warn "$self $k $for $v"; + $ret{$k} = $for->get_column($v); + #warn %ret; + } else { + $ret{"${rel}.${k}"} = "${for}.${v}"; + } + } + return \%ret; + } elsif (ref $cond eq 'ARRAY') { + return [ map { $self->resolve_condition($_, $rel, $for) } @$cond ]; + } else { + die("Can't handle this yet :("); + } +} + +=head2 resolve_prefetch (hashref/arrayref/scalar) + +Accepts one or more relationships for the current source and returns an +array of column names for each of those relationships. Column names are +prefixed relative to the current source, in accordance with where they appear +in the supplied relationships. Examples: + + my $source = $schema->$resultset('Tag')->source; + @columns = $source->resolve_prefetch( { cd => 'artist' } ); + + # @columns = + #( + # 'cd.cdid', + # 'cd.artist', + # 'cd.title', + # 'cd.year', + # 'cd.artist.artistid', + # 'cd.artist.name' + #) + + @columns = $source->resolve_prefetch( qw[/ cd /] ); + + # @columns = + #( + # 'cd.cdid', + # 'cd.artist', + # 'cd.title', + # 'cd.year' + #) + + $source = $schema->resultset('CD')->source; + @columns = $source->resolve_prefetch( qw[/ artist producer /] ); + + # @columns = + #( + # 'artist.artistid', + # 'artist.name', + # 'producer.producerid', + # 'producer.name' + #) + +=cut + +sub resolve_prefetch { + my( $self, $pre, $alias ) = @_; + use Data::Dumper; + #$alias ||= $self->name; + #warn $alias, Dumper $pre; + if( ref $pre eq 'ARRAY' ) { + return map { $self->resolve_prefetch( $_, $alias ) } @$pre; + } + elsif( ref $pre eq 'HASH' ) { + my @ret = + map { + $self->resolve_prefetch($_, $alias), + $self->related_source($_)->resolve_prefetch( $pre->{$_}, $_ ) + } + keys %$pre; + #die Dumper \@ret; + return @ret; + } + elsif( ref $pre ) { + croak( "don't know how to resolve prefetch reftype " . ref $pre); + } + else { + my $rel_info = $self->relationship_info( $pre ); + croak( $self->name . " has no such relationship '$pre'" ) unless $rel_info; + my $prefix = $alias && $alias ne 'me' ? "$alias.$pre" : $pre; + my @ret = map { "$prefix.$_" } $self->related_source($pre)->columns; + #warn $alias, Dumper (\@ret); + return @ret; } } @@ -325,6 +489,9 @@ Returns the result source for the given relationship sub related_source { my ($self, $rel) = @_; + if( !$self->has_relationship( $rel ) ) { + croak "No such relationship '$rel'"; + } return $self->schema->source($self->relationship_info($rel)->{source}); }