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