From: Rafael Kitover Date: Fri, 12 Jun 2009 00:05:30 +0000 (+0000) Subject: Merge 'trunk' into 'sybase' X-Git-Tag: v0.08112~14^2~134 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=bb3e90c8291b0927ae3e39f29c01c5911374b3e3;hp=46e3af47978fa1c4ca72de368879486e21ff44df;p=dbsrgits%2FDBIx-Class.git Merge 'trunk' into 'sybase' r5443@hlagh (orig r6597): ribasushi | 2009-06-10 05:48:39 -0700 Adjust changelog r5446@hlagh (orig r6600): ribasushi | 2009-06-10 06:50:43 -0700 Release 0.08104 r5467@hlagh (orig r6614): ribasushi | 2009-06-11 05:29:48 -0700 Move around inflation tests r5468@hlagh (orig r6615): ribasushi | 2009-06-11 05:32:07 -0700 explicitly remove manifest on author mode make r5469@hlagh (orig r6616): ribasushi | 2009-06-11 06:02:41 -0700 IC::DT changes: Switch SQLite storage to DT::F::SQLite Fix exception when undef_if_invalid and timezone are both set on a column Split t/89inflate_datetime into separate tests Adjust makefile author dependencies r5470@hlagh (orig r6617): ribasushi | 2009-06-11 06:07:41 -0700 Move file_column test to inflate/ too r5472@hlagh (orig r6620): ribasushi | 2009-06-11 07:16:20 -0700 r5713@Thesaurus (orig r5712): ribasushi | 2009-03-08 23:53:28 +0100 Branch for datatype-aware updates r6604@Thesaurus (orig r6603): ribasushi | 2009-06-10 18:08:25 +0200 Test for type-aware update r6607@Thesaurus (orig r6606): ribasushi | 2009-06-10 19:57:04 +0200 Datatype aware update works r6609@Thesaurus (orig r6608): ribasushi | 2009-06-10 20:06:40 +0200 Whoops r6614@Thesaurus (orig r6613): ribasushi | 2009-06-11 09:23:54 +0200 Add attribute doc r6620@Thesaurus (orig r6619): ribasushi | 2009-06-11 16:15:53 +0200 Use equality, not comparison r5474@hlagh (orig r6622): ribasushi | 2009-06-11 07:21:53 -0700 Changes r5486@hlagh (orig r6635): ribasushi | 2009-06-11 12:29:10 -0700 Release 0.08105 r5487@hlagh (orig r6636): frew | 2009-06-11 13:02:11 -0700 Escape filename so that t\9... won't look like octal r5488@hlagh (orig r6637): ribasushi | 2009-06-11 13:09:55 -0700 Adjust tests for the DT::F::MySQL -> DT::F::SQLite change r5489@hlagh (orig r6638): arcanez | 2009-06-11 13:29:53 -0700 cookbook tweak for count distinct r5490@hlagh (orig r6639): ribasushi | 2009-06-11 14:39:12 -0700 Factor out as_query properly r5491@hlagh (orig r6640): ribasushi | 2009-06-11 14:50:41 -0700 Release 0.08106 r5493@hlagh (orig r6642): caelum | 2009-06-11 16:48:28 -0700 add support for DBICTEST_MSSQL_PERL5LIB to 74mssql.t --- diff --git a/Makefile.PL b/Makefile.PL index 3713df1..a9ad2d4 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -97,6 +97,8 @@ my %force_requires_if_author = ( 'namespace::clean' => 0.11, 'Hash::Merge', => 0.11, + # t/746sybase.t + 'DateTime::Format::Strptime' => 0, ); if ($Module::Install::AUTHOR) { diff --git a/lib/DBIx/Class/ResultSource/Table.pm b/lib/DBIx/Class/ResultSource/Table.pm index cf263d1..90c9f9a 100644 --- a/lib/DBIx/Class/ResultSource/Table.pm +++ b/lib/DBIx/Class/ResultSource/Table.pm @@ -26,6 +26,9 @@ Returns the FROM entry for the table (i.e. the table name) =cut +use overload + '""' => \&from; + sub from { shift->name; } 1; diff --git a/lib/DBIx/Class/Storage/DBI.pm b/lib/DBIx/Class/Storage/DBI.pm index 717177c..7b392cb 100644 --- a/lib/DBIx/Class/Storage/DBI.pm +++ b/lib/DBIx/Class/Storage/DBI.pm @@ -927,6 +927,22 @@ sub _fix_bind_params { } @bind; } +sub _flatten_bind_params { + my ($self, @bind) = @_; + + ### Turn @bind from something like this: + ### ( [ "artist", 1 ], [ "cdid", 1, 3 ] ) + ### to this: + ### ( 1, 1, 3 ) + return + map { + if ( defined( $_ && $_->[1] ) ) { + @{$_}[ 1 .. $#$_ ]; + } + else { undef; } + } @bind; +} + sub _query_start { my ( $self, $sql, @bind ) = @_; @@ -1295,13 +1311,20 @@ sub _resolve_ident_sources { return $alias2source; } -sub count { +sub _copy_attributes_for_count { my ($self, $source, $attrs) = @_; + my %attrs = %$attrs; + + # take off any column specs, any pagers, record_filter is cdbi, and no point of ordering a count + delete @attrs{qw/select as rows offset page order_by record_filter/}; - my $tmp_attrs = { %$attrs }; + return \%attrs; +} + +sub count { + my ($self, $source, $attrs) = @_; - # take off any pagers, record_filter is cdbi, and no point of ordering a count - delete $tmp_attrs->{$_} for (qw/select as rows offset page order_by record_filter/); + my $tmp_attrs = $self->_copy_attributes_for_count($source, $attrs); # overwrite the selector $tmp_attrs->{select} = { count => '*' }; diff --git a/lib/DBIx/Class/Storage/DBI/NoBindVars.pm b/lib/DBIx/Class/Storage/DBI/NoBindVars.pm index 7027ad6..3256846 100644 --- a/lib/DBIx/Class/Storage/DBI/NoBindVars.pm +++ b/lib/DBIx/Class/Storage/DBI/NoBindVars.pm @@ -4,6 +4,8 @@ use strict; use warnings; use base 'DBIx::Class::Storage::DBI'; +use Scalar::Util (); +use Carp::Clan qw/^DBIx::Class/; =head1 NAME @@ -39,7 +41,7 @@ Manually subs in the values for the usual C placeholders. sub _prep_for_execute { my $self = shift; - my ($op, $extra_bind, $ident) = @_; + my ($op, $extra_bind, $ident, $args) = @_; my ($sql, $bind) = $self->next::method(@_); @@ -48,14 +50,25 @@ sub _prep_for_execute { my @sql_part = split /\?/, $sql; my $new_sql; + my $alias2src = $self->_resolve_ident_sources($ident); + foreach my $bound (@$bind) { my $col = shift @$bound; - my $datatype = 'FIXME!!!'; + + my $name_sep = $self->_sql_maker_opts->{name_sep} || '.'; + + $col =~ s/^([^\Q${name_sep}\E]*)\Q${name_sep}\E//; + my $alias = $1 || 'me'; + + my $rsrc = $alias2src->{$alias}; + + my $datatype = $rsrc && $rsrc->column_info($col)->{data_type}; + foreach my $data (@$bound) { if(ref $data) { $data = ''.$data; } - $data = $self->_dbh->quote($data); + $data = $self->_dbh->quote($data) if $self->should_quote_data_type($datatype, $data); $new_sql .= shift(@sql_part) . $data; } } @@ -64,6 +77,25 @@ sub _prep_for_execute { return ($new_sql, []); } +=head2 should_quote_data_type + +This method is called by L for every column in +order to determine if its value should be quoted or not. The arguments +are the current column data type and the actual bind value. The return +value is interpreted as: true - do quote, false - do not quote. You should +override this in you Storage::DBI:: subclass, if your RDBMS +does not like quotes around certain datatypes (e.g. Sybase and integer +columns). The default method always returns true (do quote). + + WARNING!!! + + Always validate that the bind-value is valid for the current datatype. + Otherwise you may very well open the door to SQL injection attacks. + +=cut + +sub should_quote_data_type { 1 } + =head1 AUTHORS Brandon Black diff --git a/lib/DBIx/Class/Storage/DBI/Sybase.pm b/lib/DBIx/Class/Storage/DBI/Sybase.pm index ec4fcf7..4ffaae2 100644 --- a/lib/DBIx/Class/Storage/DBI/Sybase.pm +++ b/lib/DBIx/Class/Storage/DBI/Sybase.pm @@ -3,27 +3,104 @@ package DBIx::Class::Storage::DBI::Sybase; use strict; use warnings; -use base qw/DBIx::Class::Storage::DBI::NoBindVars/; +use Class::C3; +use base qw/DBIx::Class::Storage::DBI/; + +use Carp::Clan qw/^DBIx::Class/; sub _rebless { - my $self = shift; + my $self = shift; + + if (ref($self) eq 'DBIx::Class::Storage::DBI::Sybase') { + my $dbtype = eval { + @{$self->dbh->selectrow_arrayref(qq{sp_server_info \@attribute_id=1})}[2] + } || ''; + + my $exception = $@; + $dbtype =~ s/\W/_/gi; + my $subclass = "DBIx::Class::Storage::DBI::Sybase::${dbtype}"; + + if (!$exception && $dbtype && $self->load_optional_class($subclass)) { + bless $self, $subclass; + $self->_rebless; + } else { + # real Sybase + if (not $self->dbh->{syb_dynamic_supported}) { + bless $self, 'DBIx::Class::Storage:DBI::Sybase::NoBindVars'; + $self->_rebless; + } + $self->_init_date_fmt; + } + } +} - my $dbtype = eval { @{$self->dbh->selectrow_arrayref(qq{sp_server_info \@attribute_id=1})}[2] }; - unless ( $@ ) { - $dbtype =~ s/\W/_/gi; - my $subclass = "DBIx::Class::Storage::DBI::Sybase::${dbtype}"; - if ($self->load_optional_class($subclass) && !$self->isa($subclass)) { - bless $self, $subclass; - $self->_rebless; - } +sub _populate_dbh { + my $self = shift; + $self->next::method(@_); + $self->_init_date_fmt; + 1; +} + +{ + my $old_dbd_warned = 0; + + sub _init_date_fmt { + my $self = shift; + my $dbh = $self->_dbh; + + if ($dbh->can('syb_date_fmt')) { + $dbh->syb_date_fmt('ISO_strict'); + } elsif (not $old_dbd_warned) { + carp "Your DBD::Sybase is too old to support ". + "DBIx::Class::InflateColumn::DateTime, please upgrade!"; + $old_dbd_warned = 1; } + + $dbh->do('set dateformat mdy'); + + 1; + } } sub _dbh_last_insert_id { - my ($self, $dbh, $source, $col) = @_; - return ($dbh->selectrow_array('select @@identity'))[0]; + my ($self, $dbh, $source, $col) = @_; + + # sorry, there's no other way! + my $sth = $dbh->prepare_cached("select max($col) from ".$source->from); + return ($dbh->selectrow_array($sth))[0]; +} + +sub count { + my $self = shift; + my ($source, $attrs) = @_; + + if (not exists $attrs->{rows}) { + return $self->next::method(@_); + } + + my $offset = $attrs->{offset} || 0; + my $total = $attrs->{rows} + $offset; + + my $new_attrs = $self->_copy_attributes_for_count($source, $attrs); + + my $first_pk = ($source->primary_columns)[0]; + + $new_attrs->{select} = $first_pk ? "me.$first_pk" : 1; + + my $tmp_rs = $source->resultset_class->new($source, $new_attrs); + + $self->dbh->{syb_rowcount} = $total; + + my $count = 0; + $count++ while $tmp_rs->cursor->next; + + $self->dbh->{syb_rowcount} = 0; + + return $count - $offset; } +sub datetime_parser_type { "DBIx::Class::Storage::DBI::Sybase::DateTime" } + 1; =head1 NAME @@ -32,27 +109,40 @@ DBIx::Class::Storage::DBI::Sybase - Storage::DBI subclass for Sybase =head1 SYNOPSIS -This subclass supports L for real Sybase databases. If -you are using an MSSQL database via L, see -L. +This subclass supports L for real Sybase databases. If you are +using an MSSQL database via L, your storage will be reblessed to +L. -=head1 CAVEATS +=head1 DESCRIPTION -This storage driver uses L as a base. -This means that bind variables will be interpolated (properly quoted of course) -into the SQL query itself, without using bind placeholders. +If your version of Sybase does not support placeholders, then your storage +will be reblessed to L. You can +also enable that driver explicitly, see the documentation for more details. -More importantly this means that caching of prepared statements is explicitly -disabled, as the interpolation renders it useless. +With this driver there is unfortunately no way to get the C +without doing a C will work +for obtainging the last insert id of an C column, instead of having to +do C