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