Protect DBIC as best we can from the failure mode in 7cb35852
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBI / Sybase / ASE.pm
CommitLineData
057db5ce 1package DBIx::Class::Storage::DBI::Sybase::ASE;
2
3use strict;
4use warnings;
5
6use base qw/
fabbd5cc 7 DBIx::Class::Storage::DBI::Sybase
8 DBIx::Class::Storage::DBI::AutoCast
9 DBIx::Class::Storage::DBI::IdentityInsert
057db5ce 10/;
11use mro 'c3';
70c28808 12use DBIx::Class::Carp;
fabbd5cc 13use Scalar::Util qw/blessed weaken/;
6298a324 14use List::Util 'first';
057db5ce 15use Sub::Name();
6298a324 16use Data::Dumper::Concise 'Dumper';
ed7ab0f4 17use Try::Tiny;
fabbd5cc 18use Context::Preserve 'preserve_context';
ddcc02d1 19use DBIx::Class::_Util qw( sigwarn_silencer dbic_internal_try );
fd323bf1 20use namespace::clean;
057db5ce 21
048c2440 22__PACKAGE__->sql_limit_dialect ('GenericSubQ');
2b8cc2f2 23__PACKAGE__->sql_quote_char ([qw/[ ]/]);
c6b7885f 24__PACKAGE__->datetime_parser_type(
25 'DBIx::Class::Storage::DBI::Sybase::ASE::DateTime::Format'
26);
6a247f33 27
057db5ce 28__PACKAGE__->mk_group_accessors('simple' =>
fabbd5cc 29 qw/_identity _identity_method _blob_log_on_update _parent_storage
30 _writer_storage _is_writer_storage
057db5ce 31 _bulk_storage _is_bulk_storage _began_bulk_work
fabbd5cc 32 /
057db5ce 33);
34
deabd575 35
057db5ce 36my @also_proxy_to_extra_storages = qw/
37 connect_call_set_auto_cast auto_cast connect_call_blob_setup
38 connect_call_datetime_setup
39
40 disconnect _connect_info _sql_maker _sql_maker_opts disable_sth_caching
41 auto_savepoint unsafe cursor_class debug debugobj schema
42/;
43
44=head1 NAME
45
46DBIx::Class::Storage::DBI::Sybase::ASE - Sybase ASE SQL Server support for
47DBIx::Class
48
49=head1 SYNOPSIS
50
51This subclass supports L<DBD::Sybase> for real (non-Microsoft) Sybase databases.
52
53=head1 DESCRIPTION
54
55If your version of Sybase does not support placeholders, then your storage will
56be reblessed to L<DBIx::Class::Storage::DBI::Sybase::ASE::NoBindVars>.
57You can also enable that driver explicitly, see the documentation for more
58details.
59
60With this driver there is unfortunately no way to get the C<last_insert_id>
61without doing a C<SELECT MAX(col)>. This is done safely in a transaction
62(locking the table.) See L</INSERTS WITH PLACEHOLDERS>.
63
8384a713 64A recommended L<connect_info|DBIx::Class::Storage::DBI/connect_info> setting:
057db5ce 65
66 on_connect_call => [['datetime_setup'], ['blob_setup', log_on_update => 0]]
67
68=head1 METHODS
69
70=cut
71
72sub _rebless {
73 my $self = shift;
74
75 my $no_bind_vars = __PACKAGE__ . '::NoBindVars';
76
aca3b4c3 77 if ($self->_using_freetds) {
70171cd7 78 carp_once <<'EOF' unless $ENV{DBIC_SYBASE_FREETDS_NOWARN};
057db5ce 79
80You are using FreeTDS with Sybase.
81
82We will do our best to support this configuration, but please consider this
83support experimental.
84
85TEXT/IMAGE columns will definitely not work.
86
87You are encouraged to recompile DBD::Sybase with the Sybase Open Client libraries
88instead.
89
90See perldoc DBIx::Class::Storage::DBI::Sybase::ASE for more details.
91
92To turn off this warning set the DBIC_SYBASE_FREETDS_NOWARN environment
93variable.
94EOF
95
bbdda281 96 if (not $self->_use_typeless_placeholders) {
97 if ($self->_use_placeholders) {
057db5ce 98 $self->auto_cast(1);
99 }
100 else {
101 $self->ensure_class_loaded($no_bind_vars);
102 bless $self, $no_bind_vars;
103 $self->_rebless;
104 }
105 }
106 }
107
108 elsif (not $self->_get_dbh->{syb_dynamic_supported}) {
109 # not necessarily FreeTDS, but no placeholders nevertheless
110 $self->ensure_class_loaded($no_bind_vars);
111 bless $self, $no_bind_vars;
112 $self->_rebless;
113 }
114 # this is highly unlikely, but we check just in case
bbdda281 115 elsif (not $self->_use_typeless_placeholders) {
057db5ce 116 $self->auto_cast(1);
117 }
118}
119
120sub _init {
121 my $self = shift;
bfec318f 122
123 $self->next::method(@_);
124
125 if ($self->_using_freetds && (my $ver = $self->_using_freetds_version||999) > 0.82) {
126 carp_once(
5c6696c8 127 "Buggy FreeTDS version $ver detected, statement caching will not work and "
bfec318f 128 . 'will be disabled.'
129 );
130 $self->disable_sth_caching(1);
131 }
132
057db5ce 133 $self->_set_max_connect(256);
134
135# create storage for insert/(update blob) transactions,
136# unless this is that storage
fabbd5cc 137 return if $self->_parent_storage;
057db5ce 138
139 my $writer_storage = (ref $self)->new;
140
fabbd5cc 141 $writer_storage->_is_writer_storage(1); # just info
057db5ce 142 $writer_storage->connect_info($self->connect_info);
143 $writer_storage->auto_cast($self->auto_cast);
144
fabbd5cc 145 weaken ($writer_storage->{_parent_storage} = $self);
057db5ce 146 $self->_writer_storage($writer_storage);
147
148# create a bulk storage unless connect_info is a coderef
149 return if ref($self->_dbi_connect_info->[0]) eq 'CODE';
150
151 my $bulk_storage = (ref $self)->new;
152
057db5ce 153 $bulk_storage->_is_bulk_storage(1); # for special ->disconnect acrobatics
154 $bulk_storage->connect_info($self->connect_info);
155
156# this is why
157 $bulk_storage->_dbi_connect_info->[0] .= ';bulkLogin=1';
158
fabbd5cc 159 weaken ($bulk_storage->{_parent_storage} = $self);
057db5ce 160 $self->_bulk_storage($bulk_storage);
161}
162
163for my $method (@also_proxy_to_extra_storages) {
164 no strict 'refs';
165 no warnings 'redefine';
166
167 my $replaced = __PACKAGE__->can($method);
168
169 *{$method} = Sub::Name::subname $method => sub {
170 my $self = shift;
171 $self->_writer_storage->$replaced(@_) if $self->_writer_storage;
172 $self->_bulk_storage->$replaced(@_) if $self->_bulk_storage;
173 return $self->$replaced(@_);
174 };
175}
176
177sub disconnect {
178 my $self = shift;
179
180# Even though we call $sth->finish for uses off the bulk API, there's still an
181# "active statement" warning on disconnect, which we throw away here.
2a6dda4b 182# This is due to the bug described in _insert_bulk.
057db5ce 183# Currently a noop because 'prepare' is used instead of 'prepare_cached'.
052a832c 184 local $SIG{__WARN__} = sigwarn_silencer(qr/active statement/i)
185 if $self->_is_bulk_storage;
057db5ce 186
187# so that next transaction gets a dbh
188 $self->_began_bulk_work(0) if $self->_is_bulk_storage;
189
190 $self->next::method;
191}
192
c1e5a9ac 193# This is only invoked for FreeTDS drivers by ::Storage::DBI::Sybase::FreeTDS
194sub _set_autocommit_stmt {
195 my ($self, $on) = @_;
196
197 return 'SET CHAINED ' . ($on ? 'OFF' : 'ON');
198}
199
057db5ce 200# Set up session settings for Sybase databases for the connection.
201#
202# Make sure we have CHAINED mode turned on if AutoCommit is off in non-FreeTDS
203# DBD::Sybase (since we don't know how DBD::Sybase was compiled.) If however
204# we're using FreeTDS, CHAINED mode turns on an implicit transaction which we
205# only want when AutoCommit is off.
057db5ce 206sub _run_connection_actions {
207 my $self = shift;
208
209 if ($self->_is_bulk_storage) {
c1e5a9ac 210 # this should be cleared on every reconnect
057db5ce 211 $self->_began_bulk_work(0);
212 return;
213 }
214
c1e5a9ac 215 $self->_dbh->{syb_chained_txn} = 1
aca3b4c3 216 unless $self->_using_freetds;
057db5ce 217
218 $self->next::method(@_);
219}
220
221=head2 connect_call_blob_setup
222
223Used as:
224
225 on_connect_call => [ [ 'blob_setup', log_on_update => 0 ] ]
226
227Does C<< $dbh->{syb_binary_images} = 1; >> to return C<IMAGE> data as raw binary
228instead of as a hex string.
229
230Recommended.
231
232Also sets the C<log_on_update> value for blob write operations. The default is
233C<1>, but C<0> is better if your database is configured for it.
234
235See
5529838f 236L<DBD::Sybase/Handling IMAGE/TEXT data with syb_ct_get_data()/syb_ct_send_data()>.
057db5ce 237
238=cut
239
240sub connect_call_blob_setup {
241 my $self = shift;
242 my %args = @_;
243 my $dbh = $self->_dbh;
244 $dbh->{syb_binary_images} = 1;
245
246 $self->_blob_log_on_update($args{log_on_update})
247 if exists $args{log_on_update};
248}
249
057db5ce 250sub _is_lob_column {
251 my ($self, $source, $column) = @_;
252
253 return $self->_is_lob_type($source->column_info($column)->{data_type});
254}
255
256sub _prep_for_execute {
048c2440 257 my ($self, $op, $ident, $args) = @_;
fabbd5cc 258
048c2440 259 my $limit; # extract and use shortcut on limit without offset
260 if ($op eq 'select' and ! $args->[4] and $limit = $args->[3]) {
261 $args = [ @$args ];
262 $args->[3] = undef;
263 }
264
265 my ($sql, $bind) = $self->next::method($op, $ident, $args);
266
267 # $limit is already sanitized by now
268 $sql = join( "\n",
269 "SET ROWCOUNT $limit",
270 $sql,
271 "SET ROWCOUNT 0",
272 ) if $limit;
057db5ce 273
fabbd5cc 274 if (my $identity_col = $self->_perform_autoinc_retrieval) {
275 $sql .= "\n" . $self->_fetch_identity_sql($ident, $identity_col)
057db5ce 276 }
277
278 return ($sql, $bind);
279}
280
fabbd5cc 281sub _fetch_identity_sql {
282 my ($self, $source, $col) = @_;
057db5ce 283
fabbd5cc 284 return sprintf ("SELECT MAX(%s) FROM %s",
285 map { $self->sql_maker->_quote ($_) } ($col, $source->from)
286 );
057db5ce 287}
288
289# Stolen from SQLT, with some modifications. This is a makeshift
290# solution before a sane type-mapping library is available, thus
291# the 'our' for easy overrides.
292our %TYPE_MAPPING = (
293 number => 'numeric',
294 money => 'money',
295 varchar => 'varchar',
296 varchar2 => 'varchar',
297 timestamp => 'datetime',
298 text => 'varchar',
299 real => 'double precision',
300 comment => 'text',
301 bit => 'bit',
302 tinyint => 'smallint',
303 float => 'double precision',
304 serial => 'numeric',
305 bigserial => 'numeric',
306 boolean => 'varchar',
307 long => 'varchar',
308);
309
310sub _native_data_type {
311 my ($self, $type) = @_;
312
313 $type = lc $type;
314 $type =~ s/\s* identity//x;
315
316 return uc($TYPE_MAPPING{$type} || $type);
317}
318
057db5ce 319
320sub _execute {
321 my $self = shift;
0e773352 322 my ($rv, $sth, @bind) = $self->next::method(@_);
057db5ce 323
044f5b3e 324 $self->_identity( ($sth->fetchall_arrayref)->[0][0] )
fabbd5cc 325 if $self->_perform_autoinc_retrieval;
057db5ce 326
327 return wantarray ? ($rv, $sth, @bind) : $rv;
328}
329
330sub last_insert_id { shift->_identity }
331
332# handles TEXT/IMAGE and transaction for last_insert_id
333sub insert {
334 my $self = shift;
335 my ($source, $to_insert) = @_;
336
e366f807 337 my $columns_info = $source->columns_info;
338
7e017742 339 my ($identity_col) = grep
340 { $columns_info->{$_}{is_auto_increment} }
341 keys %$columns_info
342 ;
343
344 $identity_col = '' if ! defined $identity_col;
057db5ce 345
fabbd5cc 346 # FIXME - this is duplication from DBI.pm. When refactored towards
347 # the LobWriter this can be folded back where it belongs.
348 local $self->{_autoinc_supplied_for_op} = exists $to_insert->{$identity_col}
349 ? 1
350 : 0
351 ;
7e017742 352
353 local $self->{_perform_autoinc_retrieval} = $self->{_autoinc_supplied_for_op}
354 ? undef
355 : $identity_col
fabbd5cc 356 ;
357
057db5ce 358 # check for empty insert
359 # INSERT INTO foo DEFAULT VALUES -- does not work with Sybase
6469dabf 360 # try to insert explicit 'DEFAULT's instead (except for identity, timestamp
361 # and computed columns)
057db5ce 362 if (not %$to_insert) {
363 for my $col ($source->columns) {
364 next if $col eq $identity_col;
6469dabf 365
366 my $info = $source->column_info($col);
367
368 next if ref $info->{default_value} eq 'SCALAR'
369 || (exists $info->{data_type} && (not defined $info->{data_type}));
370
371 next if $info->{data_type} && $info->{data_type} =~ /^timestamp\z/i;
372
057db5ce 373 $to_insert->{$col} = \'DEFAULT';
374 }
375 }
376
377 my $blob_cols = $self->_remove_blob_cols($source, $to_insert);
378
216f29d9 379 # if a new txn is needed - it must happen on the _writer/new connection (for now)
380 my $guard;
7e017742 381 if (
216f29d9 382 ! $self->transaction_depth
383 and
384 (
385 $blob_cols
386 or
387 # do we need the horrific SELECT MAX(COL) hack?
388 (
389 $self->_perform_autoinc_retrieval
390 and
391 ( ($self->_identity_method||'') ne '@@IDENTITY' )
392 )
393 )
057db5ce 394 ) {
216f29d9 395 $self = $self->_writer_storage;
396 $guard = $self->txn_scope_guard;
7e017742 397 }
057db5ce 398
216f29d9 399 my $updated_cols = $self->next::method ($source, $to_insert);
057db5ce 400
7e017742 401 $self->_insert_blobs (
402 $source,
403 $blob_cols,
404 {
405 ( $identity_col
406 ? ( $identity_col => $self->last_insert_id($source, $identity_col) )
407 : ()
408 ),
409 %$to_insert,
410 %$updated_cols,
411 },
412 ) if $blob_cols;
057db5ce 413
216f29d9 414 $guard->commit if $guard;
415
057db5ce 416 return $updated_cols;
417}
418
419sub update {
420 my $self = shift;
421 my ($source, $fields, $where, @rest) = @_;
422
fabbd5cc 423 #
424 # When *updating* identities, ASE requires SET IDENTITY_UPDATE called
425 #
426 if (my $blob_cols = $self->_remove_blob_cols($source, $fields)) {
057db5ce 427
fabbd5cc 428 # If there are any blobs in $where, Sybase will return a descriptive error
429 # message.
430 # XXX blobs can still be used with a LIKE query, and this should be handled.
057db5ce 431
fabbd5cc 432 # update+blob update(s) done atomically on separate connection
433 $self = $self->_writer_storage;
057db5ce 434
fabbd5cc 435 my $guard = $self->txn_scope_guard;
057db5ce 436
fabbd5cc 437 # First update the blob columns to be updated to '' (taken from $fields, where
438 # it is originally put by _remove_blob_cols .)
439 my %blobs_to_empty = map { ($_ => delete $fields->{$_}) } keys %$blob_cols;
057db5ce 440
fabbd5cc 441 # We can't only update NULL blobs, because blobs cannot be in the WHERE clause.
442 $self->next::method($source, \%blobs_to_empty, $where, @rest);
057db5ce 443
fabbd5cc 444 # Now update the blobs before the other columns in case the update of other
445 # columns makes the search condition invalid.
446 my $rv = $self->_update_blobs($source, $blob_cols, $where);
057db5ce 447
fabbd5cc 448 if (keys %$fields) {
057db5ce 449
fabbd5cc 450 # Now set the identity update flags for the actual update
451 local $self->{_autoinc_supplied_for_op} = (first
452 { $_->{is_auto_increment} }
453 values %{ $source->columns_info([ keys %$fields ]) }
454 ) ? 1 : 0;
057db5ce 455
fabbd5cc 456 my $next = $self->next::can;
457 my $args = \@_;
458 return preserve_context {
459 $self->$next(@$args);
460 } after => sub { $guard->commit };
057db5ce 461 }
462 else {
fabbd5cc 463 $guard->commit;
464 return $rv;
057db5ce 465 }
466 }
fabbd5cc 467 else {
468 # Set the identity update flags for the actual update
469 local $self->{_autoinc_supplied_for_op} = (first
470 { $_->{is_auto_increment} }
471 values %{ $source->columns_info([ keys %$fields ]) }
472 ) ? 1 : 0;
473
474 return $self->next::method(@_);
475 }
057db5ce 476}
477
2a6dda4b 478sub _insert_bulk {
057db5ce 479 my $self = shift;
480 my ($source, $cols, $data) = @_;
481
e366f807 482 my $columns_info = $source->columns_info;
483
6298a324 484 my $identity_col =
e366f807 485 first { $columns_info->{$_}{is_auto_increment} }
486 keys %$columns_info;
057db5ce 487
fabbd5cc 488 # FIXME - this is duplication from DBI.pm. When refactored towards
489 # the LobWriter this can be folded back where it belongs.
490 local $self->{_autoinc_supplied_for_op} =
491 (first { $_ eq $identity_col } @$cols)
492 ? 1
493 : 0
494 ;
057db5ce 495
057db5ce 496 my $use_bulk_api =
497 $self->_bulk_storage &&
498 $self->_get_dbh->{syb_has_blk};
499
6d5679b2 500 if (! $use_bulk_api and ref($self->_dbi_connect_info->[0]) eq 'CODE') {
501 carp_unique( join ' ',
502 'Bulk API support disabled due to use of a CODEREF connect_info.',
503 'Reverting to regular array inserts.',
504 );
057db5ce 505 }
506
507 if (not $use_bulk_api) {
508 my $blob_cols = $self->_remove_blob_cols_array($source, $cols, $data);
509
52cef7e3 510# next::method uses a txn anyway, but it ends too early in case we need to
057db5ce 511# select max(col) to get the identity for inserting blobs.
7e017742 512 ($self, my $guard) = $self->transaction_depth
513 ? ($self, undef)
514 : ($self->_writer_storage, $self->_writer_storage->txn_scope_guard)
515 ;
057db5ce 516
057db5ce 517 $self->next::method(@_);
518
519 if ($blob_cols) {
fabbd5cc 520 if ($self->_autoinc_supplied_for_op) {
057db5ce 521 $self->_insert_blobs_array ($source, $blob_cols, $cols, $data);
522 }
523 else {
524 my @cols_with_identities = (@$cols, $identity_col);
525
526 ## calculate identities
527 # XXX This assumes identities always increase by 1, which may or may not
528 # be true.
529 my ($last_identity) =
530 $self->_dbh->selectrow_array (
531 $self->_fetch_identity_sql($source, $identity_col)
532 );
533 my @identities = (($last_identity - @$data + 1) .. $last_identity);
534
535 my @data_with_identities = map [@$_, shift @identities], @$data;
536
537 $self->_insert_blobs_array (
538 $source, $blob_cols, \@cols_with_identities, \@data_with_identities
539 );
540 }
541 }
542
543 $guard->commit if $guard;
544
545 return;
546 }
547
548# otherwise, use the bulk API
549
550# rearrange @$data so that columns are in database order
6d5679b2 551# and so we submit a full column list
552 my %orig_order = map { $cols->[$_] => $_ } 0..$#$cols;
057db5ce 553
6d5679b2 554 my @source_columns = $source->columns;
555
556 # bcp identity index is 1-based
557 my $identity_idx = first { $source_columns[$_] eq $identity_col } (0..$#source_columns);
558 $identity_idx = defined $identity_idx ? $identity_idx + 1 : 0;
057db5ce 559
560 my @new_data;
6d5679b2 561 for my $slice_idx (0..$#$data) {
562 push @new_data, [map {
fabbd5cc 563 # identity data will be 'undef' if not _autoinc_supplied_for_op()
6d5679b2 564 # columns with defaults will also be 'undef'
565 exists $orig_order{$_}
566 ? $data->[$slice_idx][$orig_order{$_}]
567 : undef
568 } @source_columns];
057db5ce 569 }
570
6d5679b2 571 my $proto_bind = $self->_resolve_bindattrs(
572 $source,
573 [map {
574 [ { dbic_colname => $source_columns[$_], _bind_data_slice_idx => $_ }
575 => $new_data[0][$_] ]
576 } (0 ..$#source_columns) ],
577 $columns_info
578 );
057db5ce 579
580## Set a client-side conversion error handler, straight from DBD::Sybase docs.
581# This ignores any data conversion errors detected by the client side libs, as
582# they are usually harmless.
583 my $orig_cslib_cb = DBD::Sybase::set_cslib_cb(
2a6dda4b 584 Sub::Name::subname _insert_bulk_cslib_errhandler => sub {
057db5ce 585 my ($layer, $origin, $severity, $errno, $errmsg, $osmsg, $blkmsg) = @_;
586
587 return 1 if $errno == 36;
588
589 carp
590 "Layer: $layer, Origin: $origin, Severity: $severity, Error: $errno" .
591 ($errmsg ? "\n$errmsg" : '') .
592 ($osmsg ? "\n$osmsg" : '') .
593 ($blkmsg ? "\n$blkmsg" : '');
594
595 return 0;
596 });
597
4edfce2f 598 my $exception = '';
ddcc02d1 599 dbic_internal_try {
057db5ce 600 my $bulk = $self->_bulk_storage;
601
602 my $guard = $bulk->txn_scope_guard;
603
6d5679b2 604## FIXME - once this is done - address the FIXME on finish() below
057db5ce 605## XXX get this to work instead of our own $sth
606## will require SQLA or *Hacks changes for ordered columns
607# $bulk->next::method($source, \@source_columns, \@new_data, {
608# syb_bcp_attribs => {
fabbd5cc 609# identity_flag => $self->_autoinc_supplied_for_op ? 1 : 0,
057db5ce 610# identity_column => $identity_idx,
611# }
612# });
613 my $sql = 'INSERT INTO ' .
614 $bulk->sql_maker->_quote($source->name) . ' (' .
615# colname list is ignored for BCP, but does no harm
616 (join ', ', map $bulk->sql_maker->_quote($_), @source_columns) . ') '.
617 ' VALUES ('. (join ', ', ('?') x @source_columns) . ')';
618
619## XXX there's a bug in the DBD::Sybase bulk support that makes $sth->finish for
620## a prepare_cached statement ineffective. Replace with ->sth when fixed, or
621## better yet the version above. Should be fixed in DBD::Sybase .
622 my $sth = $bulk->_get_dbh->prepare($sql,
623# 'insert', # op
624 {
625 syb_bcp_attribs => {
fabbd5cc 626 identity_flag => $self->_autoinc_supplied_for_op ? 1 : 0,
057db5ce 627 identity_column => $identity_idx,
628 }
629 }
630 );
631
6d5679b2 632 {
633 # FIXME the $sth->finish in _execute_array does a rollback for some
634 # reason. Disable it temporarily until we fix the SQLMaker thing above
635 no warnings 'redefine';
636 no strict 'refs';
637 local *{ref($sth).'::finish'} = sub {};
057db5ce 638
52cef7e3 639 $self->_dbh_execute_for_fetch(
6d5679b2 640 $source, $sth, $proto_bind, \@source_columns, \@new_data
641 );
642 }
643
644 $guard->commit;
057db5ce 645
646 $bulk->_query_end($sql);
ed7ab0f4 647 } catch {
648 $exception = shift;
057db5ce 649 };
650
057db5ce 651 DBD::Sybase::set_cslib_cb($orig_cslib_cb);
652
653 if ($exception =~ /-Y option/) {
f32e99f9 654 my $w = 'Sybase bulk API operation failed due to character set incompatibility, '
5241250d 655 . 'reverting to regular array inserts. Try unsetting the LC_ALL environment variable'
f32e99f9 656 ;
657 $w .= "\n$exception" if $self->debug;
658 carp $w;
057db5ce 659
057db5ce 660 $self->_bulk_storage(undef);
661 unshift @_, $self;
2a6dda4b 662 goto \&_insert_bulk;
057db5ce 663 }
664 elsif ($exception) {
665# rollback makes the bulkLogin connection unusable
666 $self->_bulk_storage->disconnect;
667 $self->throw_exception($exception);
668 }
669}
670
057db5ce 671# Make sure blobs are not bound as placeholders, and return any non-empty ones
672# as a hash.
673sub _remove_blob_cols {
674 my ($self, $source, $fields) = @_;
675
676 my %blob_cols;
677
678 for my $col (keys %$fields) {
679 if ($self->_is_lob_column($source, $col)) {
680 my $blob_val = delete $fields->{$col};
681 if (not defined $blob_val) {
682 $fields->{$col} = \'NULL';
683 }
684 else {
685 $fields->{$col} = \"''";
d52c4a75 686 $blob_cols{$col} = $blob_val
687 if length $blob_val;
057db5ce 688 }
689 }
690 }
691
692 return %blob_cols ? \%blob_cols : undef;
693}
694
2a6dda4b 695# same for _insert_bulk
057db5ce 696sub _remove_blob_cols_array {
697 my ($self, $source, $cols, $data) = @_;
698
699 my @blob_cols;
700
701 for my $i (0..$#$cols) {
702 my $col = $cols->[$i];
703
704 if ($self->_is_lob_column($source, $col)) {
705 for my $j (0..$#$data) {
706 my $blob_val = delete $data->[$j][$i];
707 if (not defined $blob_val) {
708 $data->[$j][$i] = \'NULL';
709 }
710 else {
711 $data->[$j][$i] = \"''";
712 $blob_cols[$j][$i] = $blob_val
d52c4a75 713 if length $blob_val;
057db5ce 714 }
715 }
716 }
717 }
718
719 return @blob_cols ? \@blob_cols : undef;
720}
721
722sub _update_blobs {
723 my ($self, $source, $blob_cols, $where) = @_;
724
ddcc02d1 725 my @primary_cols = dbic_internal_try
56ad42bb 726 { $source->_pri_cols_or_die }
9780718f 727 catch {
728 $self->throw_exception("Cannot update TEXT/IMAGE column(s): $_")
729 };
057db5ce 730
564986d6 731 my @pks_to_update;
732 if (
733 ref $where eq 'HASH'
734 and
7e017742 735 ! grep { ! defined $where->{$_} } @primary_cols
564986d6 736 ) {
057db5ce 737 my %row_to_update;
738 @row_to_update{@primary_cols} = @{$where}{@primary_cols};
564986d6 739 @pks_to_update = \%row_to_update;
740 }
741 else {
057db5ce 742 my $cursor = $self->select ($source, \@primary_cols, $where, {});
564986d6 743 @pks_to_update = map {
057db5ce 744 my %row; @row{@primary_cols} = @$_; \%row
745 } $cursor->all;
746 }
747
564986d6 748 for my $ident (@pks_to_update) {
749 $self->_insert_blobs($source, $blob_cols, $ident);
057db5ce 750 }
751}
752
753sub _insert_blobs {
7e017742 754 my ($self, $source, $blob_cols, $row_data) = @_;
057db5ce 755
756 my $table = $source->name;
757
ddcc02d1 758 my @primary_cols = dbic_internal_try
56ad42bb 759 { $source->_pri_cols_or_die }
9780718f 760 catch {
761 $self->throw_exception("Cannot update TEXT/IMAGE column(s): $_")
762 };
057db5ce 763
764 $self->throw_exception('Cannot update TEXT/IMAGE column(s) without primary key values')
7e017742 765 if grep { ! defined $row_data->{$_} } @primary_cols;
766
b8e92dac 767 # if we are 2-phase inserting a blob - there is nothing to retrieve anymore,
768 # regardless of the previous state of the flag
769 local $self->{_perform_autoinc_retrieval}
770 if $self->_perform_autoinc_retrieval;
771
7e017742 772 my %where = map {( $_ => $row_data->{$_} )} @primary_cols;
057db5ce 773
774 for my $col (keys %$blob_cols) {
775 my $blob = $blob_cols->{$col};
776
057db5ce 777 my $cursor = $self->select ($source, [$col], \%where, {});
778 $cursor->next;
779 my $sth = $cursor->sth;
780
781 if (not $sth) {
057db5ce 782 $self->throw_exception(
783 "Could not find row in table '$table' for blob update:\n"
6298a324 784 . (Dumper \%where)
057db5ce 785 );
786 }
787
ddcc02d1 788 dbic_internal_try {
057db5ce 789 do {
790 $sth->func('CS_GET', 1, 'ct_data_info') or die $sth->errstr;
791 } while $sth->fetch;
792
793 $sth->func('ct_prepare_send') or die $sth->errstr;
794
795 my $log_on_update = $self->_blob_log_on_update;
796 $log_on_update = 1 if not defined $log_on_update;
797
798 $sth->func('CS_SET', 1, {
799 total_txtlen => length($blob),
800 log_on_update => $log_on_update
801 }, 'ct_data_info') or die $sth->errstr;
802
803 $sth->func($blob, length($blob), 'ct_send_data') or die $sth->errstr;
804
805 $sth->func('ct_finish_send') or die $sth->errstr;
9780718f 806 }
807 catch {
aca3b4c3 808 if ($self->_using_freetds) {
057db5ce 809 $self->throw_exception (
9780718f 810 "TEXT/IMAGE operation failed, probably because you are using FreeTDS: $_"
057db5ce 811 );
9780718f 812 }
813 else {
814 $self->throw_exception($_);
057db5ce 815 }
816 }
9780718f 817 finally {
818 $sth->finish if $sth;
819 };
057db5ce 820 }
821}
822
823sub _insert_blobs_array {
824 my ($self, $source, $blob_cols, $cols, $data) = @_;
825
826 for my $i (0..$#$data) {
827 my $datum = $data->[$i];
828
829 my %row;
830 @row{ @$cols } = @$datum;
831
832 my %blob_vals;
833 for my $j (0..$#$cols) {
834 if (exists $blob_cols->[$i][$j]) {
835 $blob_vals{ $cols->[$j] } = $blob_cols->[$i][$j];
836 }
837 }
838
839 $self->_insert_blobs ($source, \%blob_vals, \%row);
840 }
841}
842
843=head2 connect_call_datetime_setup
844
845Used as:
846
847 on_connect_call => 'datetime_setup'
848
8384a713 849In L<connect_info|DBIx::Class::Storage::DBI/connect_info> to set:
057db5ce 850
851 $dbh->syb_date_fmt('ISO_strict'); # output fmt: 2004-08-21T14:36:48.080Z
852 $dbh->do('set dateformat mdy'); # input fmt: 08/13/1979 18:08:55.080
853
c1e5a9ac 854This works for both C<DATETIME> and C<SMALLDATETIME> columns, note that
057db5ce 855C<SMALLDATETIME> columns only have minute precision.
856
857=cut
858
70c28808 859sub connect_call_datetime_setup {
860 my $self = shift;
861 my $dbh = $self->_get_dbh;
057db5ce 862
70c28808 863 if ($dbh->can('syb_date_fmt')) {
864 # amazingly, this works with FreeTDS
865 $dbh->syb_date_fmt('ISO_strict');
866 }
867 else {
868 carp_once
869 'Your DBD::Sybase is too old to support '
870 .'DBIx::Class::InflateColumn::DateTime, please upgrade!';
057db5ce 871
c6b7885f 872 # FIXME - in retrospect this is a rather bad US-centric choice
873 # of format. Not changing as a bugwards compat, though in reality
874 # the only piece that sees the results of $dt object formatting
875 # (as opposed to parsing) is the database itself, so theoretically
876 # changing both this SET command and the formatter definition of
877 # ::S::D::Sybase::ASE::DateTime::Format below should be safe and
878 # transparent
879
057db5ce 880 $dbh->do('SET DATEFORMAT mdy');
057db5ce 881 }
882}
883
057db5ce 884
90d7422f 885sub _exec_txn_begin {
057db5ce 886 my $self = shift;
887
888# bulkLogin=1 connections are always in a transaction, and can only call BEGIN
889# TRAN once. However, we need to make sure there's a $dbh.
890 return if $self->_is_bulk_storage && $self->_dbh && $self->_began_bulk_work;
891
892 $self->next::method(@_);
893
057db5ce 894 $self->_began_bulk_work(1) if $self->_is_bulk_storage;
895}
896
057db5ce 897# savepoint support using ASE syntax
898
90d7422f 899sub _exec_svp_begin {
057db5ce 900 my ($self, $name) = @_;
901
90d7422f 902 $self->_dbh->do("SAVE TRANSACTION $name");
057db5ce 903}
904
905# A new SAVE TRANSACTION with the same name releases the previous one.
90d7422f 906sub _exec_svp_release { 1 }
057db5ce 907
90d7422f 908sub _exec_svp_rollback {
057db5ce 909 my ($self, $name) = @_;
910
90d7422f 911 $self->_dbh->do("ROLLBACK TRANSACTION $name");
057db5ce 912}
913
deabd575 914package # hide from PAUSE
915 DBIx::Class::Storage::DBI::Sybase::ASE::DateTime::Format;
916
917my $datetime_parse_format = '%Y-%m-%dT%H:%M:%S.%3NZ';
918my $datetime_format_format = '%m/%d/%Y %H:%M:%S.%3N';
919
920my ($datetime_parser, $datetime_formatter);
921
922sub parse_datetime {
923 shift;
924 require DateTime::Format::Strptime;
925 $datetime_parser ||= DateTime::Format::Strptime->new(
926 pattern => $datetime_parse_format,
927 on_error => 'croak',
928 );
929 return $datetime_parser->parse_datetime(shift);
930}
931
932sub format_datetime {
933 shift;
934 require DateTime::Format::Strptime;
935 $datetime_formatter ||= DateTime::Format::Strptime->new(
936 pattern => $datetime_format_format,
937 on_error => 'croak',
938 );
939 return $datetime_formatter->format_datetime(shift);
940}
941
057db5ce 9421;
943
944=head1 Schema::Loader Support
945
290da7d6 946As of version C<0.05000>, L<DBIx::Class::Schema::Loader> should work well with
c1e5a9ac 947most versions of Sybase ASE.
057db5ce 948
949=head1 FreeTDS
950
951This driver supports L<DBD::Sybase> compiled against FreeTDS
952(L<http://www.freetds.org/>) to the best of our ability, however it is
953recommended that you recompile L<DBD::Sybase> against the Sybase Open Client
954libraries. They are a part of the Sybase ASE distribution:
955
956The Open Client FAQ is here:
957L<http://www.isug.com/Sybase_FAQ/ASE/section7.html>.
958
959Sybase ASE for Linux (which comes with the Open Client libraries) may be
960downloaded here: L<http://response.sybase.com/forms/ASE_Linux_Download>.
961
aca3b4c3 962To see if you're using FreeTDS run:
057db5ce 963
964 perl -MDBI -le 'my $dbh = DBI->connect($dsn, $user, $pass); print $dbh->{syb_oc_version}'
965
c1e5a9ac 966It is recommended to set C<tds version> for your ASE server to C<5.0> in
967C</etc/freetds/freetds.conf>.
968
969Some versions or configurations of the libraries involved will not support
970placeholders, in which case the storage will be reblessed to
057db5ce 971L<DBIx::Class::Storage::DBI::Sybase::ASE::NoBindVars>.
972
973In some configurations, placeholders will work but will throw implicit type
974conversion errors for anything that's not expecting a string. In such a case,
975the C<auto_cast> option from L<DBIx::Class::Storage::DBI::AutoCast> is
976automatically set, which you may enable on connection with
c1e5a9ac 977L<connect_call_set_auto_cast|DBIx::Class::Storage::DBI::AutoCast/connect_call_set_auto_cast>.
978The type info for the C<CAST>s is taken from the
979L<DBIx::Class::ResultSource/data_type> definitions in your Result classes, and
980are mapped to a Sybase type (if it isn't already) using a mapping based on
981L<SQL::Translator>.
057db5ce 982
48580715 983In other configurations, placeholders will work just as they do with the Sybase
057db5ce 984Open Client libraries.
985
986Inserts or updates of TEXT/IMAGE columns will B<NOT> work with FreeTDS.
987
988=head1 INSERTS WITH PLACEHOLDERS
989
990With placeholders enabled, inserts are done in a transaction so that there are
991no concurrency issues with getting the inserted identity value using
992C<SELECT MAX(col)>, which is the only way to get the C<IDENTITY> value in this
993mode.
994
995In addition, they are done on a separate connection so that it's possible to
996have active cursors when doing an insert.
997
998When using C<DBIx::Class::Storage::DBI::Sybase::ASE::NoBindVars> transactions
c1e5a9ac 999are unnecessary and not used, as there are no concurrency issues with C<SELECT
1000@@IDENTITY> which is a session variable.
057db5ce 1001
1002=head1 TRANSACTIONS
1003
c1e5a9ac 1004Due to limitations of the TDS protocol and L<DBD::Sybase>, you cannot begin a
1005transaction while there are active cursors, nor can you use multiple active
1006cursors within a transaction. An active cursor is, for example, a
057db5ce 1007L<ResultSet|DBIx::Class::ResultSet> that has been executed using C<next> or
1008C<first> but has not been exhausted or L<reset|DBIx::Class::ResultSet/reset>.
1009
1010For example, this will not work:
1011
1012 $schema->txn_do(sub {
1013 my $rs = $schema->resultset('Book');
47d7b769 1014 while (my $result = $rs->next) {
057db5ce 1015 $schema->resultset('MetaData')->create({
47d7b769 1016 book_id => $result->id,
057db5ce 1017 ...
1018 });
1019 }
1020 });
1021
1022This won't either:
1023
1024 my $first_row = $large_rs->first;
1025 $schema->txn_do(sub { ... });
1026
1027Transactions done for inserts in C<AutoCommit> mode when placeholders are in use
1028are not affected, as they are done on an extra database handle.
1029
1030Some workarounds:
1031
1032=over 4
1033
1034=item * use L<DBIx::Class::Storage::DBI::Replicated>
1035
1036=item * L<connect|DBIx::Class::Schema/connect> another L<Schema|DBIx::Class::Schema>
1037
1038=item * load the data from your cursor with L<DBIx::Class::ResultSet/all>
1039
1040=back
1041
1042=head1 MAXIMUM CONNECTIONS
1043
1044The TDS protocol makes separate connections to the server for active statements
1045in the background. By default the number of such connections is limited to 25,
1046on both the client side and the server side.
1047
1048This is a bit too low for a complex L<DBIx::Class> application, so on connection
1049the client side setting is set to C<256> (see L<DBD::Sybase/maxConnect>.) You
1050can override it to whatever setting you like in the DSN.
1051
1052See
1053L<http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.help.ase_15.0.sag1/html/sag1/sag1272.htm>
1054for information on changing the setting on the server side.
1055
1056=head1 DATES
1057
1058See L</connect_call_datetime_setup> to setup date formats
1059for L<DBIx::Class::InflateColumn::DateTime>.
1060
9c510ba5 1061=head1 LIMITED QUERIES
1062
5529838f 1063Because ASE does not have a good way to limit results in SQL that works for
1064all types of queries, the limit dialect is set to
1065L<GenericSubQ|DBIx::Class::SQLMaker::LimitDialects/GenericSubQ>.
9c510ba5 1066
1067Fortunately, ASE and L<DBD::Sybase> support cursors properly, so when
5529838f 1068L<GenericSubQ|DBIx::Class::SQLMaker::LimitDialects/GenericSubQ> is too slow
1069you can use the L<software_limit|DBIx::Class::ResultSet/software_limit>
1070L<DBIx::Class::ResultSet> attribute to simulate limited queries by skipping
1071over records.
9c510ba5 1072
057db5ce 1073=head1 TEXT/IMAGE COLUMNS
1074
1075L<DBD::Sybase> compiled with FreeTDS will B<NOT> allow you to insert or update
1076C<TEXT/IMAGE> columns.
1077
1078Setting C<< $dbh->{LongReadLen} >> will also not work with FreeTDS use either:
1079
1080 $schema->storage->dbh->do("SET TEXTSIZE $bytes");
1081
1082or
1083
1084 $schema->storage->set_textsize($bytes);
1085
1086instead.
1087
1088However, the C<LongReadLen> you pass in
8384a713 1089L<connect_info|DBIx::Class::Storage::DBI/connect_info> is used to execute the
1090equivalent C<SET TEXTSIZE> command on connection.
057db5ce 1091
8384a713 1092See L</connect_call_blob_setup> for a
1093L<connect_info|DBIx::Class::Storage::DBI/connect_info> setting you need to work
1094with C<IMAGE> columns.
057db5ce 1095
1096=head1 BULK API
1097
1098The experimental L<DBD::Sybase> Bulk API support is used for
1099L<populate|DBIx::Class::ResultSet/populate> in B<void> context, in a transaction
1100on a separate connection.
1101
1102To use this feature effectively, use a large number of rows for each
1103L<populate|DBIx::Class::ResultSet/populate> call, eg.:
1104
1105 while (my $rows = $data_source->get_100_rows()) {
1106 $rs->populate($rows);
1107 }
1108
1109B<NOTE:> the L<add_columns|DBIx::Class::ResultSource/add_columns>
1110calls in your C<Result> classes B<must> list columns in database order for this
5241250d 1111to work. Also, you may have to unset the C<LC_ALL> environment variable before
c1e5a9ac 1112loading your app, as C<BCP -Y> is not yet supported in DBD::Sybase .
057db5ce 1113
1114When inserting IMAGE columns using this method, you'll need to use
1115L</connect_call_blob_setup> as well.
1116
6476fd66 1117=head1 COMPUTED COLUMNS
1118
1119If you have columns such as:
1120
1121 created_dtm AS getdate()
1122
1123represent them in your Result classes as:
1124
1125 created_dtm => {
1126 data_type => undef,
1127 default_value => \'getdate()',
1128 is_nullable => 0,
c1e5a9ac 1129 inflate_datetime => 1,
6476fd66 1130 }
1131
1132The C<data_type> must exist and must be C<undef>. Then empty inserts will work
1133on tables with such columns.
1134
1135=head1 TIMESTAMP COLUMNS
1136
1137C<timestamp> columns in Sybase ASE are not really timestamps, see:
1138L<http://dba.fyicenter.com/Interview-Questions/SYBASE/The_timestamp_datatype_in_Sybase_.html>.
1139
1140They should be defined in your Result classes as:
1141
1142 ts => {
1143 data_type => 'timestamp',
1144 is_nullable => 0,
1145 inflate_datetime => 0,
1146 }
1147
1148The C<<inflate_datetime => 0>> is necessary if you use
1149L<DBIx::Class::InflateColumn::DateTime>, and most people do, and still want to
1150be able to read these values.
1151
1152The values will come back as hexadecimal.
1153
057db5ce 1154=head1 TODO
1155
1156=over
1157
1158=item *
1159
1160Transitions to AutoCommit=0 (starting a transaction) mode by exhausting
1161any active cursors, using eager cursors.
1162
1163=item *
1164
1165Real limits and limited counts using stored procedures deployed on startup.
1166
1167=item *
1168
057db5ce 1169Blob update with a LIKE query on a blob, without invalidating the WHERE condition.
1170
1171=item *
1172
1173bulk_insert using prepare_cached (see comments.)
1174
1175=back
1176
a2bd3796 1177=head1 FURTHER QUESTIONS?
057db5ce 1178
a2bd3796 1179Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>.
057db5ce 1180
a2bd3796 1181=head1 COPYRIGHT AND LICENSE
057db5ce 1182
a2bd3796 1183This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE>
1184by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can
1185redistribute it and/or modify it under the same terms as the
1186L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>.