Merge 'DBIx-Class-current' into 'resultset-new-refactor'
Luke Saunders [Thu, 11 May 2006 12:21:49 +0000 (12:21 +0000)]
merged recent -current changes into this branch

13 files changed:
Build.PL
lib/DBIx/Class/Relationship.pm
lib/DBIx/Class/Relationship/Accessor.pm
lib/DBIx/Class/Relationship/Base.pm
lib/DBIx/Class/ResultSet.pm
lib/DBIx/Class/Storage/DBI.pm
script/dbicadmin
t/basicrels/146db2_400.t [new file with mode: 0644]
t/helperrels/146db2_400.t [new file with mode: 0644]
t/run/01core.tl
t/run/06relationship.tl
t/run/146db2_400.tl [new file with mode: 0644]
t/run/23cache.tl

index f1d2ad8..364a8d2 100644 (file)
--- a/Build.PL
+++ b/Build.PL
@@ -26,7 +26,8 @@ my %arguments = (
     },
     create_makefile_pl => 'passthrough',
     create_readme      => 1,
-    test_files         => [ glob('t/*.t'), glob('t/*/*.t') ]
+    test_files         => [ glob('t/*.t'), glob('t/*/*.t') ],
+    script_files       => [ glob('script/*') ],
 );
 
 Module::Build->new(%arguments)->create_build_script;
index 44ed65b..b5d6932 100644 (file)
@@ -131,6 +131,8 @@ of C<has_a>.
     { prefetch => [qw/book/],
   });
   my @book_objs = $obj->books;
+  my $books_rs = $obj->books;
+  ( $books_rs ) = $obj->books_rs;
 
   $obj->add_to_books(\%col_data);
 
@@ -139,9 +141,14 @@ foreign class store the calling class's primary key in one (or more) of its
 columns. You should pass the name of the column in the foreign class as the
 $cond argument, or specify a complete join condition.
 
-As well as the accessor method, a method named C<< add_to_<relname> >>
-will also be added to your Row items, this allows you to insert new
-related items, using the same mechanism as in L<DBIx::Class::Relationship::Base/"create_related">.
+Three methods are created when you create a has_many relationship.  The first
+method is the expected accessor method.  The second is almost exactly the same
+as the accessor method but "_rs" is added to the end of the method name.  This
+method works just like the normal accessor, except that it returns a resultset
+no matter what, even in list context. The third method, named
+C<< add_to_<relname> >>, will also be added to your Row items, this allows
+you to insert new related items, using the same mechanism as in
+L<DBIx::Class::Relationship::Base/"create_related">.
 
 If you delete an object in a class with a C<has_many> relationship, all
 related objects will be deleted as well. However, any database-level
index 035661a..b20eb16 100644 (file)
@@ -48,6 +48,7 @@ sub add_relationship_accessor {
     );
   } elsif ($acc_type eq 'multi') {
     $meth{$rel} = sub { shift->search_related($rel, @_) };
+    $meth{"${rel}_rs"} = sub { shift->search_related_rs($rel, @_) };
     $meth{"add_to_${rel}"} = sub { shift->create_related($rel, @_); };
   } else {
     $class->throw_exception("No such relationship accessor type $acc_type");
index b193aa0..bfe63b3 100644 (file)
@@ -175,7 +175,8 @@ sub related_resultset {
 
 =head2 search_related
 
-  $rs->search_related('relname', $cond, $attrs);
+  @objects = $rs->search_related('relname', $cond, $attrs);
+  $objects_rs = $rs->search_related('relname', $cond, $attrs);
 
 Run a search on a related resultset. The search will be restricted to the
 item or items represented by the L<DBIx::Class::ResultSet> it was called
@@ -187,6 +188,19 @@ sub search_related {
   return shift->related_resultset(shift)->search(@_);
 }
 
+=head2 search_related_rs
+
+  ( $objects_rs ) = $rs->search_related_rs('relname', $cond, $attrs);
+
+This method works exactly the same as search_related, except that 
+it garauntees a restultset, even in list context.
+
+=cut
+
+sub search_related_rs {
+  return shift->related_resultset(shift)->search_rs(@_);
+}
+
 =head2 count_related
 
   $obj->count_related('relname', $cond, $attrs);
index 8916f56..93588e0 100644 (file)
@@ -136,7 +136,28 @@ call it as C<search(undef, \%attrs)>.
 
 sub search {
   my $self = shift;
-    
+  my $rs = $self->search_rs( @_ );
+  return (wantarray ? $rs->all : $rs);
+}
+
+=head2 search_rs
+
+=over 4
+
+=item Arguments: $cond, \%attrs?
+
+=item Return Value: $resultset
+
+=back
+
+This method does the same exact thing as search() except it will 
+always return a resultset, even in list context.
+
+=cut
+
+sub search_rs {
+  my $self = shift;
+
   my $our_attrs = { %{$self->{attrs}} };
   my $having = delete $our_attrs->{having};
   my $attrs = {};
@@ -153,7 +174,7 @@ sub search {
       }
       delete $attrs->{$key};
   }
-  $our_attrs = { %{$our_attrs}, %{$attrs} };
+  my $new_attrs = { %{$our_attrs}, %{$attrs} };
 
   # merge new where and having into old
   my $where = (@_
@@ -165,33 +186,32 @@ sub search {
                         : {@_}))
                 : undef());
   if (defined $where) {
-    $our_attrs->{where} = (defined $our_attrs->{where}
+    $new_attrs->{where} = (defined $new_attrs->{where}
               ? { '-and' =>
                   [ map { ref $_ eq 'ARRAY' ? [ -or => $_ ] : $_ }
-                      $where, $our_attrs->{where} ] }
+                      $where, $new_attrs->{where} ] }
               : $where);
   }
 
   if (defined $having) {
-    $our_attrs->{having} = (defined $our_attrs->{having}
+    $new_attrs->{having} = (defined $new_attrs->{having}
               ? { '-and' =>
                   [ map { ref $_ eq 'ARRAY' ? [ -or => $_ ] : $_ }
-                      $having, $our_attrs->{having} ] }
+                      $having, $new_attrs->{having} ] }
               : $having);
   }
-#  use Data::Dumper; warn "attrs: " . Dumper($our_attrs);
 
-  my $rs = (ref $self)->new($self->result_source, $our_attrs);
+  my $rs = (ref $self)->new($self->result_source, $new_attrs);
   $rs->{_parent_rs} = $self->{_parent_rs} if ($self->{_parent_rs}); #XXX - hack to pass through parent of related resultsets
 
   unless (@_) { # no search, effectively just a clone
     my $rows = $self->get_cache;
-    if( @{$rows} ) {
+    if ($rows) {
       $rs->set_cache($rows);
     }
   }
   
-  return (wantarray ? $rs->all : $rs);
+  return $rs;
 }
 
 =head2 search_literal
@@ -277,10 +297,14 @@ sub find {
     $hash = {};
     @{$hash}{@cols} = @_;
   }
+  elsif (@_) {
+    # For backwards compatibility
+    $hash = {@_};
+  }
   else {
     $self->throw_exception(
       "Arguments to find must be a hashref or match the number of columns in the "
-        . exists $attrs->{key} ? "$attrs->{key} unique constraint" : "primary key"
+        . (exists $attrs->{key} ? "$attrs->{key} unique constraint" : "primary key")
     );
   }
 
@@ -534,9 +558,9 @@ first record from the resultset.
 
 sub next {
   my ($self) = @_;
-  if (@{$self->{all_cache} || []}) {
+  if (my $cache = $self->get_cache) {
     $self->{all_cache_position} ||= 0;
-    return $self->{all_cache}->[$self->{all_cache_position}++];
+    return $cache->[$self->{all_cache_position}++];
   }
   if ($self->{attrs}{cache}) {
     $self->{all_cache_position} = 1;
@@ -700,7 +724,8 @@ sub _collapse_result {
       $row = $self->{stashed_row} = \@raw;
       $tree = $self->_collapse_result($as, $row, $c_prefix);
     }
-    @$target = @final;
+    @$target = (@final ? @final : [ {}, {} ]); 
+      # single empty result to indicate an empty prefetched has_many
   }
   return $info;
 }
@@ -746,7 +771,7 @@ clause.
 sub count {
   my $self = shift;
   return $self->search(@_)->count if @_ and defined $_[0];
-  return scalar @{ $self->get_cache } if @{ $self->get_cache };
+  return scalar @{ $self->get_cache } if $self->get_cache;
 
   my $count = $self->_count;
   return 0 unless $count;
@@ -824,7 +849,7 @@ is returned in list context.
 
 sub all {
   my ($self) = @_;
-  return @{ $self->get_cache } if @{ $self->get_cache };
+  return @{ $self->get_cache } if $self->get_cache;
 
   my @obj;
 
@@ -1279,8 +1304,7 @@ sub update_or_create {
 
   my $row = $self->find($hash, $attrs);
   if (defined $row) {
-    $row->set_columns($hash);
-    $row->update;
+    $row->update($hash);
     return $row;
   }
 
@@ -1302,7 +1326,7 @@ Gets the contents of the cache for the resultset, if the cache is set.
 =cut
 
 sub get_cache {
-  shift->{all_cache} || [];
+  shift->{all_cache};
 }
 
 =head2 set_cache
@@ -1325,13 +1349,7 @@ than re-querying the database even if the cache attr is not set.
 sub set_cache {
   my ( $self, $data ) = @_;
   $self->throw_exception("set_cache requires an arrayref")
-    if ref $data ne 'ARRAY';
-  my $result_class = $self->result_class;
-  foreach( @$data ) {
-    $self->throw_exception(
-      "cannot cache object of type '$_', expected '$result_class'"
-    ) if ref $_ ne $result_class;
-  }
+      if defined($data) && (ref $data ne 'ARRAY');
   $self->{all_cache} = $data;
 }
 
@@ -1350,7 +1368,7 @@ Clears the cache for the resultset.
 =cut
 
 sub clear_cache {
-  shift->set_cache([]);
+  shift->set_cache(undef);
 }
 
 =head2 related_resultset
index 196fdc9..364b265 100644 (file)
@@ -835,6 +835,11 @@ is produced (as when the L<debug> method is set).
 If the value is of the form C<1=/path/name> then the trace output is
 written to the file C</path/name>.
 
+This environment variable is checked when the storage object is first
+created (when you call connect on your schema).  So, run-time changes 
+to this environment variable will not take effect unless you also 
+re-connect on your schema.
+
 =head1 AUTHORS
 
 Matt S. Trout <mst@shadowcatsystems.co.uk>
index e873745..9eec9b7 100755 (executable)
@@ -70,7 +70,7 @@ if ($op eq 'insert') {
     print ''.ref($resultset).' ID: '.join(',',$obj->id())."\n";
 }
 elsif ($op eq 'update') {
-    $resultset = $resultset->search( $where );
+    $resultset = $resultset->search( ($where||{}) );
     my $count = $resultset->count();
     print "This action will modify $count ".ref($resultset)." records.\n" if (!$quiet);
     if ( $force || confirm() ) {
@@ -79,7 +79,7 @@ elsif ($op eq 'update') {
 }
 elsif ($op eq 'delete') {
     die('Do not use the set option with the delete op') if ($set);
-    $resultset = $resultset->search( $where, $attrs );
+    $resultset = $resultset->search( ($where||{}), ($attrs||()) );
     my $count = $resultset->count();
     print "This action will delete $count ".ref($resultset)." records.\n" if (!$quiet);
     if ( $force || confirm() ) {
@@ -91,7 +91,7 @@ elsif ($op eq 'select') {
     my $csv = $csv_class->new({
         sep_char => ( $format eq 'tsv' ? "\t" : ',' ),
     });
-    $resultset = $resultset->search( $where, $attrs );
+    $resultset = $resultset->search( ($where||{}), ($attrs||()) );
     my @columns = $resultset->result_source->columns();
     $csv->combine( @columns );
     print $csv->string()."\n";
diff --git a/t/basicrels/146db2_400.t b/t/basicrels/146db2_400.t
new file mode 100644 (file)
index 0000000..2ac494c
--- /dev/null
@@ -0,0 +1,7 @@
+use Test::More;
+use lib qw(t/lib);
+use DBICTest;
+use DBICTest::BasicRels;
+
+require "t/run/146db2_400.tl";
+run_tests(DBICTest->schema);
diff --git a/t/helperrels/146db2_400.t b/t/helperrels/146db2_400.t
new file mode 100644 (file)
index 0000000..655bc05
--- /dev/null
@@ -0,0 +1,7 @@
+use Test::More;
+use lib qw(t/lib);
+use DBICTest;
+use DBICTest::HelperRels;
+
+require "t/run/146db2_400.tl";
+run_tests(DBICTest->schema);
index 68c2eac..0c54d42 100644 (file)
@@ -1,7 +1,7 @@
 sub run_tests {
 my $schema = shift;
 
-plan tests => 57;
+plan tests => 59;
 
 # figure out if we've got a version of sqlite that is older than 3.2.6, in
 # which case COUNT(DISTINCT()) doesn't work
@@ -94,6 +94,13 @@ is($new_again->name, 'Man With A Spoon', 'Retrieved correctly');
 
 is($new_again->ID, 'DBICTest::Artist|artist|artistid=4', 'unique object id generated correctly');
 
+# Test backwards compatibility
+{
+  my $artist_by_hash = $schema->resultset('Artist')->find(artistid => 4);
+  is($artist_by_hash->name, 'Man With A Spoon', 'Retrieved correctly');
+  is($artist_by_hash->ID, 'DBICTest::Artist|artist|artistid=4', 'unique object id generated correctly');
+}
+
 is($schema->resultset("Artist")->count, 4, 'count ok');
 
 # test find_or_new
@@ -166,7 +173,7 @@ is($schema->class("Artist")->field_name_for->{name}, 'artist name', 'mk_classdat
 
 my $search = [ { 'tags.tag' => 'Cheesy' }, { 'tags.tag' => 'Blue' } ];
 
-my $or_rs = $schema->resultset("CD")->search($search, { join => 'tags',
+my( $or_rs ) = $schema->resultset("CD")->search_rs($search, { join => 'tags',
                                                   order_by => 'cdid' });
 
 cmp_ok($or_rs->count, '==', 5, 'Search with OR ok');
index b85fea1..a66211e 100644 (file)
@@ -3,7 +3,7 @@ my $schema = shift;
 
 use strict;
 use warnings;  
-plan tests => 30;
+plan tests => 32;
 
 # has_a test
 my $cd = $schema->resultset("CD")->find(4);
@@ -38,6 +38,12 @@ if ($INC{'DBICTest/HelperRels.pm'}) {
 
 is( ($artist->search_related('cds'))[3]->title, 'Big Flop', 'create_related ok' );
 
+my( $rs_from_list ) = $artist->search_related_rs('cds');
+is( ref($rs_from_list), 'DBIx::Class::ResultSet', 'search_related_rs in list context returns rs' );
+
+( $rs_from_list ) = $artist->cds_rs();
+is( ref($rs_from_list), 'DBIx::Class::ResultSet', 'relation_rs in list context returns rs' );
+
 # count_related
 is( $artist->count_related('cds'), 4, 'count_related ok' );
 
diff --git a/t/run/146db2_400.tl b/t/run/146db2_400.tl
new file mode 100644 (file)
index 0000000..ac6cd47
--- /dev/null
@@ -0,0 +1,74 @@
+sub run_tests {
+my $schema = shift;
+
+my ($dsn, $user, $pass) = @ENV{map { "DBICTEST_DB2_400_${_}" } qw/DSN USER PASS/};
+
+#warn "$dsn $user $pass";
+
+# Probably best to pass the DBQ option in the DSN to specify a specific
+# libray.  Something like:
+# DBICTEST_DB2_400_DSN='dbi:ODBC:dsn=MyAS400;DBQ=MYLIB'
+plan skip_all, 'Set $ENV{DBICTEST_DB2_400_DSN}, _USER and _PASS to run this test'
+  unless ($dsn && $user);
+
+plan tests => 6;
+
+DBICTest::Schema->compose_connection('DB2Test' => $dsn, $user, $pass);
+
+my $dbh = DB2Test->schema->storage->dbh;
+
+$dbh->do("DROP TABLE artist", { RaiseError => 0, PrintError => 0 });
+
+$dbh->do("CREATE TABLE artist (artistid INTEGER GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), name VARCHAR(255), charfield CHAR(10))");
+
+DB2Test::Artist->load_components('PK::Auto');
+
+# test primary key handling
+my $new = DB2Test::Artist->create({ name => 'foo' });
+ok($new->artistid, "Auto-PK worked");
+
+# test LIMIT support
+for (1..6) {
+    DB2Test::Artist->create({ name => 'Artist ' . $_ });
+}
+my $it = DB2Test::Artist->search( {},
+    { rows => 3,
+      order_by => 'artistid'
+      }
+);
+is( $it->count, 3, "LIMIT count ok" );
+is( $it->next->name, "foo", "iterator->next ok" );
+$it->next;
+is( $it->next->name, "Artist 2", "iterator->next ok" );
+is( $it->next, undef, "next past end of resultset ok" );
+
+my $test_type_info = {
+    'artistid' => {
+        'data_type' => 'INTEGER',
+        'is_nullable' => 0,
+        'size' => 10
+    },
+    'name' => {
+        'data_type' => 'VARCHAR',
+        'is_nullable' => 1,
+        'size' => 255
+    },
+    'charfield' => {
+        'data_type' => 'CHAR',
+        'is_nullable' => 1,
+        'size' => 10 
+    },
+};
+
+
+my $type_info = DB2Test->schema->storage->columns_info_for('artist');
+is_deeply($type_info, $test_type_info, 'columns_info_for - column data types');
+
+
+
+# clean up our mess
+$dbh->do("DROP TABLE artist");
+
+}
+
+1;
index 74a6ae9..a822601 100644 (file)
@@ -6,7 +6,7 @@ $schema->storage->debugcb( sub{ $queries++ } );
 
 eval "use DBD::SQLite";
 plan skip_all => 'needs DBD::SQLite for testing' if $@;
-plan tests => 23;
+plan tests => 22;
 
 my $rs = $schema->resultset("Artist")->search(
   { artistid => 1 }
@@ -14,7 +14,7 @@ my $rs = $schema->resultset("Artist")->search(
 
 my $artist = $rs->first;
 
-is( scalar @{ $rs->get_cache }, 0, 'cache is not populated without cache attribute' );
+ok( !defined($rs->get_cache), 'cache is not populated without cache attribute' );
 
 $rs = $schema->resultset('Artist')->search( undef, { cache => 1 } );
 my $artists = [ $rs->all ];
@@ -23,7 +23,7 @@ is( scalar @{$rs->get_cache}, 3, 'all() populates cache for search with cache at
 
 $rs->clear_cache;
 
-is( scalar @{$rs->get_cache}, 0, 'clear_cache is functional' );
+ok( !defined($rs->get_cache), 'clear_cache is functional' );
 
 $rs->next;
 
@@ -38,12 +38,6 @@ $cd = $schema->resultset('CD')->find(1);
 
 $rs->clear_cache;
 
-eval {
-  $rs->set_cache( [ $cd ] );
-};
-
-is( scalar @{$rs->get_cache}, 0, 'set_cache() only accepts objects of correct type for the resultset' );
-
 $queries = 0;
 $schema->storage->debug(1);