Merge 'trunk' into 'DBIx-Class-current'
Brandon L. Black [Tue, 22 May 2007 01:40:14 +0000 (01:40 +0000)]
r30913@brandon-blacks-computer (orig r3263):  matthewt | 2007-05-06 11:52:36 -0500
patch from soulchild (thanks!)
r31140@brandon-blacks-computer (orig r3318):  castaway | 2007-05-17 07:56:49 -0500
Applied patch from Pedro Melo to fix order of components in the example

r31938@brandon-blacks-computer (orig r3348):  ilmari | 2007-05-21 15:23:36 -0500
Copy the working mk_hash from HashRefInflator in -current into Cookbook

r31946@brandon-blacks-computer (orig r3355):  blblack | 2007-05-21 20:21:42 -0500
connect_info should return the same data it was given

lib/DBIx/Class/InflateColumn/DateTime.pm
lib/DBIx/Class/Manual/Cookbook.pod
lib/DBIx/Class/Storage/DBI.pm

index 6b012d8..f92481d 100644 (file)
@@ -14,7 +14,7 @@ Load this component and then declare one or more
 columns to be of the datetime, timestamp or date datatype.
 
   package Event;
-  __PACKAGE__->load_components(qw/Core InflateColumn::DateTime/);
+  __PACKAGE__->load_components(qw/InflateColumn::DateTime Core/);
   __PACKAGE__->add_columns(
     starts_when => { data_type => 'datetime' }
   );
index 4dec7ae..3e5aa7b 100644 (file)
@@ -70,6 +70,56 @@ This results in the following C<WHERE> clause:
 For more information on generating complex queries, see
 L<SQL::Abstract/WHERE CLAUSES>.
 
+=head3 Arbitrary SQL through a custom ResultSource
+
+Sometimes you have to run arbitrary SQL because your query is too complex
+(e.g. it contains Unions, Sub-Selects, Stored Procedures, etc.) or has to
+be optimized for your database in a special way, but you still want to 
+get the results as a L<DBIx::Class::ResultSet>. 
+The recommended way to accomplish this is by defining a separate ResultSource 
+for your query. You can then inject complete SQL statements using a scalar 
+reference (this is a feature of L<SQL::Abstract>).
+
+Say you want to run a complex custom query on your user data, here's what
+you have to add to your User class:
+
+  package My::Schema::User;
+  
+  use base qw/DBIx::Class/;
+  
+  # ->load_components, ->table, ->add_columns, etc.
+
+  # Make a new ResultSource based on the User class
+  my $source = __PACKAGE__->result_source_instance();
+  my $new_source = $source->new( $source );
+  $new_source->source_name( 'UserFriendsComplex' );
+  
+  # Hand in your query as a scalar reference
+  # It will be added as a sub-select after FROM,
+  # so pay attention to the surrounding brackets!
+  $new_source->name( \<<SQL );
+  ( SELECT u.* FROM user u 
+  INNER JOIN user_friends f ON u.id = f.user_id 
+  WHERE f.friend_user_id = ?
+  UNION 
+  SELECT u.* FROM user u 
+  INNER JOIN user_friends f ON u.id = f.friend_user_id 
+  WHERE f.user_id = ? )
+  SQL 
+
+  # Finally, register your new ResultSource with your Schema
+  My::Schema->register_source( 'UserFriendsComplex' => $new_source );
+
+Next, you can execute your complex query using bind parameters like this:
+
+  my $friends = [ $schema->resultset( 'UserFriendsComplex' )->search( {}, 
+    {
+      bind  => [ 12345, 12345 ]
+    }
+  ) ];
+  
+... and you'll get back a perfect L<DBIx::Class::ResultSet>.
+
 =head3 Using specific columns
 
 When you only want specific columns from a table, you can use
index 860cc36..7e86c00 100644 (file)
@@ -12,10 +12,10 @@ use DBIx::Class::Storage::Statistics;
 use IO::File;
 use Scalar::Util 'blessed';
 
-__PACKAGE__->mk_group_accessors(
-  'simple' =>
-    qw/_connect_info _dbh _sql_maker _sql_maker_opts _conn_pid _conn_tid
-       disable_sth_caching cursor on_connect_do transaction_depth/
+__PACKAGE__->mk_group_accessors('simple' =>
+    qw/_connect_info _dbi_connect_info _dbh _sql_maker _sql_maker_opts
+       _conn_pid _conn_tid disable_sth_caching cursor on_connect_do
+       transaction_depth/
 );
 
 BEGIN {
@@ -464,9 +464,10 @@ sub connect_info {
   #  the new set of options
   $self->_sql_maker(undef);
   $self->_sql_maker_opts({});
+  $self->_connect_info($info_arg);
 
-  my $info = [ @$info_arg ]; # copy because we can alter it
-  my $last_info = $info->[-1];
+  my $dbi_info = [@$info_arg]; # copy for DBI
+  my $last_info = $dbi_info->[-1];
   if(ref $last_info eq 'HASH') {
     for my $storage_opt (qw/on_connect_do disable_sth_caching/) {
       if(my $value = delete $last_info->{$storage_opt}) {
@@ -480,29 +481,31 @@ sub connect_info {
     }
 
     # Get rid of any trailing empty hashref
-    pop(@$info) if !keys %$last_info;
+    pop(@$dbi_info) if !keys %$last_info;
   }
 
-  if(ref $info->[0] ne 'CODE') {
+  $self->_dbi_connect_info($dbi_info);
+
+  if(ref $dbi_info->[0] ne 'CODE') {
       # Extend to 3 arguments with undefs, if necessary
-      while(scalar(@$info) < 3) { push(@$info, undef) }
+      while(scalar(@$dbi_info) < 3) { push(@$dbi_info, undef) }
 
       # Complain if 4th argument is defined and is not a HASH
-      if(defined $info->[3] && ref $info->[3] ne 'HASH') {
+      if(defined $dbi_info->[3] && ref $dbi_info->[3] ne 'HASH') {
           warn "4th argument of DBI connect info is defined "
                . " but is not a hashref!";
       }
 
       # Set AutoCommit to 1 if not specified manually
       else {
-          $info->[3] ||= {};
-          if(!defined $info->[3]->{AutoCommit}) {
-              $info->[3]->{AutoCommit} = 1;
+          $dbi_info->[3] ||= {};
+          if(!defined $dbi_info->[3]->{AutoCommit}) {
+              $dbi_info->[3]->{AutoCommit} = 1;
           }
       }
   }
 
-  $self->_connect_info($info);
+  $self->_connect_info;
 }
 
 =head2 on_connect_do
@@ -725,7 +728,7 @@ sub sql_maker {
 
 sub _populate_dbh {
   my ($self) = @_;
-  my @info = @{$self->_connect_info || []};
+  my @info = @{$self->_dbi_connect_info || []};
   $self->_dbh($self->_connect(@info));
 
   # Always set the transaction depth on connect, since