X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FStorage%2FDBI%2FSybase.pm;h=41b0c81bb297e2557717b25f779ed064c3832335;hb=26283ee38f220f6c6bae720ea5a189c9c0f47f6f;hp=155bffda0235deac99f58d9e88e0546822c45551;hpb=07a5866eb23a8af07aa040d2fcc8ff58db34cc17;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/Storage/DBI/Sybase.pm b/lib/DBIx/Class/Storage/DBI/Sybase.pm index 155bffd..41b0c81 100644 --- a/lib/DBIx/Class/Storage/DBI/Sybase.pm +++ b/lib/DBIx/Class/Storage/DBI/Sybase.pm @@ -4,639 +4,63 @@ use strict; use warnings; use base qw/ - DBIx::Class::Storage::DBI::Sybase::Common - DBIx::Class::Storage::DBI::AutoCast + DBIx::Class::Storage::DBI::Sybase::Base + DBIx::Class::Storage::DBI::NoBindVars /; use mro 'c3'; -use Carp::Clan qw/^DBIx::Class/; -use List::Util (); - -__PACKAGE__->mk_group_accessors('simple' => - qw/_identity _blob_log_on_update insert_txn/ -); - -=head1 NAME - -DBIx::Class::Storage::DBI::Sybase - Sybase support for DBIx::Class - -=head1 SYNOPSIS - -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 DESCRIPTION - -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. - -With this driver there is unfortunately no way to get the C -without doing a C when placeholders are enabled. - -When using C transactions are -disabled. - -To turn off transactions for inserts (for an application that doesn't need -concurrency, or a loader, for example) use this setting in -L, - - on_connect_call => ['unsafe_insert'] - -To manipulate this setting at runtime, use: - - $schema->storage->insert_txn(0); # 1 to re-enable - -=cut - -sub connect_call_unsafe_insert { - my $self = shift; - $self->insert_txn(0); -} - -sub _is_lob_type { - my $self = shift; - my $type = shift; - $type && $type =~ /(?:text|image|lob|bytea|binary|memo)/i; -} - -# The select-piggybacking-on-insert trick stolen from mssql -sub _prep_for_execute { - my $self = shift; - my ($op, $extra_bind, $ident, $args) = @_; - - my ($sql, $bind) = $self->next::method (@_); - - if ($op eq 'insert') { - my $table = $ident->from; - - my $bind_info = $self->_resolve_column_info( - $ident, [map $_->[0], @{$bind}] - ); - my $identity_col = -List::Util::first { $bind_info->{$_}{is_auto_increment} } (keys %$bind_info); - - if ($identity_col) { - $sql = -"SET IDENTITY_INSERT $table ON\n" . -"$sql\n" . -"SET IDENTITY_INSERT $table OFF" - } else { - $identity_col = List::Util::first { - $ident->column_info($_)->{is_auto_increment} - } $ident->columns; - } - - if ($identity_col) { - $sql = - "$sql\n" . - $self->_fetch_identity_sql($ident, $identity_col); - } - } - - return ($sql, $bind); -} - -# Stolen from SQLT, with some modifications. This will likely change when the -# SQLT Sybase stuff is redone/fixed-up. -my %TYPE_MAPPING = ( - number => 'numeric', - money => 'money', - varchar => 'varchar', - varchar2 => 'varchar', - timestamp => 'datetime', - text => 'varchar', - real => 'double precision', - comment => 'text', - bit => 'bit', - tinyint => 'smallint', - float => 'double precision', - serial => 'numeric', - bigserial => 'numeric', - boolean => 'varchar', - long => 'varchar', -); - -sub _native_data_type { - my ($self, $type) = @_; - - $type = lc $type; - $type =~ s/ identity//; - - return uc($TYPE_MAPPING{$type} || $type); -} - -sub _fetch_identity_sql { - my ($self, $source, $col) = @_; - - return "SELECT MAX($col) FROM ".$source->from; -} - -sub _execute { - my $self = shift; - my ($op) = @_; - - my ($rv, $sth, @bind) = $self->dbh_do($self->can('_dbh_execute'), @_); - - if ($op eq 'insert') { - $self->_identity($sth->fetchrow_array); - $sth->finish; - } - - return wantarray ? ($rv, $sth, @bind) : $rv; -} - -sub last_insert_id { shift->_identity } - -# override to handle TEXT/IMAGE and to do a transaction if necessary -sub insert { - my ($self, $source, $to_insert) = splice @_, 0, 3; - my $dbh = $self->_dbh; - - my $blob_cols = $self->_remove_blob_cols($source, $to_insert); - - my $need_last_insert_id = 0; - - my ($identity_col) = - map $_->[0], - grep $_->[1]{is_auto_increment}, - map [ $_, $source->column_info($_) ], - $source->columns; - - $need_last_insert_id = 1 - if $identity_col && (not exists $to_insert->{$identity_col}); - -# We have to do the insert in a transaction to avoid race conditions with the -# SELECT MAX(COL) identity method used when placeholders are enabled. - my $updated_cols = do { - if ($need_last_insert_id && $self->insert_txn && - (not $self->{transaction_depth})) { - my $args = \@_; - my $method = $self->next::can; - $self->txn_do( - sub { $self->$method($source, $to_insert, @$args) } - ); - } else { - $self->next::method($source, $to_insert, @_); - } - }; - - $self->_insert_blobs($source, $blob_cols, $to_insert) if %$blob_cols; - - return $updated_cols; -} - -sub update { - my ($self, $source) = splice @_, 0, 2; - my ($fields, $where) = @_; - my $wantarray = wantarray; - - my $blob_cols = $self->_remove_blob_cols($source, $fields); - - my @res; - if ($wantarray) { - @res = $self->next::method($source, @_); - } else { - $res[0] = $self->next::method($source, @_); - } - - $self->_update_blobs($source, $blob_cols, $where) if %$blob_cols; - - return $wantarray ? @res : $res[0]; -} - -sub _remove_blob_cols { - my ($self, $source, $fields) = @_; - - my %blob_cols; - - for my $col (keys %$fields) { - if ($self->_is_lob_type($source->column_info($col)->{data_type})) { - $blob_cols{$col} = delete $fields->{$col}; - $fields->{$col} = \"''"; } - } - - return \%blob_cols; } -sub _update_blobs { - my ($self, $source, $blob_cols, $where) = @_; - - my (@primary_cols) = $source->primary_columns; - - croak "Cannot update TEXT/IMAGE column(s) without a primary key" - unless @primary_cols; - -# check if we're updating a single row by PK - my $pk_cols_in_where = 0; - for my $col (@primary_cols) { - $pk_cols_in_where++ if defined $where->{$col}; - } - my @rows; - - if ($pk_cols_in_where == @primary_cols) { - my %row_to_update; - @row_to_update{@primary_cols} = @{$where}{@primary_cols}; - @rows = \%row_to_update; - } else { - my $rs = $source->resultset->search( - $where, - { - result_class => 'DBIx::Class::ResultClass::HashRefInflator', - select => \@primary_cols - } - ); - @rows = $rs->all; # statement must finish - } - - for my $row (@rows) { - $self->_insert_blobs($source, $blob_cols, $row); - } -} - -sub _insert_blobs { - my ($self, $source, $blob_cols, $row) = @_; - my $dbh = $self->dbh; - - my $table = $source->from; - - my %row = %$row; - my (@primary_cols) = $source->primary_columns; - - croak "Cannot update TEXT/IMAGE column(s) without a primary key" - unless @primary_cols; - - if ((grep { defined $row{$_} } @primary_cols) != @primary_cols) { - if (@primary_cols == 1) { - my $col = $primary_cols[0]; - $row{$col} = $self->last_insert_id($source, $col); - } else { - croak "Cannot update TEXT/IMAGE column(s) without primary key values"; - } - } - - for my $col (keys %$blob_cols) { - my $blob = $blob_cols->{$col}; - - my %where = map { ($_, $row{$_}) } @primary_cols; - my $cursor = $source->resultset->search(\%where, { - select => [$col] - })->cursor; - $cursor->next; - my $sth = $cursor->sth; - - eval { - do { - $sth->func('CS_GET', 1, 'ct_data_info') or die $sth->errstr; - } while $sth->fetch; - - $sth->func('ct_prepare_send') or die $sth->errstr; - - my $log_on_update = $self->_blob_log_on_update; - $log_on_update = 1 if not defined $log_on_update; - - $sth->func('CS_SET', 1, { - total_txtlen => length($blob), - log_on_update => $log_on_update - }, 'ct_data_info') or die $sth->errstr; - - $sth->func($blob, length($blob), 'ct_send_data') or die $sth->errstr; - - $sth->func('ct_finish_send') or die $sth->errstr; - }; - my $exception = $@; - $sth->finish if $sth; - if ($exception) { - if ($self->using_freetds) { - croak -"TEXT/IMAGE operation failed, probably because you're using FreeTDS: " . -$exception; - } else { - croak $exception; - } - } - } -} - -=head2 connect_call_datetime_setup - -Used as: - - on_connect_call => 'datetime_setup' - -In L to set: - - $dbh->syb_date_fmt('ISO_strict'); # output fmt: 2004-08-21T14:36:48.080Z - $dbh->do('set dateformat mdy'); # input fmt: 08/13/1979 18:08:55.080 - -On connection for use with L, using -L, which you will need to install. - -This works for both C and C columns, although -C columns only have minute precision. - -=cut - -{ - my $old_dbd_warned = 0; - - sub connect_call_datetime_setup { - my $self = shift; - my $dbh = $self->_dbh; - - if ($dbh->can('syb_date_fmt')) { -# amazingly, this works with FreeTDS - $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 datetime_parser_type { "DateTime::Format::Sybase" } - -# ->begin_work and such have no effect with FreeTDS but we run them anyway to -# let the DBD keep any state it needs to. -# -# If they ever do start working, the extra statements will do no harm (because -# Sybase supports nested transactions.) - -sub _dbh_begin_work { - my $self = shift; - $self->next::method(@_); - if ($self->using_freetds) { - $self->dbh->do('BEGIN TRAN'); - } -} - -sub _dbh_commit { - my $self = shift; - if ($self->using_freetds) { - $self->_dbh->do('COMMIT'); - } - return $self->next::method(@_); -} - -sub _dbh_rollback { - my $self = shift; - if ($self->using_freetds) { - $self->_dbh->do('ROLLBACK'); - } - return $self->next::method(@_); -} - -# savepoint support using ASE syntax - -sub _svp_begin { - my ($self, $name) = @_; - - $self->dbh->do("SAVE TRANSACTION $name"); -} - -# A new SAVE TRANSACTION with the same name releases the previous one. -sub _svp_release { 1 } - -sub _svp_rollback { - my ($self, $name) = @_; - - $self->dbh->do("ROLLBACK TRANSACTION $name"); +sub _dbh_last_insert_id { + my ($self, $dbh, $source, $col) = @_; + return ($dbh->selectrow_array('select @@identity'))[0]; } 1; -=head1 Schema::Loader Support - -There is an experimental branch of L that will -allow you to dump a schema from most (if not all) versions of Sybase. - -It is available via subversion from: - - http://dev.catalyst.perl.org/repos/bast/branches/DBIx-Class-Schema-Loader/current/ - -=head1 FreeTDS - -This driver supports L compiled against FreeTDS -(L) to the best of our ability, however it is -recommended that you recompile L against the Sybase Open Client -libraries. They are a part of the Sybase ASE distribution: - -The Open Client FAQ is here: -L. - -Sybase ASE for Linux (which comes with the Open Client libraries) may be -downloaded here: L. - -To see if you're using FreeTDS check C<< $schema->storage->using_freetds >>, or run: - - perl -MDBI -le 'my $dbh = DBI->connect($dsn, $user, $pass); print $dbh->{syb_oc_version}' - -Some versions of the libraries involved will not support placeholders, in which -case the storage will be reblessed to -L. - -In some configurations, placeholders will work but will throw implicit type -conversion errors for anything that's not expecting a string. In such a case, -the C option from L is -automatically set, which you may enable on connection with -L. The type info -for the Cs is taken from the L -definitions in your Result classes, and are mapped to a Sybase type (if it isn't -already) using a mapping based on L. - -In other configurations, placeholers will work just as they do with the Sybase -Open Client libraries. - -Inserts or updates of TEXT/IMAGE columns will B work with FreeTDS. - -=head1 MAXIMUM CONNECTIONS - -The TDS protocol makes separate connections to the server for active statements -in the background. By default the number of such connections is limited to 25, -on both the client side and the server side. - -This is a bit too low for a complex L application, so on connection -the client side setting is set to C<256> (see L.) You -can override it to whatever setting you like in the DSN. - -See -L -for information on changing the setting on the server side. - -=head1 DATES - -See L to setup date formats -for L. - -=head1 TEXT/IMAGE COLUMNS - -L compiled with FreeTDS will B allow you to insert or update -C columns. - -Setting C<< $dbh->{LongReadLen} >> will also not work with FreeTDS use either: +=head1 NAME - $schema->storage->dbh->do("SET TEXTSIZE $bytes"); +DBIx::Class::Storage::DBI::Sybase - Storage::DBI subclass for Sybase -or +=head1 SYNOPSIS - $schema->storage->set_textsize($bytes); +This subclass supports L for real Sybase databases. If +you are using an MSSQL database via L, see +L. -instead. +=head1 CAVEATS -However, the C you pass in -L is used to execute the equivalent -C command on connection. +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. -See L for a L -setting you need to work with C columns. +More importantly this means that caching of prepared statements is explicitly +disabled, as the interpolation renders it useless. =head1 AUTHORS -See L. +Brandon L Black + +Justin Hunter =head1 LICENSE You may distribute this code under the same terms as Perl itself. =cut -# vim:sts=2 sw=2: