Extra cleanup of leftovers from 70171cd7
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBI / Sybase / ASE.pm
1 package DBIx::Class::Storage::DBI::Sybase::ASE;
2
3 use strict;
4 use warnings;
5
6 use base qw/
7     DBIx::Class::Storage::DBI::Sybase
8     DBIx::Class::Storage::DBI::AutoCast
9 /;
10 use mro 'c3';
11 use DBIx::Class::Carp;
12 use Scalar::Util 'blessed';
13 use List::Util 'first';
14 use Sub::Name();
15 use Data::Dumper::Concise 'Dumper';
16 use Try::Tiny;
17 use namespace::clean;
18
19 __PACKAGE__->sql_limit_dialect ('RowCountOrGenericSubQ');
20 __PACKAGE__->sql_quote_char ([qw/[ ]/]);
21 __PACKAGE__->datetime_parser_type(
22   'DBIx::Class::Storage::DBI::Sybase::ASE::DateTime::Format'
23 );
24
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
32
33 my @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
43 DBIx::Class::Storage::DBI::Sybase::ASE - Sybase ASE SQL Server support for
44 DBIx::Class
45
46 =head1 SYNOPSIS
47
48 This subclass supports L<DBD::Sybase> for real (non-Microsoft) Sybase databases.
49
50 =head1 DESCRIPTION
51
52 If your version of Sybase does not support placeholders, then your storage will
53 be reblessed to L<DBIx::Class::Storage::DBI::Sybase::ASE::NoBindVars>.
54 You can also enable that driver explicitly, see the documentation for more
55 details.
56
57 With this driver there is unfortunately no way to get the C<last_insert_id>
58 without doing a C<SELECT MAX(col)>. This is done safely in a transaction
59 (locking the table.) See L</INSERTS WITH PLACEHOLDERS>.
60
61 A recommended L<connect_info|DBIx::Class::Storage::DBI/connect_info> setting:
62
63   on_connect_call => [['datetime_setup'], ['blob_setup', log_on_update => 0]]
64
65 =head1 METHODS
66
67 =cut
68
69 sub _rebless {
70   my $self = shift;
71
72   my $no_bind_vars = __PACKAGE__ . '::NoBindVars';
73
74   if ($self->using_freetds) {
75     carp_once <<'EOF' unless $ENV{DBIC_SYBASE_FREETDS_NOWARN};
76
77 You are using FreeTDS with Sybase.
78
79 We will do our best to support this configuration, but please consider this
80 support experimental.
81
82 TEXT/IMAGE columns will definitely not work.
83
84 You are encouraged to recompile DBD::Sybase with the Sybase Open Client libraries
85 instead.
86
87 See perldoc DBIx::Class::Storage::DBI::Sybase::ASE for more details.
88
89 To turn off this warning set the DBIC_SYBASE_FREETDS_NOWARN environment
90 variable.
91 EOF
92
93     if (not $self->_use_typeless_placeholders) {
94       if ($self->_use_placeholders) {
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
112   elsif (not $self->_use_typeless_placeholders) {
113     $self->auto_cast(1);
114   }
115 }
116
117 sub _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
148 for 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
162 sub 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
179 # This is only invoked for FreeTDS drivers by ::Storage::DBI::Sybase::FreeTDS
180 sub _set_autocommit_stmt {
181   my ($self, $on) = @_;
182
183   return 'SET CHAINED ' . ($on ? 'OFF' : 'ON');
184 }
185
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.
192 sub _run_connection_actions {
193   my $self = shift;
194
195   if ($self->_is_bulk_storage) {
196     # this should be cleared on every reconnect
197     $self->_began_bulk_work(0);
198     return;
199   }
200
201   $self->_dbh->{syb_chained_txn} = 1
202     unless $self->using_freetds;
203
204   $self->next::method(@_);
205 }
206
207 =head2 connect_call_blob_setup
208
209 Used as:
210
211   on_connect_call => [ [ 'blob_setup', log_on_update => 0 ] ]
212
213 Does C<< $dbh->{syb_binary_images} = 1; >> to return C<IMAGE> data as raw binary
214 instead of as a hex string.
215
216 Recommended.
217
218 Also sets the C<log_on_update> value for blob write operations. The default is
219 C<1>, but C<0> is better if your database is configured for it.
220
221 See
222 L<DBD::Sybase/Handling_IMAGE/TEXT_data_with_syb_ct_get_data()/syb_ct_send_data()>.
223
224 =cut
225
226 sub 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
236 sub _is_lob_column {
237   my ($self, $source, $column) = @_;
238
239   return $self->_is_lob_type($source->column_info($column)->{data_type});
240 }
241
242 sub _prep_for_execute {
243   my $self = shift;
244   my ($op, $ident, $args) = @_;
245
246   my ($sql, $bind) = $self->next::method (@_);
247
248   my $table = blessed $ident ? $ident->from : $ident;
249
250   my $bind_info = $self->_resolve_column_info(
251     $ident, [map { $_->[0]{dbic_colname} || () } @{$bind}]
252   );
253   my $bound_identity_col =
254     first { $bind_info->{$_}{is_auto_increment} }
255     keys %$bind_info
256   ;
257
258   my $columns_info = blessed $ident && $ident->columns_info;
259
260   my $identity_col =
261     $columns_info &&
262     first { $columns_info->{$_}{is_auto_increment} }
263       keys %$columns_info
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
285 sub _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.
295 our %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
313 sub _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
322 sub _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
330 sub _execute {
331   my $self = shift;
332   my ($op) = @_;
333
334   my ($rv, $sth, @bind) = $self->next::method(@_);
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
344 sub last_insert_id { shift->_identity }
345
346 # handles TEXT/IMAGE and transaction for last_insert_id
347 sub insert {
348   my $self = shift;
349   my ($source, $to_insert) = @_;
350
351   my $columns_info = $source->columns_info;
352
353   my $identity_col =
354     (first { $columns_info->{$_}{is_auto_increment} }
355       keys %$columns_info )
356     || '';
357
358   # check for empty insert
359   # INSERT INTO foo DEFAULT VALUES -- does not work with Sybase
360   # try to insert explicit 'DEFAULT's instead (except for identity, timestamp
361   # and computed columns)
362   if (not %$to_insert) {
363     for my $col ($source->columns) {
364       next if $col eq $identity_col;
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
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
412 sub _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
429 sub update {
430   my $self = shift;
431   my ($source, $fields, $where, @rest) = @_;
432
433   my $blob_cols = $self->_remove_blob_cols($source, $fields);
434
435   my $table = $source->name;
436
437   my $columns_info = $source->columns_info;
438
439   my $identity_col =
440     first { $columns_info->{$_}{is_auto_increment} }
441       keys %$columns_info;
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) {
470     if (wantarray) {
471       @res    = $self->next::method(@_);
472     }
473     elsif (defined wantarray) {
474       $res[0] = $self->next::method(@_);
475     }
476     else {
477       $self->next::method(@_);
478     }
479   }
480
481   $guard->commit;
482
483   return wantarray ? @res : $res[0];
484 }
485
486 sub insert_bulk {
487   my $self = shift;
488   my ($source, $cols, $data) = @_;
489
490   my $columns_info = $source->columns_info;
491
492   my $identity_col =
493     first { $columns_info->{$_}{is_auto_increment} }
494       keys %$columns_info;
495
496   my $is_identity_insert = (first { $_ eq $identity_col } @{$cols}) ? 1 : 0;
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';
510 Bulk API support disabled due to use of a CODEREF connect_info. Reverting to
511 regular array inserts.
512 EOF
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
602   my $exception = '';
603   try {
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
635     my @bind = map { [ $source_columns[$_] => $_ ] } (0 .. $#source_columns);
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);
644   } catch {
645     $exception = shift;
646   };
647
648   DBD::Sybase::set_cslib_cb($orig_cslib_cb);
649
650   if ($exception =~ /-Y option/) {
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;
656
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
668 sub _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.
679 sub _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
701 sub _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
727 sub _update_blobs {
728   my ($self, $source, $blob_cols, $where) = @_;
729
730   my @primary_cols = try
731     { $source->_pri_cols }
732     catch {
733       $self->throw_exception("Cannot update TEXT/IMAGE column(s): $_")
734     };
735
736   my @pks_to_update;
737   if (
738     ref $where eq 'HASH'
739       and
740     @primary_cols == grep { defined $where->{$_} } @primary_cols
741   ) {
742     my %row_to_update;
743     @row_to_update{@primary_cols} = @{$where}{@primary_cols};
744     @pks_to_update = \%row_to_update;
745   }
746   else {
747     my $cursor = $self->select ($source, \@primary_cols, $where, {});
748     @pks_to_update = map {
749       my %row; @row{@primary_cols} = @$_; \%row
750     } $cursor->all;
751   }
752
753   for my $ident (@pks_to_update) {
754     $self->_insert_blobs($source, $blob_cols, $ident);
755   }
756 }
757
758 sub _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;
765   my @primary_cols = try
766     { $source->_pri_cols }
767     catch {
768       $self->throw_exception("Cannot update TEXT/IMAGE column(s): $_")
769     };
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) {
784       $self->throw_exception(
785           "Could not find row in table '$table' for blob update:\n"
786         . (Dumper \%where)
787       );
788     }
789
790     try {
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;
808     }
809     catch {
810       if ($self->using_freetds) {
811         $self->throw_exception (
812           "TEXT/IMAGE operation failed, probably because you are using FreeTDS: $_"
813         );
814       }
815       else {
816         $self->throw_exception($_);
817       }
818     }
819     finally {
820       $sth->finish if $sth;
821     };
822   }
823 }
824
825 sub _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
847 Used as:
848
849   on_connect_call => 'datetime_setup'
850
851 In L<connect_info|DBIx::Class::Storage::DBI/connect_info> to set:
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
856 This works for both C<DATETIME> and C<SMALLDATETIME> columns, note that
857 C<SMALLDATETIME> columns only have minute precision.
858
859 =cut
860
861 sub connect_call_datetime_setup {
862   my $self = shift;
863   my $dbh = $self->_get_dbh;
864
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!';
873
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
882     $dbh->do('SET DATEFORMAT mdy');
883   }
884 }
885
886
887 sub _exec_txn_begin {
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
896   $self->_began_bulk_work(1) if $self->_is_bulk_storage;
897 }
898
899 # savepoint support using ASE syntax
900
901 sub _exec_svp_begin {
902   my ($self, $name) = @_;
903
904   $self->_dbh->do("SAVE TRANSACTION $name");
905 }
906
907 # A new SAVE TRANSACTION with the same name releases the previous one.
908 sub _exec_svp_release { 1 }
909
910 sub _exec_svp_rollback {
911   my ($self, $name) = @_;
912
913   $self->_dbh->do("ROLLBACK TRANSACTION $name");
914 }
915
916 package # hide from PAUSE
917   DBIx::Class::Storage::DBI::Sybase::ASE::DateTime::Format;
918
919 my $datetime_parse_format  = '%Y-%m-%dT%H:%M:%S.%3NZ';
920 my $datetime_format_format = '%m/%d/%Y %H:%M:%S.%3N';
921
922 my ($datetime_parser, $datetime_formatter);
923
924 sub 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
934 sub 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
944 1;
945
946 =head1 Schema::Loader Support
947
948 As of version C<0.05000>, L<DBIx::Class::Schema::Loader> should work well with
949 most versions of Sybase ASE.
950
951 =head1 FreeTDS
952
953 This driver supports L<DBD::Sybase> compiled against FreeTDS
954 (L<http://www.freetds.org/>) to the best of our ability, however it is
955 recommended that you recompile L<DBD::Sybase> against the Sybase Open Client
956 libraries. They are a part of the Sybase ASE distribution:
957
958 The Open Client FAQ is here:
959 L<http://www.isug.com/Sybase_FAQ/ASE/section7.html>.
960
961 Sybase ASE for Linux (which comes with the Open Client libraries) may be
962 downloaded here: L<http://response.sybase.com/forms/ASE_Linux_Download>.
963
964 To 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
968 It is recommended to set C<tds version> for your ASE server to C<5.0> in
969 C</etc/freetds/freetds.conf>.
970
971 Some versions or configurations of the libraries involved will not support
972 placeholders, in which case the storage will be reblessed to
973 L<DBIx::Class::Storage::DBI::Sybase::ASE::NoBindVars>.
974
975 In some configurations, placeholders will work but will throw implicit type
976 conversion errors for anything that's not expecting a string. In such a case,
977 the C<auto_cast> option from L<DBIx::Class::Storage::DBI::AutoCast> is
978 automatically set, which you may enable on connection with
979 L<connect_call_set_auto_cast|DBIx::Class::Storage::DBI::AutoCast/connect_call_set_auto_cast>.
980 The type info for the C<CAST>s is taken from the
981 L<DBIx::Class::ResultSource/data_type> definitions in your Result classes, and
982 are mapped to a Sybase type (if it isn't already) using a mapping based on
983 L<SQL::Translator>.
984
985 In other configurations, placeholders will work just as they do with the Sybase
986 Open Client libraries.
987
988 Inserts or updates of TEXT/IMAGE columns will B<NOT> work with FreeTDS.
989
990 =head1 INSERTS WITH PLACEHOLDERS
991
992 With placeholders enabled, inserts are done in a transaction so that there are
993 no concurrency issues with getting the inserted identity value using
994 C<SELECT MAX(col)>, which is the only way to get the C<IDENTITY> value in this
995 mode.
996
997 In addition, they are done on a separate connection so that it's possible to
998 have active cursors when doing an insert.
999
1000 When using C<DBIx::Class::Storage::DBI::Sybase::ASE::NoBindVars> transactions
1001 are unnecessary and not used, as there are no concurrency issues with C<SELECT
1002 @@IDENTITY> which is a session variable.
1003
1004 =head1 TRANSACTIONS
1005
1006 Due to limitations of the TDS protocol and L<DBD::Sybase>, you cannot begin a
1007 transaction while there are active cursors, nor can you use multiple active
1008 cursors within a transaction. An active cursor is, for example, a
1009 L<ResultSet|DBIx::Class::ResultSet> that has been executed using C<next> or
1010 C<first> but has not been exhausted or L<reset|DBIx::Class::ResultSet/reset>.
1011
1012 For 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
1024 This won't either:
1025
1026   my $first_row = $large_rs->first;
1027   $schema->txn_do(sub { ... });
1028
1029 Transactions done for inserts in C<AutoCommit> mode when placeholders are in use
1030 are not affected, as they are done on an extra database handle.
1031
1032 Some 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
1046 The TDS protocol makes separate connections to the server for active statements
1047 in the background. By default the number of such connections is limited to 25,
1048 on both the client side and the server side.
1049
1050 This is a bit too low for a complex L<DBIx::Class> application, so on connection
1051 the client side setting is set to C<256> (see L<DBD::Sybase/maxConnect>.) You
1052 can override it to whatever setting you like in the DSN.
1053
1054 See
1055 L<http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.help.ase_15.0.sag1/html/sag1/sag1272.htm>
1056 for information on changing the setting on the server side.
1057
1058 =head1 DATES
1059
1060 See L</connect_call_datetime_setup> to setup date formats
1061 for L<DBIx::Class::InflateColumn::DateTime>.
1062
1063 =head1 TEXT/IMAGE COLUMNS
1064
1065 L<DBD::Sybase> compiled with FreeTDS will B<NOT> allow you to insert or update
1066 C<TEXT/IMAGE> columns.
1067
1068 Setting C<< $dbh->{LongReadLen} >> will also not work with FreeTDS use either:
1069
1070   $schema->storage->dbh->do("SET TEXTSIZE $bytes");
1071
1072 or
1073
1074   $schema->storage->set_textsize($bytes);
1075
1076 instead.
1077
1078 However, the C<LongReadLen> you pass in
1079 L<connect_info|DBIx::Class::Storage::DBI/connect_info> is used to execute the
1080 equivalent C<SET TEXTSIZE> command on connection.
1081
1082 See L</connect_call_blob_setup> for a
1083 L<connect_info|DBIx::Class::Storage::DBI/connect_info> setting you need to work
1084 with C<IMAGE> columns.
1085
1086 =head1 BULK API
1087
1088 The experimental L<DBD::Sybase> Bulk API support is used for
1089 L<populate|DBIx::Class::ResultSet/populate> in B<void> context, in a transaction
1090 on a separate connection.
1091
1092 To use this feature effectively, use a large number of rows for each
1093 L<populate|DBIx::Class::ResultSet/populate> call, eg.:
1094
1095   while (my $rows = $data_source->get_100_rows()) {
1096     $rs->populate($rows);
1097   }
1098
1099 B<NOTE:> the L<add_columns|DBIx::Class::ResultSource/add_columns>
1100 calls in your C<Result> classes B<must> list columns in database order for this
1101 to work. Also, you may have to unset the C<LANG> environment variable before
1102 loading your app, as C<BCP -Y> is not yet supported in DBD::Sybase .
1103
1104 When inserting IMAGE columns using this method, you'll need to use
1105 L</connect_call_blob_setup> as well.
1106
1107 =head1 COMPUTED COLUMNS
1108
1109 If you have columns such as:
1110
1111   created_dtm AS getdate()
1112
1113 represent them in your Result classes as:
1114
1115   created_dtm => {
1116     data_type => undef,
1117     default_value => \'getdate()',
1118     is_nullable => 0,
1119     inflate_datetime => 1,
1120   }
1121
1122 The C<data_type> must exist and must be C<undef>. Then empty inserts will work
1123 on tables with such columns.
1124
1125 =head1 TIMESTAMP COLUMNS
1126
1127 C<timestamp> columns in Sybase ASE are not really timestamps, see:
1128 L<http://dba.fyicenter.com/Interview-Questions/SYBASE/The_timestamp_datatype_in_Sybase_.html>.
1129
1130 They 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
1138 The C<<inflate_datetime => 0>> is necessary if you use
1139 L<DBIx::Class::InflateColumn::DateTime>, and most people do, and still want to
1140 be able to read these values.
1141
1142 The values will come back as hexadecimal.
1143
1144 =head1 TODO
1145
1146 =over
1147
1148 =item *
1149
1150 Transitions to AutoCommit=0 (starting a transaction) mode by exhausting
1151 any active cursors, using eager cursors.
1152
1153 =item *
1154
1155 Real limits and limited counts using stored procedures deployed on startup.
1156
1157 =item *
1158
1159 Blob update with a LIKE query on a blob, without invalidating the WHERE condition.
1160
1161 =item *
1162
1163 bulk_insert using prepare_cached (see comments.)
1164
1165 =back
1166
1167 =head1 AUTHOR
1168
1169 See L<DBIx::Class/AUTHOR> and L<DBIx::Class/CONTRIBUTORS>.
1170
1171 =head1 LICENSE
1172
1173 You may distribute this code under the same terms as Perl itself.
1174
1175 =cut
1176 # vim:sts=2 sw=2: