X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FStorage%2FDBI.pm;h=9be6d421daa485e602eb35f03b58761f3f00d91f;hb=0165dca7b467181498854473bad67ef39e0e0958;hp=71c14aa0cb2eff504a23976410928ac62629eec2;hpb=35ae601901c0fa43f5a5ec69d1834b84caf86323;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/Storage/DBI.pm b/lib/DBIx/Class/Storage/DBI.pm index 71c14aa..9be6d42 100644 --- a/lib/DBIx/Class/Storage/DBI.pm +++ b/lib/DBIx/Class/Storage/DBI.pm @@ -10,7 +10,13 @@ use SQL::Abstract::Limit; use DBIx::Class::Storage::DBI::Cursor; use DBIx::Class::Storage::Statistics; use IO::File; -use Carp::Clan qw/DBIx::Class/; + +__PACKAGE__->mk_group_accessors( + 'simple' => + qw/_connect_info _dbh _sql_maker _sql_maker_opts _conn_pid _conn_tid + cursor on_connect_do transaction_depth/ +); + BEGIN { package DBIC::SQL::Abstract; # Would merge upstream, but nate doesn't reply :( @@ -276,14 +282,6 @@ sub name_sep { } # End of BEGIN block -use base qw/DBIx::Class/; - -__PACKAGE__->load_components(qw/AccessorGroup/); - -__PACKAGE__->mk_group_accessors('simple' => - qw/_connect_info _dbh _sql_maker _sql_maker_opts _conn_pid _conn_tid - debug debugobj cursor on_connect_do transaction_depth/); - =head1 NAME DBIx::Class::Storage::DBI - DBI storage handler @@ -292,49 +290,22 @@ DBIx::Class::Storage::DBI - DBI storage handler =head1 DESCRIPTION -This class represents the connection to the database +This class represents the connection to an RDBMS via L. See +L for general information. This pod only +documents DBI-specific methods and behaviors. =head1 METHODS -=head2 new - =cut sub new { - my $new = {}; - bless $new, (ref $_[0] || $_[0]); + my $new = shift->next::method(@_); $new->cursor("DBIx::Class::Storage::DBI::Cursor"); $new->transaction_depth(0); - - $new->debugobj(new DBIx::Class::Storage::Statistics()); - - my $fh; - - my $debug_env = $ENV{DBIX_CLASS_STORAGE_DBI_DEBUG} - || $ENV{DBIC_TRACE}; - - if (defined($debug_env) && ($debug_env =~ /=(.+)$/)) { - $fh = IO::File->new($1, 'w') - or $new->throw_exception("Cannot open trace file $1"); - } else { - $fh = IO::File->new('>&STDERR'); - } - $new->debugfh($fh); - $new->debug(1) if $debug_env; $new->_sql_maker_opts({}); - return $new; -} - -=head2 throw_exception - -Throws an exception - croaks. - -=cut -sub throw_exception { - my ($self, $msg) = @_; - croak($msg); + $new; } =head2 connect_info @@ -395,6 +366,12 @@ 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. +Important note: DBIC expects the returned database handle provided by +a subref argument to have RaiseError set on it. If it doesn't, things +might not work very well, YMMV. If you don't use a subref, DBIC will +force this setting for you anyways. Setting HandleError to anything +other than simple exception object wrapper might cause problems too. + Examples: # Simple SQLite connection @@ -436,63 +413,166 @@ Examples: ] ); +=cut + +sub connect_info { + my ($self, $info_arg) = @_; + + return $self->_connect_info if !$info_arg; + + # 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({}); + + my $info = [ @$info_arg ]; # copy because we can alter it + my $last_info = $info->[-1]; + if(ref $last_info eq 'HASH') { + if(my $on_connect_do = delete $last_info->{on_connect_do}) { + $self->on_connect_do($on_connect_do); + } + for my $sql_maker_opt (qw/limit_dialect quote_char name_sep/) { + if(my $opt_val = delete $last_info->{$sql_maker_opt}) { + $self->_sql_maker_opts->{$sql_maker_opt} = $opt_val; + } + } + + # Get rid of any trailing empty hashref + pop(@$info) if !keys %$last_info; + } + + $self->_connect_info($info); +} + =head2 on_connect_do This method is deprecated in favor of setting via L. -=head2 debug +=head2 dbh_do + +Arguments: $subref, @extra_coderef_args? -Causes SQL trace information to be emitted on the C object. -(or C if C has not specifically been set). +Execute the given subref with the underlying database handle as its +first argument, using the new exception-based connection management. -This is the equivalent to setting L in your -shell environment. +Any additional arguments will be passed verbatim to the called subref +as arguments 2 and onwards. -=head2 debugfh +Example: -Set or retrieve the filehandle used for trace/debug output. This should be -an IO::Handle compatible ojbect (only the C method is used. Initially -set to be STDERR - although see information on the -L environment variable. + my @stuff = $schema->storage->dbh_do( + sub { + my $dbh = shift; + my $cols = join(q{, }, @_); + shift->selectrow_array("SELECT $cols FROM foo") + }, + @column_list + ); =cut -sub debugfh { - my $self = shift; +sub dbh_do { + my $self = shift; + my $coderef = shift; - if ($self->debugobj->can('debugfh')) { - return $self->debugobj->debugfh(@_); + return $coderef->($self->_dbh, @_) if $self->{_in_txn_do}; + + ref $coderef eq 'CODE' or $self->throw_exception + ('$coderef must be a CODE reference'); + + my @result; + my $want_array = wantarray; + + eval { + $self->_verify_pid if $self->_dbh; + $self->_populate_dbh if !$self->_dbh; + if($want_array) { + @result = $coderef->($self->_dbh, @_); + } + elsif(defined $want_array) { + $result[0] = $coderef->($self->_dbh, @_); } + else { + $coderef->($self->_dbh, @_); + } + }; + + my $exception = $@; + if(!$exception) { return $want_array ? @result : $result[0] } + + $self->throw_exception($exception) if $self->connected; + + # We were not connected - reconnect and retry, but let any + # exception fall right through this time + $self->_populate_dbh; + $coderef->($self->_dbh, @_); } -=head2 debugobj +# This is basically a blend of dbh_do above and DBIx::Class::Storage::txn_do. +# It also informs dbh_do to bypass itself while under the direction of txn_do, +# via $self->{_in_txn_do} (this saves some redundant eval and errorcheck, etc) +sub txn_do { + my $self = shift; + my $coderef = shift; -Sets or retrieves the object used for metric collection. Defaults to an instance -of L that is campatible with the original -method of using a coderef as a callback. See the aforementioned Statistics -class for more information. + ref $coderef eq 'CODE' or $self->throw_exception + ('$coderef must be a CODE reference'); -=head2 debugcb + local $self->{_in_txn_do} = 1; -Sets a callback to be executed each time a statement is run; takes a sub -reference. Callback is executed as $sub->($op, $info) where $op is -SELECT/INSERT/UPDATE/DELETE and $info is what would normally be printed. + my $tried = 0; -See L for a better way. + my @result; + my $want_array = wantarray; -=cut + START_TXN: eval { + $self->_verify_pid if $self->_dbh; + $self->_populate_dbh if !$self->_dbh; -sub debugcb { - my $self = shift; + $self->txn_begin; + if($want_array) { + @result = $coderef->(@_); + } + elsif(defined $want_array) { + $result[0] = $coderef->(@_); + } + else { + $coderef->(@_); + } + $self->txn_commit; + }; - if ($self->debugobj->can('callback')) { - return $self->debugobj->callback(@_); + my $exception = $@; + if(!$exception) { return $want_array ? @result : $result[0] } + + if($tried++ > 0 || $self->connected) { + eval { $self->txn_rollback }; + my $rollback_exception = $@; + if($rollback_exception) { + my $exception_class = "DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION"; + $self->throw_exception($exception) # propagate nested rollback + if $rollback_exception =~ /$exception_class/; + + $self->throw_exception( + "Transaction aborted: ${exception}. " + . "Rollback failed: ${rollback_exception}" + ); } + $self->throw_exception($exception) + } + + # We were not connected, and was first try - reconnect and retry + # XXX I know, gotos are evil. If you can find a better way + # to write this that doesn't duplicate a lot of code/structure, + # and behaves identically, feel free... + + $self->_populate_dbh; + goto START_TXN; } =head2 disconnect -Disconnect the L handle, performing a rollback first if the +Our C method also performs a rollback first if the database is not in C mode. =cut @@ -507,22 +587,15 @@ sub disconnect { } } -=head2 connected - -Check if the L handle is connected. Returns true if the handle -is connected. - -=cut - -sub connected { my ($self) = @_; +sub connected { + my ($self) = @_; if(my $dbh = $self->_dbh) { if(defined $self->_conn_tid && $self->_conn_tid != threads->tid) { return $self->_dbh(undef); } - elsif($self->_conn_pid != $$) { - $self->_dbh->{InactiveDestroy} = 1; - return $self->_dbh(undef); + else { + $self->_verify_pid; } return ($dbh->FETCH('Active') && $dbh->ping); } @@ -530,12 +603,18 @@ sub connected { my ($self) = @_; return 0; } -=head2 ensure_connected +# handle pid changes correctly +# NOTE: assumes $self->_dbh is a valid $dbh +sub _verify_pid { + my ($self) = @_; -Check whether the database handle is connected - if not then make a -connection. + return if $self->_conn_pid == $$; -=cut + $self->_dbh->{InactiveDestroy} = 1; + $self->_dbh(undef); + + return; +} sub ensure_connected { my ($self) = @_; @@ -564,13 +643,6 @@ sub _sql_maker_args { return ( limit_dialect => $self->dbh, %{$self->_sql_maker_opts} ); } -=head2 sql_maker - -Returns a C object - normally an object of class -C. - -=cut - sub sql_maker { my ($self) = @_; unless ($self->_sql_maker) { @@ -579,37 +651,6 @@ sub sql_maker { return $self->_sql_maker; } -sub connect_info { - my ($self, $info_arg) = @_; - - if($info_arg) { - # 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({}); - - my $info = [ @$info_arg ]; # copy because we can alter it - my $last_info = $info->[-1]; - if(ref $last_info eq 'HASH') { - if(my $on_connect_do = delete $last_info->{on_connect_do}) { - $self->on_connect_do($on_connect_do); - } - for my $sql_maker_opt (qw/limit_dialect quote_char name_sep/) { - if(my $opt_val = delete $last_info->{$sql_maker_opt}) { - $self->_sql_maker_opts->{$sql_maker_opt} = $opt_val; - } - } - - # Get rid of any trailing empty hashref - pop(@$info) if !keys %$last_info; - } - - $self->_connect_info($info); - } - - $self->_connect_info; -} - sub _populate_dbh { my ($self) = @_; my @info = @{$self->_connect_info || []}; @@ -648,9 +689,14 @@ sub _connect { } eval { - $dbh = ref $info[0] eq 'CODE' - ? &{$info[0]} - : DBI->connect(@info); + if(ref $info[0] eq 'CODE') { + $dbh = &{$info[0]} + } + else { + $dbh = DBI->connect(@info); + $dbh->{RaiseError} = 1; + $dbh->{PrintError} = 0; + } }; $DBI::connect_via = $old_connect_via if $old_connect_via; @@ -662,36 +708,23 @@ sub _connect { $dbh; } -=head2 txn_begin - -Calls begin_work on the current dbh. - -See L for the txn_do() method, which allows for -an entire code block to be executed transactionally. - -=cut +sub __txn_begin { + my ($dbh, $self) = @_; + if ($dbh->{AutoCommit}) { + $self->debugobj->txn_begin() + if ($self->debug); + $dbh->begin_work; + } +} sub txn_begin { my $self = shift; - if ($self->{transaction_depth}++ == 0) { - my $dbh = $self->dbh; - if ($dbh->{AutoCommit}) { - $self->debugobj->txn_begin() - if ($self->debug); - $dbh->begin_work; - } - } + $self->dbh_do(\&__txn_begin, $self) + if $self->{transaction_depth}++ == 0; } -=head2 txn_commit - -Issues a commit against the current dbh. - -=cut - -sub txn_commit { - my $self = shift; - my $dbh = $self->dbh; +sub __txn_commit { + my ($dbh, $self) = @_; if ($self->{transaction_depth} == 0) { unless ($dbh->{AutoCommit}) { $self->debugobj->txn_commit() @@ -708,38 +741,35 @@ sub txn_commit { } } -=head2 txn_rollback - -Issues a rollback against the current dbh. A nested rollback will -throw a L exception, -which allows the rollback to propagate to the outermost transaction. - -=cut - -sub txn_rollback { +sub txn_commit { my $self = shift; + $self->dbh_do(\&__txn_commit, $self); +} - eval { - my $dbh = $self->dbh; - if ($self->{transaction_depth} == 0) { - unless ($dbh->{AutoCommit}) { - $self->debugobj->txn_rollback() - if ($self->debug); - $dbh->rollback; - } +sub __txn_rollback { + my ($dbh, $self) = @_; + if ($self->{transaction_depth} == 0) { + unless ($dbh->{AutoCommit}) { + $self->debugobj->txn_rollback() + if ($self->debug); + $dbh->rollback; + } + } + else { + if (--$self->{transaction_depth} == 0) { + $self->debugobj->txn_rollback() + if ($self->debug); + $dbh->rollback; } else { - if (--$self->{transaction_depth} == 0) { - $self->debugobj->txn_rollback() - if ($self->debug); - $dbh->rollback; - } - else { - die DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION->new; - } + die DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION->new; } - }; + } +} +sub txn_rollback { + my $self = shift; + eval { $self->dbh_do(\&__txn_rollback, $self) }; if ($@) { my $error = $@; my $exception_class = "DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION"; @@ -844,19 +874,11 @@ sub select { return $self->cursor->new($self, \@_, $attrs); } -=head2 select_single - -Performs a select, fetch and return of data - handles a single row -only. - -=cut - -# Need to call finish() to work round broken DBDs - sub select_single { my $self = shift; my ($rv, $sth, @bind) = $self->_select(@_); my @row = $sth->fetchrow_array; + # Need to call finish() to work round broken DBDs $sth->finish(); return @row; } @@ -873,32 +895,27 @@ Returns a L sth (statement handle) for the supplied SQL. =cut -sub sth { - my ($self, $sql) = @_; +sub __sth { + my ($dbh, $sql) = @_; # 3 is the if_active parameter which avoids active sth re-use - return $self->dbh->prepare_cached($sql, {}, 3); + $dbh->prepare_cached($sql, {}, 3); } -=head2 columns_info_for - -Returns database type info for a given table column. - -=cut +sub sth { + my ($self, $sql) = @_; + $self->dbh_do(\&__sth, $sql); +} -sub columns_info_for { - my ($self, $table) = @_; - my $dbh = $self->dbh; +sub __columns_info_for { + my ($dbh, $self, $table) = @_; if ($dbh->can('column_info')) { my %result; - local $dbh->{RaiseError} = 1; - local $dbh->{PrintError} = 0; eval { my ($schema,$tab) = $table =~ /^(.+?)\.(.+)$/ ? ($1,$2) : (undef,$table); my $sth = $dbh->column_info( undef,$schema, $tab, '%' ); $sth->execute(); - while ( my $info = $sth->fetchrow_hashref() ){ my %column_info; $column_info{data_type} = $info->{TYPE_NAME}; @@ -941,6 +958,11 @@ sub columns_info_for { return \%result; } +sub columns_info_for { + my ($self, $table) = @_; + $self->dbh_do(\&__columns_info_for, $self, $table); +} + =head2 last_insert_id Return the row id of the last insert. @@ -950,8 +972,7 @@ Return the row id of the last insert. sub last_insert_id { my ($self, $row) = @_; - return $self->dbh->func('last_insert_rowid'); - + $self->dbh_do(sub { shift->func('last_insert_rowid') }); } =head2 sqlt_type @@ -960,7 +981,7 @@ Returns the database driver name. =cut -sub sqlt_type { shift->dbh->{Driver}->{Name} } +sub sqlt_type { shift->dbh_do(sub { shift->{Driver}->{Name} }) } =head2 create_ddl_dir (EXPERIMENTAL) @@ -1087,14 +1108,6 @@ sub deployment_statements { } -=head2 deploy - -Sends the appropriate statements to create or modify tables to the -db. This would normally be called through -L. - -=cut - sub deploy { my ($self, $schema, $type, $sqltargs, $dir) = @_; foreach my $statement ( $self->deployment_statements($schema, $type, undef, $dir, { no_comments => 1, %{ $sqltargs || {} } } ) ) { @@ -1106,7 +1119,7 @@ sub deploy { next if($_ =~ /^COMMIT/m); next if $_ =~ /^\s+$/; # skip whitespace only $self->debugobj->query_start($_) if $self->debug; - $self->dbh->do($_) or warn "SQL was:\n $_"; + $self->dbh->do($_) or warn "SQL was:\n $_"; # XXX exceptions? $self->debugobj->query_end($_) if $self->debug; } } @@ -1147,14 +1160,9 @@ sub build_datetime_parser { } sub DESTROY { - # NOTE: if there's a merge conflict here when -current is pushed - # back to trunk, take -current's version and ignore this trunk one :) my $self = shift; - - if($self->_dbh && $self->_conn_pid != $$) { - $self->_dbh->{InactiveDestroy} = 1; - } - + return if !$self->_dbh; + $self->_verify_pid; $self->_dbh(undef); } @@ -1195,25 +1203,6 @@ For setting, this method is deprecated in favor of L. =back -=head1 ENVIRONMENT VARIABLES - -=head2 DBIC_TRACE - -If C is set then SQL trace information -is produced (as when the L method is set). - -If the value is of the form C<1=/path/name> then the trace output is -written to the file C. - -This environment variable is checked when the storage object is first -created (when you call connect on your schema). So, run-time changes -to this environment variable will not take effect unless you also -re-connect on your schema. - -=head2 DBIX_CLASS_STORAGE_DBI_DEBUG - -Old name for DBIC_TRACE - =head1 AUTHORS Matt S. Trout