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