X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FStorage%2FDBI.pm;h=36f69eeaa70f6df291c4dd1e3dac8df74a4d3ed8;hb=aaba95249e9b4a986ff3f5820e6c95e2ecae68f9;hp=87f84e651dc5eb8d9df2983b5ce76dc18656e700;hpb=d3abf3fe9466ce87d09d73c9af8ead9ff8d1e88e;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/Storage/DBI.pm b/lib/DBIx/Class/Storage/DBI.pm index 87f84e6..36f69ee 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 :( @@ -250,14 +256,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 @@ -266,47 +264,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({}, 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 @@ -367,6 +340,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 @@ -408,106 +387,166 @@ Examples: ] ); -=head2 on_connect_do +=cut -This method is deprecated in favor of setting via L. +sub connect_info { + my ($self, $info_arg) = @_; -=head2 debug + return $self->_connect_info if !$info_arg; -Causes SQL trace information to be emitted on the C object. -(or C if C has not specifically been set). + # 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({}); -This is the equivalent to setting L in your -shell environment. + 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; + } + } -=head2 debugfh + # Get rid of any trailing empty hashref + pop(@$info) if !keys %$last_info; + } -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. + $self->_connect_info($info); +} -=cut +=head2 on_connect_do -sub debugfh { - my $self = shift; +This method is deprecated in favor of setting via L. - if ($self->debugobj->can('debugfh')) { - return $self->debugobj->debugfh(@_); - } -} +=head2 dbh_do -=head2 debugobj +Arguments: $subref, @extra_coderef_args? -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. +Execute the given subref with the underlying database handle as its +first argument, using the new exception-based connection management. -=head2 debugcb +Any additional arguments will be passed verbatim to the called subref +as arguments 2 and onwards. -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. +Example: -See L for a better way. + 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 debugcb { - my $self = shift; +sub dbh_do { + my $self = shift; + my $coderef = shift; + + return $coderef->($self->_dbh, @_) if $self->{_in_txn_do}; + + ref $coderef eq 'CODE' or $self->throw_exception + ('$coderef must be a CODE reference'); - if ($self->debugobj->can('callback')) { - return $self->debugobj->callback(@_); + 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, @_); + } + }; -=head2 dbh_do + my $exception = $@; + if(!$exception) { return $want_array ? @result : $result[0] } -Execute the given subref with the underlying -database handle as its first argument, using our -normal exception-based connection management. Example: + $self->throw_exception($exception) if $self->connected; - $schema->storage->dbh_do(sub { shift->do("blah blah") }); + # We were not connected - reconnect and retry, but let any + # exception fall right through this time + $self->_populate_dbh; + $coderef->($self->_dbh, @_); +} -=cut +# 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; -sub dbh_do { - my ($self, $todo) = @_; + ref $coderef eq 'CODE' or $self->throw_exception + ('$coderef must be a CODE reference'); + + local $self->{_in_txn_do} = 1; + + my $tried = 0; my @result; my $want_array = wantarray; - eval { - $self->_verify_pid; + START_TXN: eval { + $self->_verify_pid if $self->_dbh; $self->_populate_dbh if !$self->_dbh; - my $dbh = $self->_dbh; - local $dbh->{RaiseError} = 1; - local $dbh->{PrintError} = 0; + + $self->txn_begin; if($want_array) { - @result = $todo->($dbh); + @result = $coderef->(@_); + } + elsif(defined $want_array) { + $result[0] = $coderef->(@_); } else { - $result[0] = $todo->($dbh); + $coderef->(@_); } + $self->txn_commit; }; - if($@) { - my $exception = $@; - $self->connected - ? $self->throw_exception($exception) - : $self->_populate_dbh; - - my $dbh = $self->_dbh; - local $dbh->{RaiseError} = 1; - local $dbh->{PrintError} = 0; - return $todo->($self->_dbh); + + 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) } - return $want_array ? @result : $result[0]; + + # 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 @@ -522,13 +561,6 @@ sub disconnect { } } -=head2 connected - -Check if the L handle is connected. Returns true if the handle -is connected. - -=cut - sub connected { my ($self) = @_; @@ -536,7 +568,9 @@ sub connected { if(defined $self->_conn_tid && $self->_conn_tid != threads->tid) { return $self->_dbh(undef); } - $self->_verify_pid; + else { + $self->_verify_pid; + } return ($dbh->FETCH('Active') && $dbh->ping); } @@ -544,10 +578,11 @@ sub connected { } # handle pid changes correctly +# NOTE: assumes $self->_dbh is a valid $dbh sub _verify_pid { my ($self) = @_; - return if !$self->_dbh || $self->_conn_pid == $$; + return if $self->_conn_pid == $$; $self->_dbh->{InactiveDestroy} = 1; $self->_dbh(undef); @@ -555,13 +590,6 @@ sub _verify_pid { return; } -=head2 ensure_connected - -Check whether the database handle is connected - if not then make a -connection. - -=cut - sub ensure_connected { my ($self) = @_; @@ -589,13 +617,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) { @@ -604,35 +625,6 @@ sub sql_maker { return $self->_sql_maker; } -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); -} - sub _populate_dbh { my ($self) = @_; my @info = @{$self->_connect_info || []}; @@ -671,9 +663,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; @@ -685,90 +682,68 @@ 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) { - $self->dbh_do(sub { - my $dbh = shift; - 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 ($dbh, $self) = @_; + if ($self->{transaction_depth} == 0) { + unless ($dbh->{AutoCommit}) { + $self->debugobj->txn_commit() + if ($self->debug); + $dbh->commit; + } + } + else { + if (--$self->{transaction_depth} == 0) { + $self->debugobj->txn_commit() + if ($self->debug); + $dbh->commit; + } + } +} sub txn_commit { my $self = shift; - $self->dbh_do(sub { - my $dbh = shift; - if ($self->{transaction_depth} == 0) { - unless ($dbh->{AutoCommit}) { - $self->debugobj->txn_commit() - if ($self->debug); - $dbh->commit; - } + $self->dbh_do(\&__txn_commit, $self); +} + +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_commit() - if ($self->debug); - $dbh->commit; - } + die DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION->new; } - }); + } } -=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 { my $self = shift; - - eval { - $self->dbh_do(sub { - my $dbh = shift; - 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 { - die DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION->new; - } - } - }); - }; - + eval { $self->dbh_do(\&__txn_rollback, $self) }; if ($@) { my $error = $@; my $exception_class = "DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION"; @@ -855,31 +830,17 @@ sub _select { return $self->_execute(@args); } -=head2 select - -Handle a SQL select statement. - -=cut - sub select { my $self = shift; my ($ident, $select, $condition, $attrs) = @_; 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; } @@ -890,27 +851,23 @@ 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_do(sub { shift->prepare_cached($sql, {}, 3) }); + $dbh->prepare_cached($sql, {}, 3); } -=head2 columns_info_for - -Returns database type info for a given table columns. - -=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, '%' ); @@ -957,6 +914,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. @@ -1005,14 +967,12 @@ sub create_ddl_dir $databases ||= ['MySQL', 'SQLite', 'PostgreSQL']; $databases = [ $databases ] if(ref($databases) ne 'ARRAY'); $version ||= $schema->VERSION || '1.x'; + $sqltargs = { ( add_drop_table => 1 ), %{$sqltargs || {}} }; eval "use SQL::Translator"; $self->throw_exception("Can't deploy without SQL::Translator: $@") if $@; - my $sqlt = SQL::Translator->new({ -# debug => 1, - add_drop_table => 1, - }); + my $sqlt = SQL::Translator->new($sqltargs); foreach my $db (@$databases) { $sqlt->reset(); @@ -1088,17 +1048,9 @@ 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) = @_; - foreach my $statement ( $self->deployment_statements($schema, $type, undef, undef, { no_comments => 1, %$sqltargs }) ) { + foreach my $statement ( $self->deployment_statements($schema, $type, undef, undef, { no_comments => 1, %{ $sqltargs || {} } } ) ) { for ( split(";\n", $statement)) { next if($_ =~ /^--/); next if(!$_); @@ -1147,7 +1099,13 @@ sub build_datetime_parser { return $type; } -sub DESTROY { shift->disconnect } +sub DESTROY { + my $self = shift; + return if !$self->_dbh; + + $self->_verify_pid; + $self->_dbh(undef); +} 1; @@ -1186,25 +1144,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