X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FStorage%2FDBI.pm;h=b7f1198fb15660adbc7c1b3955660137e5817197;hb=9b83fccd091065fcebbb6fb6fb7bf2c2da38ffe2;hp=196fdc910f4661e44020ec6f3a99d0ecf5d93495;hpb=4d272ce5ce62edfd0e26fb499c585da9db3e4e4a;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/Storage/DBI.pm b/lib/DBIx/Class/Storage/DBI.pm index 196fdc9..b7f1198 100644 --- a/lib/DBIx/Class/Storage/DBI.pm +++ b/lib/DBIx/Class/Storage/DBI.pm @@ -8,9 +8,9 @@ use warnings; use DBI; use SQL::Abstract::Limit; use DBIx::Class::Storage::DBI::Cursor; +use DBIx::Class::Storage::Statistics; use IO::File; use Carp::Clan qw/DBIx::Class/; - BEGIN { package DBIC::SQL::Abstract; # Would merge upstream, but nate doesn't reply :( @@ -21,6 +21,8 @@ sub select { my ($self, $table, $fields, $where, $order, @rest) = @_; $table = $self->_quote($table) unless ref($table); @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 local $self->{having_bind} = []; my ($sql, @ret) = $self->SUPER::select( $table, $self->_recurse_fields($fields), $where, $order, @rest @@ -223,17 +225,6 @@ sub name_sep { return $self->{name_sep}; } - - - -package DBIx::Class::Storage::DBI::DebugCallback; - -sub print { - my ($self, $string) = @_; - $string =~ m/^(\w+)/; - ${$self}->($1, $string); -} - } # End of BEGIN block use base qw/DBIx::Class/; @@ -241,24 +232,39 @@ 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 debugfh + qw/_connect_info _dbh _sql_maker _conn_pid _conn_tid debug debugobj cursor on_connect_do transaction_depth/); +=head2 new + +=cut + sub new { my $new = bless({}, 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} =~ /=(.+)$/)) { - $new->debugfh(IO::File->new($1, 'w')) + $fh = IO::File->new($1, 'w') or $new->throw_exception("Cannot open trace file $1"); } else { - $new->debugfh(IO::File->new('>&STDERR')); + $fh = IO::File->new('>&STDERR'); } + $new->debugfh($fh); $new->debug(1) if $ENV{DBIX_CLASS_STORAGE_DBI_DEBUG}; return $new; } +=head2 throw_exception + +Throws an exception - croaks. + +=cut + sub throw_exception { my ($self, $msg) = @_; croak($msg); @@ -301,33 +307,78 @@ C, and C. Examples: Executes the sql statements given as a listref on every db connect. +=head2 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 +table/column name is placed between the elements. + +For example under MySQL you'd use C, and user SQL Server you'd +use C. + +=head2 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<.>. + =head2 debug -Causes SQL trace information to be emitted on C filehandle -(or C if C has not specifically been set). +Causes SQL trace information to be emitted on the C object. +(or C if C has not specifically been set). =head2 debugfh -Sets or retrieves the filehandle used for trace/debug output. This -should be an IO::Handle compatible object (only the C method is -used). Initially set to be STDERR - although see information on the +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 + +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. + =head2 debugcb Sets a callback to be executed each time a statement is run; takes a sub -reference. Overrides debugfh. Callback is executed as $sub->($op, $info) -where $op is SELECT/INSERT/UPDATE/DELETE and $info is what would normally -be printed. +reference. Callback is executed as $sub->($op, $info) where $op is +SELECT/INSERT/UPDATE/DELETE and $info is what would normally be printed. + +See L for a better way. =cut sub debugcb { - my ($self, $cb) = @_; - my $cb_obj = bless(\$cb, 'DBIx::Class::Storage::DBI::DebugCallback'); - $self->debugfh($cb_obj); + my $self = shift; + + if ($self->debugobj->can('callback')) { + return $self->debugobj->callback(@_); + } } +=head2 disconnect + +Disconnect the L handle, performing a rollback first if the +database is not in C mode. + +=cut + sub disconnect { my ($self) = @_; @@ -338,8 +389,14 @@ sub disconnect { } } -sub connected { - my ($self) = @_; +=head2 connected + +Check if the L handle is connected. Returns true if the handle +is connected. + +=cut + +sub connected { my ($self) = @_; if(my $dbh = $self->_dbh) { if(defined $self->_conn_tid && $self->_conn_tid != threads->tid) { @@ -357,6 +414,13 @@ sub connected { return 0; } +=head2 ensure_connected + +Check whether the database handle is connected - if not then make a +connection. + +=cut + sub ensure_connected { my ($self) = @_; @@ -378,10 +442,23 @@ sub dbh { return $self->_dbh; } +sub _sql_maker_args { + my ($self) = @_; + + return ( limit_dialect => $self->dbh ); +} + +=head2 sql_maker + +Returns a C object - normally an object of class +C. + +=cut + sub sql_maker { my ($self) = @_; unless ($self->_sql_maker) { - $self->_sql_maker(new DBIC::SQL::Abstract( limit_dialect => $self->dbh )); + $self->_sql_maker(new DBIC::SQL::Abstract( $self->_sql_maker_args )); } return $self->_sql_maker; } @@ -421,18 +498,17 @@ sub _populate_dbh { my ($self) = @_; my @info = @{$self->_connect_info || []}; $self->_dbh($self->_connect(@info)); - my $dbh = $self->_dbh; - my $driver = $dbh->{Driver}->{Name}; - if ( $driver eq 'ODBC' and $dbh->get_info(17) =~ m{^DB2/400} ) { - $driver = 'ODBC400'; - } + 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 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(); $self->_dbh->do($sql_statement); + $self->debugobj->query_end($sql_statement) if $self->debug(); } $self->_conn_pid($$); @@ -484,7 +560,7 @@ sub txn_begin { if ($self->{transaction_depth}++ == 0) { my $dbh = $self->dbh; if ($dbh->{AutoCommit}) { - $self->debugfh->print("BEGIN WORK\n") + $self->debugobj->txn_begin() if ($self->debug); $dbh->begin_work; } @@ -499,19 +575,19 @@ Issues a commit against the current dbh. sub txn_commit { my $self = shift; + my $dbh = $self->dbh; if ($self->{transaction_depth} == 0) { - my $dbh = $self->dbh; unless ($dbh->{AutoCommit}) { - $self->debugfh->print("COMMIT\n") + $self->debugobj->txn_commit() if ($self->debug); $dbh->commit; } } else { if (--$self->{transaction_depth} == 0) { - $self->debugfh->print("COMMIT\n") + $self->debugobj->txn_commit() if ($self->debug); - $self->dbh->commit; + $dbh->commit; } } } @@ -528,19 +604,19 @@ sub txn_rollback { my $self = shift; eval { + my $dbh = $self->dbh; if ($self->{transaction_depth} == 0) { - my $dbh = $self->dbh; unless ($dbh->{AutoCommit}) { - $self->debugfh->print("ROLLBACK\n") + $self->debugobj->txn_rollback() if ($self->debug); $dbh->rollback; } } else { if (--$self->{transaction_depth} == 0) { - $self->debugfh->print("ROLLBACK\n") + $self->debugobj->txn_rollback() if ($self->debug); - $self->dbh->rollback; + $dbh->rollback; } else { die DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION->new; @@ -563,17 +639,19 @@ sub _execute { unshift(@bind, @$extra_bind) if $extra_bind; if ($self->debug) { my @debug_bind = map { defined $_ ? qq{'$_'} : q{'NULL'} } @bind; - $self->debugfh->print("$sql: " . join(', ', @debug_bind) . "\n"); + $self->debugobj->query_start($sql, @debug_bind); } my $sth = eval { $self->sth($sql,$op) }; if (!$sth || $@) { - $self->throw_exception('no sth generated via sql (' . ($@ || $self->_dbh->errstr) . "): $sql"); + $self->throw_exception( + 'no sth generated via sql (' . ($@ || $self->_dbh->errstr) . "): $sql" + ); } - @bind = map { ref $_ ? ''.$_ : $_ } @bind; # stringify args my $rv; if ($sth) { + my $time = time(); $rv = eval { $sth->execute(@bind) }; if ($@ || !$rv) { @@ -582,6 +660,10 @@ sub _execute { } else { $self->throw_exception("'$sql' did not generate a statement."); } + if ($self->debug) { + my @debug_bind = map { defined $_ ? qq{`$_'} : q{`NULL'} } @bind; + $self->debugobj->query_end($sql, @debug_bind); + } return (wantarray ? ($rv, $sth, @bind) : $rv); } @@ -621,17 +703,32 @@ sub _select { $self->sql_maker->_default_limit_syntax eq "GenericSubQ") { $attrs->{software_limit} = 1; } else { + $self->throw_exception("rows attribute must be positive if present") + if (defined($attrs->{rows}) && !($attrs->{rows} > 0)); push @args, $attrs->{rows}, $attrs->{offset}; } 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 { @@ -642,6 +739,12 @@ sub select_single { return @row; } +=head2 sth + +Returns a L sth (statement handle) for the supplied SQL. + +=cut + sub sth { my ($self, $sql) = @_; # 3 is the if_active parameter which avoids active sth re-use @@ -675,8 +778,10 @@ sub columns_info_for { $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{$info->{COLUMN_NAME}} = \%column_info; + $result{$col_name} = \%column_info; } }; $dbh->{RaiseError} = $old_raise_err; @@ -711,6 +816,12 @@ sub columns_info_for { return \%result; } +=head2 last_insert_id + +Return the row id of the last insert. + +=cut + sub last_insert_id { my ($self, $row) = @_; @@ -718,8 +829,30 @@ sub last_insert_id { } +=head2 sqlt_type + +Returns the database driver name. + +=cut + sub sqlt_type { shift->dbh->{Driver}->{Name} } +=head2 create_ddl_dir (EXPERIMENTAL) + +=over 4 + +=item Arguments: $schema \@databases, $version, $directory, $sqlt_args + +=back + +Creates an SQL file based on the Schema, for each of the specified +database types, in the given directory. + +Note that this feature is currently EXPERIMENTAL and may not work correctly +across all databases, or fully handle complex relationships. + +=cut + sub create_ddl_dir { my ($self, $schema, $databases, $version, $dir, $sqltargs) = @_; @@ -772,6 +905,13 @@ sub create_ddl_dir } +=head2 deployment_statements + +Create the statements for L and +L. + +=cut + sub deployment_statements { my ($self, $schema, $type, $version, $dir, $sqltargs) = @_; $type ||= $self->sqlt_type; @@ -806,6 +946,14 @@ 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, $sqltargs) ) { @@ -815,16 +963,77 @@ sub deploy { # next if($_ =~ /^DROP/m); next if($_ =~ /^BEGIN TRANSACTION/m); next if($_ =~ /^COMMIT/m); - $self->debugfh->print("$_\n") if $self->debug; + $self->debugobj->query_begin($_) if $self->debug; $self->dbh->do($_) or warn "SQL was:\n $_"; + $self->debugobj->query_end($_) if $self->debug; } } } +=head2 datetime_parser + +Returns the datetime parser class + +=cut + +sub datetime_parser { + my $self = shift; + return $self->{datetime_parser} ||= $self->build_datetime_parser(@_); +} + +=head2 datetime_parser_type + +Defines (returns) the datetime parser class - currently hardwired to +L + +=cut + +sub datetime_parser_type { "DateTime::Format::MySQL"; } + +=head2 build_datetime_parser + +See L + +=cut + +sub build_datetime_parser { + my $self = shift; + my $type = $self->datetime_parser_type(@_); + eval "use ${type}"; + $self->throw_exception("Couldn't load ${type}: $@") if $@; + return $type; +} + sub DESTROY { shift->disconnect } 1; +=head1 SQL METHODS + +The module defines a set of methods within the DBIC::SQL::Abstract +namespace. These build on L to provide the +SQL query functions. + +The following methods are extended:- + +=over 4 + +=item delete + +=item insert + +=item select + +=item update + +=item limit_dialect + +=item quote_char + +=item name_sep + +=back + =head1 ENVIRONMENT VARIABLES =head2 DBIX_CLASS_STORAGE_DBI_DEBUG @@ -835,6 +1044,11 @@ 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. + =head1 AUTHORS Matt S. Trout