From: Peter Rabbitson Date: Thu, 30 Oct 2008 11:20:09 +0000 (+0000) Subject: Massive cleanup of Storage::DBI::connect_info, based on patches from Oleg Kostyk X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=92fe218153fcfdb968bfa929ed7f31389738335f;p=dbsrgits%2FDBIx-Class-Historic.git Massive cleanup of Storage::DBI::connect_info, based on patches from Oleg Kostyk --- diff --git a/lib/DBIx/Class/Storage/DBI.pm b/lib/DBIx/Class/Storage/DBI.pm index 35c31a3..e6586c2 100644 --- a/lib/DBIx/Class/Storage/DBI.pm +++ b/lib/DBIx/Class/Storage/DBI.pm @@ -14,11 +14,18 @@ use Scalar::Util qw/blessed weaken/; __PACKAGE__->mk_group_accessors('simple' => qw/_connect_info _dbi_connect_info _dbh _sql_maker _sql_maker_opts - _conn_pid _conn_tid disable_sth_caching on_connect_do - on_disconnect_do transaction_depth unsafe _dbh_autocommit - auto_savepoint savepoints/ + _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 +/; +__PACKAGE__->mk_group_accessors('simple' => @storage_options); + + +# default cursor class, overridable in connect_info attributes __PACKAGE__->cursor_class('DBIx::Class::Storage::DBI::Cursor'); __PACKAGE__->mk_group_accessors('inherited' => qw/sql_maker_class/); @@ -339,25 +346,76 @@ sub new { =head2 connect_info -The arguments of C are always a single array reference. +This method is normally called by L, which +encapsulates its argument list in an arrayref before passing them here. + +The argument list may contain: + +=over + +=item * + +The same 4-element argument set one would normally pass to L, +optionally followed by L +recognized by DBIx::Class: + + $connect_info_args = [ $dsn, $user, $pass, \%dbi_attributes, \%extra_attributes ]; + +=item * -This is normally accessed via L, which -encapsulates its argument list in an arrayref before calling -C here. +A lone code reference which returns a connected L +optinally followed by L +recognized by DBIx::Class: -The arrayref can either contain the same set of arguments one would -normally pass to L, or a lone code reference which returns -a connected database handle. Please note that the L docs + $connect_info_args = [ sub { DBI->connect (...) }, \%extra_attributes ]; + +=item * + +A lone hashref with all the attributes and the dsn/user/pass mixed together: + + $connect_info_args = [{ + dsn => $dsn, + user => $user, + pass => $pass, + %dbi_attributes, + %extra_attributes, + }]; + +This is particularly useful for L based applications, allowing the +following config: + + + schema_class App::DB + + dsn dbi:mysql:database=test + user testuser + password TestPass + AutoCommit 1 + + + +=back + +Please note that the L docs recommend that you always explicitly set C to either C<0> or C<1>. L further recommends that it be set to C<1>, and that you perform transactions via our L method. L will set it to C<1> if you do not do explicitly -set it to zero. This is the default for most DBDs. See below for more -details. +set it to zero. This is the default for most DBDs. See +L for details. + +=head3 DBIx::Class specific connection attributes + +In addition to the standard L +L attributes, DBIx::Class recognizes +the following connection options. These options can be mixed in with your other +L connection attributes, or placed in a seperate hashref +(C<\%extra_attributes>) as shown above. + +Every time C is invoked, any previous settings for +these options will be cleared before setting the new ones, regardless of +whether any options are specified in the new C. -In either case, if the final argument in your connect_info happens -to be a hashref, C will look there for several -connection-specific options: =over 4 @@ -441,30 +499,7 @@ transaction without having to abort all outer transactions. =back -These options can be mixed in with your other L connection attributes, -or placed in a seperate hashref after all other normal L connection -arguments. - -Every time C is invoked, any previous settings for -these options will be cleared before setting the new ones, regardless of -whether any options are specified in the new C. - -Another Important Note: - -DBIC can do some wonderful magic with handling exceptions, -disconnections, and transactions when you use C<< AutoCommit => 1 >> -combined with C for transaction support. - -If you set C<< AutoCommit => 0 >> in your connect info, then you are always -in an assumed transaction between commits, and you're telling us you'd -like to manage that manually. A lot of DBIC's magic protections -go away. We can't protect you from exceptions due to database -disconnects because we don't know anything about how to restart your -transactions. You're on your own for handling all sorts of exceptional -cases if you choose the C<< AutoCommit => 0 >> path, just as you would -be with raw DBI. - -Examples: +Some real-life examples of arguments to L and L # Simple SQLite connection ->connect_info([ 'dbi:SQLite:./foo.db' ]); @@ -493,7 +528,20 @@ Examples: ] ); - # Subref + DBIC-specific connection options + # Same, but with hashref as argument + # See C for explanation + ->connect_info( + [{ + dsn => 'dbi:Pg:dbname=foo', + user => 'postgres', + password => 'my_pg_password', + AutoCommit => 1, + quote_char => q{"}, + name_sep => q{.}, + }] + ); + + # Subref + DBIx::Class-specific connection options ->connect_info( [ sub { DBI->connect(...) }, @@ -506,6 +554,8 @@ Examples: ] ); + + =cut sub connect_info { @@ -513,39 +563,53 @@ sub connect_info { return $self->_connect_info if !$info_arg; + my @args = @$info_arg; # take a shallow copy for further mutilation + $self->_connect_info([@args]); # copy for _connect_info + + + # combine/pre-parse arguments depending on invocation style + + my %attrs; + if (ref $args[0] eq 'CODE') { # coderef with optional \%extra_attributes + %attrs = %{ $args[1] || {} }; + @args = $args[0]; + } + elsif (ref $args[0] eq 'HASH') { # single hashref (i.e. Catalyst config) + %attrs = %{$args[0]}; + @args = (); + for (qw/password user dsn/) { + unshift @args, delete $attrs{$_}; + } + } + else { # otherwise assume dsn/user/pass + \%attrs + \%extra_attrs + %attrs = ( + % { $args[3] || {} }, + % { $args[4] || {} }, + ); + @args = @args[0,1,2]; + } + # Kill sql_maker/_sql_maker_opts, so we get a fresh one with only # the new set of options $self->_sql_maker(undef); $self->_sql_maker_opts({}); - $self->_connect_info([@$info_arg]); # copy for _connect_info - - my $dbi_info = [@$info_arg]; # copy for _dbi_connect_info - my $last_info = $dbi_info->[-1]; - if(ref $last_info eq 'HASH') { - $last_info = { %$last_info }; # so delete is non-destructive - my @storage_option = qw( - on_connect_do on_disconnect_do disable_sth_caching unsafe cursor_class - auto_savepoint - ); - for my $storage_opt (@storage_option) { - if(my $value = delete $last_info->{$storage_opt}) { + if(keys %attrs) { + for my $storage_opt (@storage_options, 'cursor_class') { # @storage_options is declared at the top of the module + if(my $value = delete $attrs{$storage_opt}) { $self->$storage_opt($value); } } for my $sql_maker_opt (qw/limit_dialect quote_char name_sep/) { - if(my $opt_val = delete $last_info->{$sql_maker_opt}) { + if(my $opt_val = delete $attrs{$sql_maker_opt}) { $self->_sql_maker_opts->{$sql_maker_opt} = $opt_val; } } - # re-insert modified hashref - $dbi_info->[-1] = $last_info; - - # Get rid of any trailing empty hashref - pop(@$dbi_info) if !keys %$last_info; } - $self->_dbi_connect_info($dbi_info); + %attrs = () if (ref $args[0] eq 'CODE'); # _connect() never looks past $args[0] in this case + + $self->_dbi_connect_info([@args, keys %attrs ? \%attrs : ()]); $self->_connect_info; } @@ -553,6 +617,7 @@ sub connect_info { This method is deprecated in favor of setting via L. + =head2 dbh_do Arguments: ($subref | $method_name), @extra_coderef_args? @@ -1773,6 +1838,24 @@ sub DESTROY { 1; +=head1 USAGE NOTES + +=head2 DBIx::Class and AutoCommit + +DBIx::Class can do some wonderful magic with handling exceptions, +disconnections, and transactions when you use C<< AutoCommit => 1 >> +combined with C for transaction support. + +If you set C<< AutoCommit => 0 >> in your connect info, then you are always +in an assumed transaction between commits, and you're telling us you'd +like to manage that manually. A lot of the magic protections offered by +this module will go away. We can't protect you from exceptions due to database +disconnects because we don't know anything about how to restart your +transactions. You're on your own for handling all sorts of exceptional +cases if you choose the C<< AutoCommit => 0 >> path, just as you would +be with raw DBI. + + =head1 SQL METHODS The module defines a set of methods within the DBIC::SQL::Abstract diff --git a/t/92storage.t b/t/92storage.t index 56fb7b4..94bdfd3 100644 --- a/t/92storage.t +++ b/t/92storage.t @@ -4,6 +4,7 @@ use warnings; use Test::More; use lib qw(t/lib); use DBICTest; +use Data::Dumper; { package DBICTest::ExplodingStorage::Sth; @@ -32,7 +33,7 @@ use DBICTest; } } -plan tests => 6; +plan tests => 17; my $schema = DBICTest->init_schema( sqlite_use_file => 1 ); @@ -64,10 +65,106 @@ is($@, "", "Exploding \$sth->execute was caught"); is(1, $schema->resultset('Artist')->search({name => "Exploding Sheep" })->count, "And the STH was retired"); -my $info = { on_connect_do => [] }; -$storage->connect_info(['foo','bar','baz',$info]); +# testing various invocations of connect_info ([ ... ]) + +my $coderef = sub { 42 }; +my $invocations = { + 'connect_info ([ $d, $u, $p, \%attr, \%extra_attr])' => { + args => [ + 'foo', + 'bar', + undef, + { + on_connect_do => [qw/a b c/], + PrintError => 0, + }, + { + AutoCommit => 1, + on_disconnect_do => [qw/d e f/], + }, + { + unsafe => 1, + auto_savepoint => 1, + }, + ], + dbi_connect_info => [ + 'foo', + 'bar', + undef, + { + PrintError => 0, + AutoCommit => 1, + }, + ], + }, + + 'connect_info ([ \%code, \%extra_attr ])' => { + args => [ + $coderef, + { + on_connect_do => [qw/a b c/], + PrintError => 0, + AutoCommit => 1, + on_disconnect_do => [qw/d e f/], + }, + { + unsafe => 1, + auto_savepoint => 1, + }, + ], + dbi_connect_info => [ + $coderef, + ], + }, + + 'connect_info ([ \%attr ])' => { + args => [ + { + on_connect_do => [qw/a b c/], + PrintError => 0, + AutoCommit => 1, + on_disconnect_do => [qw/d e f/], + user => 'bar', + dsn => 'foo', + }, + { + unsafe => 1, + auto_savepoint => 1, + }, + ], + dbi_connect_info => [ + 'foo', + 'bar', + undef, + { + PrintError => 0, + AutoCommit => 1, + }, + ], + }, +}; + +for my $type (keys %$invocations) { + + # we can not use a cloner portably because of the coderef + # so compare dumps instead + local $Data::Dumper::Sortkeys = 1; + my $arg_dump = Dumper ($invocations->{$type}{args}); + + $storage->connect_info ($invocations->{$type}{args}); -ok(exists($info->{on_connect_do}), q{Didn't kill key passed to storage}); + is ($arg_dump, Dumper ($invocations->{$type}{args}), "$type didn't modify passed arguments"); + + + is_deeply ($storage->_dbi_connect_info, $invocations->{$type}{dbi_connect_info}, "$type produced correct _dbi_connect_info"); + ok ( (not $storage->auto_savepoint and not $storage->unsafe), "$type correctly ignored extra hashref"); + + is_deeply ( + [$storage->on_connect_do, $storage->on_disconnect_do ], + [ [qw/a b c/], [qw/d e f/] ], + "$type correctly parsed DBIC specific on_[dis]connect_do", + ); +} 1;