X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FStorage%2FDBI%2FSybase.pm;h=abf15bfbcfd2b243b20c0a7adc7f27ae5050e5a3;hb=64ae166780d0cb2b9577e506da9b9b240c146d20;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..abf15bf 100644 --- a/lib/DBIx/Class/Storage/DBI/Sybase.pm +++ b/lib/DBIx/Class/Storage/DBI/Sybase.pm @@ -2,45 +2,20 @@ package DBIx::Class::Storage::DBI::Sybase; use strict; use warnings; +use Try::Tiny; +use namespace::clean; -use base qw/ - DBIx::Class::Storage::DBI::Sybase::Common - DBIx::Class::Storage::DBI::AutoCast -/; -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/ -); +use base qw/DBIx::Class::Storage::DBI/; =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. +DBIx::Class::Storage::DBI::Sybase - Base class for drivers using +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); -} + local $dbh->{RaiseError} = 1; + local $dbh->{PrintError} = 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; + if ($dbh->{syb_no_child_con}) { +# if extra connections are not allowed, then ->ping is reliable + return try { $dbh->ping } catch { 0; }; } - 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, @_); - } + return try { +# XXX if the main connection goes stale, does opening another for this statement +# really determine anything? + $dbh->do('select 1'); + 1; + } catch { + 0; }; - - $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) = @_; +sub _set_max_connect { + my $self = shift; + my $val = shift || 256; - my (@primary_cols) = $source->primary_columns; + my $dsn = $self->_dbi_connect_info->[0]; - croak "Cannot update TEXT/IMAGE column(s) without a primary key" - unless @primary_cols; + return if ref($dsn) eq 'CODE'; -# 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); + if ($dsn !~ /maxConnect=/) { + $self->_dbi_connect_info->[0] = "$dsn;maxConnect=$val"; + my $connected = defined $self->_dbh; + $self->disconnect; + $self->ensure_connected if $connected; } } -sub _insert_blobs { - my ($self, $source, $blob_cols, $row) = @_; - my $dbh = $self->dbh; +=head2 using_freetds - my $table = $source->from; +Whether or not L was compiled against FreeTDS. If false, it means +the Sybase OpenClient libraries were used. - my %row = %$row; - my (@primary_cols) = $source->primary_columns; - - croak "Cannot update TEXT/IMAGE column(s) without a primary key" - unless @primary_cols; +=cut - 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"; - } - } +sub using_freetds { + my $self = shift; - 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; - } - } - } + return $self->_get_dbh->{syb_oc_version} =~ /freetds/i; } -=head2 connect_call_datetime_setup +=head2 set_textsize -Used as: +When using FreeTDS and/or MSSQL, C<< $dbh->{LongReadLen} >> is not available, +use this function instead. It does: - on_connect_call => 'datetime_setup' + $dbh->do("SET TEXTSIZE $bytes"); -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. +Takes the number of bytes, or uses the C value from your +L if omitted, lastly falls +back to the C<32768> which is the L default. =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 { +sub set_textsize { my $self = shift; - if ($self->using_freetds) { - $self->_dbh->do('ROLLBACK'); - } - return $self->next::method(@_); -} - -# savepoint support using ASE syntax + my $text_size = + shift + || + try { $self->_dbi_connect_info->[-1]->{LongReadLen} } + || + 32768; # the DBD::Sybase default -sub _svp_begin { - my ($self, $name) = @_; + return unless defined $text_size; - $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"); + $self->_dbh->do("SET TEXTSIZE $text_size"); } 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: - - $schema->storage->dbh->do("SET TEXTSIZE $bytes"); - -or - - $schema->storage->set_textsize($bytes); - -instead. - -However, the C you pass in -L is used to execute the equivalent -C command on connection. - -See L for a L -setting you need to work with C columns. - =head1 AUTHORS See L. @@ -639,4 +135,3 @@ See L. You may distribute this code under the same terms as Perl itself. =cut -# vim:sts=2 sw=2: