X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FStorage%2FDBI.pm;h=938a2f013439eb714d181a9b35b5a0bdba19e82a;hb=f47aebfcd1b2b620778662c49d0ee1f75440a07c;hp=707b3053fbb67649dbb6fb3f2ac076e425521631;hpb=5960a19503062a9725068f9e8ed067b9ab6e8293;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/Storage/DBI.pm b/lib/DBIx/Class/Storage/DBI.pm index 707b305..938a2f0 100644 --- a/lib/DBIx/Class/Storage/DBI.pm +++ b/lib/DBIx/Class/Storage/DBI.pm @@ -13,14 +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); @@ -177,6 +178,91 @@ immediately before disconnecting from the database. Note, this only runs if you explicitly call L on the storage object. +=item on_connect_call + +A more generalized form of L that calls the specified +C 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 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 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 and L use. + +It can take: + +=over + +=item a scalar + +Will execute the scalar as SQL. + +=item an arrayref + +Taken to be arguments to L, 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. + +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 and executes them +immediately before disconnecting from the database. + +Calls the C methods as opposed to the +C methods called by L. + +Note, this only runs if you explicitly call L on the +storage object. + =item disable_sth_caching If set to a true value, this option will disable the caching of @@ -359,6 +445,34 @@ sub connect_info { This method is deprecated in favour of setting via L. +=cut + +=head2 on_disconnect_do + +This method is deprecated in favour of setting via L. + +=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 @@ 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; @@ -594,7 +712,7 @@ sub dbh { sub _sql_maker_args { my ($self) = @_; - + return ( bindtype=>'columns', array_datatypes => 1, limit_dialect => $self->dbh, %{$self->_sql_maker_opts} ); } @@ -624,8 +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 { @@ -650,25 +772,41 @@ sub _determine_driver { } 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) = @_; @@ -927,6 +1065,22 @@ sub _fix_bind_params { } @bind; } +sub _flatten_bind_params { + my ($self, @bind) = @_; + + ### Turn @bind from something like this: + ### ( [ "artist", 1 ], [ "cdid", 1, 3 ] ) + ### to this: + ### ( 1, 1, 3 ) + return + map { + if ( defined( $_ && $_->[1] ) ) { + @{$_}[ 1 .. $#$_ ]; + } + else { undef; } + } @bind; +} + sub _query_start { my ( $self, $sql, @bind ) = @_; @@ -1296,6 +1450,7 @@ sub _adjust_select_args_for_limited_prefetch { $self->throw_exception ('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/; @@ -1314,13 +1469,31 @@ sub _adjust_select_args_for_limited_prefetch { ]; } + # mangle {from} + $from = [ @$from ]; + my $select_root = shift @$from; + my @outer_from = @$from; - # mangle the head of the {from} - my $self_ident = shift @$from; - + my %inner_joins; my %join_info = map { $_->[0]{-alias} => $_->[0] } (@$from); - my (%inner_joins); + # in complex search_related chains $alias may *not* be 'me' + # so always include it in the inner join, and also shift away + # from the outer stack, so that the two datasets actually do + # meet + if ($select_root->{-alias} ne $alias) { + $inner_joins{$alias} = 1; + + while (@outer_from && $outer_from[0][0]{-alias} ne $alias) { + shift @outer_from; + } + if (! @outer_from) { + $self->throw_exception ("Unable to find '$alias' in the {from} stack, something is wrong"); + } + + shift @outer_from; # the new subquery will represent this alias, so get rid of it + } + # decide which parts of the join will remain on the inside # @@ -1379,22 +1552,21 @@ sub _adjust_select_args_for_limited_prefetch { } # construct the inner $from for the subquery - my $inner_from = [ $self_ident ]; - if (keys %inner_joins) { - for my $j (@$from) { - push @$inner_from, $j if $inner_joins{$j->[0]{-alias}}; - } + my $inner_from = [ $select_root ]; + for my $j (@$from) { + push @$inner_from, $j if $inner_joins{$j->[0]{-alias}}; + } - # if a multi-type join was needed in the subquery ("multi" is indicated by - # presence in {collapse}) - add a group_by to simulate the collapse in the subq - for my $alias (keys %inner_joins) { + # if a multi-type join was needed in the subquery ("multi" is indicated by + # presence in {collapse}) - add a group_by to simulate the collapse in the subq - # the dot comes from some weirdness in collapse - # remove after the rewrite - if ($attrs->{collapse}{".$alias"}) { - $sub_attrs->{group_by} = $sub_select; - last; - } + for my $alias (keys %inner_joins) { + + # the dot comes from some weirdness in collapse + # remove after the rewrite + if ($attrs->{collapse}{".$alias"}) { + $sub_attrs->{group_by} = $sub_select; + last; } } @@ -1406,14 +1578,14 @@ sub _adjust_select_args_for_limited_prefetch { $sub_attrs ); - # put it back in $from - unshift @$from, { $alias => $subq }; + # put it in the new {from} + unshift @outer_from, { $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 # # OTOH it can be seen as a plus: (notes that this query would make a DBA cry ;) - return ($from, $select, $where, $attrs); + return (\@outer_from, $select, $where, $attrs); } sub _resolve_ident_sources {