Merge 'sybase' into 'sybase_bulk_insert'
[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
523 my $is_identity_insert = (List::Util::first
af9e4a5e 524 { $source->column_info ($_)->{is_auto_increment} } @{$cols}
525 ) ? 1 : 0;
526
527 my @source_columns = $source->columns;
528
529 my $use_bulk_api =
530 $self->_bulk_storage &&
531 $self->_get_dbh->{syb_has_blk};
532
533 if ((not $use_bulk_api) &&
534 (Scalar::Util::reftype($self->_dbi_connect_info->[0])||'') eq 'CODE' &&
535 (not $self->_bulk_disabled_due_to_coderef_connect_info_warned)) {
536 carp <<'EOF';
537Bulk API support disabled due to use of a CODEREF connect_info. Reverting to
538array inserts.
539EOF
540 $self->_bulk_disabled_due_to_coderef_connect_info_warned(1);
40531ea8 541 }
542
af9e4a5e 543 if (not $use_bulk_api) {
544 if ($is_identity_insert) {
545 $self->_set_identity_insert ($source->name);
546 }
547
548 $self->next::method(@_);
549
550 if ($is_identity_insert) {
551 $self->_unset_identity_insert ($source->name);
552 }
40531ea8 553
af9e4a5e 554 return;
40531ea8 555 }
40531ea8 556
af9e4a5e 557# otherwise, use the bulk API
558
559# rearrange @$data so that columns are in database order
560 my %orig_idx;
561 @orig_idx{@$cols} = 0..$#$cols;
562
563 my %new_idx;
564 @new_idx{@source_columns} = 0..$#source_columns;
565
566 my @new_data;
567 for my $datum (@$data) {
568 my $new_datum = [];
569 for my $col (@source_columns) {
570# identity data will be 'undef' if not $is_identity_insert
571# columns with defaults will also be 'undef'
572 $new_datum->[ $new_idx{$col} ] =
573 exists $orig_idx{$col} ? $datum->[ $orig_idx{$col} ] : undef;
574 }
575 push @new_data, $new_datum;
576 }
577
578 my $identity_col = List::Util::first
579 { $source->column_info($_)->{is_auto_increment} } @source_columns;
580
581# bcp identity index is 1-based
582 my $identity_idx = exists $new_idx{$identity_col} ?
583 $new_idx{$identity_col} + 1 : 0;
584
585## Set a client-side conversion error handler, straight from DBD::Sybase docs.
586# This ignores any data conversion errors detected by the client side libs, as
587# they are usually harmless.
588 my $orig_cslib_cb = DBD::Sybase::set_cslib_cb(
589 Sub::Name::subname insert_bulk => sub {
590 my ($layer, $origin, $severity, $errno, $errmsg, $osmsg, $blkmsg) = @_;
591
592 return 1 if $errno == 36;
593
594 carp
595 "Layer: $layer, Origin: $origin, Severity: $severity, Error: $errno" .
596 ($errmsg ? "\n$errmsg" : '') .
597 ($osmsg ? "\n$osmsg" : '') .
598 ($blkmsg ? "\n$blkmsg" : '');
599
600 return 0;
601 });
602
603 eval {
604 my $bulk = $self->_bulk_storage;
605
606 my $guard = $bulk->txn_scope_guard;
607
608## XXX get this to work instead of our own $sth
609## will require SQLA or *Hacks changes for ordered columns
610# $bulk->next::method($source, \@source_columns, \@new_data, {
611# syb_bcp_attribs => {
612# identity_flag => $is_identity_insert,
613# identity_column => $identity_idx,
614# }
615# });
616 my $sql = 'INSERT INTO ' .
617 $bulk->sql_maker->_quote($source->name) . ' (' .
618# colname list is ignored for BCP, but does no harm
619 (join ', ', map $bulk->sql_maker->_quote($_), @source_columns) . ') '.
620 ' VALUES ('. (join ', ', ('?') x @source_columns) . ')';
621
622## XXX there's a bug in the DBD::Sybase bulk support that makes $sth->finish for
623## a prepare_cached statement ineffective. Replace with ->sth when fixed, or
624## better yet the version above. Should be fixed in DBD::Sybase .
625 my $sth = $bulk->_get_dbh->prepare($sql,
626# 'insert', # op
627 {
628 syb_bcp_attribs => {
629 identity_flag => $is_identity_insert,
630 identity_column => $identity_idx,
631 }
632 }
633 );
634
635 my $bind_attributes = $self->source_bind_attributes($source);
636
637 foreach my $slice_idx (0..$#source_columns) {
638 my $col = $source_columns[$slice_idx];
639
640 my $attributes = $bind_attributes->{$col}
641 if $bind_attributes && defined $bind_attributes->{$col};
642
643 my @slice = map $_->[$slice_idx], @new_data;
644
645 $sth->bind_param_array(($slice_idx + 1), \@slice, $attributes);
646 }
647
648 $bulk->_query_start($sql);
649
650# this is stolen from DBI::insert_bulk
651 my $tuple_status = [];
652 my $rv = eval { $sth->execute_array({ArrayTupleStatus => $tuple_status}) };
653
654 if (my $err = $@ || $sth->errstr) {
655 my $i = 0;
656 ++$i while $i <= $#$tuple_status && !ref $tuple_status->[$i];
657
658 $self->throw_exception("Unexpected populate error: $err")
659 if ($i > $#$tuple_status);
660
661 require Data::Dumper;
662 local $Data::Dumper::Terse = 1;
663 local $Data::Dumper::Indent = 1;
664 local $Data::Dumper::Useqq = 1;
665 local $Data::Dumper::Quotekeys = 0;
666 local $Data::Dumper::Sortkeys = 1;
667
668 $self->throw_exception(sprintf "%s for populate slice:\n%s",
669 ($tuple_status->[$i][1] || $err),
670 Data::Dumper::Dumper(
671 { map { $source_columns[$_] => $new_data[$i][$_] } (0 .. $#$cols) }
672 ),
673 );
674 }
675
676 $guard->commit;
677 $sth->finish;
678
679 $bulk->_query_end($sql);
680 };
681 my $exception = $@;
682 if ($exception =~ /-Y option/) {
683 carp <<"EOF";
684
685Sybase bulk API operation failed due to character set incompatibility, reverting
686to regular array inserts:
687
688*** Try unsetting the LANG environment variable.
689
690$@
691EOF
692 $self->_bulk_storage(undef);
693 DBD::Sybase::set_cslib_cb($orig_cslib_cb);
694 unshift @_, $self;
695 goto \&insert_bulk;
696 }
697 elsif ($exception) {
698 DBD::Sybase::set_cslib_cb($orig_cslib_cb);
699# rollback makes the bulkLogin connection unusable
700 $self->_bulk_storage->disconnect;
701 $self->throw_exception($exception);
702 }
703
704 DBD::Sybase::set_cslib_cb($orig_cslib_cb);
705}
40531ea8 706
89cb2a63 707# Make sure blobs are not bound as placeholders, and return any non-empty ones
708# as a hash.
7d17f469 709sub _remove_blob_cols {
710 my ($self, $source, $fields) = @_;
fd5a07e4 711
712 my %blob_cols;
713
7d17f469 714 for my $col (keys %$fields) {
9b3dabe0 715 if ($self->_is_lob_type($source->column_info($col)->{data_type})) {
89cb2a63 716 my $blob_val = delete $fields->{$col};
717 if (not defined $blob_val) {
718 $fields->{$col} = \'NULL';
719 }
720 else {
721 $fields->{$col} = \"''";
722 $blob_cols{$col} = $blob_val unless $blob_val eq '';
723 }
9b3dabe0 724 }
fd5a07e4 725 }
726
c966cf1b 727 return keys %blob_cols ? \%blob_cols : undef;
fd5a07e4 728}
729
730sub _update_blobs {
5370e479 731 my ($self, $source, $blob_cols, $where) = @_;
078a332f 732
733 my (@primary_cols) = $source->primary_columns;
734
735 croak "Cannot update TEXT/IMAGE column(s) without a primary key"
736 unless @primary_cols;
737
738# check if we're updating a single row by PK
739 my $pk_cols_in_where = 0;
740 for my $col (@primary_cols) {
5370e479 741 $pk_cols_in_where++ if defined $where->{$col};
078a332f 742 }
743 my @rows;
744
745 if ($pk_cols_in_where == @primary_cols) {
746 my %row_to_update;
5370e479 747 @row_to_update{@primary_cols} = @{$where}{@primary_cols};
078a332f 748 @rows = \%row_to_update;
749 } else {
6fcb1409 750 my $cursor = $self->select ($source, \@primary_cols, $where, {});
751 @rows = map {
752 my %row; @row{@primary_cols} = @$_; \%row
753 } $cursor->all;
078a332f 754 }
755
756 for my $row (@rows) {
757 $self->_insert_blobs($source, $blob_cols, $row);
758 }
759}
760
761sub _insert_blobs {
762 my ($self, $source, $blob_cols, $row) = @_;
75227502 763 my $dbh = $self->_get_dbh;
fd5a07e4 764
7ef97d26 765 my $table = $source->name;
fd5a07e4 766
078a332f 767 my %row = %$row;
fd5a07e4 768 my (@primary_cols) = $source->primary_columns;
769
9b3dabe0 770 croak "Cannot update TEXT/IMAGE column(s) without a primary key"
fd5a07e4 771 unless @primary_cols;
772
078a332f 773 if ((grep { defined $row{$_} } @primary_cols) != @primary_cols) {
c453ddd9 774 croak "Cannot update TEXT/IMAGE column(s) without primary key values";
9b3dabe0 775 }
fd5a07e4 776
777 for my $col (keys %$blob_cols) {
778 my $blob = $blob_cols->{$col};
779
a3a526cc 780 my %where = map { ($_, $row{$_}) } @primary_cols;
6fcb1409 781
782 my $cursor = $self->select ($source, [$col], \%where, {});
a3a526cc 783 $cursor->next;
5137d252 784 my $sth = $cursor->sth;
fd5a07e4 785
7ef97d26 786 if (not $sth) {
787 require Data::Dumper;
788 local $Data::Dumper::Terse = 1;
789 local $Data::Dumper::Indent = 1;
790 local $Data::Dumper::Useqq = 1;
791 local $Data::Dumper::Quotekeys = 0;
792 local $Data::Dumper::Sortkeys = 1;
793
794 croak "\nCould not find row in table '$table' for blob update:\n".
795 Data::Dumper::Dumper(\%where)."\n";
796 }
797
fd5a07e4 798 eval {
a3a526cc 799 do {
fd5a07e4 800 $sth->func('CS_GET', 1, 'ct_data_info') or die $sth->errstr;
a3a526cc 801 } while $sth->fetch;
802
fd5a07e4 803 $sth->func('ct_prepare_send') or die $sth->errstr;
804
805 my $log_on_update = $self->_blob_log_on_update;
806 $log_on_update = 1 if not defined $log_on_update;
807
808 $sth->func('CS_SET', 1, {
809 total_txtlen => length($blob),
810 log_on_update => $log_on_update
811 }, 'ct_data_info') or die $sth->errstr;
812
813 $sth->func($blob, length($blob), 'ct_send_data') or die $sth->errstr;
814
815 $sth->func('ct_finish_send') or die $sth->errstr;
816 };
817 my $exception = $@;
a3a526cc 818 $sth->finish if $sth;
819 if ($exception) {
e97a6ee2 820 if ($self->using_freetds) {
0ac07712 821 croak (
822 'TEXT/IMAGE operation failed, probably because you are using FreeTDS: '
823 . $exception
824 );
a3a526cc 825 } else {
826 croak $exception;
827 }
828 }
fd5a07e4 829 }
63d46bb3 830}
831
9539eeb1 832=head2 connect_call_datetime_setup
833
834Used as:
835
836 on_connect_call => 'datetime_setup'
837
838In L<DBIx::Class::Storage::DBI/connect_info> to set:
839
3abafb11 840 $dbh->syb_date_fmt('ISO_strict'); # output fmt: 2004-08-21T14:36:48.080Z
841 $dbh->do('set dateformat mdy'); # input fmt: 08/13/1979 18:08:55.080
9539eeb1 842
843On connection for use with L<DBIx::Class::InflateColumn::DateTime>, using
3abafb11 844L<DateTime::Format::Sybase>, which you will need to install.
845
846This works for both C<DATETIME> and C<SMALLDATETIME> columns, although
847C<SMALLDATETIME> columns only have minute precision.
9539eeb1 848
849=cut
850
9041a97a 851{
852 my $old_dbd_warned = 0;
853
9539eeb1 854 sub connect_call_datetime_setup {
6b1f5ef7 855 my $self = shift;
6b1f5ef7 856 my $dbh = $self->_dbh;
857
858 if ($dbh->can('syb_date_fmt')) {
0ac07712 859 # amazingly, this works with FreeTDS
6b1f5ef7 860 $dbh->syb_date_fmt('ISO_strict');
861 } elsif (not $old_dbd_warned) {
862 carp "Your DBD::Sybase is too old to support ".
863 "DBIx::Class::InflateColumn::DateTime, please upgrade!";
864 $old_dbd_warned = 1;
865 }
866
e97a6ee2 867 $dbh->do('SET DATEFORMAT mdy');
c5ce7cd6 868
6b1f5ef7 869 1;
c5ce7cd6 870 }
6b1f5ef7 871}
872
6636ad53 873sub datetime_parser_type { "DateTime::Format::Sybase" }
874
e97a6ee2 875# ->begin_work and such have no effect with FreeTDS but we run them anyway to
876# let the DBD keep any state it needs to.
877#
878# If they ever do start working, the extra statements will do no harm (because
879# Sybase supports nested transactions.)
a3a526cc 880
881sub _dbh_begin_work {
882 my $self = shift;
af9e4a5e 883
884# bulkLogin=1 connections are always in a transaction, and can only call BEGIN
885# TRAN once. However, we need to make sure there's a $dbh.
886 return if $self->_is_bulk_storage && $self->_dbh && $self->_began_bulk_work;
887
e97a6ee2 888 $self->next::method(@_);
af9e4a5e 889
e97a6ee2 890 if ($self->using_freetds) {
75227502 891 $self->_get_dbh->do('BEGIN TRAN');
a3a526cc 892 }
af9e4a5e 893
894 $self->_began_bulk_work(1) if $self->_is_bulk_storage;
a3a526cc 895}
896
897sub _dbh_commit {
898 my $self = shift;
e97a6ee2 899 if ($self->using_freetds) {
a3a526cc 900 $self->_dbh->do('COMMIT');
901 }
e97a6ee2 902 return $self->next::method(@_);
a3a526cc 903}
904
905sub _dbh_rollback {
906 my $self = shift;
e97a6ee2 907 if ($self->using_freetds) {
a3a526cc 908 $self->_dbh->do('ROLLBACK');
909 }
e97a6ee2 910 return $self->next::method(@_);
a3a526cc 911}
912
1816be4f 913# savepoint support using ASE syntax
914
915sub _svp_begin {
916 my ($self, $name) = @_;
917
75227502 918 $self->_get_dbh->do("SAVE TRANSACTION $name");
1816be4f 919}
920
921# A new SAVE TRANSACTION with the same name releases the previous one.
922sub _svp_release { 1 }
923
924sub _svp_rollback {
925 my ($self, $name) = @_;
926
75227502 927 $self->_get_dbh->do("ROLLBACK TRANSACTION $name");
1816be4f 928}
929
3885cff6 9301;
931
efe75aaa 932=head1 Schema::Loader Support
933
934There is an experimental branch of L<DBIx::Class::Schema::Loader> that will
935allow you to dump a schema from most (if not all) versions of Sybase.
936
937It is available via subversion from:
938
07a5866e 939 http://dev.catalyst.perl.org/repos/bast/branches/DBIx-Class-Schema-Loader/current/
efe75aaa 940
e97a6ee2 941=head1 FreeTDS
942
943This driver supports L<DBD::Sybase> compiled against FreeTDS
944(L<http://www.freetds.org/>) to the best of our ability, however it is
945recommended that you recompile L<DBD::Sybase> against the Sybase Open Client
946libraries. They are a part of the Sybase ASE distribution:
947
948The Open Client FAQ is here:
949L<http://www.isug.com/Sybase_FAQ/ASE/section7.html>.
950
951Sybase ASE for Linux (which comes with the Open Client libraries) may be
952downloaded here: L<http://response.sybase.com/forms/ASE_Linux_Download>.
953
954To see if you're using FreeTDS check C<< $schema->storage->using_freetds >>, or run:
955
956 perl -MDBI -le 'my $dbh = DBI->connect($dsn, $user, $pass); print $dbh->{syb_oc_version}'
957
958Some versions of the libraries involved will not support placeholders, in which
959case the storage will be reblessed to
960L<DBIx::Class::Storage::DBI::Sybase::NoBindVars>.
961
07a5866e 962In some configurations, placeholders will work but will throw implicit type
e97a6ee2 963conversion errors for anything that's not expecting a string. In such a case,
07a5866e 964the C<auto_cast> option from L<DBIx::Class::Storage::DBI::AutoCast> is
965automatically set, which you may enable on connection with
966L<DBIx::Class::Storage::DBI::AutoCast/connect_call_set_auto_cast>. The type info
967for the C<CAST>s is taken from the L<DBIx::Class::ResultSource/data_type>
968definitions in your Result classes, and are mapped to a Sybase type (if it isn't
969already) using a mapping based on L<SQL::Translator>.
e97a6ee2 970
971In other configurations, placeholers will work just as they do with the Sybase
972Open Client libraries.
973
974Inserts or updates of TEXT/IMAGE columns will B<NOT> work with FreeTDS.
975
322b7a6b 976=head1 INSERTS WITH PLACEHOLDERS
977
978With placeholders enabled, inserts are done in a transaction so that there are
979no concurrency issues with getting the inserted identity value using
980C<SELECT MAX(col)>, which is the only way to get the C<IDENTITY> value in this
981mode.
982
6fcb1409 983In addition, they are done on a separate connection so that it's possible to
984have active cursors when doing an insert.
985
322b7a6b 986When using C<DBIx::Class::Storage::DBI::Sybase::NoBindVars> transactions are
987disabled, as there are no concurrency issues with C<SELECT @@IDENTITY> as it's a
988session variable.
989
166c6561 990=head1 TRANSACTIONS
991
992Due to limitations of the TDS protocol, L<DBD::Sybase>, or both; you cannot
993begin a transaction while there are active cursors. An active cursor is, for
994example, a L<ResultSet|DBIx::Class::ResultSet> that has been executed using
995C<next> or C<first> but has not been exhausted or
75227502 996L<reset|DBIx::Class::ResultSet/reset>.
166c6561 997
322b7a6b 998For example, this will not work:
999
1000 $schema->txn_do(sub {
1001 my $rs = $schema->resultset('Book');
1002 while (my $row = $rs->next) {
1003 $schema->resultset('MetaData')->create({
1004 book_id => $row->id,
1005 ...
1006 });
1007 }
1008 });
1009
166c6561 1010Transactions done for inserts in C<AutoCommit> mode when placeholders are in use
6fcb1409 1011are not affected, as they are done on an extra database handle.
75227502 1012
1013Some workarounds:
1014
1015=over 4
1016
75227502 1017=item * use L<DBIx::Class::Storage::DBI::Replicated>
1018
1019=item * L<connect|DBIx::Class::Schema/connect> another L<Schema|DBIx::Class::Schema>
1020
1021=item * load the data from your cursor with L<DBIx::Class::ResultSet/all>
1022
75227502 1023=back
166c6561 1024
41c93b1b 1025=head1 MAXIMUM CONNECTIONS
1026
e97a6ee2 1027The TDS protocol makes separate connections to the server for active statements
1028in the background. By default the number of such connections is limited to 25,
1029on both the client side and the server side.
41c93b1b 1030
e97a6ee2 1031This is a bit too low for a complex L<DBIx::Class> application, so on connection
1032the client side setting is set to C<256> (see L<DBD::Sybase/maxConnect>.) You
1033can override it to whatever setting you like in the DSN.
41c93b1b 1034
1035See
1036L<http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.help.ase_15.0.sag1/html/sag1/sag1272.htm>
1037for information on changing the setting on the server side.
1038
c5ce7cd6 1039=head1 DATES
1040
3abafb11 1041See L</connect_call_datetime_setup> to setup date formats
1042for L<DBIx::Class::InflateColumn::DateTime>.
c5ce7cd6 1043
e97a6ee2 1044=head1 TEXT/IMAGE COLUMNS
63d46bb3 1045
a3a526cc 1046L<DBD::Sybase> compiled with FreeTDS will B<NOT> allow you to insert or update
1047C<TEXT/IMAGE> columns.
1048
e97a6ee2 1049Setting C<< $dbh->{LongReadLen} >> will also not work with FreeTDS use either:
1050
1051 $schema->storage->dbh->do("SET TEXTSIZE $bytes");
a3a526cc 1052
e97a6ee2 1053or
1054
1055 $schema->storage->set_textsize($bytes);
a3a526cc 1056
1057instead.
5703eb14 1058
e97a6ee2 1059However, the C<LongReadLen> you pass in
1060L<DBIx::Class::Storage::DBI/connect_info> is used to execute the equivalent
1061C<SET TEXTSIZE> command on connection.
1062
63d46bb3 1063See L</connect_call_blob_setup> for a L<DBIx::Class::Storage::DBI/connect_info>
1064setting you need to work with C<IMAGE> columns.
1065
af9e4a5e 1066=head1 BULK API
1067
1068The experimental L<DBD::Sybase> Bulk API support is used for
1069L<populate|DBIx::Class::ResultSet/populate> in B<void> context, in a transaction
1070on a separate connection.
1071
1072To use this feature effectively, use a large number of rows for each
1073L<populate|DBIx::Class::ResultSet/populate> call, eg.:
1074
1075 while (my $rows = $data_source->get_100_rows()) {
1076 $rs->populate($rows);
1077 }
1078
1079B<NOTE:> the L<add_columns|DBIx::Class::ResultSource/add_columns>
1080calls in your C<Result> classes B<must> list columns in database order for this
1081to work. Also, you may have to unset the C<LANG> environment variable before
1082loading your app, if it doesn't match the character set of your database.
1083
1084When inserting IMAGE columns using this method, you'll need to use
1085L</connect_call_blob_setup> as well.
1086
58e3556d 1087=head1 AUTHOR
3885cff6 1088
7e8cecc1 1089See L<DBIx::Class/CONTRIBUTORS>.
c5ce7cd6 1090
3885cff6 1091=head1 LICENSE
1092
1093You may distribute this code under the same terms as Perl itself.
1094
1095=cut
c5ce7cd6 1096# vim:sts=2 sw=2: