Fix ::Sybase::ASE incorrect attempt to retrieve an autoinc on blob inserts
[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';
052a832c 19use DBIx::Class::_Util 'sigwarn_silencer';
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 = '';
ed7ab0f4 599 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} = \"''";
686 $blob_cols{$col} = $blob_val unless $blob_val eq '';
687 }
688 }
689 }
690
691 return %blob_cols ? \%blob_cols : undef;
692}
693
2a6dda4b 694# same for _insert_bulk
057db5ce 695sub _remove_blob_cols_array {
696 my ($self, $source, $cols, $data) = @_;
697
698 my @blob_cols;
699
700 for my $i (0..$#$cols) {
701 my $col = $cols->[$i];
702
703 if ($self->_is_lob_column($source, $col)) {
704 for my $j (0..$#$data) {
705 my $blob_val = delete $data->[$j][$i];
706 if (not defined $blob_val) {
707 $data->[$j][$i] = \'NULL';
708 }
709 else {
710 $data->[$j][$i] = \"''";
711 $blob_cols[$j][$i] = $blob_val
712 unless $blob_val eq '';
713 }
714 }
715 }
716 }
717
718 return @blob_cols ? \@blob_cols : undef;
719}
720
721sub _update_blobs {
722 my ($self, $source, $blob_cols, $where) = @_;
723
9780718f 724 my @primary_cols = try
56ad42bb 725 { $source->_pri_cols_or_die }
9780718f 726 catch {
727 $self->throw_exception("Cannot update TEXT/IMAGE column(s): $_")
728 };
057db5ce 729
564986d6 730 my @pks_to_update;
731 if (
732 ref $where eq 'HASH'
733 and
7e017742 734 ! grep { ! defined $where->{$_} } @primary_cols
564986d6 735 ) {
057db5ce 736 my %row_to_update;
737 @row_to_update{@primary_cols} = @{$where}{@primary_cols};
564986d6 738 @pks_to_update = \%row_to_update;
739 }
740 else {
057db5ce 741 my $cursor = $self->select ($source, \@primary_cols, $where, {});
564986d6 742 @pks_to_update = map {
057db5ce 743 my %row; @row{@primary_cols} = @$_; \%row
744 } $cursor->all;
745 }
746
564986d6 747 for my $ident (@pks_to_update) {
748 $self->_insert_blobs($source, $blob_cols, $ident);
057db5ce 749 }
750}
751
752sub _insert_blobs {
7e017742 753 my ($self, $source, $blob_cols, $row_data) = @_;
057db5ce 754
755 my $table = $source->name;
756
9780718f 757 my @primary_cols = try
56ad42bb 758 { $source->_pri_cols_or_die }
9780718f 759 catch {
760 $self->throw_exception("Cannot update TEXT/IMAGE column(s): $_")
761 };
057db5ce 762
763 $self->throw_exception('Cannot update TEXT/IMAGE column(s) without primary key values')
7e017742 764 if grep { ! defined $row_data->{$_} } @primary_cols;
765
b8e92dac 766 # if we are 2-phase inserting a blob - there is nothing to retrieve anymore,
767 # regardless of the previous state of the flag
768 local $self->{_perform_autoinc_retrieval}
769 if $self->_perform_autoinc_retrieval;
770
7e017742 771 my %where = map {( $_ => $row_data->{$_} )} @primary_cols;
057db5ce 772
773 for my $col (keys %$blob_cols) {
774 my $blob = $blob_cols->{$col};
775
057db5ce 776 my $cursor = $self->select ($source, [$col], \%where, {});
777 $cursor->next;
778 my $sth = $cursor->sth;
779
780 if (not $sth) {
057db5ce 781 $self->throw_exception(
782 "Could not find row in table '$table' for blob update:\n"
6298a324 783 . (Dumper \%where)
057db5ce 784 );
785 }
786
9780718f 787 try {
057db5ce 788 do {
789 $sth->func('CS_GET', 1, 'ct_data_info') or die $sth->errstr;
790 } while $sth->fetch;
791
792 $sth->func('ct_prepare_send') or die $sth->errstr;
793
794 my $log_on_update = $self->_blob_log_on_update;
795 $log_on_update = 1 if not defined $log_on_update;
796
797 $sth->func('CS_SET', 1, {
798 total_txtlen => length($blob),
799 log_on_update => $log_on_update
800 }, 'ct_data_info') or die $sth->errstr;
801
802 $sth->func($blob, length($blob), 'ct_send_data') or die $sth->errstr;
803
804 $sth->func('ct_finish_send') or die $sth->errstr;
9780718f 805 }
806 catch {
aca3b4c3 807 if ($self->_using_freetds) {
057db5ce 808 $self->throw_exception (
9780718f 809 "TEXT/IMAGE operation failed, probably because you are using FreeTDS: $_"
057db5ce 810 );
9780718f 811 }
812 else {
813 $self->throw_exception($_);
057db5ce 814 }
815 }
9780718f 816 finally {
817 $sth->finish if $sth;
818 };
057db5ce 819 }
820}
821
822sub _insert_blobs_array {
823 my ($self, $source, $blob_cols, $cols, $data) = @_;
824
825 for my $i (0..$#$data) {
826 my $datum = $data->[$i];
827
828 my %row;
829 @row{ @$cols } = @$datum;
830
831 my %blob_vals;
832 for my $j (0..$#$cols) {
833 if (exists $blob_cols->[$i][$j]) {
834 $blob_vals{ $cols->[$j] } = $blob_cols->[$i][$j];
835 }
836 }
837
838 $self->_insert_blobs ($source, \%blob_vals, \%row);
839 }
840}
841
842=head2 connect_call_datetime_setup
843
844Used as:
845
846 on_connect_call => 'datetime_setup'
847
8384a713 848In L<connect_info|DBIx::Class::Storage::DBI/connect_info> to set:
057db5ce 849
850 $dbh->syb_date_fmt('ISO_strict'); # output fmt: 2004-08-21T14:36:48.080Z
851 $dbh->do('set dateformat mdy'); # input fmt: 08/13/1979 18:08:55.080
852
c1e5a9ac 853This works for both C<DATETIME> and C<SMALLDATETIME> columns, note that
057db5ce 854C<SMALLDATETIME> columns only have minute precision.
855
856=cut
857
70c28808 858sub connect_call_datetime_setup {
859 my $self = shift;
860 my $dbh = $self->_get_dbh;
057db5ce 861
70c28808 862 if ($dbh->can('syb_date_fmt')) {
863 # amazingly, this works with FreeTDS
864 $dbh->syb_date_fmt('ISO_strict');
865 }
866 else {
867 carp_once
868 'Your DBD::Sybase is too old to support '
869 .'DBIx::Class::InflateColumn::DateTime, please upgrade!';
057db5ce 870
c6b7885f 871 # FIXME - in retrospect this is a rather bad US-centric choice
872 # of format. Not changing as a bugwards compat, though in reality
873 # the only piece that sees the results of $dt object formatting
874 # (as opposed to parsing) is the database itself, so theoretically
875 # changing both this SET command and the formatter definition of
876 # ::S::D::Sybase::ASE::DateTime::Format below should be safe and
877 # transparent
878
057db5ce 879 $dbh->do('SET DATEFORMAT mdy');
057db5ce 880 }
881}
882
057db5ce 883
90d7422f 884sub _exec_txn_begin {
057db5ce 885 my $self = shift;
886
887# bulkLogin=1 connections are always in a transaction, and can only call BEGIN
888# TRAN once. However, we need to make sure there's a $dbh.
889 return if $self->_is_bulk_storage && $self->_dbh && $self->_began_bulk_work;
890
891 $self->next::method(@_);
892
057db5ce 893 $self->_began_bulk_work(1) if $self->_is_bulk_storage;
894}
895
057db5ce 896# savepoint support using ASE syntax
897
90d7422f 898sub _exec_svp_begin {
057db5ce 899 my ($self, $name) = @_;
900
90d7422f 901 $self->_dbh->do("SAVE TRANSACTION $name");
057db5ce 902}
903
904# A new SAVE TRANSACTION with the same name releases the previous one.
90d7422f 905sub _exec_svp_release { 1 }
057db5ce 906
90d7422f 907sub _exec_svp_rollback {
057db5ce 908 my ($self, $name) = @_;
909
90d7422f 910 $self->_dbh->do("ROLLBACK TRANSACTION $name");
057db5ce 911}
912
deabd575 913package # hide from PAUSE
914 DBIx::Class::Storage::DBI::Sybase::ASE::DateTime::Format;
915
916my $datetime_parse_format = '%Y-%m-%dT%H:%M:%S.%3NZ';
917my $datetime_format_format = '%m/%d/%Y %H:%M:%S.%3N';
918
919my ($datetime_parser, $datetime_formatter);
920
921sub parse_datetime {
922 shift;
923 require DateTime::Format::Strptime;
924 $datetime_parser ||= DateTime::Format::Strptime->new(
925 pattern => $datetime_parse_format,
926 on_error => 'croak',
927 );
928 return $datetime_parser->parse_datetime(shift);
929}
930
931sub format_datetime {
932 shift;
933 require DateTime::Format::Strptime;
934 $datetime_formatter ||= DateTime::Format::Strptime->new(
935 pattern => $datetime_format_format,
936 on_error => 'croak',
937 );
938 return $datetime_formatter->format_datetime(shift);
939}
940
057db5ce 9411;
942
943=head1 Schema::Loader Support
944
290da7d6 945As of version C<0.05000>, L<DBIx::Class::Schema::Loader> should work well with
c1e5a9ac 946most versions of Sybase ASE.
057db5ce 947
948=head1 FreeTDS
949
950This driver supports L<DBD::Sybase> compiled against FreeTDS
951(L<http://www.freetds.org/>) to the best of our ability, however it is
952recommended that you recompile L<DBD::Sybase> against the Sybase Open Client
953libraries. They are a part of the Sybase ASE distribution:
954
955The Open Client FAQ is here:
956L<http://www.isug.com/Sybase_FAQ/ASE/section7.html>.
957
958Sybase ASE for Linux (which comes with the Open Client libraries) may be
959downloaded here: L<http://response.sybase.com/forms/ASE_Linux_Download>.
960
aca3b4c3 961To see if you're using FreeTDS run:
057db5ce 962
963 perl -MDBI -le 'my $dbh = DBI->connect($dsn, $user, $pass); print $dbh->{syb_oc_version}'
964
c1e5a9ac 965It is recommended to set C<tds version> for your ASE server to C<5.0> in
966C</etc/freetds/freetds.conf>.
967
968Some versions or configurations of the libraries involved will not support
969placeholders, in which case the storage will be reblessed to
057db5ce 970L<DBIx::Class::Storage::DBI::Sybase::ASE::NoBindVars>.
971
972In some configurations, placeholders will work but will throw implicit type
973conversion errors for anything that's not expecting a string. In such a case,
974the C<auto_cast> option from L<DBIx::Class::Storage::DBI::AutoCast> is
975automatically set, which you may enable on connection with
c1e5a9ac 976L<connect_call_set_auto_cast|DBIx::Class::Storage::DBI::AutoCast/connect_call_set_auto_cast>.
977The type info for the C<CAST>s is taken from the
978L<DBIx::Class::ResultSource/data_type> definitions in your Result classes, and
979are mapped to a Sybase type (if it isn't already) using a mapping based on
980L<SQL::Translator>.
057db5ce 981
48580715 982In other configurations, placeholders will work just as they do with the Sybase
057db5ce 983Open Client libraries.
984
985Inserts or updates of TEXT/IMAGE columns will B<NOT> work with FreeTDS.
986
987=head1 INSERTS WITH PLACEHOLDERS
988
989With placeholders enabled, inserts are done in a transaction so that there are
990no concurrency issues with getting the inserted identity value using
991C<SELECT MAX(col)>, which is the only way to get the C<IDENTITY> value in this
992mode.
993
994In addition, they are done on a separate connection so that it's possible to
995have active cursors when doing an insert.
996
997When using C<DBIx::Class::Storage::DBI::Sybase::ASE::NoBindVars> transactions
c1e5a9ac 998are unnecessary and not used, as there are no concurrency issues with C<SELECT
999@@IDENTITY> which is a session variable.
057db5ce 1000
1001=head1 TRANSACTIONS
1002
c1e5a9ac 1003Due to limitations of the TDS protocol and L<DBD::Sybase>, you cannot begin a
1004transaction while there are active cursors, nor can you use multiple active
1005cursors within a transaction. An active cursor is, for example, a
057db5ce 1006L<ResultSet|DBIx::Class::ResultSet> that has been executed using C<next> or
1007C<first> but has not been exhausted or L<reset|DBIx::Class::ResultSet/reset>.
1008
1009For example, this will not work:
1010
1011 $schema->txn_do(sub {
1012 my $rs = $schema->resultset('Book');
47d7b769 1013 while (my $result = $rs->next) {
057db5ce 1014 $schema->resultset('MetaData')->create({
47d7b769 1015 book_id => $result->id,
057db5ce 1016 ...
1017 });
1018 }
1019 });
1020
1021This won't either:
1022
1023 my $first_row = $large_rs->first;
1024 $schema->txn_do(sub { ... });
1025
1026Transactions done for inserts in C<AutoCommit> mode when placeholders are in use
1027are not affected, as they are done on an extra database handle.
1028
1029Some workarounds:
1030
1031=over 4
1032
1033=item * use L<DBIx::Class::Storage::DBI::Replicated>
1034
1035=item * L<connect|DBIx::Class::Schema/connect> another L<Schema|DBIx::Class::Schema>
1036
1037=item * load the data from your cursor with L<DBIx::Class::ResultSet/all>
1038
1039=back
1040
1041=head1 MAXIMUM CONNECTIONS
1042
1043The TDS protocol makes separate connections to the server for active statements
1044in the background. By default the number of such connections is limited to 25,
1045on both the client side and the server side.
1046
1047This is a bit too low for a complex L<DBIx::Class> application, so on connection
1048the client side setting is set to C<256> (see L<DBD::Sybase/maxConnect>.) You
1049can override it to whatever setting you like in the DSN.
1050
1051See
1052L<http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.help.ase_15.0.sag1/html/sag1/sag1272.htm>
1053for information on changing the setting on the server side.
1054
1055=head1 DATES
1056
1057See L</connect_call_datetime_setup> to setup date formats
1058for L<DBIx::Class::InflateColumn::DateTime>.
1059
9c510ba5 1060=head1 LIMITED QUERIES
1061
5529838f 1062Because ASE does not have a good way to limit results in SQL that works for
1063all types of queries, the limit dialect is set to
1064L<GenericSubQ|DBIx::Class::SQLMaker::LimitDialects/GenericSubQ>.
9c510ba5 1065
1066Fortunately, ASE and L<DBD::Sybase> support cursors properly, so when
5529838f 1067L<GenericSubQ|DBIx::Class::SQLMaker::LimitDialects/GenericSubQ> is too slow
1068you can use the L<software_limit|DBIx::Class::ResultSet/software_limit>
1069L<DBIx::Class::ResultSet> attribute to simulate limited queries by skipping
1070over records.
9c510ba5 1071
057db5ce 1072=head1 TEXT/IMAGE COLUMNS
1073
1074L<DBD::Sybase> compiled with FreeTDS will B<NOT> allow you to insert or update
1075C<TEXT/IMAGE> columns.
1076
1077Setting C<< $dbh->{LongReadLen} >> will also not work with FreeTDS use either:
1078
1079 $schema->storage->dbh->do("SET TEXTSIZE $bytes");
1080
1081or
1082
1083 $schema->storage->set_textsize($bytes);
1084
1085instead.
1086
1087However, the C<LongReadLen> you pass in
8384a713 1088L<connect_info|DBIx::Class::Storage::DBI/connect_info> is used to execute the
1089equivalent C<SET TEXTSIZE> command on connection.
057db5ce 1090
8384a713 1091See L</connect_call_blob_setup> for a
1092L<connect_info|DBIx::Class::Storage::DBI/connect_info> setting you need to work
1093with C<IMAGE> columns.
057db5ce 1094
1095=head1 BULK API
1096
1097The experimental L<DBD::Sybase> Bulk API support is used for
1098L<populate|DBIx::Class::ResultSet/populate> in B<void> context, in a transaction
1099on a separate connection.
1100
1101To use this feature effectively, use a large number of rows for each
1102L<populate|DBIx::Class::ResultSet/populate> call, eg.:
1103
1104 while (my $rows = $data_source->get_100_rows()) {
1105 $rs->populate($rows);
1106 }
1107
1108B<NOTE:> the L<add_columns|DBIx::Class::ResultSource/add_columns>
1109calls in your C<Result> classes B<must> list columns in database order for this
5241250d 1110to work. Also, you may have to unset the C<LC_ALL> environment variable before
c1e5a9ac 1111loading your app, as C<BCP -Y> is not yet supported in DBD::Sybase .
057db5ce 1112
1113When inserting IMAGE columns using this method, you'll need to use
1114L</connect_call_blob_setup> as well.
1115
6476fd66 1116=head1 COMPUTED COLUMNS
1117
1118If you have columns such as:
1119
1120 created_dtm AS getdate()
1121
1122represent them in your Result classes as:
1123
1124 created_dtm => {
1125 data_type => undef,
1126 default_value => \'getdate()',
1127 is_nullable => 0,
c1e5a9ac 1128 inflate_datetime => 1,
6476fd66 1129 }
1130
1131The C<data_type> must exist and must be C<undef>. Then empty inserts will work
1132on tables with such columns.
1133
1134=head1 TIMESTAMP COLUMNS
1135
1136C<timestamp> columns in Sybase ASE are not really timestamps, see:
1137L<http://dba.fyicenter.com/Interview-Questions/SYBASE/The_timestamp_datatype_in_Sybase_.html>.
1138
1139They should be defined in your Result classes as:
1140
1141 ts => {
1142 data_type => 'timestamp',
1143 is_nullable => 0,
1144 inflate_datetime => 0,
1145 }
1146
1147The C<<inflate_datetime => 0>> is necessary if you use
1148L<DBIx::Class::InflateColumn::DateTime>, and most people do, and still want to
1149be able to read these values.
1150
1151The values will come back as hexadecimal.
1152
057db5ce 1153=head1 TODO
1154
1155=over
1156
1157=item *
1158
1159Transitions to AutoCommit=0 (starting a transaction) mode by exhausting
1160any active cursors, using eager cursors.
1161
1162=item *
1163
1164Real limits and limited counts using stored procedures deployed on startup.
1165
1166=item *
1167
057db5ce 1168Blob update with a LIKE query on a blob, without invalidating the WHERE condition.
1169
1170=item *
1171
1172bulk_insert using prepare_cached (see comments.)
1173
1174=back
1175
a2bd3796 1176=head1 FURTHER QUESTIONS?
057db5ce 1177
a2bd3796 1178Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>.
057db5ce 1179
a2bd3796 1180=head1 COPYRIGHT AND LICENSE
057db5ce 1181
a2bd3796 1182This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE>
1183by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can
1184redistribute it and/or modify it under the same terms as the
1185L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>.