X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FStorage%2FDBI.pm;h=59f0357397f86e28729b1a41d96d9c4d80b338e0;hb=89d794d486e3c73b489817059e9c3983deebde7f;hp=b7f1198fb15660adbc7c1b3955660137e5817197;hpb=9b83fccd091065fcebbb6fb6fb7bf2c2da38ffe2;p=dbsrgits%2FDBIx-Class-Historic.git diff --git a/lib/DBIx/Class/Storage/DBI.pm b/lib/DBIx/Class/Storage/DBI.pm index b7f1198..59f0357 100644 --- a/lib/DBIx/Class/Storage/DBI.pm +++ b/lib/DBIx/Class/Storage/DBI.pm @@ -17,9 +17,30 @@ package DBIC::SQL::Abstract; # Would merge upstream, but nate doesn't reply :( use base qw/SQL::Abstract::Limit/; +# This prevents the caching of $dbh in S::A::L, I believe +sub new { + my $self = shift->SUPER::new(@_); + + # If limit_dialect is a ref (like a $dbh), go ahead and replace + # it with what it resolves to: + $self->{limit_dialect} = $self->_find_syntax($self->{limit_dialect}) + if ref $self->{limit_dialect}; + + $self; +} + +# While we're at it, this should make LIMIT queries more efficient, +# without digging into things too deeply +sub _find_syntax { + my ($self, $syntax) = @_; + $self->{_cached_syntax} ||= $self->SUPER::_find_syntax($syntax); +} + sub select { my ($self, $table, $fields, $where, $order, @rest) = @_; $table = $self->_quote($table) unless ref($table); + local $self->{rownum_hack_count} = 1 + if (defined $rest[0] && $self->{limit_dialect} eq 'RowNum'); @rest = (-1) unless defined $rest[0]; die "LIMIT 0 Does Not Compute" if $rest[0] == 0; # and anyway, SQL::Abstract::Limit will cause a barf if we don't first @@ -67,7 +88,12 @@ sub _recurse_fields { return $$fields if $ref eq 'SCALAR'; if ($ref eq 'ARRAY') { - return join(', ', map { $self->_recurse_fields($_) } @$fields); + return join(', ', map { + $self->_recurse_fields($_) + .(exists $self->{rownum_hack_count} + ? ' AS col'.$self->{rownum_hack_count}++ + : '') + } @$fields); } elsif ($ref eq 'HASH') { foreach my $func (keys %$fields) { return $self->_sqlcase($func) @@ -92,10 +118,18 @@ sub _order_by { $ret .= $self->_sqlcase(' having ').$frag; } if (defined $_[0]->{order_by}) { - $ret .= $self->SUPER::_order_by($_[0]->{order_by}); + $ret .= $self->_order_by($_[0]->{order_by}); } - } elsif(ref $_[0] eq 'SCALAR') { + } elsif (ref $_[0] eq 'SCALAR') { $ret = $self->_sqlcase(' order by ').${ $_[0] }; + } elsif (ref $_[0] eq 'ARRAY' && @{$_[0]}) { + my @order = @{+shift}; + $ret = $self->_sqlcase(' order by ') + .join(', ', map { + my $r = $self->_order_by($_, @_); + $r =~ s/^ ?ORDER BY //i; + $r; + } @order); } else { $ret = $self->SUPER::_order_by(@_); } @@ -133,8 +167,9 @@ sub _recurse_from { # check whether a join type exists my $join_clause = ''; - if (ref($to) eq 'HASH' and exists($to->{-join_type})) { - $join_clause = ' '.uc($to->{-join_type}).' JOIN '; + my $to_jt = ref($to) eq 'ARRAY' ? $to->[0] : $to; + if (ref($to_jt) eq 'HASH' and exists($to_jt->{-join_type})) { + $join_clause = ' '.uc($to_jt->{-join_type}).' JOIN '; } else { $join_clause = ' JOIN '; } @@ -195,18 +230,6 @@ sub _quote { return $self->SUPER::_quote($label); } -sub _RowNum { - my $self = shift; - my $c; - $_[0] =~ s/SELECT (.*?) FROM/ - 'SELECT '.join(', ', map { $_.' AS col'.++$c } split(', ', $1)).' FROM'/e; - $self->SUPER::_RowNum(@_); -} - -# Accessor for setting limit dialect. This is useful -# for JDBC-bridge among others where the remote SQL-dialect cannot -# be determined by the name of the driver alone. -# sub limit_dialect { my $self = shift; $self->{limit_dialect} = shift if @_; @@ -232,30 +255,48 @@ use base qw/DBIx::Class/; __PACKAGE__->load_components(qw/AccessorGroup/); __PACKAGE__->mk_group_accessors('simple' => - qw/_connect_info _dbh _sql_maker _conn_pid _conn_tid debug debugobj - cursor on_connect_do transaction_depth/); + 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 + +=head1 SYNOPSIS + +=head1 DESCRIPTION + +This class represents the connection to the database + +=head1 METHODS =head2 new =cut sub new { - my $new = bless({}, ref $_[0] || $_[0]); + my $new = {}; + bless $new, (ref $_[0] || $_[0]); + $new->cursor("DBIx::Class::Storage::DBI::Cursor"); $new->transaction_depth(0); $new->debugobj(new DBIx::Class::Storage::Statistics()); my $fh; - if (defined($ENV{DBIX_CLASS_STORAGE_DBI_DEBUG}) && - ($ENV{DBIX_CLASS_STORAGE_DBI_DEBUG} =~ /=(.+)$/)) { + + 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 $ENV{DBIX_CLASS_STORAGE_DBI_DEBUG}; + $new->debug(1) if $debug_env; + $new->_sql_maker_opts({}); return $new; } @@ -270,72 +311,123 @@ sub throw_exception { croak($msg); } -=head1 NAME - -DBIx::Class::Storage::DBI - DBI storage handler - -=head1 SYNOPSIS +=head2 connect_info -=head1 DESCRIPTION +The arguments of C are always a single array reference. -This class represents the connection to the database +This is normally accessed via L, which +encapsulates its argument list in an arrayref before calling +C here. -=head1 METHODS +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. -=cut +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: -=head2 connect_info +=over 4 -Connection information arrayref. Can either be the same arguments -one would pass to DBI->connect, or a code-reference which returns -a connected database handle. In either case, there is an optional -final element in the arrayref, which can hold a hashref of -connection-specific Storage::DBI options. These include -C, and the sql_maker options C, -C, and C. Examples: +=item on_connect_do - ->connect_info([ 'dbi:SQLite:./foo.db' ]); - ->connect_info(sub { DBI->connect(...) }); - ->connect_info([ 'dbi:Pg:dbname=foo', - 'postgres', - '', - { AutoCommit => 0 }, - { quote_char => q{`}, name_sep => q{@} }, - ]); +This can be set to an arrayref of literal sql statements, which will +be executed immediately after making the connection to the database +every time we [re-]connect. -=head2 on_connect_do +=item limit_dialect -Executes the sql statements given as a listref on every db connect. +Sets the limit dialect. This is useful for JDBC-bridge among others +where the remote SQL-dialect cannot be determined by the name of the +driver alone. -=head2 quote_char +=item quote_char Specifies what characters to use to quote table and column names. If you use this you will want to specify L as well. -quote_char expectes either a single character, in which case is it is placed -on either side of the table/column, or an array of length 2 in which case the +quote_char expects either a single character, in which case is it is placed +on either side of the table/column, or an arrayref of length 2 in which case the table/column name is placed between the elements. -For example under MySQL you'd use C, and user SQL Server you'd -use C. +For example under MySQL you'd use C '`'>, and user SQL Server you'd +use C [qw/[ ]/]>. -=head2 name_sep +=item name_sep This only needs to be used in conjunction with L, and is used to specify the charecter that seperates elements (schemas, tables, columns) from each other. In most cases this is simply a C<.>. +=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. + +Examples: + + # Simple SQLite connection + ->connect_info([ 'dbi:SQLite:./foo.db' ]); + + # Connect via subref + ->connect_info([ sub { DBI->connect(...) } ]); + + # A bit more complicated + ->connect_info( + [ + 'dbi:Pg:dbname=foo', + 'postgres', + 'my_pg_password', + { AutoCommit => 0 }, + { quote_char => q{"}, name_sep => q{.} }, + ] + ); + + # Equivalent to the previous example + ->connect_info( + [ + 'dbi:Pg:dbname=foo', + 'postgres', + 'my_pg_password', + { AutoCommit => 0, quote_char => q{"}, name_sep => q{.} }, + ] + ); + + # Subref + DBIC-specific connection options + ->connect_info( + [ + sub { DBI->connect(...) }, + { + quote_char => q{`}, + name_sep => q{@}, + on_connect_do => ['SET search_path TO myschema,otherschema,public'], + }, + ] + ); + +=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. +L environment variable. =cut @@ -400,13 +492,11 @@ sub connected { my ($self) = @_; if(my $dbh = $self->_dbh) { if(defined $self->_conn_tid && $self->_conn_tid != threads->tid) { - $self->_sql_maker(undef); return $self->_dbh(undef); } elsif($self->_conn_pid != $$) { $self->_dbh->{InactiveDestroy} = 1; - $self->_sql_maker(undef); - return $self->_dbh(undef) + return $self->_dbh(undef); } return ($dbh->FETCH('Active') && $dbh->ping); } @@ -445,7 +535,7 @@ sub dbh { sub _sql_maker_args { my ($self) = @_; - return ( limit_dialect => $self->dbh ); + return ( limit_dialect => $self->dbh, %{$self->_sql_maker_opts} ); } =head2 sql_maker @@ -464,46 +554,49 @@ sub sql_maker { } sub connect_info { - my ($self, $info_arg) = @_; - - if($info_arg) { - my $info = [ @$info_arg ]; # copy because we can alter it - my $last_info = $info->[-1]; - if(ref $last_info eq 'HASH') { - my $used; - if(my $on_connect_do = $last_info->{on_connect_do}) { - $used = 1; - $self->on_connect_do($on_connect_do); - } - for my $sql_maker_opt (qw/limit_dialect quote_char name_sep/) { - if(my $opt_val = $last_info->{$sql_maker_opt}) { - $used = 1; - $self->sql_maker->$sql_maker_opt($opt_val); - } - } - - # remove our options hashref if it was there, to avoid confusing - # DBI in the case the user didn't use all 4 DBI options, as in: - # [ 'dbi:SQLite:foo.db', { quote_char => q{`} } ] - pop(@$info) if $used; + 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; } + } - $self->_connect_info($info); + # Get rid of any trailing empty hashref + pop(@$info) if !keys %$last_info; } - $self->_connect_info; + $self->_connect_info($info); + } + + $self->_connect_info; } sub _populate_dbh { my ($self) = @_; my @info = @{$self->_connect_info || []}; $self->_dbh($self->_connect(@info)); - my $driver = $self->_dbh->{Driver}->{Name}; - eval "require DBIx::Class::Storage::DBI::${driver}"; - unless ($@) { - bless $self, "DBIx::Class::Storage::DBI::${driver}"; - $self->_rebless() if $self->can('_rebless'); + + if(ref $self eq 'DBIx::Class::Storage::DBI') { + my $driver = $self->_dbh->{Driver}->{Name}; + if ($self->load_optional_class("DBIx::Class::Storage::DBI::${driver}")) { + bless $self, "DBIx::Class::Storage::DBI::${driver}"; + $self->_rebless() if $self->can('_rebless'); + } } + # if on-connect sql statements are given execute them foreach my $sql_statement (@{$self->on_connect_do || []}) { $self->debugobj->query_start($sql_statement) if $self->debug(); @@ -529,12 +622,9 @@ sub _connect { } eval { - if(ref $info[0] eq 'CODE') { - $dbh = &{$info[0]}; - } - else { - $dbh = DBI->connect(@info); - } + $dbh = ref $info[0] eq 'CODE' + ? &{$info[0]} + : DBI->connect(@info); }; $DBI::connect_via = $old_connect_via if $old_connect_via; @@ -764,14 +854,13 @@ sub columns_info_for { 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; + 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}; @@ -784,9 +873,7 @@ sub columns_info_for { $result{$col_name} = \%column_info; } }; - $dbh->{RaiseError} = $old_raise_err; - $dbh->{PrintError} = $old_print_err; - return \%result if !$@; + return \%result if !$@ && scalar keys %result; } my %result; @@ -865,14 +952,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(); @@ -914,6 +999,8 @@ L. sub deployment_statements { my ($self, $schema, $type, $version, $dir, $sqltargs) = @_; + # Need to be connected to get the correct sqlt_type + $self->ensure_connected() unless $type; $type ||= $self->sqlt_type; $version ||= $schema->VERSION || '1.x'; $dir ||= './'; @@ -956,14 +1043,15 @@ L. sub deploy { my ($self, $schema, $type, $sqltargs) = @_; - foreach my $statement ( $self->deployment_statements($schema, $type, undef, undef, $sqltargs) ) { + foreach my $statement ( $self->deployment_statements($schema, $type, undef, undef, { no_comments => 1, %{ $sqltargs || {} } } ) ) { for ( split(";\n", $statement)) { next if($_ =~ /^--/); next if(!$_); # next if($_ =~ /^DROP/m); next if($_ =~ /^BEGIN TRANSACTION/m); next if($_ =~ /^COMMIT/m); - $self->debugobj->query_begin($_) if $self->debug; + next if $_ =~ /^\s+$/; # skip whitespace only + $self->debugobj->query_start($_) if $self->debug; $self->dbh->do($_) or warn "SQL was:\n $_"; $self->debugobj->query_end($_) if $self->debug; } @@ -1004,7 +1092,17 @@ sub build_datetime_parser { return $type; } -sub DESTROY { shift->disconnect } +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; + } + + $self->_dbh(undef); +} 1; @@ -1028,17 +1126,26 @@ The following methods are extended:- =item limit_dialect +See L for details. +For setting, this method is deprecated in favor of L. + =item quote_char +See L for details. +For setting, this method is deprecated in favor of L. + =item name_sep +See L for details. +For setting, this method is deprecated in favor of L. + =back =head1 ENVIRONMENT VARIABLES -=head2 DBIX_CLASS_STORAGE_DBI_DEBUG +=head2 DBIC_TRACE -If C is set then SQL trace information +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 @@ -1049,6 +1156,10 @@ 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