X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FStorage%2FDBI.pm;h=37a083d434761cb95ffe978e4d790f60025d3b1c;hb=382a78385abc2213ea09b3f6c6732694bf522e63;hp=22aa2c11dd0173e55d31d1552a7aee93f9dd6903;hpb=a9196897efe0c699d743adcf41ba5ebcdaf30931;p=dbsrgits%2FDBIx-Class-Historic.git diff --git a/lib/DBIx/Class/Storage/DBI.pm b/lib/DBIx/Class/Storage/DBI.pm index 22aa2c1..37a083d 100644 --- a/lib/DBIx/Class/Storage/DBI.pm +++ b/lib/DBIx/Class/Storage/DBI.pm @@ -10,8 +10,13 @@ use SQL::Abstract::Limit; use DBIx::Class::Storage::DBI::Cursor; use DBIx::Class::Storage::Statistics; use IO::File; -use Scalar::Util qw/weaken/; -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 :( @@ -30,10 +35,36 @@ sub new { $self; } +sub _RowNumberOver { + my ($self, $sql, $order, $rows, $offset ) = @_; + + $offset += 1; + my $last = $rows + $offset; + my ( $order_by ) = $self->_order_by( $order ); + + $sql = <<""; +SELECT * FROM +( + SELECT Q1.*, ROW_NUMBER() OVER( ) AS ROW_NUM FROM ( + $sql + $order_by + ) Q1 +) Q2 +WHERE ROW_NUM BETWEEN $offset AND $last + + return $sql; +} + + # While we're at it, this should make LIMIT queries more efficient, # without digging into things too deeply sub _find_syntax { my ($self, $syntax) = @_; + my $dbhname = ref $syntax eq 'HASH' ? $syntax->{Driver}{Name} : ''; + if(ref($self) && $dbhname && $dbhname eq 'DB2') { + return 'RowNumberOver'; + } + $self->{_cached_syntax} ||= $self->SUPER::_find_syntax($syntax); } @@ -251,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 schema/); - =head1 NAME DBIx::Class::Storage::DBI - DBI storage handler @@ -267,69 +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 - -Constructor. Only argument is the schema which instantiated us. - =cut sub new { - my ($self, $schema) = @_; + my $new = shift->next::method(@_); - my $new = {}; - bless $new, (ref $_[0] || $_[0]); - $new->set_schema($schema); $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 set_schema - -Used to reset the schema class or object which owns this -storage object, such as after a C. - -=cut - -sub set_schema { - my ($self, $schema) = @_; - $self->schema($schema); - weaken($self->{schema}) if ref $self->{schema}; -} - -=head2 throw_exception - -Throws an exception - croaks. - -=cut - -sub throw_exception { - my $self = shift; - - $self->schema->throw_exception(@_) if $self->schema; - croak @_; + $new; } =head2 connect_info @@ -437,76 +413,72 @@ Examples: ] ); -=head2 on_connect_do - -This method is deprecated in favor of setting via L. - -=head2 debug - -Causes SQL trace information to be emitted on the C object. -(or C if C has not specifically been set). - -This is the equivalent to setting L in your -shell environment. - -=head2 debugfh - -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. - =cut -sub debugfh { - my $self = shift; - - if ($self->debugobj->can('debugfh')) { - return $self->debugobj->debugfh(@_); - } -} - -=head2 debugobj +sub connect_info { + my ($self, $info_arg) = @_; -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. + return $self->_connect_info if !$info_arg; -=head2 debugcb + # 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({}); -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 $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; + } + } -See L for a better way. + # Get rid of any trailing empty hashref + pop(@$info) if !keys %$last_info; + } -=cut + $self->_connect_info($info); +} -sub debugcb { - my $self = shift; +=head2 on_connect_do - if ($self->debugobj->can('callback')) { - return $self->debugobj->callback(@_); - } -} +This method is deprecated in favor of setting via L. =head2 dbh_do +Arguments: $subref, @extra_coderef_args? + Execute the given subref with the underlying database handle as its first argument, using the new exception-based connection management. + +Any additional arguments will be passed verbatim to the called subref +as arguments 2 and onwards. + Example: my @stuff = $schema->storage->dbh_do( sub { - shift->selectrow_array("SELECT * FROM foo") - } + my $dbh = shift; + my $cols = join(q{, }, @_); + shift->selectrow_array("SELECT $cols FROM foo") + }, + @column_list ); =cut sub dbh_do { - my ($self, $todo) = @_; + 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'); my @result; my $want_array = wantarray; @@ -514,34 +486,93 @@ sub dbh_do { eval { $self->_verify_pid if $self->_dbh; $self->_populate_dbh if !$self->_dbh; - my $dbh = $self->_dbh; if($want_array) { - @result = $todo->($dbh); + @result = $coderef->($self->_dbh, @_); } elsif(defined $want_array) { - $result[0] = $todo->($dbh); + $result[0] = $coderef->($self->_dbh, @_); } else { - $todo->($dbh); + $coderef->($self->_dbh, @_); } }; - if($@) { - my $exception = $@; - $self->connected - ? $self->throw_exception($exception) - : $self->_populate_dbh; + my $exception = $@; + if(!$exception) { return $want_array ? @result : $result[0] } + + $self->throw_exception($exception) if $self->connected; - my $dbh = $self->_dbh; - return $todo->($dbh); + # We were not connected - reconnect and retry, but let any + # exception fall right through this time + $self->_populate_dbh; + $coderef->($self->_dbh, @_); +} + +# 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; + + 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; + + START_TXN: eval { + $self->_verify_pid if $self->_dbh; + $self->_populate_dbh if !$self->_dbh; + + $self->txn_begin; + if($want_array) { + @result = $coderef->(@_); + } + elsif(defined $want_array) { + $result[0] = $coderef->(@_); + } + else { + $coderef->(@_); + } + $self->txn_commit; + }; + + 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 @@ -556,13 +587,6 @@ sub disconnect { } } -=head2 connected - -Check if the L handle is connected. Returns true if the handle -is connected. - -=cut - sub connected { my ($self) = @_; @@ -592,13 +616,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) = @_; @@ -626,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) { @@ -641,35 +651,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 || []}; @@ -727,90 +708,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"; @@ -897,31 +856,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; } @@ -932,71 +877,72 @@ 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) = @_; - $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 !$@; - } +sub __columns_info_for { + my ($dbh, $self, $table) = @_; + if ($dbh->can('column_info')) { 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; + 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; } - $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; + }; + return \%result if !$@ && scalar keys %result; + } - if ($column_info{data_type} =~ m/^(.*?)\((.*?)\)$/) { - $column_info{data_type} = $1; - $column_info{size} = $2; - } + 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; - $result{$columns[$i]} = \%column_info; + if ($column_info{data_type} =~ m/^(.*?)\((.*?)\)$/) { + $column_info{data_type} = $1; + $column_info{size} = $2; } - return \%result; - }); + $result{$columns[$i]} = \%column_info; + } + + return \%result; +} + +sub columns_info_for { + my ($self, $table) = @_; + $self->dbh_do(\&__columns_info_for, $self, $table); } =head2 last_insert_id @@ -1128,17 +1074,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 || {} } } ) ) { + my ($self, $schema, $type, $sqltargs, $dir) = @_; + foreach my $statement ( $self->deployment_statements($schema, $type, undef, $dir, { no_comments => 1, %{ $sqltargs || {} } } ) ) { for ( split(";\n", $statement)) { next if($_ =~ /^--/); next if(!$_); @@ -1190,7 +1128,6 @@ sub build_datetime_parser { sub DESTROY { my $self = shift; return if !$self->_dbh; - $self->_verify_pid; $self->_dbh(undef); } @@ -1232,25 +1169,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