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