switch to ::DBI::AutoCast
[dbsrgits/DBIx-Class-Historic.git] / lib / DBIx / Class / Storage / DBI / Sybase.pm
CommitLineData
3885cff6 1package DBIx::Class::Storage::DBI::Sybase;
2
3use strict;
4use warnings;
2ad62d97 5
eabab5d0 6use base qw/
2f92e90b 7 DBIx::Class::Storage::DBI::Sybase::Common
07a5866e 8 DBIx::Class::Storage::DBI::AutoCast
eabab5d0 9/;
2ad62d97 10use mro 'c3';
6b1f5ef7 11use Carp::Clan qw/^DBIx::Class/;
289877b0 12use List::Util ();
6b1f5ef7 13
285baccb 14__PACKAGE__->mk_group_accessors('simple' =>
07a5866e 15 qw/_identity _blob_log_on_update insert_txn/
285baccb 16);
17
98259fe4 18=head1 NAME
19
928f0af8 20DBIx::Class::Storage::DBI::Sybase - Sybase support for DBIx::Class
98259fe4 21
22=head1 SYNOPSIS
23
24This subclass supports L<DBD::Sybase> for real Sybase databases. If you are
25using an MSSQL database via L<DBD::Sybase>, your storage will be reblessed to
26L<DBIx::Class::Storage::DBI::Sybase::Microsoft_SQL_Server>.
27
28=head1 DESCRIPTION
29
30If your version of Sybase does not support placeholders, then your storage
31will be reblessed to L<DBIx::Class::Storage::DBI::Sybase::NoBindVars>. You can
32also enable that driver explicitly, see the documentation for more details.
33
34With this driver there is unfortunately no way to get the C<last_insert_id>
310a0a0a 35without doing a C<SELECT MAX(col)>. This is done safely in a transaction
36(locking the table.) The transaction can be turned off if concurrency is not an
37issue, see L<DBIx::Class::Storage::DBI::Sybase/connect_call_unsafe_insert>.
98259fe4 38
39But your queries will be cached.
40
61cfaef7 41A recommended L<DBIx::Class::Storage::DBI/connect_info> setting:
98259fe4 42
61cfaef7 43 on_connect_call => [['datetime_setup'], ['blob_setup', log_on_update => 0]]
98259fe4 44
45=head1 METHODS
46
47=cut
48
47d9646a 49sub _rebless {
b50a5275 50 my $self = shift;
c5ce7cd6 51
52 if (ref($self) eq 'DBIx::Class::Storage::DBI::Sybase') {
53 my $dbtype = eval {
2eef8633 54 @{$self->_get_dbh->selectrow_arrayref(qq{sp_server_info \@attribute_id=1})}[2]
c5ce7cd6 55 } || '';
56
57 my $exception = $@;
58 $dbtype =~ s/\W/_/gi;
59 my $subclass = "DBIx::Class::Storage::DBI::Sybase::${dbtype}";
60
61 if (!$exception && $dbtype && $self->load_optional_class($subclass)) {
62 bless $self, $subclass;
63 $self->_rebless;
5703eb14 64 } else { # real Sybase
65 my $no_bind_vars = 'DBIx::Class::Storage::DBI::Sybase::NoBindVars';
66
a3a526cc 67# This is reset to 0 in ::NoBindVars, only necessary because we use max(col) to
68# get the identity.
310a0a0a 69 $self->insert_txn(1);
a3a526cc 70
e97a6ee2 71 if ($self->using_freetds) {
a3a526cc 72 carp <<'EOF' unless $ENV{DBIC_SYBASE_FREETDS_NOWARN};
73
74You are using FreeTDS with Sybase.
5703eb14 75
a3a526cc 76We will do our best to support this configuration, but please consider this
77support experimental.
5703eb14 78
a3a526cc 79TEXT/IMAGE columns will definitely not work.
8c4b6c50 80
e97a6ee2 81You are encouraged to recompile DBD::Sybase with the Sybase Open Client libraries
a3a526cc 82instead.
5703eb14 83
84See perldoc DBIx::Class::Storage::DBI::Sybase for more details.
a3a526cc 85
86To turn off this warning set the DBIC_SYBASE_FREETDS_NOWARN environment
87variable.
5703eb14 88EOF
70ced519 89 if (not $self->_typeless_placeholders_supported) {
90 if ($self->_placeholders_supported) {
e97a6ee2 91 $self->auto_cast(1);
a3a526cc 92 } else {
93 $self->ensure_class_loaded($no_bind_vars);
94 bless $self, $no_bind_vars;
95 $self->_rebless;
96 }
97 }
61cfaef7 98
e97a6ee2 99 $self->set_textsize; # based on LongReadLen in connect_info
100
101 } elsif (not $self->dbh->{syb_dynamic_supported}) {
102# not necessarily FreeTDS, but no placeholders nevertheless
61cfaef7 103 $self->ensure_class_loaded($no_bind_vars);
104 bless $self, $no_bind_vars;
105 $self->_rebless;
310a0a0a 106 } elsif (not $self->_typeless_placeholders_supported) {
107# this is highly unlikely, but we check just in case
108 $self->auto_cast(1);
61cfaef7 109 }
110
a3a526cc 111 $self->_set_max_connect(256);
47d9646a 112 }
c5ce7cd6 113 }
b50a5275 114}
115
a3a526cc 116# Make sure we have CHAINED mode turned on if AutoCommit is off in non-FreeTDS
117# DBD::Sybase (since we don't know how DBD::Sybase was compiled.) If however
118# we're using FreeTDS, CHAINED mode turns on an implicit transaction which we
119# only want when AutoCommit is off.
f6de7111 120sub _populate_dbh {
121 my $self = shift;
41c93b1b 122
a3a526cc 123 $self->next::method(@_);
41c93b1b 124
e97a6ee2 125 if (not $self->using_freetds) {
a3a526cc 126 $self->_dbh->{syb_chained_txn} = 1;
127 } else {
128 if ($self->_dbh_autocommit) {
129 $self->_dbh->do('SET CHAINED OFF');
130 } else {
131 $self->_dbh->do('SET CHAINED ON');
132 }
41c93b1b 133 }
134}
135
63d46bb3 136=head2 connect_call_blob_setup
137
138Used as:
139
61cfaef7 140 on_connect_call => [ [ 'blob_setup', log_on_update => 0 ] ]
63d46bb3 141
142Does C<< $dbh->{syb_binary_images} = 1; >> to return C<IMAGE> data as raw binary
143instead of as a hex string.
144
6636ad53 145Recommended.
146
fd5a07e4 147Also sets the C<log_on_update> value for blob write operations. The default is
148C<1>, but C<0> is better if your database is configured for it.
149
150See
151L<DBD::Sybase/Handling_IMAGE/TEXT_data_with_syb_ct_get_data()/syb_ct_send_data()>.
152
63d46bb3 153=cut
154
155sub connect_call_blob_setup {
156 my $self = shift;
fd5a07e4 157 my %args = @_;
63d46bb3 158 my $dbh = $self->_dbh;
159 $dbh->{syb_binary_images} = 1;
fd5a07e4 160
161 $self->_blob_log_on_update($args{log_on_update})
162 if exists $args{log_on_update};
163}
164
310a0a0a 165=head2 connect_call_unsafe_insert
166
167With placeholders enabled, inserts are done in a transaction so that there are
168no concurrency issues with getting the inserted identity value using
169C<SELECT MAX(col)> when placeholders are enabled.
170
171When using C<DBIx::Class::Storage::DBI::Sybase::NoBindVars> transactions are
172disabled.
173
174To turn off transactions for inserts (for an application that doesn't need
175concurrency, or a loader, for example) use this setting in
176L<DBIx::Class::Storage::DBI/connect_info>,
177
178 on_connect_call => ['unsafe_insert']
179
180To manipulate this setting at runtime, use:
181
182 $schema->storage->insert_txn(0); # 1 to re-enable
183
184=cut
185
186sub connect_call_unsafe_insert {
187 my $self = shift;
188 $self->insert_txn(0);
189}
190
fd5a07e4 191sub _is_lob_type {
192 my $self = shift;
5703eb14 193 my $type = shift;
078a332f 194 $type && $type =~ /(?:text|image|lob|bytea|binary|memo)/i;
fd5a07e4 195}
196
07a5866e 197# The select-piggybacking-on-insert trick stolen from mssql
285baccb 198sub _prep_for_execute {
199 my $self = shift;
200 my ($op, $extra_bind, $ident, $args) = @_;
201
202 my ($sql, $bind) = $self->next::method (@_);
203
204 if ($op eq 'insert') {
285baccb 205 my $table = $ident->from;
206
a3a526cc 207 my $bind_info = $self->_resolve_column_info(
208 $ident, [map $_->[0], @{$bind}]
209 );
ec15b3fe 210 my $identity_col =
285baccb 211List::Util::first { $bind_info->{$_}{is_auto_increment} } (keys %$bind_info);
212
213 if ($identity_col) {
ec15b3fe 214 $sql =
215"SET IDENTITY_INSERT $table ON\n" .
216"$sql\n" .
217"SET IDENTITY_INSERT $table OFF"
285baccb 218 } else {
219 $identity_col = List::Util::first {
220 $ident->column_info($_)->{is_auto_increment}
221 } $ident->columns;
222 }
223
224 if ($identity_col) {
285baccb 225 $sql =
285baccb 226 "$sql\n" .
a3a526cc 227 $self->_fetch_identity_sql($ident, $identity_col);
285baccb 228 }
229 }
230
231 return ($sql, $bind);
232}
233
a3a526cc 234# Stolen from SQLT, with some modifications. This will likely change when the
235# SQLT Sybase stuff is redone/fixed-up.
236my %TYPE_MAPPING = (
237 number => 'numeric',
238 money => 'money',
239 varchar => 'varchar',
240 varchar2 => 'varchar',
241 timestamp => 'datetime',
242 text => 'varchar',
243 real => 'double precision',
244 comment => 'text',
245 bit => 'bit',
246 tinyint => 'smallint',
247 float => 'double precision',
248 serial => 'numeric',
249 bigserial => 'numeric',
250 boolean => 'varchar',
251 long => 'varchar',
252);
253
07a5866e 254sub _native_data_type {
a3a526cc 255 my ($self, $type) = @_;
256
257 $type = lc $type;
258 $type =~ s/ identity//;
259
260 return uc($TYPE_MAPPING{$type} || $type);
261}
262
285baccb 263sub _fetch_identity_sql {
264 my ($self, $source, $col) = @_;
265
266 return "SELECT MAX($col) FROM ".$source->from;
267}
268
269sub _execute {
270 my $self = shift;
271 my ($op) = @_;
272
273 my ($rv, $sth, @bind) = $self->dbh_do($self->can('_dbh_execute'), @_);
274
275 if ($op eq 'insert') {
276 $self->_identity($sth->fetchrow_array);
277 $sth->finish;
278 }
279
280 return wantarray ? ($rv, $sth, @bind) : $rv;
281}
282
283sub last_insert_id { shift->_identity }
284
a3a526cc 285# override to handle TEXT/IMAGE and to do a transaction if necessary
fd5a07e4 286sub insert {
7d17f469 287 my ($self, $source, $to_insert) = splice @_, 0, 3;
289877b0 288 my $dbh = $self->_dbh;
7d17f469 289
290 my $blob_cols = $self->_remove_blob_cols($source, $to_insert);
291
310a0a0a 292 my $need_last_insert_id = 0;
293
294 my ($identity_col) =
295 map $_->[0],
296 grep $_->[1]{is_auto_increment},
297 map [ $_, $source->column_info($_) ],
298 $source->columns;
299
300 $need_last_insert_id = 1
301 if $identity_col && (not exists $to_insert->{$identity_col});
302
a3a526cc 303# We have to do the insert in a transaction to avoid race conditions with the
304# SELECT MAX(COL) identity method used when placeholders are enabled.
f6de7111 305 my $updated_cols = do {
310a0a0a 306 if ($need_last_insert_id && $self->insert_txn &&
307 (not $self->{transaction_depth})) {
a3a526cc 308 my $args = \@_;
309 my $method = $self->next::can;
310 $self->txn_do(
311 sub { $self->$method($source, $to_insert, @$args) }
312 );
313 } else {
314 $self->next::method($source, $to_insert, @_);
315 }
f6de7111 316 };
7d17f469 317
078a332f 318 $self->_insert_blobs($source, $blob_cols, $to_insert) if %$blob_cols;
7d17f469 319
320 return $updated_cols;
321}
322
078a332f 323sub update {
324 my ($self, $source) = splice @_, 0, 2;
325 my ($fields, $where) = @_;
326 my $wantarray = wantarray;
327
328 my $blob_cols = $self->_remove_blob_cols($source, $fields);
329
330 my @res;
331 if ($wantarray) {
332 @res = $self->next::method($source, @_);
333 } else {
334 $res[0] = $self->next::method($source, @_);
335 }
336
337 $self->_update_blobs($source, $blob_cols, $where) if %$blob_cols;
338
339 return $wantarray ? @res : $res[0];
340}
7d17f469 341
342sub _remove_blob_cols {
343 my ($self, $source, $fields) = @_;
fd5a07e4 344
345 my %blob_cols;
346
7d17f469 347 for my $col (keys %$fields) {
9b3dabe0 348 if ($self->_is_lob_type($source->column_info($col)->{data_type})) {
349 $blob_cols{$col} = delete $fields->{$col};
350 $fields->{$col} = \"''";
351 }
fd5a07e4 352 }
353
7d17f469 354 return \%blob_cols;
fd5a07e4 355}
356
357sub _update_blobs {
078a332f 358 my ($self, $source, $blob_cols, $where) = @_;
359
360 my (@primary_cols) = $source->primary_columns;
361
362 croak "Cannot update TEXT/IMAGE column(s) without a primary key"
363 unless @primary_cols;
364
365# check if we're updating a single row by PK
366 my $pk_cols_in_where = 0;
367 for my $col (@primary_cols) {
368 $pk_cols_in_where++ if defined $where->{$col};
369 }
370 my @rows;
371
372 if ($pk_cols_in_where == @primary_cols) {
373 my %row_to_update;
374 @row_to_update{@primary_cols} = @{$where}{@primary_cols};
375 @rows = \%row_to_update;
376 } else {
377 my $rs = $source->resultset->search(
378 $where,
379 {
380 result_class => 'DBIx::Class::ResultClass::HashRefInflator',
381 select => \@primary_cols
382 }
383 );
384 @rows = $rs->all; # statement must finish
385 }
386
387 for my $row (@rows) {
388 $self->_insert_blobs($source, $blob_cols, $row);
389 }
390}
391
392sub _insert_blobs {
393 my ($self, $source, $blob_cols, $row) = @_;
fd5a07e4 394 my $dbh = $self->dbh;
395
396 my $table = $source->from;
397
078a332f 398 my %row = %$row;
fd5a07e4 399 my (@primary_cols) = $source->primary_columns;
400
9b3dabe0 401 croak "Cannot update TEXT/IMAGE column(s) without a primary key"
fd5a07e4 402 unless @primary_cols;
403
078a332f 404 if ((grep { defined $row{$_} } @primary_cols) != @primary_cols) {
9b3dabe0 405 if (@primary_cols == 1) {
406 my $col = $primary_cols[0];
078a332f 407 $row{$col} = $self->last_insert_id($source, $col);
9b3dabe0 408 } else {
409 croak "Cannot update TEXT/IMAGE column(s) without primary key values";
410 }
411 }
fd5a07e4 412
413 for my $col (keys %$blob_cols) {
414 my $blob = $blob_cols->{$col};
415
a3a526cc 416 my %where = map { ($_, $row{$_}) } @primary_cols;
417 my $cursor = $source->resultset->search(\%where, {
418 select => [$col]
419 })->cursor;
420 $cursor->next;
5137d252 421 my $sth = $cursor->sth;
fd5a07e4 422
423 eval {
a3a526cc 424 do {
fd5a07e4 425 $sth->func('CS_GET', 1, 'ct_data_info') or die $sth->errstr;
a3a526cc 426 } while $sth->fetch;
427
fd5a07e4 428 $sth->func('ct_prepare_send') or die $sth->errstr;
429
430 my $log_on_update = $self->_blob_log_on_update;
431 $log_on_update = 1 if not defined $log_on_update;
432
433 $sth->func('CS_SET', 1, {
434 total_txtlen => length($blob),
435 log_on_update => $log_on_update
436 }, 'ct_data_info') or die $sth->errstr;
437
438 $sth->func($blob, length($blob), 'ct_send_data') or die $sth->errstr;
439
440 $sth->func('ct_finish_send') or die $sth->errstr;
441 };
442 my $exception = $@;
a3a526cc 443 $sth->finish if $sth;
444 if ($exception) {
e97a6ee2 445 if ($self->using_freetds) {
a3a526cc 446 croak
447"TEXT/IMAGE operation failed, probably because you're using FreeTDS: " .
448$exception;
449 } else {
450 croak $exception;
451 }
452 }
fd5a07e4 453 }
63d46bb3 454}
455
9539eeb1 456=head2 connect_call_datetime_setup
457
458Used as:
459
460 on_connect_call => 'datetime_setup'
461
462In L<DBIx::Class::Storage::DBI/connect_info> to set:
463
3abafb11 464 $dbh->syb_date_fmt('ISO_strict'); # output fmt: 2004-08-21T14:36:48.080Z
465 $dbh->do('set dateformat mdy'); # input fmt: 08/13/1979 18:08:55.080
9539eeb1 466
467On connection for use with L<DBIx::Class::InflateColumn::DateTime>, using
3abafb11 468L<DateTime::Format::Sybase>, which you will need to install.
469
470This works for both C<DATETIME> and C<SMALLDATETIME> columns, although
471C<SMALLDATETIME> columns only have minute precision.
9539eeb1 472
473=cut
474
9041a97a 475{
476 my $old_dbd_warned = 0;
477
9539eeb1 478 sub connect_call_datetime_setup {
6b1f5ef7 479 my $self = shift;
6b1f5ef7 480 my $dbh = $self->_dbh;
481
482 if ($dbh->can('syb_date_fmt')) {
e97a6ee2 483# amazingly, this works with FreeTDS
6b1f5ef7 484 $dbh->syb_date_fmt('ISO_strict');
485 } elsif (not $old_dbd_warned) {
486 carp "Your DBD::Sybase is too old to support ".
487 "DBIx::Class::InflateColumn::DateTime, please upgrade!";
488 $old_dbd_warned = 1;
489 }
490
e97a6ee2 491 $dbh->do('SET DATEFORMAT mdy');
c5ce7cd6 492
6b1f5ef7 493 1;
c5ce7cd6 494 }
6b1f5ef7 495}
496
6636ad53 497sub datetime_parser_type { "DateTime::Format::Sybase" }
498
e97a6ee2 499# ->begin_work and such have no effect with FreeTDS but we run them anyway to
500# let the DBD keep any state it needs to.
501#
502# If they ever do start working, the extra statements will do no harm (because
503# Sybase supports nested transactions.)
a3a526cc 504
505sub _dbh_begin_work {
506 my $self = shift;
e97a6ee2 507 $self->next::method(@_);
508 if ($self->using_freetds) {
a3a526cc 509 $self->dbh->do('BEGIN TRAN');
510 }
511}
512
513sub _dbh_commit {
514 my $self = shift;
e97a6ee2 515 if ($self->using_freetds) {
a3a526cc 516 $self->_dbh->do('COMMIT');
517 }
e97a6ee2 518 return $self->next::method(@_);
a3a526cc 519}
520
521sub _dbh_rollback {
522 my $self = shift;
e97a6ee2 523 if ($self->using_freetds) {
a3a526cc 524 $self->_dbh->do('ROLLBACK');
525 }
e97a6ee2 526 return $self->next::method(@_);
a3a526cc 527}
528
1816be4f 529# savepoint support using ASE syntax
530
531sub _svp_begin {
532 my ($self, $name) = @_;
533
534 $self->dbh->do("SAVE TRANSACTION $name");
535}
536
537# A new SAVE TRANSACTION with the same name releases the previous one.
538sub _svp_release { 1 }
539
540sub _svp_rollback {
541 my ($self, $name) = @_;
542
543 $self->dbh->do("ROLLBACK TRANSACTION $name");
544}
545
3885cff6 5461;
547
efe75aaa 548=head1 Schema::Loader Support
549
550There is an experimental branch of L<DBIx::Class::Schema::Loader> that will
551allow you to dump a schema from most (if not all) versions of Sybase.
552
553It is available via subversion from:
554
07a5866e 555 http://dev.catalyst.perl.org/repos/bast/branches/DBIx-Class-Schema-Loader/current/
efe75aaa 556
e97a6ee2 557=head1 FreeTDS
558
559This driver supports L<DBD::Sybase> compiled against FreeTDS
560(L<http://www.freetds.org/>) to the best of our ability, however it is
561recommended that you recompile L<DBD::Sybase> against the Sybase Open Client
562libraries. They are a part of the Sybase ASE distribution:
563
564The Open Client FAQ is here:
565L<http://www.isug.com/Sybase_FAQ/ASE/section7.html>.
566
567Sybase ASE for Linux (which comes with the Open Client libraries) may be
568downloaded here: L<http://response.sybase.com/forms/ASE_Linux_Download>.
569
570To see if you're using FreeTDS check C<< $schema->storage->using_freetds >>, or run:
571
572 perl -MDBI -le 'my $dbh = DBI->connect($dsn, $user, $pass); print $dbh->{syb_oc_version}'
573
574Some versions of the libraries involved will not support placeholders, in which
575case the storage will be reblessed to
576L<DBIx::Class::Storage::DBI::Sybase::NoBindVars>.
577
07a5866e 578In some configurations, placeholders will work but will throw implicit type
e97a6ee2 579conversion errors for anything that's not expecting a string. In such a case,
07a5866e 580the C<auto_cast> option from L<DBIx::Class::Storage::DBI::AutoCast> is
581automatically set, which you may enable on connection with
582L<DBIx::Class::Storage::DBI::AutoCast/connect_call_set_auto_cast>. The type info
583for the C<CAST>s is taken from the L<DBIx::Class::ResultSource/data_type>
584definitions in your Result classes, and are mapped to a Sybase type (if it isn't
585already) using a mapping based on L<SQL::Translator>.
e97a6ee2 586
587In other configurations, placeholers will work just as they do with the Sybase
588Open Client libraries.
589
590Inserts or updates of TEXT/IMAGE columns will B<NOT> work with FreeTDS.
591
41c93b1b 592=head1 MAXIMUM CONNECTIONS
593
e97a6ee2 594The TDS protocol makes separate connections to the server for active statements
595in the background. By default the number of such connections is limited to 25,
596on both the client side and the server side.
41c93b1b 597
e97a6ee2 598This is a bit too low for a complex L<DBIx::Class> application, so on connection
599the client side setting is set to C<256> (see L<DBD::Sybase/maxConnect>.) You
600can override it to whatever setting you like in the DSN.
41c93b1b 601
602See
603L<http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.help.ase_15.0.sag1/html/sag1/sag1272.htm>
604for information on changing the setting on the server side.
605
c5ce7cd6 606=head1 DATES
607
3abafb11 608See L</connect_call_datetime_setup> to setup date formats
609for L<DBIx::Class::InflateColumn::DateTime>.
c5ce7cd6 610
e97a6ee2 611=head1 TEXT/IMAGE COLUMNS
63d46bb3 612
a3a526cc 613L<DBD::Sybase> compiled with FreeTDS will B<NOT> allow you to insert or update
614C<TEXT/IMAGE> columns.
615
e97a6ee2 616Setting C<< $dbh->{LongReadLen} >> will also not work with FreeTDS use either:
617
618 $schema->storage->dbh->do("SET TEXTSIZE $bytes");
a3a526cc 619
e97a6ee2 620or
621
622 $schema->storage->set_textsize($bytes);
a3a526cc 623
624instead.
5703eb14 625
e97a6ee2 626However, the C<LongReadLen> you pass in
627L<DBIx::Class::Storage::DBI/connect_info> is used to execute the equivalent
628C<SET TEXTSIZE> command on connection.
629
63d46bb3 630See L</connect_call_blob_setup> for a L<DBIx::Class::Storage::DBI/connect_info>
631setting you need to work with C<IMAGE> columns.
632
3885cff6 633=head1 AUTHORS
634
7e8cecc1 635See L<DBIx::Class/CONTRIBUTORS>.
c5ce7cd6 636
3885cff6 637=head1 LICENSE
638
639You may distribute this code under the same terms as Perl itself.
640
641=cut
c5ce7cd6 642# vim:sts=2 sw=2: