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