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