Merge 'trunk' into 'DBIx-Class-current'
Matt S Trout [Sun, 20 Aug 2006 04:17:17 +0000 (04:17 +0000)]
r26007@cain (orig r2674):  matthewt | 2006-08-08 11:32:42 +0000
tweaked search_rs to not be destructive to passed
r26476@cain (orig r2675):  nigel | 2006-08-08 12:05:39 +0000
Test for search_rs destructive effects on attributes - see fix in [2674]

r26477@cain (orig r2676):  blblack | 2006-08-08 13:53:55 +0000
adding draft of pod index generator based on zby script
r26478@cain (orig r2677):  blblack | 2006-08-09 03:29:12 +0000
bump Alg::C3 req to 0.03
r26479@cain (orig r2678):  blblack | 2006-08-09 17:05:33 +0000
backport the DBIx::Class::Storage::DBI::DESTROY fix for peopel sharing $dbh to other code
r26483@cain (orig r2682):  dyfrgi | 2006-08-10 18:22:05 +0000
Support for relationship attributes in many_to_many accessors.
Merge new where condition, old where condition, cond in ResultSet::search_rs
Collapse cond in ResultSet::new_result so that it doesn't try to create columns with names like "-and".
Add a bunch of tests for the where condition things, which also happen to require the cond collapsing.
Create supporting classes and associated tables for these tests.
r26484@cain (orig r2683):  dyfrgi | 2006-08-10 19:49:02 +0000
Support default aliases in many_to_many accessors. Update where test classes to use this.
Remove warning about pseudohashes with array ref where/cond in ResultSet::search_rs.
r26485@cain (orig r2684):  gphat | 2006-08-11 21:13:50 +0000
Move unless to next line to prevent stabbings.

r26532@cain (orig r2685):  blblack | 2006-08-14 16:38:04 +0000
added sth method to Storage::DBI::NoBindVars, updated related docs
r26533@cain (orig r2686):  blblack | 2006-08-14 16:41:03 +0000
Alg::C3 req bump to 0.04
r26534@cain (orig r2687):  ash | 2006-08-14 17:20:50 +0000
Changed the docs for relationship attributes, and added rel order_by example
to cookbook.

r26535@cain (orig r2688):  jester | 2006-08-16 15:22:37 +0000
minor doc clarifications
r26549@cain (orig r2689):  castaway | 2006-08-17 18:59:09 +0000
Added recent Changes

r26550@cain (orig r2690):  castaway | 2006-08-18 10:58:27 +0000
Update to 0.07001

r26551@cain (orig r2691):  castaway | 2006-08-18 11:00:24 +0000
Assume scalar refs need fetching in PK::Auto (to allow \'DEFAULT'
Add RowNumberOver for limits with DB2

r26552@cain (orig r2692):  castaway | 2006-08-18 11:03:25 +0000
Allow deploy to supply a directory for the SQL file to deploy from (Penguin)

r26553@cain (orig r2693):  castaway | 2006-08-18 18:52:59 +0000
0.07001 released

r26554@cain (orig r2694):  claco | 2006-08-20 04:16:30 +0000
remove_columns now deletes columns from _columns fixing has_columns false positives

27 files changed:
.gitignore [new file with mode: 0644]
Build.PL
Changes
lib/DBIx/Class.pm
lib/DBIx/Class/Manual/Cookbook.pod
lib/DBIx/Class/PK/Auto.pm
lib/DBIx/Class/Relationship.pm
lib/DBIx/Class/Relationship/Base.pm
lib/DBIx/Class/Relationship/ManyToMany.pm
lib/DBIx/Class/ResultSet.pm
lib/DBIx/Class/ResultSource.pm
lib/DBIx/Class/Row.pm
lib/DBIx/Class/Schema.pm
lib/DBIx/Class/Storage/DBI.pm
lib/DBIx/Class/Storage/DBI/NoBindVars.pm
maint/gen-pod-index.pl [new file with mode: 0755]
t/46where_attribute.t [new file with mode: 0644]
t/60core.t
t/76joins.t
t/lib/DBICTest.pm
t/lib/DBICTest/Schema.pm
t/lib/DBICTest/Schema/BooksInLibrary.pm [new file with mode: 0644]
t/lib/DBICTest/Schema/Collection.pm [new file with mode: 0644]
t/lib/DBICTest/Schema/CollectionObject.pm [new file with mode: 0644]
t/lib/DBICTest/Schema/Owners.pm [new file with mode: 0644]
t/lib/DBICTest/Schema/TypedObject.pm [new file with mode: 0644]
t/lib/sqlite.sql

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..b0be36a
--- /dev/null
@@ -0,0 +1,6 @@
+Build
+Build.bat
+Makefile
+_build/
+blib/
+t/var/
index a43f626..cda0ada 100644 (file)
--- a/Build.PL
+++ b/Build.PL
@@ -11,7 +11,7 @@ my %arguments = (
         'Scalar::Util'              => 0,
         'SQL::Abstract'             => 1.20,
         'SQL::Abstract::Limit'      => 0.101,
-        'Algorithm::C3'             => 0.02,
+        'Algorithm::C3'             => 0.04,
         'Class::C3'                 => 0.11,
         'Storable'                  => 0,
         'Class::Data::Accessor'     => 0.01,
diff --git a/Changes b/Changes
index 5f687a3..beee9bd 100644 (file)
--- a/Changes
+++ b/Changes
@@ -1,6 +1,14 @@
 Revision history for DBIx::Class
 
-0.07001
+        - remove_columns now deletes columns from _columns
+
+0.07001 2006-08-18 19:55:00
+        - add directory argument to deploy()
+        - support default aliases in many_to_many accessors.
+        - support for relationship attributes in many_to_many accessors.
+        - stop search_rs being destructive to attrs
+        - better error reporting when loading components
+        - UTF8Columns changed to use "utf8" instead of "Encode"
         - restore automatic aliasing in ResultSet::find() on nonunique queries
         - allow aliases in ResultSet::find() queries (in cases of relationships
           with prefetch)
@@ -11,6 +19,7 @@ Revision history for DBIx::Class
           https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=196836
         - fix a pathological prefetch case
         - table case fix for Oracle in columns_info_for
+        - stopped search_rs deleting attributes from passed hash
 
 0.07000 2006-07-23 02:30:00
         - supress warnings for possibly non-unique queries, since
index d615aad..2c635b3 100644 (file)
@@ -234,6 +234,8 @@ wdh: Will Hawes
 
 gphat: Cory G Watson <gphat@cpan.org>
 
+dyfrgi: Michael Leuchtenmurg <michael@slashhome.org>
+
 =head1 LICENSE
 
 You may distribute this code under the same terms as Perl itself.
index 12bde93..1c3d645 100644 (file)
@@ -419,6 +419,17 @@ Deletes only the book named Titanic by the author in $author.
 
   my $author->delete_related('books', { name => 'Titanic' });
 
+=head3 Ordering a relationship result set
+
+If you always want a relation to be ordered, you can specify this when you 
+create the relationship.
+
+To order C<< $book->pages >> by descending page_number.
+
+  Book->has_many('pages' => 'Page', 'book', { order_by => \'page_number DESC'} );
+
+
+
 =head2 Transactions
 
 As of version 0.04001, there is improved transaction support in
index b7ebda7..41c14a6 100644 (file)
@@ -42,13 +42,15 @@ sub insert {
   my ($self, @rest) = @_;
   my $ret = $self->next::method(@rest);
 
-  my ($pri, $too_many) = grep { !defined $self->get_column($_) } $self->primary_columns;
+  my ($pri, $too_many) = grep { !defined $self->get_column($_) || 
+                                    ref($self->get_column($_)) eq 'SCALAR'} $self->primary_columns;
   return $ret unless defined $pri; # if all primaries are already populated, skip auto-inc
   $self->throw_exception( "More than one possible key found for auto-inc on ".ref $self )
     if defined $too_many;
 
   my $storage = $self->result_source->storage;
-  $self->throw_exception( "Missing primary key but Storage doesn't support last_insert_id" ) unless $storage->can('last_insert_id');
+  $self->throw_exception( "Missing primary key but Storage doesn't support last_insert_id" )
+    unless $storage->can('last_insert_id');
   my $id = $storage->last_insert_id($self->result_source,$pri);
   $self->throw_exception( "Can't get last insert id" ) unless $id;
   $self->store_column($pri => $id);
index 3a23108..c6720e9 100644 (file)
@@ -113,33 +113,34 @@ See L<DBIx::Class::Relationship::Base> for a list of valid attributes.
   $obj->author($new_author_obj);
 
 Creates a relationship where the calling class stores the foreign class's
-primary key in one (or more) of its columns. If $cond is a column name
+primary key in one (or more) of its columns. If C<$cond> is a column name
 instead of a join condition hash, that is used as the name of the column
-holding the foreign key. If $cond is not given, the relname is used as
+holding the foreign key. If C<$cond> is not given, the relname is used as
 the column name.
 
-If the relationship is optional - ie the column containing the foreign
+If the relationship is optional - i.e. the column containing the foreign
 key can be NULL - then the belongs_to relationship does the right
-thing - so in the example above C<$obj->author> would return C<undef>.
+thing - so in the example above C<$obj-E<gt>author> would return C<undef>.
 However in this case you would probably want to set the C<join_type>
 attribute so that a C<LEFT JOIN> is done, which makes complex
 resultsets involving C<join> or C<prefetch> operations work correctly.
-The modified declaration is shown below:-
+The modified declaration is shown below:
 
-  # in a Book class (where Author has many Books)
+  # in a Book class (where Author has_many Books)
   __PACKAGE__->belongs_to(author => 'My::DBIC::Schema::Author',
                           'author', {join_type => 'left'});
 
 
-Cascading deletes are off per default on a C<belongs_to> relationship, to turn
-them on, pass C<< cascade_delete => 1 >> in the $attr hashref.
+Cascading deletes are off by default on a C<belongs_to>
+relationship. To turn them on, pass C<< cascade_delete => 1 >>
+in the $attr hashref.
 
 NOTE: If you are used to L<Class::DBI> relationships, this is the equivalent
 of C<has_a>.
 
 =head2 has_many
 
-  # in an Author class (where Author has many Books)
+  # in an Author class (where Author has_many Books)
   My::DBIC::Schema::Author->has_many(books => 'My::DBIC::Schema::Book', 'author');
   my $booklist = $obj->books;
   my $booklist = $obj->books({
@@ -155,21 +156,22 @@ of C<has_a>.
 Creates a one-to-many relationship, where the corresponding elements of the
 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.
+C<$cond> argument, or specify a complete join condition.
 
 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
+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
-the related objects will be deleted as well. However, any database-level
-cascade or restrict will take precedence. To turn this behavior off, pass
-C<< cascade_delete => 0 >> in the $attr hashref.
+the related objects will be deleted as well.  To turn this behaviour off,
+pass C<< cascade_delete => 0 >> in the C<$attr> hashref. However, any
+database-level cascade or restrict will take precedence over a
+DBIx-Class-based cascading delete.
 
 =head2 might_have
 
@@ -179,12 +181,13 @@ C<< cascade_delete => 0 >> in the $attr hashref.
 
 Creates an optional one-to-one relationship with a class, where the foreign
 class stores our primary key in one of its columns. Defaults to the primary
-key of the foreign class unless $cond specifies a column or join condition.
+key of the foreign class unless C<$cond> specifies a column or join condition.
 
 If you update or delete an object in a class with a C<might_have>
-relationship, the related object will be updated or deleted as well.
-Any database-level update or delete constraints will override this behaviour.
-To turn off this behavior, add C<< cascade_delete => 0 >> to the $attr hashref.
+relationship, the related object will be updated or deleted as well. To
+turn off this behavior, add C<< cascade_delete => 0 >> to the C<$attr>
+hashref. Any database-level update or delete constraints will override
+this behavior.
 
 =head2 has_one
 
@@ -217,7 +220,7 @@ left join.
   My::DBIC::Schema::Actor->many_to_many( roles => 'actor_roles',
                                          'role' );
 
-Creates a accessors bridging two relationships; not strictly a relationship in
+Creates accessors bridging two relationships; not strictly a relationship in
 its own right, although the accessor will return a resultset or collection of
 objects just as a has_many would.
 
index 6b8a7a9..9797b7c 100644 (file)
@@ -67,7 +67,7 @@ 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:
+In addition to standard result set attributes, the following attributes are also valid:
 
 =over 4
 
index 23b971e..65eab45 100644 (file)
@@ -14,9 +14,12 @@ sub many_to_many {
     my $remove_meth = "remove_from_${meth}";
     my $set_meth = "set_${meth}";
 
+    $rel_attrs->{alias} ||= $f_rel;
+
     *{"${class}::${meth}"} = sub {
       my $self = shift;
       my $attrs = @_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {};
+      my @args = ($f_rel, @_ > 0 ? @_ : undef, { %{$rel_attrs||{}}, %$attrs });
       $self->search_related($rel)->search_related(
         $f_rel, @_ > 0 ? @_ : undef, { %{$rel_attrs||{}}, %$attrs }
       );
@@ -32,10 +35,19 @@ sub many_to_many {
       my $rel_source_name = $source->relationship_info($rel)->{source};
       my $rel_source = $schema->resultset($rel_source_name)->result_source;
       my $f_rel_source_name = $rel_source->relationship_info($f_rel)->{source};
-      my $f_rel_rs = $schema->resultset($f_rel_source_name);
-      my $obj = ref $_[0]
-        ? ( ref $_[0] eq 'HASH' ? $f_rel_rs->create($_[0]) : $_[0] )
-        : ( $f_rel_rs->create({@_}) );
+      my $f_rel_rs = $schema->resultset($f_rel_source_name)->search({}, $rel_attrs||{});
+
+      my $obj;
+      if (ref $_[0]) {
+        if (ref $_[0] eq 'HASH') {
+          $obj = $f_rel_rs->create($_[0]);
+        } else {
+          $obj = $_[0];
+        }
+      } else {
+        $obj = $f_rel_rs->create({@_});
+      }
+
       my $link_vals = @_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {};
       my $link = $self->search_related($rel)->new_result({});
       $link->set_from_related($f_rel, $obj);
index 3c1f112..7a8f44d 100644 (file)
@@ -171,15 +171,17 @@ sub search_rs {
   $attrs = pop(@_) if @_ > 1 and ref $_[$#_] eq 'HASH';
   my $our_attrs = { %{$self->{attrs}} };
   my $having = delete $our_attrs->{having};
+  my $where = delete $our_attrs->{where};
+
+  my $new_attrs = { %{$our_attrs}, %{$attrs} };
 
   # merge new attrs into inherited
   foreach my $key (qw/join prefetch/) {
     next unless exists $attrs->{$key};
-    $our_attrs->{$key} = $self->_merge_attr($our_attrs->{$key}, delete $attrs->{$key});
+    $new_attrs->{$key} = $self->_merge_attr($our_attrs->{$key}, $attrs->{$key});
   }
-  
-  my $new_attrs = { %{$our_attrs}, %{$attrs} };
-  my $where = (@_
+
+  my $cond = (@_
     ? (
         (@_ == 1 || ref $_[0] eq "HASH")
           ? shift
@@ -203,6 +205,17 @@ sub search_rs {
           }
         : $where);
   }
+  if (defined $cond) {
+    $new_attrs->{where} = (
+      defined $new_attrs->{where}
+        ? { '-and' => [
+              map {
+                ref $_ eq 'ARRAY' ? [ -or => $_ ] : $_
+              } $cond, $new_attrs->{where}
+            ]
+          }
+        : $cond);
+  }
 
   if (defined $having) {
     $new_attrs->{having} = (
@@ -1107,7 +1120,7 @@ sub update_all {
 
 Deletes the contents of the resultset from its result source. Note that this
 will not run DBIC cascade triggers. See L</delete_all> if you need triggers
-to run.
+to run. See also L<DBIx::Class::Row/delete>.
 
 =cut
 
@@ -1210,9 +1223,10 @@ sub new_result {
   ) if ($self->{cond} && !(ref $self->{cond} eq 'HASH'));
 
   my $alias = $self->{attrs}{alias};
+  my $collapsed_cond = $self->{cond} ? $self->_collapse_cond($self->{cond}) : {};
   my %new = (
     %{ $self->_remove_alias($values, $alias) },
-    %{ $self->_remove_alias($self->{cond}, $alias) },
+    %{ $self->_remove_alias($collapsed_cond, $alias) },
   );
 
   my $obj = $self->result_class->new(\%new);
@@ -1220,6 +1234,41 @@ sub new_result {
   return $obj;
 }
 
+# _collapse_cond
+#
+# Recursively collapse the condition.
+
+sub _collapse_cond {
+  my ($self, $cond, $collapsed) = @_;
+
+  $collapsed ||= {};
+
+  if (ref $cond eq 'ARRAY') {
+    foreach my $subcond (@$cond) {
+      next unless ref $subcond;  # -or
+#      warn "ARRAY: " . Dumper $subcond;
+      $collapsed = $self->_collapse_cond($subcond, $collapsed);
+    }
+  }
+  elsif (ref $cond eq 'HASH') {
+    if (keys %$cond and (keys %$cond)[0] eq '-and') {
+      foreach my $subcond (@{$cond->{-and}}) {
+#        warn "HASH: " . Dumper $subcond;
+        $collapsed = $self->_collapse_cond($subcond, $collapsed);
+      }
+    }
+    else {
+#      warn "LEAF: " . Dumper $cond;
+      foreach my $col (keys %$cond) {
+        my $value = $cond->{$col};
+        $collapsed->{$col} = $value;
+      }
+    }
+  }
+
+  return $collapsed;
+}
+
 # _remove_alias
 #
 # Remove the specified alias from the specified query hash. A copy is made so
index f498490..75b1487 100644 (file)
@@ -257,7 +257,7 @@ sub remove_columns {
   }
 
   foreach (@cols) {
-    undef $columns->{$_};
+    delete $columns->{$_};
   };
 
   $self->_ordered_columns(\@remaining);
index ceed5a6..e03ab16 100644 (file)
@@ -55,9 +55,11 @@ sub new {
 
   $obj->insert;
 
-Inserts an object into the database if it isn't already in there. Returns
-the object itself. Requires the object's result source to be set, or the
-class to have a result_source_instance method.
+Inserts an object into the database if it isn't already in
+there. Returns the object itself. Requires the object's result source to
+be set, or the class to have a result_source_instance method. To insert
+an entirely new object into the database, use C<create> (see
+L<DBIx::Class::ResultSet/create>).
 
 =cut
 
@@ -126,9 +128,14 @@ sub update {
 
   $obj->delete
 
-Deletes the object from the database. The object is still perfectly usable,
-but ->in_storage() will now return 0 and the object must re inserted using
-->insert() before ->update() can be used on it.
+Deletes the object from the database. The object is still perfectly
+usable, but C<-E<gt>in_storage()> will now return 0 and the object must
+reinserted using C<-E<gt>insert()> before C<-E(<gt>update()> can be used
+on it. If you delete an object in a class with a C<has_many>
+relationship, all the related objects will be deleted as well. To turn
+this behavior off, pass C<cascade_delete => 0> in the C<$attr>
+hashref. Any database-level cascade or restrict will take precedence
+over a DBIx-Class-based cascading delete. See also L<DBIx::Class::ResultSet/delete>.
 
 =cut
 
index c8c6d2c..1945f65 100644 (file)
@@ -676,7 +676,7 @@ sub throw_exception {
 
 =over 4
 
-=item Arguments: $sqlt_args
+=item Arguments: $sqlt_args, $dir
 
 =back
 
@@ -692,9 +692,9 @@ produced include a DROP TABLE statement for each table created.
 =cut
 
 sub deploy {
-  my ($self, $sqltargs) = @_;
+  my ($self, $sqltargs, $dir) = @_;
   $self->throw_exception("Can't deploy without storage") unless $self->storage;
-  $self->storage->deploy($self, undef, $sqltargs);
+  $self->storage->deploy($self, undef, $sqltargs, $dir);
 }
 
 =head2 create_ddl_dir (EXPERIMENTAL)
@@ -713,8 +713,7 @@ across all databases, or fully handle complex relationships.
 
 =cut
 
-sub create_ddl_dir
-{
+sub create_ddl_dir {
   my $self = shift;
 
   $self->throw_exception("Can't create_ddl_dir without storage") unless $self->storage;
@@ -730,8 +729,7 @@ intended for direct end user use.
 
 =cut
 
-sub ddl_filename
-{
+sub ddl_filename {
     my ($self, $type, $dir, $version) = @_;
 
     my $filename = ref($self);
@@ -752,4 +750,3 @@ Matt S. Trout <mst@shadowcatsystems.co.uk>
 You may distribute this code under the same terms as Perl itself.
 
 =cut
-
index d2da6bc..8d2956b 100644 (file)
@@ -35,10 +35,36 @@ sub new {
   $self;
 }
 
+sub _RowNumberOver {
+  my ($self, $sql, $order, $rows, $offset ) = @_;
+
+  $offset += 1;
+  my $last = $rows + $offset;
+  my ( $order_by ) = $self->_order_by( $order );
+
+  $sql = <<"";
+SELECT * FROM
+(
+   SELECT Q1.*, ROW_NUMBER() OVER( ) AS ROW_NUM FROM (
+      $sql
+      $order_by
+   ) Q1
+) Q2
+WHERE ROW_NUM BETWEEN $offset AND $last
+
+  return $sql;
+}
+
+
 # While we're at it, this should make LIMIT queries more efficient,
 #  without digging into things too deeply
 sub _find_syntax {
   my ($self, $syntax) = @_;
+  my $dbhname = eval { $syntax->{Driver}->{Name}} || '';
+  if(ref($self) && $dbhname && $dbhname eq 'DB2') {
+    return 'RowNumberOver';
+  }
+
   $self->{_cached_syntax} ||= $self->SUPER::_find_syntax($syntax);
 }
 
@@ -1049,8 +1075,8 @@ sub deployment_statements {
 }
 
 sub deploy {
-  my ($self, $schema, $type, $sqltargs) = @_;
-  foreach my $statement ( $self->deployment_statements($schema, $type, undef, undef, { no_comments => 1, %{ $sqltargs || {} } } ) ) {
+  my ($self, $schema, $type, $sqltargs, $dir) = @_;
+  foreach my $statement ( $self->deployment_statements($schema, $type, undef, $dir, { no_comments => 1, %{ $sqltargs || {} } } ) ) {
     for ( split(";\n", $statement)) {
       next if($_ =~ /^--/);
       next if(!$_);
@@ -1102,7 +1128,6 @@ sub build_datetime_parser {
 sub DESTROY {
   my $self = shift;
   return if !$self->_dbh;
-
   $self->_verify_pid;
   $self->_dbh(undef);
 }
index b8684fd..565178e 100644 (file)
@@ -5,6 +5,35 @@ use warnings;
 
 use base 'DBIx::Class::Storage::DBI';
 
+=head1 NAME 
+
+DBIx::Class::Storage::DBI::NoBindVars - Sometime DBDs have poor to no support for bind variables
+
+=head1 DESCRIPTION
+
+This class allows queries to work when the DBD or underlying library does not
+support the usual C<?> placeholders, or at least doesn't support them very
+well, as is the case with L<DBD::Sybase>
+
+=head1 METHODS
+
+=head2 sth
+
+Uses C<prepare> instead of the usual C<prepare_cached>, seeing as we can't cache very effectively without bind variables.
+
+=cut
+
+sub sth {
+  my ($self, $sql) = @_;
+  return $self->dbh->prepare($sql);
+}
+
+=head2 _execute
+
+Manually subs in the values for the usual C<?> placeholders before calling L</sth> on the generated SQL.
+
+=cut
+
 sub _execute {
   my ($self, $op, $extra_bind, $ident, @args) = @_;
   my ($sql, @bind) = $self->sql_maker->$op($ident, @args);
@@ -45,23 +74,10 @@ sub _execute {
   return (wantarray ? ($rv, $sth, @bind) : $rv);
 }
 
-1;
-
-=head1 NAME 
-
-DBIx::Class::Storage::DBI::NoBindVars - Sometime DBDs have poor to no support for bind variables
-
-=head1 SYNOPSIS
-
-=head1 DESCRIPTION
-
-This class allows queries to work when the DBD or underlying library does not
-support the usual C<?> placeholders, or at least doesn't support them very
-well, as is the case with L<DBD::Sybase>
-
 =head1 AUTHORS
 
 Brandon Black <blblack@gmail.com>
+
 Trym Skaar <trym@tryms.no>
 
 =head1 LICENSE
@@ -69,3 +85,5 @@ Trym Skaar <trym@tryms.no>
 You may distribute this code under the same terms as Perl itself.
 
 =cut
+
+1;
diff --git a/maint/gen-pod-index.pl b/maint/gen-pod-index.pl
new file mode 100755 (executable)
index 0000000..9d2fbe6
--- /dev/null
@@ -0,0 +1,64 @@
+#!/usr/bin/perl -w
+
+# Originally by: Zbigniew Lukasiak, C<zz bb yy@gmail.com>
+#  but refactored and modified to our nefarious purposes
+
+# XXX I'm not done refactoring this yet --blblack
+
+use strict;
+use warnings;
+
+use Pod::Coverage;
+use Data::Dumper;
+use File::Find::Rule;
+use File::Slurp;
+use Path::Class;
+use Template;
+
+# Convert filename to package name
+sub getpac {
+    my $file = shift;
+    my $filecont = read_file( $file );
+    $filecont =~ /package\s*(.*?);/s or return;
+    my $pac = $1;
+    $pac =~ /\s+(.*)$/;
+    return $1;
+}
+
+my @files = File::Find::Rule->file()->name('*.pm', '*.pod')->in('lib');
+
+my %docsyms;
+for my $file (@files){
+    my $package = getpac( $file ) or next;
+    my $pc = Pod::Coverage->new(package => $package);
+    my %allsyms = map {$_ => 1} $pc->_get_syms($package);
+    my $podarr = $pc->_get_pods();
+    next if !$podarr;
+    for my $sym (@{$podarr}){
+        $docsyms{$sym}{$package} = $file if $allsyms{$sym};
+    }
+}
+
+my @lines;
+for my $sym (sort keys %docsyms){
+    for my $pac (sort keys %{$docsyms{$sym}}){
+        push @lines, {symbol => $sym, package => $pac};
+    }
+}
+
+my $tt = Template->new({})
+|| die Template->error(), "\n";
+
+$tt->process(\*DATA, { lines => \@lines })
+|| die $tt->error(), "\n";
+
+
+__DATA__
+
+=head1 NAME
+
+Method Index
+
+[% FOR line = lines %]
+L<[% line.symbol %] ([% line.package %])|[% line.package %]/[% line.symbol %]>
+[% END %]
diff --git a/t/46where_attribute.t b/t/46where_attribute.t
new file mode 100644 (file)
index 0000000..764d7cc
--- /dev/null
@@ -0,0 +1,68 @@
+use strict;\r
+use warnings;\r
+\r
+use Test::More;\r
+use Data::Dumper;\r
+use lib qw(t/lib);\r
+use DBICTest;\r
+my $schema = DBICTest->init_schema();\r
+\r
+plan tests => 14;\r
+\r
+# select from a class with resultset_attributes\r
+my $resultset = $schema->resultset('BooksInLibrary');\r
+is($resultset, 3, "select from a class with resultset_attributes okay");\r
+\r
+# now test out selects through a resultset\r
+my $owner = $schema->resultset('Owners')->find({name => "Newton"});\r
+my $programming_perl = $owner->books->find_or_create({ title => "Programming Perl" });\r
+is($programming_perl->id, 1, 'select from a resultset with find_or_create for existing entry ok');\r
+\r
+# and inserts?\r
+my $see_spot;\r
+$see_spot = eval { $owner->books->find_or_create({ title => "See Spot Run" }) };\r
+if ($@) { print $@ }\r
+ok(!$@, 'find_or_create on resultset with attribute for non-existent entry did not throw');\r
+ok(defined $see_spot, 'successfully did insert on resultset with attribute for non-existent entry');\r
+\r
+# many_to_many tests\r
+my $collection = $schema->resultset('Collection')->search({collectionid => 1});\r
+my $pointy_objects = $collection->search_related('collection_object')->search_related('object', { type => "pointy"});\r
+my $pointy_count = $pointy_objects->count();\r
+is($pointy_count, 2, 'many_to_many explicit query through linking table with query starting from resultset count correct');\r
+\r
+$collection = $schema->resultset('Collection')->find(1);\r
+$pointy_objects = $collection->search_related('collection_object')->search_related('object', { type => "pointy"});\r
+$pointy_count = $pointy_objects->count();\r
+is($pointy_count, 2, 'many_to_many explicit query through linking table with query starting from row count correct');\r
+\r
+# use where on many_to_many query\r
+$collection = $schema->resultset('Collection')->find(1);\r
+$pointy_objects = $collection->search_related('collection_object')->search_related('object', {}, { where => { 'object.type' => 'pointy' } });\r
+is($pointy_objects->count(), 2, 'many_to_many explicit query through linking table with where starting from row count correct');\r
+\r
+$collection = $schema->resultset('Collection')->find(1);\r
+$pointy_objects = $collection->pointy_objects();\r
+$pointy_count = $pointy_objects->count();\r
+is($pointy_count, 2, 'many_to_many resultset with where in resultset attrs count correct');\r
+\r
+# add_to_$rel on many_to_many with where containing a required field\r
+eval {$collection->add_to_pointy_objects({ value => "Nail" }) };\r
+if ($@) { print $@ }\r
+ok( !$@, 'many_to_many add_to_$rel($hash) with where in relationship attrs did not throw');\r
+is($pointy_objects->count, $pointy_count+1, 'many_to_many add_to_$rel($hash) with where in relationship attrs count correct');\r
+$pointy_count = $pointy_objects->count();\r
+\r
+my $pen = $schema->resultset('TypedObject')->create({ value => "Pen", type => "pointy"});\r
+eval {$collection->add_to_pointy_objects($pen)};\r
+if ($@) { print $@ }\r
+ok( !$@, 'many_to_many add_to_$rel($object) with where in relationship attrs did not throw');\r
+is($pointy_objects->count, $pointy_count+1, 'many_to_many add_to_$rel($object) with where in relationship attrs count correct');\r
+$pointy_count = $pointy_objects->count();\r
+\r
+my $round_objects = $collection->round_objects();\r
+my $round_count = $round_objects->count();\r
+eval {$collection->add_to_objects({ value => "Wheel", type => "round" })};\r
+if ($@) { print $@ }\r
+ok( !$@, 'many_to_many add_to_$rel($hash) did not throw');\r
+is($round_objects->count, $round_count+1, 'many_to_many add_to_$rel($hash) count correct');\r
index a468515..22eead9 100644 (file)
@@ -7,7 +7,7 @@ use DBICTest;
 
 my $schema = DBICTest->init_schema();
 
-plan tests => 62;
+plan tests => 63;
 
 # 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
@@ -290,5 +290,6 @@ ok(!$@, "stringify to false value doesn't cause error");
   is_deeply([$schema->source('CD')->columns], [qw/cdid artist title year/]);
   $schema->source('CD')->remove_columns('year');
   is_deeply([$schema->source('CD')->columns], [qw/cdid artist title/]);
+  ok(! exists $schema->source('CD')->_columns->{'year'}, 'year still exists in _columns');
 }
 
index dff7046..1033b53 100644 (file)
@@ -4,6 +4,7 @@ use warnings;
 use Test::More;
 use lib qw(t/lib);
 use DBICTest;
+use Data::Dumper;
 
 my $schema = DBICTest->init_schema();
 
@@ -15,7 +16,7 @@ BEGIN {
     eval "use DBD::SQLite";
     plan $@
         ? ( skip_all => 'needs DBD::SQLite for testing' )
-        : ( tests => 47 );
+        : ( tests => 49 );
 }
 
 # figure out if we've got a version of sqlite that is older than 3.2.6, in
@@ -133,11 +134,18 @@ cmp_ok( $rs->count, '==', 1, "Single record in resultset");
 
 is($rs->first->name, 'We Are Goth', 'Correct record returned');
 
-$rs = $schema->resultset("CD")->search(
-           { 'artist.name' => 'Caterwauler McCrae' },
-           { prefetch => [ qw/artist liner_notes/ ],
-             order_by => 'me.cdid' });
+# bug in 0.07000 caused attr (join/prefetch) to be modifed by search
+# so we check the search & attr arrays are not modified
+my $search = { 'artist.name' => 'Caterwauler McCrae' };
+my $attr = { prefetch => [ qw/artist liner_notes/ ],
+             order_by => 'me.cdid' };
+my $search_str = Dumper($search);
+my $attr_str = Dumper($attr);
+
+$rs = $schema->resultset("CD")->search($search, $attr);
 
+is(Dumper($search), $search_str, 'Search hash untouched after search()');
+is(Dumper($attr), $attr_str, 'Attribute hash untouched after search()');
 cmp_ok($rs + 0, '==', 3, 'Correct number of records returned');
 
 my $queries = 0;
index a050862..97855cb 100755 (executable)
@@ -87,7 +87,7 @@ sub deploy_schema {
         my $sql;
         { local $/ = undef; $sql = <IN>; }
         close IN;
-        $schema->storage->dbh->do($_) for split(/;\n/, $sql);
+        ($schema->storage->dbh->do($_) || print "Error on SQL: $_\n") for split(/;\n/, $sql);
     }
 }
 
@@ -233,6 +233,43 @@ sub populate_schema {
         [ qw/id link/ ],
         [ 1, 1 ]
     ]);
+
+    $schema->populate('Collection', [
+        [ qw/collectionid name/ ],
+        [ 1, "Tools" ],
+        [ 2, "Body Parts" ],
+    ]);
+
+    $schema->populate('CollectionObject', [
+        [ qw/collection object/ ],
+        [ 1, 1 ],
+        [ 1, 2 ],
+        [ 1, 3 ],
+        [ 2, 4 ],
+        [ 2, 5 ],
+    ]);
+
+    $schema->populate('TypedObject', [
+        [ qw/objectid type value/ ],
+        [ 1, "pointy", "Awl" ],
+        [ 2, "round", "Bearing" ],
+        [ 3, "pointy", "Knife" ],
+        [ 4, "pointy", "Tooth" ],
+        [ 5, "round", "Head" ],
+    ]);
+
+    $schema->populate('Owners', [
+        [ qw/ownerid name/ ],
+        [ 1, "Newton" ],
+        [ 2, "Waltham" ],
+    ]);
+
+    $schema->populate('BooksInLibrary', [
+        [ qw/id owner title source/ ],
+        [ 1, 1, "Programming Perl", "Library" ],
+        [ 2, 1, "Dynamical Systems", "Library" ],
+        [ 3, 2, "Best Recipe Cookbook", "Library" ],
+    ]);
 }
 
 1;
index 8e7597d..f8b2cd9 100644 (file)
@@ -33,7 +33,9 @@ __PACKAGE__->load_classes(qw/
     'Producer',
     'CD_to_Producer',
   ),
-  qw/SelfRefAlias TreeLike TwoKeyTreeLike Event NoPrimaryKey/
+  qw/SelfRefAlias TreeLike TwoKeyTreeLike Event NoPrimaryKey/,
+  qw/Collection CollectionObject TypedObject/,
+  qw/Owners BooksInLibrary/
 );
 
 1;
diff --git a/t/lib/DBICTest/Schema/BooksInLibrary.pm b/t/lib/DBICTest/Schema/BooksInLibrary.pm
new file mode 100644 (file)
index 0000000..ba6f94d
--- /dev/null
@@ -0,0 +1,28 @@
+package # hide from PAUSE \r
+    DBICTest::Schema::BooksInLibrary;\r
+\r
+use base qw/DBIx::Class::Core/;\r
+\r
+__PACKAGE__->table('books');\r
+__PACKAGE__->add_columns(\r
+  'id' => {\r
+    data_type => 'integer',\r
+    is_auto_increment => 1,\r
+  },\r
+  'source' => {\r
+    data_type => 'varchar',\r
+    size      => '100',\r
+  },\r
+  'owner' => {\r
+    data_type => 'integer',\r
+  },\r
+  'title' => {\r
+    data_type => 'varchar',\r
+    size      => '100',\r
+  },\r
+);\r
+__PACKAGE__->set_primary_key('id');\r
+\r
+__PACKAGE__->resultset_attributes({where => { source => "Library" } });\r
+\r
+1;\r
diff --git a/t/lib/DBICTest/Schema/Collection.pm b/t/lib/DBICTest/Schema/Collection.pm
new file mode 100644 (file)
index 0000000..1c11dc6
--- /dev/null
@@ -0,0 +1,30 @@
+package # hide from PAUSE \r
+    DBICTest::Schema::Collection;\r
+\r
+use base qw/DBIx::Class::Core/;\r
+\r
+__PACKAGE__->table('collection');\r
+__PACKAGE__->add_columns(\r
+  'collectionid' => {\r
+    data_type => 'integer',\r
+    is_auto_increment => 1,\r
+  },\r
+  'name' => {\r
+    data_type => 'varchar',\r
+    size      => 100,\r
+  },\r
+);\r
+__PACKAGE__->set_primary_key('collectionid');\r
+\r
+__PACKAGE__->has_many( collection_object => "DBICTest::Schema::CollectionObject",\r
+                       { "foreign.collection" => "self.collectionid" }\r
+                     );\r
+__PACKAGE__->many_to_many( objects => collection_object => "object" );\r
+__PACKAGE__->many_to_many( pointy_objects => collection_object => "object",\r
+                           { where => { "object.type" => "pointy" } }\r
+                         );\r
+__PACKAGE__->many_to_many( round_objects => collection_object => "object",\r
+                           { where => { "object.type" => "round" } } \r
+                         );\r
+\r
+1;\r
diff --git a/t/lib/DBICTest/Schema/CollectionObject.pm b/t/lib/DBICTest/Schema/CollectionObject.pm
new file mode 100644 (file)
index 0000000..d05ae5d
--- /dev/null
@@ -0,0 +1,24 @@
+package # hide from PAUSE \r
+    DBICTest::Schema::CollectionObject;\r
+\r
+use base qw/DBIx::Class::Core/;\r
+\r
+__PACKAGE__->table('collection_object');\r
+__PACKAGE__->add_columns(\r
+  'collection' => {\r
+    data_type => 'integer',\r
+  },\r
+  'object' => {\r
+    data_type => 'integer',\r
+  },\r
+);\r
+__PACKAGE__->set_primary_key(qw/collection object/);\r
+\r
+__PACKAGE__->belongs_to( collection => "DBICTest::Schema::Collection",\r
+                         { "foreign.collectionid" => "self.collection" }\r
+                       );\r
+__PACKAGE__->belongs_to( object => "DBICTest::Schema::TypedObject",\r
+                         { "foreign.objectid" => "self.object" }\r
+                       );\r
+\r
+1;\r
diff --git a/t/lib/DBICTest/Schema/Owners.pm b/t/lib/DBICTest/Schema/Owners.pm
new file mode 100644 (file)
index 0000000..acaf5ed
--- /dev/null
@@ -0,0 +1,21 @@
+package # hide from PAUSE \r
+    DBICTest::Schema::Owners;\r
+\r
+use base qw/DBIx::Class::Core/;\r
+\r
+__PACKAGE__->table('owners');\r
+__PACKAGE__->add_columns(\r
+  'ownerid' => {\r
+    data_type => 'integer',\r
+    is_auto_increment => 1,\r
+  },\r
+  'name' => {\r
+    data_type => 'varchar',\r
+    size      => '100',\r
+  },\r
+);\r
+__PACKAGE__->set_primary_key('ownerid');\r
+\r
+__PACKAGE__->has_many(books => "DBICTest::Schema::BooksInLibrary", "owner");\r
+\r
+1;\r
diff --git a/t/lib/DBICTest/Schema/TypedObject.pm b/t/lib/DBICTest/Schema/TypedObject.pm
new file mode 100644 (file)
index 0000000..6498add
--- /dev/null
@@ -0,0 +1,28 @@
+package # hide from PAUSE 
+    DBICTest::Schema::TypedObject;
+
+use base qw/DBIx::Class::Core/;
+
+__PACKAGE__->table('typed_object');
+__PACKAGE__->add_columns(
+  'objectid' => {
+    data_type => 'integer',
+    is_auto_increment => 1,
+  },
+  'type' => {
+    data_type => 'varchar',
+    size      => '100',
+  },
+  'value' => {
+    data_type => 'varchar',
+    size      => 100,
+  },
+);
+__PACKAGE__->set_primary_key('objectid');
+
+__PACKAGE__->has_many( collection_object => "DBICTest::Schema::CollectionObject",
+                       { "foreign.object" => "self.objectid" }
+                     );
+__PACKAGE__->many_to_many( collections => collection_object => "collection" );
+
+1;
index 228e448..2ce5dad 100644 (file)
@@ -210,6 +210,50 @@ CREATE TABLE onekey (
   cd integer NOT NULL
 );
 
+--
+-- Table: typed_object
+--
+CREATE TABLE typed_object (
+  objectid INTEGER PRIMARY KEY NOT NULL,
+  type VARCHAR(100) NOT NULL,
+  value VARCHAR(100)
+);
+
+--
+-- Table: collection
+--
+CREATE TABLE collection (
+  collectionid INTEGER PRIMARY KEY NOT NULL,
+  name VARCHAR(100)
+);
+
+--
+-- Table: collection_object
+--
+CREATE TABLE collection_object (
+  collection INTEGER NOT NULL,
+  object INTEGER NOT NULL
+);
+
+--
+-- Table: owners
+--
+CREATE TABLE owners (
+  ownerid INTEGER PRIMARY KEY NOT NULL,
+  name varchar(100)
+);
+
+--
+-- Table: books
+--
+CREATE TABLE books (
+  id INTEGER PRIMARY KEY NOT NULL,
+  owner INTEGER,
+  source varchar(100),
+  title varchar(100)
+);
+
+
 CREATE UNIQUE INDEX tktlnameunique_twokeytreelike on twokeytreelike (name);
 CREATE UNIQUE INDEX cd_artist_title_cd on cd (artist, title);
 CREATE UNIQUE INDEX track_cd_position_track on track (cd, position);