Merge 'trunk' into 'DBIx-Class-current'
Matt S Trout [Wed, 3 May 2006 14:19:00 +0000 (14:19 +0000)]
r1642@thor (orig r1505):  matthewt | 2006-04-22 16:29:28 +0000
cycle tests and a weaken call
r1657@thor (orig r1520):  bluefeet | 2006-04-26 22:15:41 +0000
Document the exitance of the DBIx::Class::ResultSource::schema() accessor.
r1660@thor (orig r1523):  matthewt | 2006-04-27 20:43:45 +0000
pod patch from ted
r1698@thor (orig r1561):  dwc | 2006-05-01 19:29:37 +0000
Add example of multi-column foreign keys
r1699@thor (orig r1562):  dwc | 2006-05-01 19:31:19 +0000
Add missing comma in example ;)

1  2 
Changes
lib/DBIx/Class/Relationship/Base.pm
lib/DBIx/Class/ResultSource.pm
lib/DBIx/Class/Schema.pm

diff --combined Changes
+++ b/Changes
@@@ -1,21 -1,8 +1,24 @@@
  Revision history for DBIx::Class
  
 +        - modified SQLT parser to skip dupe table names
 +        - added remove_column(s) to ResultSource/ResultSourceProxy
 +        - added add_column alias to ResultSourceProxy
 +        - added source_name to ResultSource
 +        - load_classes now uses source_name and sets it if necessary
 +        - add update_or_create_related to Relationship::Base
 +        - add find_or_new to ResultSet/ResultSetProxy and find_or_new_related
 +          to Relationship::Base
 +        - add accessors for unique constraint names and coulums to
 +          ResultSource/ResultSourceProxy
 +        - rework ResultSet::find() to search unique constraints
 +        - CDBICompat: modify retrieve to fix column casing when ColumnCase is
 +          loaded
 +        - CDBICompat: override find_or_create to fix column casing when
 +          ColumnCase is loaded
 +
+ 0.06003
+         - added memory cycle tests and a long-needed weaken call
  0.06002 2006-04-20 00:42:41
          - fix set_from_related to accept undef
          - fix to Dumper-induced hash iteration bug
          - remove build dependency on version.pm
  
  0.05004 2006-02-13 20:59:00
 -        - allow specification of related columns via cols attr when primary 
 +        - allow specification of related columns via cols attr when primary
            keys of the related table are not fetched
          - fix count for group_by as scalar
          - add horrific fix to make Oracle's retarded limit syntax work
@@@ -29,27 -29,42 +29,42 @@@ methods, for predefined ones, look in L
  
    __PACKAGE__->add_relationship('relname', 'Foreign::Class', $cond, $attrs);
  
- The condition needs to be an SQL::Abstract-style representation of the
- join between the tables. When resolving the condition for use in a JOIN,
- keys using the pseudo-table I<foreign> are resolved to mean "the Table on the
- other side of the relationship", and values using the pseudo-table I<self>
+ The condition needs to be an L<SQL::Abstract>-style representation of the
+ join between the tables. When resolving the condition for use in a C<JOIN>,
+ keys using the pseudo-table C<foreign> are resolved to mean "the Table on the
+ other side of the relationship", and values using the pseudo-table C<self>
  are resolved to mean "the Table this class is representing". Other
  restrictions, such as by value, sub-select and other tables, may also be
- used. Please check your database for JOIN parameter support.
+ used. Please check your database for C<JOIN> parameter support.
  
- For example, if you're creating a rel from Author to Book, where the Book
- table has a column author_id containing the ID of the Author row:
+ For example, if you're creating a relationship from C<Author> to C<Book>, where
+ the C<Book> table has a column C<author_id> containing the ID of the C<Author>
+ row:
  
    { 'foreign.author_id' => 'self.id' }
  
- will result in the JOIN clause
+ will result in the C<JOIN> clause
  
-   author me JOIN book book ON bar.author_id = me.id
+   author me JOIN book book ON book.author_id = me.id
  
- You can specify as many foreign => self mappings as necessary. Each key/value
- pair provided in a hashref will be used as ANDed conditions, to add an ORed
- condition, use an arrayref of hashrefs. See the L<SQL::Abstract> documentation
- for more details.
+ For multi-column foreign keys, you will need to specify a C<foreign>-to-C<self>
+ mapping for each column in the key. For example, if you're creating a
+ relationship from C<Book> to C<Edition>, where the C<Edition> table refers to a
+ publisher and a type (e.g. "paperback"):
+   {
+     'foreign.publisher_id' => 'self.publisher_id',
+     'foreign.type_id'      => 'self.type_id',
+   }
+ This will result in the C<JOIN> clause:
+   book me JOIN edition edition ON edition.publisher_id = me.publisher_id
+     AND edition.type_id = me.type_id
+ Each key-value pair provided in a hashref will be used as C<AND>ed conditions.
+ To add an C<OR>ed condition, use an arrayref of hashrefs. See the
+ L<SQL::Abstract> documentation for more details.
  
  Valid attributes are as follows:
  
@@@ -238,27 -253,12 +253,27 @@@ sub find_related 
    return $self->search_related($rel)->find(@_);
  }
  
 +=head2 find_or_new_related
 +
 +  my $new_obj = $obj->find_or_new_related('relname', \%col_data);
 +
 +Find an item of a related class. If none exists, instantiate a new item of the
 +related class. The object will not be saved into your storage until you call
 +L<DBIx::Class::Row/insert> on it.
 +
 +=cut
 +
 +sub find_or_new_related {
 +  my $self = shift;
 +  return $self->find_related(@_) || $self->new_related(@_);
 +}
 +
  =head2 find_or_create_related
  
    my $new_obj = $obj->find_or_create_related('relname', \%col_data);
  
  Find or create an item of a related class. See
 -L<DBIx::Class::ResultSet/"find_or_create"> for details.
 +L<DBIx::Class::ResultSet/find_or_create> for details.
  
  =cut
  
@@@ -267,21 -267,6 +282,21 @@@ sub find_or_create_related 
    return $self->find_related(@_) || $self->create_related(@_);
  }
  
 +=head2 update_or_create_related
 +
 +  my $updated_item = $obj->update_or_create_related('relname', \%col_data, \%attrs?);
 +
 +Update or create an item of a related class. See
 +L<DBIx::Class::ResultSet/update_or_create> for details.
 +
 +=cut
 +
 +sub update_or_create_related {
 +  my $self = shift;
 +  my $rel = shift;
 +  return $self->related_resultset($rel)->update_or_create(@_);
 +}
 +
  =head2 set_from_related
  
    $book->set_from_related('author', $author_obj);
@@@ -15,7 -15,7 +15,7 @@@ __PACKAGE__->mk_group_accessors('simple
    schema from _relationships/);
  
  __PACKAGE__->mk_group_accessors('component_class' => qw/resultset_class
 -  result_class/);
 +  result_class source_name/);
  
  =head1 NAME
  
@@@ -127,7 -127,7 +127,7 @@@ Convenience alias to add_columns
  sub add_columns {
    my ($self, @cols) = @_;
    $self->_ordered_columns(\@cols) unless $self->_ordered_columns;
 -  
 +
    my @added;
    my $columns = $self->_columns;
    while (my $col = shift @cols) {
@@@ -205,41 -205,6 +205,41 @@@ sub columns 
    return @{$self->{_ordered_columns}||[]};
  }
  
 +=head2 remove_columns
 +
 +  $table->remove_columns(qw/col1 col2 col3/);
 +
 +Removes columns from the result source.
 +
 +=head2 remove_column
 +
 +  $table->remove_column('col');
 +
 +Convenience alias to remove_columns.
 +
 +=cut
 +
 +sub remove_columns {
 +  my ($self, @cols) = @_;
 +
 +  return unless $self->_ordered_columns;
 +
 +  my $columns = $self->_columns;
 +  my @remaining;
 +
 +  foreach my $col (@{$self->_ordered_columns}) {
 +    push @remaining, $col unless grep(/$col/, @cols);
 +  }
 +
 +  foreach (@cols) {
 +    undef $columns->{$_};
 +  };
 +
 +  $self->_ordered_columns(\@remaining);
 +}
 +
 +*remove_column = \&remove_columns;
 +
  =head2 set_primary_key
  
  =over 4
@@@ -283,16 -248,15 +283,16 @@@ sub primary_columns 
  =head2 add_unique_constraint
  
  Declare a unique constraint on this source. Call once for each unique
 -constraint. Unique constraints are used when you call C<find> on a
 -L<DBIx::Class::ResultSet>. Only columns in the constraint are searched,
 -for example:
 +constraint.
  
    # For UNIQUE (column1, column2)
    __PACKAGE__->add_unique_constraint(
      constraint_name => [ qw/column1 column2/ ],
    );
  
 +Unique constraints are used, for example, when you call
 +L<DBIx::Class::ResultSet/find>. Only columns in the constraint are searched.
 +
  =cut
  
  sub add_unique_constraint {
@@@ -318,45 -282,16 +318,48 @@@ sub unique_constraints 
    return %{shift->_unique_constraints||{}};
  }
  
 +=head2 unique_constraint_names
 +
 +Returns the list of unique constraint names defined on this source.
 +
 +=cut
 +
 +sub unique_constraint_names {
 +  my ($self) = @_;
 +
 +  my %unique_constraints = $self->unique_constraints;
 +
 +  return keys %unique_constraints;
 +}
 +
 +=head2 unique_constraint_columns
 +
 +Returns the list of columns that make up the specified unique constraint.
 +
 +=cut
 +
 +sub unique_constraint_columns {
 +  my ($self, $constraint_name) = @_;
 +
 +  my %unique_constraints = $self->unique_constraints;
 +
 +  $self->throw_exception(
 +    "Unknown unique constraint $constraint_name on '" . $self->name . "'"
 +  ) unless exists $unique_constraints{$constraint_name};
 +
 +  return @{ $unique_constraints{$constraint_name} };
 +}
 +
  =head2 from
  
  Returns an expression of the source to be supplied to storage to specify
  retrieval from this source. In the case of a database, the required FROM
  clause contents.
  
- =cut
+ =head2 schema
+ Returns the L<DBIx::Class::Schema> object that this result source 
+ belongs too.
  
  =head2 storage
  
@@@ -407,11 -342,11 +410,11 @@@ the SQL command immediately before C<JO
  
  An arrayref containing a list of accessors in the foreign class to proxy in
  the main class. If, for example, you do the following:
 -  
 +
    CD->might_have(liner_notes => 'LinerNotes', undef, {
      proxy => [ qw/notes/ ],
    });
 -  
 +
  Then, assuming LinerNotes has an accessor named notes, you can do:
  
    my $cd = CD->find(1);
@@@ -518,113 -453,6 +521,113 @@@ sub has_relationship 
    return exists $self->_relationships->{$rel};
  }
  
 +=head2 reverse_relationship_info
 +
 +=over 4
 +
 +=item Arguments: $relname
 +
 +=back
 +
 +Returns an array of hash references of relationship information for
 +the other side of the specified relationship name.
 +
 +=cut
 +
 +sub reverse_relationship_info {
 +  my ($self, $rel) = @_;
 +  my $rel_info = $self->relationship_info($rel);
 +  my $ret = {};
 +
 +  return $ret unless ((ref $rel_info->{cond}) eq 'HASH');
 +
 +  my @cond = keys(%{$rel_info->{cond}});
 +  my @refkeys = map {/^\w+\.(\w+)$/} @cond;
 +  my @keys = map {$rel_info->{cond}->{$_} =~ /^\w+\.(\w+)$/} @cond;
 +
 +  # Get the related result source for this relationship
 +  my $othertable = $self->related_source($rel);
 +
 +  # Get all the relationships for that source that related to this source
 +  # whose foreign column set are our self columns on $rel and whose self
 +  # columns are our foreign columns on $rel.
 +  my @otherrels = $othertable->relationships();
 +  my $otherrelationship;
 +  foreach my $otherrel (@otherrels) {
 +    my $otherrel_info = $othertable->relationship_info($otherrel);
 +
 +    my $back = $othertable->related_source($otherrel);
 +    next unless $back->name eq $self->name;
 +
 +    my @othertestconds;
 +
 +    if (ref $otherrel_info->{cond} eq 'HASH') {
 +      @othertestconds = ($otherrel_info->{cond});
 +    }
 +    elsif (ref $otherrel_info->{cond} eq 'ARRAY') {
 +      @othertestconds = @{$otherrel_info->{cond}};
 +    }
 +    else {
 +      next;
 +    }
 +
 +    foreach my $othercond (@othertestconds) {
 +      my @other_cond = keys(%$othercond);
 +      my @other_refkeys = map {/^\w+\.(\w+)$/} @other_cond;
 +      my @other_keys = map {$othercond->{$_} =~ /^\w+\.(\w+)$/} @other_cond;
 +      next if (!$self->compare_relationship_keys(\@refkeys, \@other_keys) ||
 +               !$self->compare_relationship_keys(\@other_refkeys, \@keys));
 +      $ret->{$otherrel} =  $otherrel_info;
 +    }
 +  }
 +  return $ret;
 +}
 +
 +=head2 compare_relationship_keys
 +
 +=over 4
 +
 +=item Arguments: $keys1, $keys2
 +
 +=back
 +
 +Returns true if both sets of keynames are the same, false otherwise.
 +
 +=cut
 +
 +sub compare_relationship_keys {
 +  my ($self, $keys1, $keys2) = @_;
 +
 +  # Make sure every keys1 is in keys2
 +  my $found;
 +  foreach my $key (@$keys1) {
 +    $found = 0;
 +    foreach my $prim (@$keys2) {
 +      if ($prim eq $key) {
 +        $found = 1;
 +        last;
 +      }
 +    }
 +    last unless $found;
 +  }
 +
 +  # Make sure every key2 is in key1
 +  if ($found) {
 +    foreach my $prim (@$keys2) {
 +      $found = 0;
 +      foreach my $key (@$keys1) {
 +        if ($prim eq $key) {
 +          $found = 1;
 +          last;
 +        }
 +      }
 +      last unless $found;
 +    }
 +  }
 +
 +  return $found;
 +}
 +
  =head2 resolve_join
  
  =over 4
@@@ -896,26 -724,6 +899,26 @@@ sub resultset 
    );
  }
  
 +=head2 source_name
 +
 +=over 4
 +
 +=item Arguments: $source_name
 +
 +=back
 +
 +Set the name of the result source when it is loaded into a schema.
 +This is usefull if you want to refer to a result source by a name other than
 +its class name.
 +
 +  package ArchivedBooks;
 +  use base qw/DBIx::Class/;
 +  __PACKAGE__->table('books_archive');
 +  __PACKAGE__->source_name('Books');
 +
 +  # from your schema...
 +  $schema->resultset('Books')->find(1);
 +
  =head2 throw_exception
  
  See L<DBIx::Class::Schema/"throw_exception">.
diff --combined lib/DBIx/Class/Schema.pm
@@@ -4,6 -4,7 +4,7 @@@ use strict
  use warnings;
  
  use Carp::Clan qw/^DBIx::Class/;
+ use Scalar::Util qw/weaken/;
  
  use base qw/DBIx::Class/;
  
@@@ -20,7 -21,7 +21,7 @@@ DBIx::Class::Schema - composable schema
  
    package Library::Schema;
    use base qw/DBIx::Class::Schema/;
 -  
 +
    # load Library::Schema::CD, Library::Schema::Book, Library::Schema::DVD
    __PACKAGE__->load_classes(qw/CD Book DVD/);
  
@@@ -36,7 -37,7 +37,7 @@@
      $password,
      { AutoCommit => 0 },
    );
 -  
 +
    my $schema2 = Library::Schema->connect($coderef_returning_dbh);
  
    # fetch objects using Library::Schema::DVD
@@@ -94,6 -95,7 +95,7 @@@ sub register_source 
    $reg{$moniker} = $source;
    $self->source_registrations(\%reg);
    $source->schema($self);
+   weaken($source->{schema}) if ref($self);
    if ($source->result_class) {
      my %map = %{$self->class_mappings};
      $map{$source->result_class} = $moniker;
@@@ -219,15 -221,15 +221,15 @@@ Example
  
  sub load_classes {
    my ($class, @params) = @_;
 -  
 +
    my %comps_for;
 -  
 +
    if (@params) {
      foreach my $param (@params) {
        if (ref $param eq 'ARRAY') {
          # filter out commented entries
          my @modules = grep { $_ !~ /^#/ } @$param;
 -        
 +
          push (@{$comps_for{$class}}, @modules);
        }
        elsif (ref $param eq 'HASH') {
            die $@ unless $@ =~ /Can't locate.+$comp_class\.pm\sin\s\@INC/;
            warn $@ if $@;
          }
 -        push(@to_register, [ $comp, $comp_class ]);
 +
 +        $comp_class->source_name($comp) unless $comp_class->source_name;
 +
 +        push(@to_register, [ $comp_class->source_name, $comp_class ]);
        }
      }
    }
@@@ -714,41 -713,6 +716,41 @@@ sub deploy 
    $self->storage->deploy($self, undef, $sqltargs);
  }
  
 +=head2 create_ddl_dir (EXPERIMENTAL)
 +
 +=over 4
 +
 +=item Arguments: \@databases, $version, $directory, $sqlt_args
 +
 +=back
 +
 +Creates an SQL file based on the Schema, for each of the specified
 +database types, in the given directory.
 +
 +Note that this feature is currently EXPERIMENTAL and may not work correctly
 +across all databases, or fully handle complex relationships.
 +
 +=cut
 +
 +sub create_ddl_dir
 +{
 +  my $self = shift;
 +
 +  $self->throw_exception("Can't create_ddl_dir without storage") unless $self->storage;
 +  $self->storage->create_ddl_dir($self, @_);
 +}
 +
 +sub ddl_filename
 +{
 +    my ($self, $type, $dir, $version) = @_;
 +
 +    my $filename = ref($self);
 +    $filename =~ s/^.*:://;
 +    $filename = "$dir$filename-$version-$type.sql";
 +
 +    return $filename;
 +}
 +
  1;
  
  =head1 AUTHORS