X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FStorage%2FDBI.pm;h=7482d81321be6c86051cc7abdf4b5c857a9c188a;hb=85f7862264752290ed6e3a3bf020f907a2cdd6fc;hp=931dd218d2a5104a3aea5fcbc94102e6d85e560d;hpb=099049b53886d1ca65db05e3dcfdd1ce0dd11679;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/Storage/DBI.pm b/lib/DBIx/Class/Storage/DBI.pm index 931dd21..7482d81 100644 --- a/lib/DBIx/Class/Storage/DBI.pm +++ b/lib/DBIx/Class/Storage/DBI.pm @@ -11,6 +11,13 @@ 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 +257,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 +265,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 +341,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,63 +388,102 @@ 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 $todo = shift; + + my @result; + my $want_array = wantarray; - if ($self->debugobj->can('callback')) { - return $self->debugobj->callback(@_); + eval { + $self->_verify_pid if $self->_dbh; + $self->_populate_dbh if !$self->_dbh; + my $dbh = $self->_dbh; + if($want_array) { + @result = $todo->($dbh, @_); + } + elsif(defined $want_array) { + $result[0] = $todo->($dbh, @_); } + else { + $todo->($dbh, @_); + } + }; + + if($@) { + my $exception = $@; + $self->connected + ? $self->throw_exception($exception) + : $self->_populate_dbh; + + my $dbh = $self->_dbh; + return $todo->($dbh, @_); + } + + return $want_array ? @result : $result[0]; } =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 @@ -479,22 +498,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); } @@ -502,12 +514,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) = @_; @@ -536,13 +554,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) { @@ -551,37 +562,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 || []}; @@ -620,9 +600,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; @@ -634,82 +619,65 @@ 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 $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(sub { + my $dbh = shift; + if ($dbh->{AutoCommit}) { + $self->debugobj->txn_begin() + if ($self->debug); + $dbh->begin_work; + } + }); } } -=head2 txn_commit - -Issues a commit against the current dbh. - -=cut - sub txn_commit { my $self = shift; - my $dbh = $self->dbh; - 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; - } - } -} - -=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 { - my $dbh = $self->dbh; + $self->dbh_do(sub { + my $dbh = shift; if ($self->{transaction_depth} == 0) { unless ($dbh->{AutoCommit}) { - $self->debugobj->txn_rollback() + $self->debugobj->txn_commit() if ($self->debug); - $dbh->rollback; + $dbh->commit; } } else { if (--$self->{transaction_depth} == 0) { - $self->debugobj->txn_rollback() + $self->debugobj->txn_commit() if ($self->debug); - $dbh->rollback; + $dbh->commit; + } + } + }); +} + +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 { - die DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION->new; + if (--$self->{transaction_depth} == 0) { + $self->debugobj->txn_rollback() + if ($self->debug); + $dbh->rollback; + } + else { + die DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION->new; + } } - } + }); }; if ($@) { @@ -798,31 +766,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; } @@ -836,78 +790,62 @@ Returns a L sth (statement handle) for the supplied SQL. sub sth { my ($self, $sql) = @_; # 3 is the if_active parameter which avoids active sth re-use - return $self->dbh->prepare_cached($sql, {}, 3); + return $self->dbh_do(sub { shift->prepare_cached($sql, {}, 3) }); } -=head2 columns_info_for - -Returns database type info for a given table columns. - -=cut - sub columns_info_for { my ($self, $table) = @_; - my $dbh = $self->dbh; + $self->dbh_do(sub { + my $dbh = shift; + + if ($dbh->can('column_info')) { + my %result; + 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}; + $column_info{size} = $info->{COLUMN_SIZE}; + $column_info{is_nullable} = $info->{NULLABLE} ? 1 : 0; + $column_info{default_value} = $info->{COLUMN_DEF}; + my $col_name = $info->{COLUMN_NAME}; + $col_name =~ s/^\"(.*)\"$/$1/; + + $result{$col_name} = \%column_info; + } + }; + return \%result if !$@; + } - if ($dbh->can('column_info')) { my %result; - my $old_raise_err = $dbh->{RaiseError}; - my $old_print_err = $dbh->{PrintError}; - $dbh->{RaiseError} = 1; - $dbh->{PrintError} = 0; - eval { - my ($schema,$tab) = $table =~ /^(.+?)\.(.+)$/ ? ($1,$2) : (undef,$table); - my $sth = $dbh->column_info( undef,$schema, $tab, '%' ); - $sth->execute(); - - # Some error occured or there is no information: - if($sth->rows <1) { - die "column_info returned no rows for $schema, $tab"; + my $sth = $dbh->prepare("SELECT * FROM $table WHERE 1=0"); + $sth->execute; + my @columns = @{$sth->{NAME_lc}}; + for my $i ( 0 .. $#columns ){ + my %column_info; + my $type_num = $sth->{TYPE}->[$i]; + my $type_name; + if(defined $type_num && $dbh->can('type_info')) { + my $type_info = $dbh->type_info($type_num); + $type_name = $type_info->{TYPE_NAME} if $type_info; } + $column_info{data_type} = $type_name ? $type_name : $type_num; + $column_info{size} = $sth->{PRECISION}->[$i]; + $column_info{is_nullable} = $sth->{NULLABLE}->[$i] ? 1 : 0; - while ( my $info = $sth->fetchrow_hashref() ){ - my %column_info; - $column_info{data_type} = $info->{TYPE_NAME}; - $column_info{size} = $info->{COLUMN_SIZE}; - $column_info{is_nullable} = $info->{NULLABLE} ? 1 : 0; - $column_info{default_value} = $info->{COLUMN_DEF}; - my $col_name = $info->{COLUMN_NAME}; - $col_name =~ s/^\"(.*)\"$/$1/; - - $result{$col_name} = \%column_info; + if ($column_info{data_type} =~ m/^(.*?)\((.*?)\)$/) { + $column_info{data_type} = $1; + $column_info{size} = $2; } - }; - $dbh->{RaiseError} = $old_raise_err; - $dbh->{PrintError} = $old_print_err; - return \%result if !$@; - } - - my %result; - my $sth = $dbh->prepare("SELECT * FROM $table WHERE 1=0"); - $sth->execute; - my @columns = @{$sth->{NAME_lc}}; - for my $i ( 0 .. $#columns ){ - my %column_info; - my $type_num = $sth->{TYPE}->[$i]; - my $type_name; - if(defined $type_num && $dbh->can('type_info')) { - my $type_info = $dbh->type_info($type_num); - $type_name = $type_info->{TYPE_NAME} if $type_info; - } - $column_info{data_type} = $type_name ? $type_name : $type_num; - $column_info{size} = $sth->{PRECISION}->[$i]; - $column_info{is_nullable} = $sth->{NULLABLE}->[$i] ? 1 : 0; - if ($column_info{data_type} =~ m/^(.*?)\((.*?)\)$/) { - $column_info{data_type} = $1; - $column_info{size} = $2; + $result{$columns[$i]} = \%column_info; } - $result{$columns[$i]} = \%column_info; - } - - return \%result; + return \%result; + }); } =head2 last_insert_id @@ -919,8 +857,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 @@ -929,7 +866,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) @@ -1040,14 +977,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) = @_; foreach my $statement ( $self->deployment_statements($schema, $type, undef, undef, { no_comments => 1, %{ $sqltargs || {} } } ) ) { @@ -1059,7 +988,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; } } @@ -1099,7 +1028,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; @@ -1138,25 +1073,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