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