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