Merge 'trunk' into 'sybase_insert_bulk'
[dbsrgits/DBIx-Class-Historic.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
1a58752c 793 $self->throw_exception('Cannot update TEXT/IMAGE column(s) without a primary key')
078a332f 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
1a58752c 828 $self->throw_exception('Cannot update TEXT/IMAGE column(s) without a primary key')
fd5a07e4 829 unless @primary_cols;
830
1a58752c 831 $self->throw_exception('Cannot update TEXT/IMAGE column(s) without primary key values')
832 if ((grep { defined $row{$_} } @primary_cols) != @primary_cols);
fd5a07e4 833
834 for my $col (keys %$blob_cols) {
835 my $blob = $blob_cols->{$col};
836
a3a526cc 837 my %where = map { ($_, $row{$_}) } @primary_cols;
6fcb1409 838
839 my $cursor = $self->select ($source, [$col], \%where, {});
a3a526cc 840 $cursor->next;
5137d252 841 my $sth = $cursor->sth;
fd5a07e4 842
2baff5da 843 if (not $sth) {
b561bb6f 844
845 $self->throw_exception(
846 "Could not find row in table '$table' for blob update:\n"
847 . $self->_pretty_print (\%where)
848 );
2baff5da 849 }
850
fd5a07e4 851 eval {
a3a526cc 852 do {
fd5a07e4 853 $sth->func('CS_GET', 1, 'ct_data_info') or die $sth->errstr;
a3a526cc 854 } while $sth->fetch;
855
fd5a07e4 856 $sth->func('ct_prepare_send') or die $sth->errstr;
857
858 my $log_on_update = $self->_blob_log_on_update;
859 $log_on_update = 1 if not defined $log_on_update;
860
861 $sth->func('CS_SET', 1, {
862 total_txtlen => length($blob),
863 log_on_update => $log_on_update
864 }, 'ct_data_info') or die $sth->errstr;
865
866 $sth->func($blob, length($blob), 'ct_send_data') or die $sth->errstr;
867
868 $sth->func('ct_finish_send') or die $sth->errstr;
869 };
870 my $exception = $@;
a3a526cc 871 $sth->finish if $sth;
872 if ($exception) {
e97a6ee2 873 if ($self->using_freetds) {
1a58752c 874 $self->throw_exception (
0ac07712 875 'TEXT/IMAGE operation failed, probably because you are using FreeTDS: '
876 . $exception
877 );
a3a526cc 878 } else {
1a58752c 879 $self->throw_exception($exception);
a3a526cc 880 }
881 }
fd5a07e4 882 }
63d46bb3 883}
884
2baff5da 885sub _insert_blobs_array {
886 my ($self, $source, $blob_cols, $cols, $data) = @_;
887
888 for my $i (0..$#$data) {
889 my $datum = $data->[$i];
890
891 my %row;
892 @row{ @$cols } = @$datum;
893
894 my %blob_vals;
895 for my $j (0..$#$cols) {
896 if (exists $blob_cols->[$i][$j]) {
897 $blob_vals{ $cols->[$j] } = $blob_cols->[$i][$j];
898 }
899 }
900
901 $self->_insert_blobs ($source, \%blob_vals, \%row);
902 }
903}
904
9539eeb1 905=head2 connect_call_datetime_setup
906
907Used as:
908
909 on_connect_call => 'datetime_setup'
910
911In L<DBIx::Class::Storage::DBI/connect_info> to set:
912
3abafb11 913 $dbh->syb_date_fmt('ISO_strict'); # output fmt: 2004-08-21T14:36:48.080Z
914 $dbh->do('set dateformat mdy'); # input fmt: 08/13/1979 18:08:55.080
9539eeb1 915
916On connection for use with L<DBIx::Class::InflateColumn::DateTime>, using
3abafb11 917L<DateTime::Format::Sybase>, which you will need to install.
918
919This works for both C<DATETIME> and C<SMALLDATETIME> columns, although
920C<SMALLDATETIME> columns only have minute precision.
9539eeb1 921
922=cut
923
9041a97a 924{
925 my $old_dbd_warned = 0;
926
9539eeb1 927 sub connect_call_datetime_setup {
6b1f5ef7 928 my $self = shift;
6b1f5ef7 929 my $dbh = $self->_dbh;
930
931 if ($dbh->can('syb_date_fmt')) {
0ac07712 932 # amazingly, this works with FreeTDS
6b1f5ef7 933 $dbh->syb_date_fmt('ISO_strict');
934 } elsif (not $old_dbd_warned) {
935 carp "Your DBD::Sybase is too old to support ".
936 "DBIx::Class::InflateColumn::DateTime, please upgrade!";
937 $old_dbd_warned = 1;
938 }
939
e97a6ee2 940 $dbh->do('SET DATEFORMAT mdy');
c5ce7cd6 941
6b1f5ef7 942 1;
c5ce7cd6 943 }
6b1f5ef7 944}
945
6636ad53 946sub datetime_parser_type { "DateTime::Format::Sybase" }
947
e97a6ee2 948# ->begin_work and such have no effect with FreeTDS but we run them anyway to
949# let the DBD keep any state it needs to.
950#
951# If they ever do start working, the extra statements will do no harm (because
952# Sybase supports nested transactions.)
a3a526cc 953
954sub _dbh_begin_work {
955 my $self = shift;
2baff5da 956
957# bulkLogin=1 connections are always in a transaction, and can only call BEGIN
958# TRAN once. However, we need to make sure there's a $dbh.
959 return if $self->_is_bulk_storage && $self->_dbh && $self->_began_bulk_work;
960
e97a6ee2 961 $self->next::method(@_);
2baff5da 962
e97a6ee2 963 if ($self->using_freetds) {
75227502 964 $self->_get_dbh->do('BEGIN TRAN');
a3a526cc 965 }
2baff5da 966
967 $self->_began_bulk_work(1) if $self->_is_bulk_storage;
a3a526cc 968}
969
970sub _dbh_commit {
971 my $self = shift;
e97a6ee2 972 if ($self->using_freetds) {
a3a526cc 973 $self->_dbh->do('COMMIT');
974 }
e97a6ee2 975 return $self->next::method(@_);
a3a526cc 976}
977
978sub _dbh_rollback {
979 my $self = shift;
e97a6ee2 980 if ($self->using_freetds) {
a3a526cc 981 $self->_dbh->do('ROLLBACK');
982 }
e97a6ee2 983 return $self->next::method(@_);
a3a526cc 984}
985
1816be4f 986# savepoint support using ASE syntax
987
988sub _svp_begin {
989 my ($self, $name) = @_;
990
75227502 991 $self->_get_dbh->do("SAVE TRANSACTION $name");
1816be4f 992}
993
994# A new SAVE TRANSACTION with the same name releases the previous one.
995sub _svp_release { 1 }
996
997sub _svp_rollback {
998 my ($self, $name) = @_;
999
75227502 1000 $self->_get_dbh->do("ROLLBACK TRANSACTION $name");
1816be4f 1001}
1002
3885cff6 10031;
1004
efe75aaa 1005=head1 Schema::Loader Support
1006
1007There is an experimental branch of L<DBIx::Class::Schema::Loader> that will
1008allow you to dump a schema from most (if not all) versions of Sybase.
1009
1010It is available via subversion from:
1011
07a5866e 1012 http://dev.catalyst.perl.org/repos/bast/branches/DBIx-Class-Schema-Loader/current/
efe75aaa 1013
e97a6ee2 1014=head1 FreeTDS
1015
1016This driver supports L<DBD::Sybase> compiled against FreeTDS
1017(L<http://www.freetds.org/>) to the best of our ability, however it is
1018recommended that you recompile L<DBD::Sybase> against the Sybase Open Client
1019libraries. They are a part of the Sybase ASE distribution:
1020
1021The Open Client FAQ is here:
1022L<http://www.isug.com/Sybase_FAQ/ASE/section7.html>.
1023
1024Sybase ASE for Linux (which comes with the Open Client libraries) may be
1025downloaded here: L<http://response.sybase.com/forms/ASE_Linux_Download>.
1026
1027To see if you're using FreeTDS check C<< $schema->storage->using_freetds >>, or run:
1028
1029 perl -MDBI -le 'my $dbh = DBI->connect($dsn, $user, $pass); print $dbh->{syb_oc_version}'
1030
1031Some versions of the libraries involved will not support placeholders, in which
1032case the storage will be reblessed to
1033L<DBIx::Class::Storage::DBI::Sybase::NoBindVars>.
1034
07a5866e 1035In some configurations, placeholders will work but will throw implicit type
e97a6ee2 1036conversion errors for anything that's not expecting a string. In such a case,
07a5866e 1037the C<auto_cast> option from L<DBIx::Class::Storage::DBI::AutoCast> is
1038automatically set, which you may enable on connection with
1039L<DBIx::Class::Storage::DBI::AutoCast/connect_call_set_auto_cast>. The type info
1040for the C<CAST>s is taken from the L<DBIx::Class::ResultSource/data_type>
1041definitions in your Result classes, and are mapped to a Sybase type (if it isn't
1042already) using a mapping based on L<SQL::Translator>.
e97a6ee2 1043
1044In other configurations, placeholers will work just as they do with the Sybase
1045Open Client libraries.
1046
1047Inserts or updates of TEXT/IMAGE columns will B<NOT> work with FreeTDS.
1048
322b7a6b 1049=head1 INSERTS WITH PLACEHOLDERS
1050
1051With placeholders enabled, inserts are done in a transaction so that there are
1052no concurrency issues with getting the inserted identity value using
1053C<SELECT MAX(col)>, which is the only way to get the C<IDENTITY> value in this
1054mode.
1055
6fcb1409 1056In addition, they are done on a separate connection so that it's possible to
1057have active cursors when doing an insert.
1058
322b7a6b 1059When using C<DBIx::Class::Storage::DBI::Sybase::NoBindVars> transactions are
1060disabled, as there are no concurrency issues with C<SELECT @@IDENTITY> as it's a
1061session variable.
1062
166c6561 1063=head1 TRANSACTIONS
1064
1065Due to limitations of the TDS protocol, L<DBD::Sybase>, or both; you cannot
1066begin a transaction while there are active cursors. An active cursor is, for
1067example, a L<ResultSet|DBIx::Class::ResultSet> that has been executed using
1068C<next> or C<first> but has not been exhausted or
75227502 1069L<reset|DBIx::Class::ResultSet/reset>.
166c6561 1070
322b7a6b 1071For example, this will not work:
1072
1073 $schema->txn_do(sub {
1074 my $rs = $schema->resultset('Book');
1075 while (my $row = $rs->next) {
1076 $schema->resultset('MetaData')->create({
1077 book_id => $row->id,
1078 ...
1079 });
1080 }
1081 });
1082
166c6561 1083Transactions done for inserts in C<AutoCommit> mode when placeholders are in use
6fcb1409 1084are not affected, as they are done on an extra database handle.
75227502 1085
1086Some workarounds:
1087
1088=over 4
1089
75227502 1090=item * use L<DBIx::Class::Storage::DBI::Replicated>
1091
1092=item * L<connect|DBIx::Class::Schema/connect> another L<Schema|DBIx::Class::Schema>
1093
1094=item * load the data from your cursor with L<DBIx::Class::ResultSet/all>
1095
75227502 1096=back
166c6561 1097
41c93b1b 1098=head1 MAXIMUM CONNECTIONS
1099
e97a6ee2 1100The TDS protocol makes separate connections to the server for active statements
1101in the background. By default the number of such connections is limited to 25,
1102on both the client side and the server side.
41c93b1b 1103
e97a6ee2 1104This is a bit too low for a complex L<DBIx::Class> application, so on connection
1105the client side setting is set to C<256> (see L<DBD::Sybase/maxConnect>.) You
1106can override it to whatever setting you like in the DSN.
41c93b1b 1107
1108See
1109L<http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.help.ase_15.0.sag1/html/sag1/sag1272.htm>
1110for information on changing the setting on the server side.
1111
c5ce7cd6 1112=head1 DATES
1113
3abafb11 1114See L</connect_call_datetime_setup> to setup date formats
1115for L<DBIx::Class::InflateColumn::DateTime>.
c5ce7cd6 1116
e97a6ee2 1117=head1 TEXT/IMAGE COLUMNS
63d46bb3 1118
a3a526cc 1119L<DBD::Sybase> compiled with FreeTDS will B<NOT> allow you to insert or update
1120C<TEXT/IMAGE> columns.
1121
e97a6ee2 1122Setting C<< $dbh->{LongReadLen} >> will also not work with FreeTDS use either:
1123
1124 $schema->storage->dbh->do("SET TEXTSIZE $bytes");
a3a526cc 1125
e97a6ee2 1126or
1127
1128 $schema->storage->set_textsize($bytes);
a3a526cc 1129
1130instead.
5703eb14 1131
e97a6ee2 1132However, the C<LongReadLen> you pass in
1133L<DBIx::Class::Storage::DBI/connect_info> is used to execute the equivalent
1134C<SET TEXTSIZE> command on connection.
1135
63d46bb3 1136See L</connect_call_blob_setup> for a L<DBIx::Class::Storage::DBI/connect_info>
1137setting you need to work with C<IMAGE> columns.
1138
2baff5da 1139=head1 BULK API
1140
1141The experimental L<DBD::Sybase> Bulk API support is used for
1142L<populate|DBIx::Class::ResultSet/populate> in B<void> context, in a transaction
1143on a separate connection.
1144
1145To use this feature effectively, use a large number of rows for each
1146L<populate|DBIx::Class::ResultSet/populate> call, eg.:
1147
1148 while (my $rows = $data_source->get_100_rows()) {
1149 $rs->populate($rows);
1150 }
1151
1152B<NOTE:> the L<add_columns|DBIx::Class::ResultSource/add_columns>
1153calls in your C<Result> classes B<must> list columns in database order for this
1154to work. Also, you may have to unset the C<LANG> environment variable before
1155loading your app, if it doesn't match the character set of your database.
1156
1157When inserting IMAGE columns using this method, you'll need to use
1158L</connect_call_blob_setup> as well.
1159
58e3556d 1160=head1 AUTHOR
3885cff6 1161
7e8cecc1 1162See L<DBIx::Class/CONTRIBUTORS>.
c5ce7cd6 1163
3885cff6 1164=head1 LICENSE
1165
1166You may distribute this code under the same terms as Perl itself.
1167
1168=cut
c5ce7cd6 1169# vim:sts=2 sw=2: