Merge 'trunk' into 'mssql_top_fixes'
Peter Rabbitson [Tue, 30 Jun 2009 15:09:03 +0000 (17:09 +0200)]
1  2 
lib/DBIx/Class/Storage/DBI.pm

@@@ -3,7 -3,7 +3,7 @@@ package DBIx::Class::Storage::DBI
  
  use base 'DBIx::Class::Storage';
  
 -use strict;    
 +use strict;
  use warnings;
  use Carp::Clan qw/^DBIx::Class/;
  use DBI;
@@@ -13,14 -13,15 +13,15 @@@ use Scalar::Util()
  use List::Util();
  
  __PACKAGE__->mk_group_accessors('simple' =>
-     qw/_connect_info _dbi_connect_info _dbh _sql_maker _sql_maker_opts
-        _conn_pid _conn_tid transaction_depth _dbh_autocommit savepoints/
+   qw/_connect_info _dbi_connect_info _dbh _sql_maker _sql_maker_opts
+      _conn_pid _conn_tid transaction_depth _dbh_autocommit savepoints/
  );
  
  # the values for these accessors are picked out (and deleted) from
  # the attribute hashref passed to connect_info
  my @storage_options = qw/
-   on_connect_do on_disconnect_do disable_sth_caching unsafe auto_savepoint
+   on_connect_call on_disconnect_call on_connect_do on_disconnect_do
+   disable_sth_caching unsafe auto_savepoint
  /;
  __PACKAGE__->mk_group_accessors('simple' => @storage_options);
  
@@@ -89,8 -90,8 +90,8 @@@ recognized by DBIx::Class
  
  =item *
  
 -A single code reference which returns a connected 
 -L<DBI database handle|DBI/connect> optionally followed by 
 +A single code reference which returns a connected
 +L<DBI database handle|DBI/connect> optionally followed by
  L<extra attributes|/DBIx::Class specific connection attributes> recognized
  by DBIx::Class:
  
@@@ -109,7 -110,7 +110,7 @@@ mixed together
      %extra_attributes,
    }];
  
 -This is particularly useful for L<Catalyst> based applications, allowing the 
 +This is particularly useful for L<Catalyst> based applications, allowing the
  following config (L<Config::General> style):
  
    <Model::DB>
@@@ -128,7 -129,7 +129,7 @@@ Please note that the L<DBI> docs recomm
  set C<AutoCommit> to either I<0> or I<1>.  L<DBIx::Class> further
  recommends that it be set to I<1>, and that you perform transactions
  via our L<DBIx::Class::Schema/txn_do> method.  L<DBIx::Class> will set it
 -to I<1> if you do not do explicitly set it to zero.  This is the default 
 +to I<1> if you do not do explicitly set it to zero.  This is the default
  for most DBDs. See L</DBIx::Class and AutoCommit> for details.
  
  =head3 DBIx::Class specific connection attributes
@@@ -177,12 -178,97 +178,97 @@@ immediately before disconnecting from t
  Note, this only runs if you explicitly call L</disconnect> on the
  storage object.
  
+ =item on_connect_call
+ A more generalized form of L</on_connect_do> that calls the specified
+ C<connect_call_METHOD> methods in your storage driver.
+   on_connect_do => 'select 1'
+ is equivalent to:
+   on_connect_call => [ [ do_sql => 'select 1' ] ]
+ Its values may contain:
+ =over
+ =item a scalar
+ Will call the C<connect_call_METHOD> method.
+ =item a code reference
+ Will execute C<< $code->($storage) >>
+ =item an array reference
+ Each value can be a method name or code reference.
+ =item an array of arrays
+ For each array, the first item is taken to be the C<connect_call_> method name
+ or code reference, and the rest are parameters to it.
+ =back
+ Some predefined storage methods you may use:
+ =over
+ =item do_sql
+ Executes a SQL string or a code reference that returns a SQL string. This is
+ what L</on_connect_do> and L</on_disconnect_do> use.
+ It can take:
+ =over
+ =item a scalar
+ Will execute the scalar as SQL.
+ =item an arrayref
+ Taken to be arguments to L<DBI/do>, the SQL string optionally followed by the
+ attributes hashref and bind values.
+ =item a code reference
+ Will execute C<< $code->($storage) >> and execute the return array refs as
+ above.
+ =back
+ =item datetime_setup
+ Execute any statements necessary to initialize the database session to return
+ and accept datetime/timestamp values used with
+ L<DBIx::Class::InflateColumn::DateTime>.
+ Only necessary for some databases, see your specific storage driver for
+ implementation details.
+ =back
+ =item on_disconnect_call
+ Takes arguments in the same form as L</on_connect_call> and executes them
+ immediately before disconnecting from the database.
+ Calls the C<disconnect_call_METHOD> methods as opposed to the
+ C<connect_call_METHOD> methods called by L</on_connect_call>.
+ Note, this only runs if you explicitly call L</disconnect> on the
+ storage object.
  =item disable_sth_caching
  
  If set to a true value, this option will disable the caching of
  statement handles via L<DBI/prepare_cached>.
  
 -=item limit_dialect 
 +=item limit_dialect
  
  Sets the limit dialect. This is useful for JDBC-bridge among others
  where the remote SQL-dialect cannot be determined by the name of the
@@@ -190,7 -276,7 +276,7 @@@ driver alone. See also L<SQL::Abstract:
  
  =item quote_char
  
 -Specifies what characters to use to quote table and column names. If 
 +Specifies what characters to use to quote table and column names. If
  you use this you will want to specify L</name_sep> as well.
  
  C<quote_char> expects either a single character, in which case is it
@@@ -202,8 -288,8 +288,8 @@@ SQL Server you should use C<< quote_cha
  
  =item name_sep
  
 -This only needs to be used in conjunction with C<quote_char>, and is used to 
 -specify the charecter that seperates elements (schemas, tables, columns) from 
 +This only needs to be used in conjunction with C<quote_char>, and is used to
 +specify the charecter that seperates elements (schemas, tables, columns) from
  each other. In most cases this is simply a C<.>.
  
  The consequences of not supplying this value is that L<SQL::Abstract>
@@@ -359,6 -445,34 +445,34 @@@ sub connect_info 
  
  This method is deprecated in favour of setting via L</connect_info>.
  
+ =cut
+ =head2 on_disconnect_do
+ This method is deprecated in favour of setting via L</connect_info>.
+ =cut
+ sub _parse_connect_do {
+   my ($self, $type) = @_;
+   my $val = $self->$type;
+   return () if not defined $val;
+   my @res;
+   if (not ref($val)) {
+     push @res, [ 'do_sql', $val ];
+   } elsif (ref($val) eq 'CODE') {
+     push @res, $val;
+   } elsif (ref($val) eq 'ARRAY') {
+     push @res, map { [ 'do_sql', $_ ] } @$val;
+   } else {
+     $self->throw_exception("Invalid type for $type: ".ref($val));
+   }
+   return \@res;
+ }
  
  =head2 dbh_do
  
@@@ -506,8 -620,12 +620,12 @@@ sub disconnect 
    my ($self) = @_;
  
    if( $self->connected ) {
-     my $connection_do = $self->on_disconnect_do;
-     $self->_do_connection_actions($connection_do) if ref($connection_do);
+     my @actions;
+     push @actions, ( $self->on_disconnect_call || () );
+     push @actions, $self->_parse_connect_do ('on_disconnect_do');
+     $self->_do_connection_actions(disconnect_call_ => $_) for @actions;
  
      $self->_dbh->rollback unless $self->_dbh_autocommit;
      $self->_dbh->disconnect;
@@@ -624,8 -742,12 +742,12 @@@ sub _populate_dbh 
    #  there is no transaction in progress by definition
    $self->{transaction_depth} = $self->_dbh_autocommit ? 0 : 1;
  
-   my $connection_do = $self->on_connect_do;
-   $self->_do_connection_actions($connection_do) if $connection_do;
+   my @actions;
+   push @actions, ( $self->on_connect_call || () );
+   push @actions, $self->_parse_connect_do ('on_connect_do');
+   $self->_do_connection_actions(connect_call_ => $_) for @actions;
  }
  
  sub _determine_driver {
        ($driver) = $self->_dbi_connect_info->[0] =~ /dbi:([^:]+):/i;
      }
  
 -    if ($self->load_optional_class("DBIx::Class::Storage::DBI::${driver}")) {
 -      bless $self, "DBIx::Class::Storage::DBI::${driver}";
 +    my $storage_class = "DBIx::Class::Storage::DBI::${driver}";
 +    if ($self->load_optional_class($storage_class)) {
 +      mro::set_mro($storage_class, 'c3');
 +      bless $self, $storage_class;
        $self->_rebless();
      }
    }
  }
  
  sub _do_connection_actions {
-   my $self = shift;
-   my $connection_do = shift;
-   if (!ref $connection_do) {
-     $self->_do_query($connection_do);
-   }
-   elsif (ref $connection_do eq 'ARRAY') {
-     $self->_do_query($_) foreach @$connection_do;
-   }
-   elsif (ref $connection_do eq 'CODE') {
-     $connection_do->($self);
-   }
-   else {
-     $self->throw_exception (sprintf ("Don't know how to process conection actions of type '%s'", ref $connection_do) );
+   my $self          = shift;
+   my $method_prefix = shift;
+   my $call          = shift;
+   if (not ref($call)) {
+     my $method = $method_prefix . $call;
+     $self->$method(@_);
+   } elsif (ref($call) eq 'CODE') {
+     $self->$call(@_);
+   } elsif (ref($call) eq 'ARRAY') {
+     if (ref($call->[0]) ne 'ARRAY') {
+       $self->_do_connection_actions($method_prefix, $_) for @$call;
+     } else {
+       $self->_do_connection_actions($method_prefix, @$_) for @$call;
+     }
+   } else {
+     $self->throw_exception (sprintf ("Don't know how to process conection actions of type '%s'", ref($call)) );
    }
  
    return $self;
  }
  
+ sub connect_call_do_sql {
+   my $self = shift;
+   $self->_do_query(@_);
+ }
+ sub disconnect_call_do_sql {
+   my $self = shift;
+   $self->_do_query(@_);
+ }
+ # override in db-specific backend when necessary
+ sub connect_call_datetime_setup { 1 }
  sub _do_query {
    my ($self, $action) = @_;
  
@@@ -755,11 -891,11 +893,11 @@@ sub svp_begin 
  
    $self->throw_exception ("Your Storage implementation doesn't support savepoints")
      unless $self->can('_svp_begin');
 -  
 +
    push @{ $self->{savepoints} }, $name;
  
    $self->debugobj->svp_begin($name) if $self->debug;
 -  
 +
    return $self->_svp_begin($name);
  }
  
@@@ -819,7 -955,7 +957,7 @@@ sub svp_rollback 
    }
  
    $self->debugobj->svp_rollback($name) if $self->debug;
 -  
 +
    return $self->_svp_rollback($name);
  }
  
@@@ -957,7 -1093,7 +1095,7 @@@ sub _dbh_execute 
  
    my $sth = $self->sth($sql,$op);
  
 -  my $placeholder_index = 1; 
 +  my $placeholder_index = 1;
  
    foreach my $bound (@$bind) {
      my $attributes = {};
@@@ -1016,7 -1152,7 +1154,7 @@@ sub insert 
  }
  
  ## Still not quite perfect, and EXPERIMENTAL
 -## Currently it is assumed that all values passed will be "normal", i.e. not 
 +## Currently it is assumed that all values passed will be "normal", i.e. not
  ## scalar refs, or at least, all the same type as the first set, the statement is
  ## only prepped once.
  sub insert_bulk {
    my $table = $source->from;
    @colvalues{@$cols} = (0..$#$cols);
    my ($sql, @bind) = $self->sql_maker->insert($table, \%colvalues);
 -  
 +
    $self->_query_start( $sql, @bind );
    my $sth = $self->sth($sql);
  
    my $bind_attributes = $self->source_bind_attributes($source);
  
    ## Bind the values and execute
 -  my $placeholder_index = 1; 
 +  my $placeholder_index = 1;
  
    foreach my $bound (@bind) {
  
@@@ -1086,7 -1222,7 +1224,7 @@@ sub update 
    my $self = shift @_;
    my $source = shift @_;
    my $bind_attributes = $self->source_bind_attributes($source);
 -  
 +
    return $self->_execute('update' => [], $source, $bind_attributes, @_);
  }
  
  sub delete {
    my $self = shift @_;
    my $source = shift @_;
 -  
 +
    my $bind_attrs = $self->source_bind_attributes($source);
 -  
 +
    return $self->_execute('delete' => [], $source, $bind_attrs, @_);
  }
  
@@@ -1195,10 -1331,10 +1333,10 @@@ sub _select 
    my $self = shift;
  
    # localization is neccessary as
 -  # 1) there is no infrastructure to pass this around (easy to do, but will wait)
 +  # 1) there is no infrastructure to pass this around before SQLA2
    # 2) _select_args sets it and _prep_for_execute consumes it
    my $sql_maker = $self->sql_maker;
 -  local $sql_maker->{for};
 +  local $sql_maker->{_dbic_rs_attrs};
  
    return $self->_execute($self->_select_args(@_));
  }
@@@ -1207,10 -1343,10 +1345,10 @@@ sub _select_args_to_query 
    my $self = shift;
  
    # localization is neccessary as
 -  # 1) there is no infrastructure to pass this around (easy to do, but will wait)
 +  # 1) there is no infrastructure to pass this around before SQLA2
    # 2) _select_args sets it and _prep_for_execute consumes it
    my $sql_maker = $self->sql_maker;
 -  local $sql_maker->{for};
 +  local $sql_maker->{_dbic_rs_attrs};
  
    # my ($op, $bind, $ident, $bind_attrs, $select, $cond, $order, $rows, $offset)
    #  = $self->_select_args($ident, $select, $cond, $attrs);
@@@ -1231,14 -1367,7 +1369,14 @@@ sub _select_args 
    my ($self, $ident, $select, $where, $attrs) = @_;
  
    my $sql_maker = $self->sql_maker;
 -  my $alias2source = $self->_resolve_ident_sources ($ident);
 +  $sql_maker->{_dbic_rs_attrs} = {
 +    %$attrs,
 +    select => $select,
 +    from => $ident,
 +    where => $where,
 +  };
 +
 +  my ($alias2source, $root_alias) = $self->_resolve_ident_sources ($ident);
  
    # calculate bind_attrs before possible $ident mangling
    my $bind_attrs = {};
        $bind_attrs->{$fqcn} = $bindtypes->{$col} if $bindtypes->{$col};
  
        # so that unqualified searches can be bound too
 -      $bind_attrs->{$col} = $bind_attrs->{$fqcn} if $alias eq 'me';
 +      $bind_attrs->{$col} = $bind_attrs->{$fqcn} if $alias eq $root_alias;
      }
    }
  
    };
  
  
 -  $sql_maker->{for} = delete $attrs->{for};
 -
    return ('select', $attrs->{bind}, $ident, $bind_attrs, $select, $where, $order, @limit);
  }
  
  sub _adjust_select_args_for_limited_prefetch {
    my ($self, $from, $select, $where, $attrs) = @_;
  
 -  if ($attrs->{group_by} and @{$attrs->{group_by}}) {
 -    $self->throw_exception ('Prefetch with limit (rows/offset) is not supported on resultsets with a group_by attribute');
 +  if ($attrs->{group_by} && @{$attrs->{group_by}}) {
 +    $self->throw_exception ('has_many prefetch with limit (rows/offset) is not supported on grouped resultsets');
    }
  
 -  $self->throw_exception ('Prefetch with limit (rows/offset) is not supported on resultsets with a custom from attribute')
 +  $self->throw_exception ('has_many prefetch with limit (rows/offset) is not supported on resultsets with a custom from attribute')
      if (ref $from ne 'ARRAY');
  
  
    # separate attributes
    my $sub_attrs = { %$attrs };
    delete $attrs->{$_} for qw/where bind rows offset/;
 -  delete $sub_attrs->{$_} for qw/for collapse select order_by/;
 +  delete $sub_attrs->{$_} for qw/for collapse select as order_by/;
  
    my $alias = $attrs->{alias};
  
    );
  
    # put it in the new {from}
 -  unshift @outer_from, { $alias => $subq };
 +  unshift @outer_from, {
 +    -alias => $alias,
 +    -source_handle => $select_root->{-source_handle},
 +    $alias => $subq,
 +  };
  
    # This is totally horrific - the $where ends up in both the inner and outer query
 -  # Unfortunately not much can be done until SQLA2 introspection arrives
 +  # Unfortunately not much can be done until SQLA2 introspection arrives, and even
 +  # then if where conditions apply to the *right* side of the prefetch, you may have
 +  # to both filter the inner select (e.g. to apply a limit) and then have to re-filter
 +  # the outer select to exclude joins you didin't want in the first place
    #
    # OTOH it can be seen as a plus: <ash> (notes that this query would make a DBA cry ;)
    return (\@outer_from, $select, $where, $attrs);
@@@ -1452,14 -1576,12 +1590,14 @@@ sub _resolve_ident_sources 
    my ($self, $ident) = @_;
  
    my $alias2source = {};
 +  my $root_alias;
  
    # the reason this is so contrived is that $ident may be a {from}
    # structure, specifying multiple tables to join
    if ( Scalar::Util::blessed($ident) && $ident->isa("DBIx::Class::ResultSource") ) {
      # this is compat mode for insert/update/delete which do not deal with aliases
      $alias2source->{me} = $ident;
 +    $root_alias = 'me';
    }
    elsif (ref $ident eq 'ARRAY') {
  
        my $tabinfo;
        if (ref $_ eq 'HASH') {
          $tabinfo = $_;
 +        $root_alias = $tabinfo->{-alias};
        }
        if (ref $_ eq 'ARRAY' and ref $_->[0] eq 'HASH') {
          $tabinfo = $_->[0];
      }
    }
  
 -  return $alias2source;
 +  return ($alias2source, $root_alias);
 +}
 +
 +# Takes $ident, \@column_names
 +#
 +# returns { $column_name => \%column_info, ... }
 +# also note: this adds -result_source => $rsrc to the column info
 +#
 +# usage:
 +#   my $col_sources = $self->_resolve_column_info($ident, [map $_->[0], @{$bind}]);
 +sub _resolve_column_info {
 +  my ($self, $ident, $colnames) = @_;
 +  my ($alias2src, $root_alias) = $self->_resolve_ident_sources($ident);
 +
 +  my $sep = $self->_sql_maker_opts->{name_sep} || '.';
 +  $sep = "\Q$sep\E";
 +
 +  my (%return, %converted);
 +  foreach my $col (@$colnames) {
 +    my ($alias, $colname) = $col =~ m/^ (?: ([^$sep]+) $sep)? (.+) $/x;
 +
 +    # deal with unqualified cols - we assume the main alias for all
 +    # unqualified ones, ugly but can't think of anything better right now
 +    $alias ||= $root_alias;
 +
 +    my $rsrc = $alias2src->{$alias};
 +    $return{$col} = $rsrc && { %{$rsrc->column_info($colname)}, -result_source => $rsrc };
 +  }
 +  return \%return;
  }
  
  # Returns a counting SELECT for a simple count
@@@ -1788,13 -1881,13 +1926,13 @@@ By default, C<\%sqlt_args> will hav
  
   { add_drop_table => 1, ignore_constraint_names => 1, ignore_index_names => 1 }
  
 -merged with the hash passed in. To disable any of those features, pass in a 
 +merged with the hash passed in. To disable any of those features, pass in a
  hashref like the following
  
   { ignore_constraint_names => 0, # ... other options }
  
  
 -Note that this feature is currently EXPERIMENTAL and may not work correctly 
 +Note that this feature is currently EXPERIMENTAL and may not work correctly
  across all databases, or fully handle complex relationships.
  
  WARNING: Please check all SQL files created, before applying them.
@@@ -1815,7 -1908,7 +1953,7 @@@ sub create_ddl_dir 
    $version ||= $schema_version;
  
    $sqltargs = {
 -    add_drop_table => 1, 
 +    add_drop_table => 1,
      ignore_constraint_names => 1,
      ignore_index_names => 1,
      %{$sqltargs || {}}
      }
      print $file $output;
      close($file);
 -  
 +
      next unless ($preversion);
  
      require SQL::Translator::Diff;
        carp("Overwriting existing diff file - $difffile");
        unlink($difffile);
      }
 -    
 +
      my $source_schema;
      {
        my $t = SQL::Translator->new($sqltargs);
          unless ( $source_schema->name );
      }
  
 -    # The "new" style of producers have sane normalization and can support 
 +    # The "new" style of producers have sane normalization and can support
      # diffing a SQL file against a DBIC->SQLT schema. Old style ones don't
      # And we have to diff parsed SQL against parsed SQL.
      my $dest_schema = $sqlt_schema;
        $dest_schema->name( $filename )
          unless $dest_schema->name;
      }
 -    
 +
      my $diff = SQL::Translator::Diff::schema_diff($source_schema, $db,
                                                    $dest_schema,   $db,
                                                    $sqltargs
                                                   );
 -    if(!open $file, ">$difffile") { 
 +    if(!open $file, ">$difffile") {
        $self->throw_exception("Can't write to $difffile ($!)");
        next;
      }
@@@ -1960,7 -2053,7 +2098,7 @@@ sub deployment_statements 
    if(-f $filename)
    {
        my $file;
 -      open($file, "<$filename") 
 +      open($file, "<$filename")
          or $self->throw_exception("Can't open $filename ($!)");
        my @rows = <$file>;
        close($file);
    eval qq{use SQL::Translator::Producer::${type}};
    $self->throw_exception($@) if $@;
  
 -  # sources needs to be a parser arg, but for simplicty allow at top level 
 +  # sources needs to be a parser arg, but for simplicty allow at top level
    # coming in
    $sqltargs->{parser_args}{sources} = delete $sqltargs->{sources}
        if exists $sqltargs->{sources};
@@@ -2080,7 -2173,7 +2218,7 @@@ returned by databases that don't suppor
  
  sub is_replicating {
      return;
 -    
 +
  }
  
  =head2 lag_behind_master