Factor out dumper copy/paste
[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' =>
2baff5da 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
2baff5da 22my @also_proxy_to_extra_storages = qw/
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) {
2baff5da 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
2baff5da 126 return if $self->_is_extra_storage;
6fcb1409 127
d69a17c8 128 my $writer_storage = (ref $self)->new;
40531ea8 129
2baff5da 130 $writer_storage->_is_extra_storage(1);
d69a17c8 131 $writer_storage->connect_info($self->connect_info);
2baff5da 132 $writer_storage->auto_cast($self->auto_cast);
40531ea8 133
d69a17c8 134 $self->_writer_storage($writer_storage);
2baff5da 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
2baff5da 152for my $method (@also_proxy_to_extra_storages) {
6fcb1409 153 no strict 'refs';
2baff5da 154 no warnings 'redefine';
6fcb1409 155
d69a17c8 156 my $replaced = __PACKAGE__->can($method);
157
2baff5da 158 *{$method} = Sub::Name::subname $method => sub {
6fcb1409 159 my $self = shift;
d69a17c8 160 $self->_writer_storage->$replaced(@_) if $self->_writer_storage;
2baff5da 161 $self->_bulk_storage->$replaced(@_) if $self->_bulk_storage;
d69a17c8 162 return $self->$replaced(@_);
6fcb1409 163 };
37b17a93 164}
165
2baff5da 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(@_);
2baff5da 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
2baff5da 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;
2baff5da 407 my ($source, $fields, $where, @rest) = @_;
0ac07712 408
409 my $wantarray = wantarray;
2baff5da 410
078a332f 411 my $blob_cols = $self->_remove_blob_cols($source, $fields);
412
2baff5da 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) {
2baff5da 422 $self->_set_identity_insert($table, 'update') if $is_identity_update;
961a1383 423 return $self->next::method(@_);
2baff5da 424 $self->_unset_identity_insert($table, 'update') if $is_identity_update;
425 }
426
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
2baff5da 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;
443
444 $self->next::method($source, \%blobs_to_empty, $where, @rest);
e19677ad 445
2baff5da 446# Now update the blobs before the other columns in case the update of other
447# columns makes the search condition invalid.
e19677ad 448 $self->_update_blobs($source, $blob_cols, $where);
078a332f 449
2baff5da 450 my @res;
451 if (%$fields) {
452 $self->_set_identity_insert($table, 'update') if $is_identity_update;
453
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 }
463
464 $self->_unset_identity_insert($table, 'update') if $is_identity_update;
465 }
466
961a1383 467 $guard->commit;
aee988d2 468
078a332f 469 return $wantarray ? @res : $res[0];
470}
7d17f469 471
2baff5da 472### the insert_bulk partially stolen from DBI/MSSQL.pm
40531ea8 473
474sub _set_identity_insert {
2baff5da 475 my ($self, $table, $op) = @_;
40531ea8 476
477 my $sql = sprintf (
2baff5da 478 'SET IDENTITY_%s %s ON',
479 (uc($op) || 'INSERT'),
40531ea8 480 $self->sql_maker->_quote ($table),
481 );
482
2baff5da 483 $self->_query_start($sql);
484
40531ea8 485 my $dbh = $self->_get_dbh;
486 eval { $dbh->do ($sql) };
2baff5da 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 {
2baff5da 500 my ($self, $table, $op) = @_;
40531ea8 501
502 my $sql = sprintf (
2baff5da 503 'SET IDENTITY_%s %s OFF',
504 (uc($op) || 'INSERT'),
40531ea8 505 $self->sql_maker->_quote ($table),
506 );
507
2baff5da 508 $self->_query_start($sql);
509
40531ea8 510 my $dbh = $self->_get_dbh;
511 $dbh->do ($sql);
2baff5da 512
513 $self->_query_end($sql);
40531ea8 514}
515
2baff5da 516# for tests
517sub _can_insert_bulk { 1 }
518
40531ea8 519sub insert_bulk {
520 my $self = shift;
521 my ($source, $cols, $data) = @_;
522
2baff5da 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
2baff5da 528 { $source->column_info ($_)->{is_auto_increment} }
529 @{$cols}
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
2baff5da 548 if (not $use_bulk_api) {
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';
af9e4a5e 555
2baff5da 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 };
565
566 $self->_set_identity_insert ($source->name) if $is_identity_insert;
567 $self->next::method(@_);
568 $self->_unset_identity_insert ($source->name) if $is_identity_insert;
569
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 }
592 }
593
594 $guard->commit if $guard;
595 return;
af9e4a5e 596 }
40531ea8 597
2baff5da 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 }
e19677ad 618
2baff5da 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
2baff5da 699 $self->throw_exception(sprintf "%s for populate slice:\n%s",
700 ($tuple_status->[$i][1] || $err),
b561bb6f 701 $self->_pretty_print ({
702 map { $source_columns[$_] => $new_data[$i][$_] } (0 .. $#$cols)
703 }),
2baff5da 704 );
705 }
706
707 $guard->commit;
708 $sth->finish;
709
710 $bulk->_query_end($sql);
711 };
712 my $exception = $@;
713 if ($exception =~ /-Y option/) {
714 carp <<"EOF";
715
716Sybase bulk API operation failed due to character set incompatibility, reverting
717to regular array inserts:
718
719*** Try unsetting the LANG environment variable.
720
721$@
722EOF
723 $self->_bulk_storage(undef);
724 DBD::Sybase::set_cslib_cb($orig_cslib_cb);
725 unshift @_, $self;
726 goto \&insert_bulk;
727 }
728 elsif ($exception) {
729 DBD::Sybase::set_cslib_cb($orig_cslib_cb);
730# rollback makes the bulkLogin connection unusable
731 $self->_bulk_storage->disconnect;
732 $self->throw_exception($exception);
733 }
734
735 DBD::Sybase::set_cslib_cb($orig_cslib_cb);
736}
737
738# Make sure blobs are not bound as placeholders, and return any non-empty ones
739# as a hash.
7d17f469 740sub _remove_blob_cols {
741 my ($self, $source, $fields) = @_;
fd5a07e4 742
743 my %blob_cols;
744
7d17f469 745 for my $col (keys %$fields) {
9b3dabe0 746 if ($self->_is_lob_type($source->column_info($col)->{data_type})) {
2baff5da 747 my $blob_val = delete $fields->{$col};
748 if (not defined $blob_val) {
749 $fields->{$col} = \'NULL';
750 }
751 else {
752 $fields->{$col} = \"''";
753 $blob_cols{$col} = $blob_val unless $blob_val eq '';
754 }
9b3dabe0 755 }
fd5a07e4 756 }
757
c966cf1b 758 return keys %blob_cols ? \%blob_cols : undef;
fd5a07e4 759}
760
2baff5da 761# same for insert_bulk
762sub _remove_blob_cols_array {
763 my ($self, $source, $cols, $data) = @_;
764
765 my @blob_cols;
766
767 for my $i (0..$#$cols) {
768 my $col = $cols->[$i];
769
770 if ($self->_is_lob_type($source->column_info($col)->{data_type})) {
771 for my $j (0..$#$data) {
772 my $blob_val = delete $data->[$j][$i];
773 if (not defined $blob_val) {
774 $data->[$j][$i] = \'NULL';
775 }
776 else {
777 $data->[$j][$i] = \"''";
778 $blob_cols[$j][$i] = $blob_val
779 unless $blob_val eq '';
780 }
781 }
782 }
783 }
784
785 return @blob_cols ? \@blob_cols : undef;
786}
787
fd5a07e4 788sub _update_blobs {
5370e479 789 my ($self, $source, $blob_cols, $where) = @_;
078a332f 790
791 my (@primary_cols) = $source->primary_columns;
792
793 croak "Cannot update TEXT/IMAGE column(s) without a primary key"
794 unless @primary_cols;
795
796# check if we're updating a single row by PK
797 my $pk_cols_in_where = 0;
798 for my $col (@primary_cols) {
5370e479 799 $pk_cols_in_where++ if defined $where->{$col};
078a332f 800 }
801 my @rows;
802
803 if ($pk_cols_in_where == @primary_cols) {
804 my %row_to_update;
5370e479 805 @row_to_update{@primary_cols} = @{$where}{@primary_cols};
078a332f 806 @rows = \%row_to_update;
807 } else {
6fcb1409 808 my $cursor = $self->select ($source, \@primary_cols, $where, {});
809 @rows = map {
810 my %row; @row{@primary_cols} = @$_; \%row
811 } $cursor->all;
078a332f 812 }
813
814 for my $row (@rows) {
815 $self->_insert_blobs($source, $blob_cols, $row);
816 }
817}
818
819sub _insert_blobs {
820 my ($self, $source, $blob_cols, $row) = @_;
75227502 821 my $dbh = $self->_get_dbh;
fd5a07e4 822
2baff5da 823 my $table = $source->name;
fd5a07e4 824
078a332f 825 my %row = %$row;
fd5a07e4 826 my (@primary_cols) = $source->primary_columns;
827
9b3dabe0 828 croak "Cannot update TEXT/IMAGE column(s) without a primary key"
fd5a07e4 829 unless @primary_cols;
830
078a332f 831 if ((grep { defined $row{$_} } @primary_cols) != @primary_cols) {
c453ddd9 832 croak "Cannot update TEXT/IMAGE column(s) without primary key values";
9b3dabe0 833 }
fd5a07e4 834
835 for my $col (keys %$blob_cols) {
836 my $blob = $blob_cols->{$col};
837
a3a526cc 838 my %where = map { ($_, $row{$_}) } @primary_cols;
6fcb1409 839
840 my $cursor = $self->select ($source, [$col], \%where, {});
a3a526cc 841 $cursor->next;
5137d252 842 my $sth = $cursor->sth;
fd5a07e4 843
2baff5da 844 if (not $sth) {
b561bb6f 845
846 $self->throw_exception(
847 "Could not find row in table '$table' for blob update:\n"
848 . $self->_pretty_print (\%where)
849 );
2baff5da 850 }
851
fd5a07e4 852 eval {
a3a526cc 853 do {
fd5a07e4 854 $sth->func('CS_GET', 1, 'ct_data_info') or die $sth->errstr;
a3a526cc 855 } while $sth->fetch;
856
fd5a07e4 857 $sth->func('ct_prepare_send') or die $sth->errstr;
858
859 my $log_on_update = $self->_blob_log_on_update;
860 $log_on_update = 1 if not defined $log_on_update;
861
862 $sth->func('CS_SET', 1, {
863 total_txtlen => length($blob),
864 log_on_update => $log_on_update
865 }, 'ct_data_info') or die $sth->errstr;
866
867 $sth->func($blob, length($blob), 'ct_send_data') or die $sth->errstr;
868
869 $sth->func('ct_finish_send') or die $sth->errstr;
870 };
871 my $exception = $@;
a3a526cc 872 $sth->finish if $sth;
873 if ($exception) {
e97a6ee2 874 if ($self->using_freetds) {
0ac07712 875 croak (
876 'TEXT/IMAGE operation failed, probably because you are using FreeTDS: '
877 . $exception
878 );
a3a526cc 879 } else {
880 croak $exception;
881 }
882 }
fd5a07e4 883 }
63d46bb3 884}
885
2baff5da 886sub _insert_blobs_array {
887 my ($self, $source, $blob_cols, $cols, $data) = @_;
888
889 for my $i (0..$#$data) {
890 my $datum = $data->[$i];
891
892 my %row;
893 @row{ @$cols } = @$datum;
894
895 my %blob_vals;
896 for my $j (0..$#$cols) {
897 if (exists $blob_cols->[$i][$j]) {
898 $blob_vals{ $cols->[$j] } = $blob_cols->[$i][$j];
899 }
900 }
901
902 $self->_insert_blobs ($source, \%blob_vals, \%row);
903 }
904}
905
9539eeb1 906=head2 connect_call_datetime_setup
907
908Used as:
909
910 on_connect_call => 'datetime_setup'
911
912In L<DBIx::Class::Storage::DBI/connect_info> to set:
913
3abafb11 914 $dbh->syb_date_fmt('ISO_strict'); # output fmt: 2004-08-21T14:36:48.080Z
915 $dbh->do('set dateformat mdy'); # input fmt: 08/13/1979 18:08:55.080
9539eeb1 916
917On connection for use with L<DBIx::Class::InflateColumn::DateTime>, using
3abafb11 918L<DateTime::Format::Sybase>, which you will need to install.
919
920This works for both C<DATETIME> and C<SMALLDATETIME> columns, although
921C<SMALLDATETIME> columns only have minute precision.
9539eeb1 922
923=cut
924
9041a97a 925{
926 my $old_dbd_warned = 0;
927
9539eeb1 928 sub connect_call_datetime_setup {
6b1f5ef7 929 my $self = shift;
6b1f5ef7 930 my $dbh = $self->_dbh;
931
932 if ($dbh->can('syb_date_fmt')) {
0ac07712 933 # amazingly, this works with FreeTDS
6b1f5ef7 934 $dbh->syb_date_fmt('ISO_strict');
935 } elsif (not $old_dbd_warned) {
936 carp "Your DBD::Sybase is too old to support ".
937 "DBIx::Class::InflateColumn::DateTime, please upgrade!";
938 $old_dbd_warned = 1;
939 }
940
e97a6ee2 941 $dbh->do('SET DATEFORMAT mdy');
c5ce7cd6 942
6b1f5ef7 943 1;
c5ce7cd6 944 }
6b1f5ef7 945}
946
6636ad53 947sub datetime_parser_type { "DateTime::Format::Sybase" }
948
e97a6ee2 949# ->begin_work and such have no effect with FreeTDS but we run them anyway to
950# let the DBD keep any state it needs to.
951#
952# If they ever do start working, the extra statements will do no harm (because
953# Sybase supports nested transactions.)
a3a526cc 954
955sub _dbh_begin_work {
956 my $self = shift;
2baff5da 957
958# bulkLogin=1 connections are always in a transaction, and can only call BEGIN
959# TRAN once. However, we need to make sure there's a $dbh.
960 return if $self->_is_bulk_storage && $self->_dbh && $self->_began_bulk_work;
961
e97a6ee2 962 $self->next::method(@_);
2baff5da 963
e97a6ee2 964 if ($self->using_freetds) {
75227502 965 $self->_get_dbh->do('BEGIN TRAN');
a3a526cc 966 }
2baff5da 967
968 $self->_began_bulk_work(1) if $self->_is_bulk_storage;
a3a526cc 969}
970
971sub _dbh_commit {
972 my $self = shift;
e97a6ee2 973 if ($self->using_freetds) {
a3a526cc 974 $self->_dbh->do('COMMIT');
975 }
e97a6ee2 976 return $self->next::method(@_);
a3a526cc 977}
978
979sub _dbh_rollback {
980 my $self = shift;
e97a6ee2 981 if ($self->using_freetds) {
a3a526cc 982 $self->_dbh->do('ROLLBACK');
983 }
e97a6ee2 984 return $self->next::method(@_);
a3a526cc 985}
986
1816be4f 987# savepoint support using ASE syntax
988
989sub _svp_begin {
990 my ($self, $name) = @_;
991
75227502 992 $self->_get_dbh->do("SAVE TRANSACTION $name");
1816be4f 993}
994
995# A new SAVE TRANSACTION with the same name releases the previous one.
996sub _svp_release { 1 }
997
998sub _svp_rollback {
999 my ($self, $name) = @_;
1000
75227502 1001 $self->_get_dbh->do("ROLLBACK TRANSACTION $name");
1816be4f 1002}
1003
3885cff6 10041;
1005
efe75aaa 1006=head1 Schema::Loader Support
1007
1008There is an experimental branch of L<DBIx::Class::Schema::Loader> that will
1009allow you to dump a schema from most (if not all) versions of Sybase.
1010
1011It is available via subversion from:
1012
07a5866e 1013 http://dev.catalyst.perl.org/repos/bast/branches/DBIx-Class-Schema-Loader/current/
efe75aaa 1014
e97a6ee2 1015=head1 FreeTDS
1016
1017This driver supports L<DBD::Sybase> compiled against FreeTDS
1018(L<http://www.freetds.org/>) to the best of our ability, however it is
1019recommended that you recompile L<DBD::Sybase> against the Sybase Open Client
1020libraries. They are a part of the Sybase ASE distribution:
1021
1022The Open Client FAQ is here:
1023L<http://www.isug.com/Sybase_FAQ/ASE/section7.html>.
1024
1025Sybase ASE for Linux (which comes with the Open Client libraries) may be
1026downloaded here: L<http://response.sybase.com/forms/ASE_Linux_Download>.
1027
1028To see if you're using FreeTDS check C<< $schema->storage->using_freetds >>, or run:
1029
1030 perl -MDBI -le 'my $dbh = DBI->connect($dsn, $user, $pass); print $dbh->{syb_oc_version}'
1031
1032Some versions of the libraries involved will not support placeholders, in which
1033case the storage will be reblessed to
1034L<DBIx::Class::Storage::DBI::Sybase::NoBindVars>.
1035
07a5866e 1036In some configurations, placeholders will work but will throw implicit type
e97a6ee2 1037conversion errors for anything that's not expecting a string. In such a case,
07a5866e 1038the C<auto_cast> option from L<DBIx::Class::Storage::DBI::AutoCast> is
1039automatically set, which you may enable on connection with
1040L<DBIx::Class::Storage::DBI::AutoCast/connect_call_set_auto_cast>. The type info
1041for the C<CAST>s is taken from the L<DBIx::Class::ResultSource/data_type>
1042definitions in your Result classes, and are mapped to a Sybase type (if it isn't
1043already) using a mapping based on L<SQL::Translator>.
e97a6ee2 1044
1045In other configurations, placeholers will work just as they do with the Sybase
1046Open Client libraries.
1047
1048Inserts or updates of TEXT/IMAGE columns will B<NOT> work with FreeTDS.
1049
322b7a6b 1050=head1 INSERTS WITH PLACEHOLDERS
1051
1052With placeholders enabled, inserts are done in a transaction so that there are
1053no concurrency issues with getting the inserted identity value using
1054C<SELECT MAX(col)>, which is the only way to get the C<IDENTITY> value in this
1055mode.
1056
6fcb1409 1057In addition, they are done on a separate connection so that it's possible to
1058have active cursors when doing an insert.
1059
322b7a6b 1060When using C<DBIx::Class::Storage::DBI::Sybase::NoBindVars> transactions are
1061disabled, as there are no concurrency issues with C<SELECT @@IDENTITY> as it's a
1062session variable.
1063
166c6561 1064=head1 TRANSACTIONS
1065
1066Due to limitations of the TDS protocol, L<DBD::Sybase>, or both; you cannot
1067begin a transaction while there are active cursors. An active cursor is, for
1068example, a L<ResultSet|DBIx::Class::ResultSet> that has been executed using
1069C<next> or C<first> but has not been exhausted or
75227502 1070L<reset|DBIx::Class::ResultSet/reset>.
166c6561 1071
322b7a6b 1072For example, this will not work:
1073
1074 $schema->txn_do(sub {
1075 my $rs = $schema->resultset('Book');
1076 while (my $row = $rs->next) {
1077 $schema->resultset('MetaData')->create({
1078 book_id => $row->id,
1079 ...
1080 });
1081 }
1082 });
1083
166c6561 1084Transactions done for inserts in C<AutoCommit> mode when placeholders are in use
6fcb1409 1085are not affected, as they are done on an extra database handle.
75227502 1086
1087Some workarounds:
1088
1089=over 4
1090
75227502 1091=item * use L<DBIx::Class::Storage::DBI::Replicated>
1092
1093=item * L<connect|DBIx::Class::Schema/connect> another L<Schema|DBIx::Class::Schema>
1094
1095=item * load the data from your cursor with L<DBIx::Class::ResultSet/all>
1096
75227502 1097=back
166c6561 1098
41c93b1b 1099=head1 MAXIMUM CONNECTIONS
1100
e97a6ee2 1101The TDS protocol makes separate connections to the server for active statements
1102in the background. By default the number of such connections is limited to 25,
1103on both the client side and the server side.
41c93b1b 1104
e97a6ee2 1105This is a bit too low for a complex L<DBIx::Class> application, so on connection
1106the client side setting is set to C<256> (see L<DBD::Sybase/maxConnect>.) You
1107can override it to whatever setting you like in the DSN.
41c93b1b 1108
1109See
1110L<http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.help.ase_15.0.sag1/html/sag1/sag1272.htm>
1111for information on changing the setting on the server side.
1112
c5ce7cd6 1113=head1 DATES
1114
3abafb11 1115See L</connect_call_datetime_setup> to setup date formats
1116for L<DBIx::Class::InflateColumn::DateTime>.
c5ce7cd6 1117
e97a6ee2 1118=head1 TEXT/IMAGE COLUMNS
63d46bb3 1119
a3a526cc 1120L<DBD::Sybase> compiled with FreeTDS will B<NOT> allow you to insert or update
1121C<TEXT/IMAGE> columns.
1122
e97a6ee2 1123Setting C<< $dbh->{LongReadLen} >> will also not work with FreeTDS use either:
1124
1125 $schema->storage->dbh->do("SET TEXTSIZE $bytes");
a3a526cc 1126
e97a6ee2 1127or
1128
1129 $schema->storage->set_textsize($bytes);
a3a526cc 1130
1131instead.
5703eb14 1132
e97a6ee2 1133However, the C<LongReadLen> you pass in
1134L<DBIx::Class::Storage::DBI/connect_info> is used to execute the equivalent
1135C<SET TEXTSIZE> command on connection.
1136
63d46bb3 1137See L</connect_call_blob_setup> for a L<DBIx::Class::Storage::DBI/connect_info>
1138setting you need to work with C<IMAGE> columns.
1139
2baff5da 1140=head1 BULK API
1141
1142The experimental L<DBD::Sybase> Bulk API support is used for
1143L<populate|DBIx::Class::ResultSet/populate> in B<void> context, in a transaction
1144on a separate connection.
1145
1146To use this feature effectively, use a large number of rows for each
1147L<populate|DBIx::Class::ResultSet/populate> call, eg.:
1148
1149 while (my $rows = $data_source->get_100_rows()) {
1150 $rs->populate($rows);
1151 }
1152
1153B<NOTE:> the L<add_columns|DBIx::Class::ResultSource/add_columns>
1154calls in your C<Result> classes B<must> list columns in database order for this
1155to work. Also, you may have to unset the C<LANG> environment variable before
1156loading your app, if it doesn't match the character set of your database.
1157
1158When inserting IMAGE columns using this method, you'll need to use
1159L</connect_call_blob_setup> as well.
1160
58e3556d 1161=head1 AUTHOR
3885cff6 1162
7e8cecc1 1163See L<DBIx::Class/CONTRIBUTORS>.
c5ce7cd6 1164
3885cff6 1165=head1 LICENSE
1166
1167You may distribute this code under the same terms as Perl itself.
1168
1169=cut
c5ce7cd6 1170# vim:sts=2 sw=2: