remove some duplicate code
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBI / Sybase.pm
CommitLineData
f68f4d44 1package DBIx::Class::Storage::DBI::Sybase;
2
3use strict;
4use warnings;
2ad62d97 5
eabab5d0 6use base qw/
d867eeda 7 DBIx::Class::Storage::DBI::Sybase::Common
8 DBIx::Class::Storage::DBI::AutoCast
eabab5d0 9/;
2ad62d97 10use mro 'c3';
d867eeda 11use Carp::Clan qw/^DBIx::Class/;
12use List::Util ();
13use Sub::Name ();
14
15__PACKAGE__->mk_group_accessors('simple' =>
0a9a9955 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
d867eeda 19 _identity_method/
20);
21
0a9a9955 22my @also_proxy_to_extra_storages = qw/
2baff5da 23 connect_call_set_auto_cast auto_cast connect_call_blob_setup
24 connect_call_datetime_setup
25
d867eeda 26 disconnect _connect_info _sql_maker _sql_maker_opts disable_sth_caching
27 auto_savepoint unsafe cursor_class debug debugobj schema
28/;
29
30=head1 NAME
31
32DBIx::Class::Storage::DBI::Sybase - Sybase support for DBIx::Class
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>
47without doing a C<SELECT MAX(col)>. This is done safely in a transaction
48(locking the table.) See L</INSERTS WITH PLACEHOLDERS>.
49
50A recommended L<DBIx::Class::Storage::DBI/connect_info> setting:
51
52 on_connect_call => [['datetime_setup'], ['blob_setup', log_on_update => 0]]
53
54=head1 METHODS
55
56=cut
f68f4d44 57
47d9646a 58sub _rebless {
d867eeda 59 my $self = shift;
d29565e0 60
d867eeda 61 if (ref($self) eq 'DBIx::Class::Storage::DBI::Sybase') {
ef131d82 62 my $dbtype = eval {
d867eeda 63 @{$self->_get_dbh->selectrow_arrayref(qq{sp_server_info \@attribute_id=1})}[2]
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;
73 } else { # real Sybase
74 my $no_bind_vars = 'DBIx::Class::Storage::DBI::Sybase::NoBindVars';
75
76 if ($self->using_freetds) {
77 carp <<'EOF' unless $ENV{DBIC_SYBASE_FREETDS_NOWARN};
78
79You are using FreeTDS with Sybase.
80
81We will do our best to support this configuration, but please consider this
82support experimental.
83
84TEXT/IMAGE columns will definitely not work.
85
86You are encouraged to recompile DBD::Sybase with the Sybase Open Client libraries
87instead.
88
89See perldoc DBIx::Class::Storage::DBI::Sybase for more details.
90
91To turn off this warning set the DBIC_SYBASE_FREETDS_NOWARN environment
92variable.
93EOF
94 if (not $self->_typeless_placeholders_supported) {
95 if ($self->_placeholders_supported) {
96 $self->auto_cast(1);
97 } else {
98 $self->ensure_class_loaded($no_bind_vars);
99 bless $self, $no_bind_vars;
d29565e0 100 $self->_rebless;
d867eeda 101 }
d29565e0 102 }
d867eeda 103 }
104 elsif (not $self->_get_dbh->{syb_dynamic_supported}) {
105 # not necessarily FreeTDS, but no placeholders nevertheless
106 $self->ensure_class_loaded($no_bind_vars);
107 bless $self, $no_bind_vars;
108 $self->_rebless;
109 } elsif (not $self->_typeless_placeholders_supported) {
0a9a9955 110 # this is highly unlikely, but we check just in case
d867eeda 111 $self->auto_cast(1);
112 }
113 }
114 }
115}
116
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;
123
124# create storage for insert/(update blob) transactions,
125# unless this is that storage
0a9a9955 126 return if $self->_is_extra_storage;
d867eeda 127
128 my $writer_storage = (ref $self)->new;
129
0a9a9955 130 $writer_storage->_is_extra_storage(1);
d867eeda 131 $writer_storage->connect_info($self->connect_info);
2baff5da 132 $writer_storage->auto_cast($self->auto_cast);
d867eeda 133
134 $self->_writer_storage($writer_storage);
0a9a9955 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);
d867eeda 150}
151
0a9a9955 152for my $method (@also_proxy_to_extra_storages) {
d867eeda 153 no strict 'refs';
2baff5da 154 no warnings 'redefine';
d867eeda 155
156 my $replaced = __PACKAGE__->can($method);
157
0a9a9955 158 *{$method} = Sub::Name::subname $method => sub {
d867eeda 159 my $self = shift;
160 $self->_writer_storage->$replaced(@_) if $self->_writer_storage;
0a9a9955 161 $self->_bulk_storage->$replaced(@_) if $self->_bulk_storage;
d867eeda 162 return $self->$replaced(@_);
163 };
164}
165
0a9a9955 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
d867eeda 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.
187sub _populate_dbh {
188 my $self = shift;
189
190 $self->next::method(@_);
0a9a9955 191
192 if ($self->_is_bulk_storage) {
193# this should be cleared on every reconnect
194 $self->_began_bulk_work(0);
195 return;
196 }
d867eeda 197
198 if (not $self->using_freetds) {
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 }
206 }
207}
208
209=head2 connect_call_blob_setup
210
211Used as:
212
213 on_connect_call => [ [ 'blob_setup', log_on_update => 0 ] ]
214
215Does C<< $dbh->{syb_binary_images} = 1; >> to return C<IMAGE> data as raw binary
216instead of as a hex string.
217
218Recommended.
219
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
226=cut
227
228sub connect_call_blob_setup {
229 my $self = shift;
230 my %args = @_;
231 my $dbh = $self->_dbh;
232 $dbh->{syb_binary_images} = 1;
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;
240 my $type = shift;
241 $type && $type =~ /(?:text|image|lob|bytea|binary|memo)/i;
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
d867eeda 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') {
257 my $table = $ident->from;
258
259 my $bind_info = $self->_resolve_column_info(
260 $ident, [map $_->[0], @{$bind}]
261 );
262 my $identity_col = List::Util::first
263 { $bind_info->{$_}{is_auto_increment} }
264 (keys %$bind_info)
265 ;
266
267 if ($identity_col) {
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 ;
279 }
280
281 if ($identity_col) {
282 $sql =
283 "$sql\n" .
284 $self->_fetch_identity_sql($ident, $identity_col);
285 }
286 }
287
288 return ($sql, $bind);
289}
290
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 = (
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
312sub _native_data_type {
313 my ($self, $type) = @_;
314
315 $type = lc $type;
316 $type =~ s/\s* identity//x;
317
318 return uc($TYPE_MAPPING{$type} || $type);
319}
320
321sub _fetch_identity_sql {
322 my ($self, $source, $col) = @_;
323
324 return sprintf ("SELECT MAX(%s) FROM %s",
325 map { $self->sql_maker->_quote ($_) } ($col, $source->from)
326 );
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
345# handles TEXT/IMAGE and transaction for last_insert_id
346sub insert {
347 my $self = shift;
348 my ($source, $to_insert) = @_;
349
350 my $blob_cols = $self->_remove_blob_cols($source, $to_insert);
351
352 my $identity_col = List::Util::first
353 { $source->column_info($_)->{is_auto_increment} }
354 $source->columns;
355
356 # do we need the horrific SELECT MAX(COL) hack?
357 my $dumb_last_insert_id =
358 $identity_col
359 && (not exists $to_insert->{$identity_col})
360 && ($self->_identity_method||'') ne '@@IDENTITY';
361
362 my $next = $self->next::can;
363
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 ) {
369 return $self->_insert (
370 $next, $source, $to_insert, $blob_cols, $identity_col
371 );
372 }
373
374 # otherwise use the _writer_storage to do the insert+transaction on another
375 # connection
376 my $guard = $self->_writer_storage->txn_scope_guard;
377
378 my $updated_cols = $self->_writer_storage->_insert (
379 $next, $source, $to_insert, $blob_cols, $identity_col
380 );
381
382 $self->_identity($self->_writer_storage->_identity);
383
384 $guard->commit;
385
386 return $updated_cols;
387}
388
389sub _insert {
390 my ($self, $next, $source, $to_insert, $blob_cols, $identity_col) = @_;
391
392 my $updated_cols = $self->$next ($source, $to_insert);
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;
401
402 return $updated_cols;
403}
404
405sub update {
406 my $self = shift;
2baff5da 407 my ($source, $fields, $where, @rest) = @_;
d867eeda 408
409 my $wantarray = wantarray;
2baff5da 410
d867eeda 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
d867eeda 421 if (not $blob_cols) {
a4b33e7f 422 $self->_set_session_identity(UPDATE => $table, 'ON')
423 if $is_identity_update;
424
d867eeda 425 return $self->next::method(@_);
a4b33e7f 426
427 $self->_set_session_identity(UPDATE => $table, 'OFF')
428 if $is_identity_update;
2baff5da 429 }
430
d390bd3c 431# If there are any blobs in $where, Sybase will return a descriptive error
432# message.
d867eeda 433
434# update+blob update(s) done atomically on separate connection
435 $self = $self->_writer_storage;
436
437 my $guard = $self->txn_scope_guard;
438
2baff5da 439# First update the blob columns to be updated to '' (taken from $fields, where
440# it is originally put by _remove_blob_cols .)
441 my %blobs_to_empty = map { ($_ => delete $fields->{$_}) } keys %$blob_cols;
d867eeda 442
2baff5da 443 $self->next::method($source, \%blobs_to_empty, $where, @rest);
e19677ad 444
2baff5da 445# Now update the blobs before the other columns in case the update of other
446# columns makes the search condition invalid.
d867eeda 447 $self->_update_blobs($source, $blob_cols, $where);
448
2baff5da 449 my @res;
450 if (%$fields) {
a4b33e7f 451 $self->_set_session_identity(UPDATE => $table, 'ON')
452 if $is_identity_update;
2baff5da 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
a4b33e7f 464 $self->_set_session_identity(UPDATE => $table, 'OFF')
465 if $is_identity_update;
2baff5da 466 }
467
d867eeda 468 $guard->commit;
469
470 return $wantarray ? @res : $res[0];
471}
472
d390bd3c 473# for IDENTITY_INSERT / IDENTITY_UPDATE
a4b33e7f 474sub _set_session_identity {
475 my ($self, $op, $table, $off_on) = @_;
d867eeda 476
477 my $sql = sprintf (
a4b33e7f 478 'SET IDENTITY_%s %s %s',
479 uc $op,
480 $self->sql_maker->_quote($table),
481 uc $off_on,
d867eeda 482 );
483
2baff5da 484 $self->_query_start($sql);
485
d867eeda 486 my $dbh = $self->_get_dbh;
a4b33e7f 487 eval {
488 local $dbh->{RaiseError} = 1;
489 local $dbh->{PrintError} = 0;
490 $dbh->do ($sql)
491 };
2baff5da 492 my $exception = $@;
493
494 $self->_query_end($sql);
495
496 if ($exception) {
d867eeda 497 $self->throw_exception (sprintf "Error executing '%s': %s",
498 $sql,
a4b33e7f 499 $exception,
d867eeda 500 );
501 }
502}
503
d867eeda 504sub insert_bulk {
505 my $self = shift;
506 my ($source, $cols, $data) = @_;
507
0a9a9955 508 my $identity_col = List::Util::first
509 { $source->column_info($_)->{is_auto_increment} }
510 $source->columns;
511
d867eeda 512 my $is_identity_insert = (List::Util::first
587daa97 513 { $_ eq $identity_col }
0a9a9955 514 @{$cols}
515 ) ? 1 : 0;
516
517 my @source_columns = $source->columns;
518
519 my $use_bulk_api =
520 $self->_bulk_storage &&
521 $self->_get_dbh->{syb_has_blk};
522
523 if ((not $use_bulk_api) &&
524 (Scalar::Util::reftype($self->_dbi_connect_info->[0])||'') eq 'CODE' &&
525 (not $self->_bulk_disabled_due_to_coderef_connect_info_warned)) {
526 carp <<'EOF';
527Bulk API support disabled due to use of a CODEREF connect_info. Reverting to
587daa97 528regular array inserts.
0a9a9955 529EOF
530 $self->_bulk_disabled_due_to_coderef_connect_info_warned(1);
d867eeda 531 }
532
0a9a9955 533 if (not $use_bulk_api) {
534 my $blob_cols = $self->_remove_blob_cols_array($source, $cols, $data);
535
0a9a9955 536 ($self, my ($guard)) = do {
d390bd3c 537 if ($self->{transaction_depth} == 0 && $blob_cols) {
0a9a9955 538 ($self->_writer_storage, $self->_writer_storage->txn_scope_guard);
539 }
540 else {
541 ($self, undef);
542 }
543 };
544
0a9a9955 545 $self->next::method(@_);
0a9a9955 546
547 if ($blob_cols) {
548 if ($is_identity_insert) {
549 $self->_insert_blobs_array ($source, $blob_cols, $cols, $data);
550 }
551 else {
552 my @cols_with_identities = (@$cols, $identity_col);
553
554 ## calculate identities
555 # XXX This assumes identities always increase by 1, which may or may not
556 # be true.
557 my ($last_identity) =
558 $self->_dbh->selectrow_array (
559 $self->_fetch_identity_sql($source, $identity_col)
560 );
561 my @identities = (($last_identity - @$data + 1) .. $last_identity);
562
563 my @data_with_identities = map [@$_, shift @identities], @$data;
564
565 $self->_insert_blobs_array (
566 $source, $blob_cols, \@cols_with_identities, \@data_with_identities
567 );
568 }
569 }
d867eeda 570
0a9a9955 571 $guard->commit if $guard;
572 return;
d867eeda 573 }
d867eeda 574
0a9a9955 575# otherwise, use the bulk API
576
577# rearrange @$data so that columns are in database order
578 my %orig_idx;
579 @orig_idx{@$cols} = 0..$#$cols;
580
581 my %new_idx;
582 @new_idx{@source_columns} = 0..$#source_columns;
583
584 my @new_data;
585 for my $datum (@$data) {
586 my $new_datum = [];
587 for my $col (@source_columns) {
588# identity data will be 'undef' if not $is_identity_insert
589# columns with defaults will also be 'undef'
590 $new_datum->[ $new_idx{$col} ] =
591 exists $orig_idx{$col} ? $datum->[ $orig_idx{$col} ] : undef;
592 }
593 push @new_data, $new_datum;
594 }
595
596# bcp identity index is 1-based
597 my $identity_idx = exists $new_idx{$identity_col} ?
598 $new_idx{$identity_col} + 1 : 0;
599
600## Set a client-side conversion error handler, straight from DBD::Sybase docs.
601# This ignores any data conversion errors detected by the client side libs, as
602# they are usually harmless.
603 my $orig_cslib_cb = DBD::Sybase::set_cslib_cb(
604 Sub::Name::subname insert_bulk => sub {
605 my ($layer, $origin, $severity, $errno, $errmsg, $osmsg, $blkmsg) = @_;
606
607 return 1 if $errno == 36;
608
609 carp
610 "Layer: $layer, Origin: $origin, Severity: $severity, Error: $errno" .
611 ($errmsg ? "\n$errmsg" : '') .
612 ($osmsg ? "\n$osmsg" : '') .
613 ($blkmsg ? "\n$blkmsg" : '');
614
615 return 0;
616 });
617
618 eval {
619 my $bulk = $self->_bulk_storage;
620
621 my $guard = $bulk->txn_scope_guard;
622
623## XXX get this to work instead of our own $sth
624## will require SQLA or *Hacks changes for ordered columns
625# $bulk->next::method($source, \@source_columns, \@new_data, {
626# syb_bcp_attribs => {
627# identity_flag => $is_identity_insert,
628# identity_column => $identity_idx,
629# }
630# });
631 my $sql = 'INSERT INTO ' .
632 $bulk->sql_maker->_quote($source->name) . ' (' .
633# colname list is ignored for BCP, but does no harm
634 (join ', ', map $bulk->sql_maker->_quote($_), @source_columns) . ') '.
635 ' VALUES ('. (join ', ', ('?') x @source_columns) . ')';
636
637## XXX there's a bug in the DBD::Sybase bulk support that makes $sth->finish for
638## a prepare_cached statement ineffective. Replace with ->sth when fixed, or
639## better yet the version above. Should be fixed in DBD::Sybase .
640 my $sth = $bulk->_get_dbh->prepare($sql,
641# 'insert', # op
642 {
643 syb_bcp_attribs => {
644 identity_flag => $is_identity_insert,
645 identity_column => $identity_idx,
646 }
647 }
648 );
649
587daa97 650 my @bind = do {
651 my $idx = 0;
652 map [ $_, $idx++ ], @source_columns;
653 };
0a9a9955 654
587daa97 655 $self->_execute_array(
656 $source, $sth, \@bind, \@source_columns, \@new_data, $guard
657 );
0a9a9955 658
659 $bulk->_query_end($sql);
660 };
661 my $exception = $@;
662 if ($exception =~ /-Y option/) {
663 carp <<"EOF";
664
665Sybase bulk API operation failed due to character set incompatibility, reverting
666to regular array inserts:
667
668*** Try unsetting the LANG environment variable.
669
670$@
671EOF
672 $self->_bulk_storage(undef);
673 DBD::Sybase::set_cslib_cb($orig_cslib_cb);
674 unshift @_, $self;
675 goto \&insert_bulk;
676 }
677 elsif ($exception) {
678 DBD::Sybase::set_cslib_cb($orig_cslib_cb);
679# rollback makes the bulkLogin connection unusable
680 $self->_bulk_storage->disconnect;
681 $self->throw_exception($exception);
682 }
683
684 DBD::Sybase::set_cslib_cb($orig_cslib_cb);
685}
d867eeda 686
2baff5da 687# Make sure blobs are not bound as placeholders, and return any non-empty ones
688# as a hash.
d867eeda 689sub _remove_blob_cols {
690 my ($self, $source, $fields) = @_;
691
692 my %blob_cols;
693
694 for my $col (keys %$fields) {
587daa97 695 if ($self->_is_lob_column($source, $col)) {
2baff5da 696 my $blob_val = delete $fields->{$col};
697 if (not defined $blob_val) {
698 $fields->{$col} = \'NULL';
699 }
700 else {
701 $fields->{$col} = \"''";
702 $blob_cols{$col} = $blob_val unless $blob_val eq '';
703 }
d867eeda 704 }
705 }
706
707 return keys %blob_cols ? \%blob_cols : undef;
708}
709
0a9a9955 710# same for insert_bulk
711sub _remove_blob_cols_array {
712 my ($self, $source, $cols, $data) = @_;
713
714 my @blob_cols;
715
716 for my $i (0..$#$cols) {
717 my $col = $cols->[$i];
718
587daa97 719 if ($self->_is_lob_column($source, $col)) {
0a9a9955 720 for my $j (0..$#$data) {
721 my $blob_val = delete $data->[$j][$i];
722 if (not defined $blob_val) {
723 $data->[$j][$i] = \'NULL';
724 }
725 else {
726 $data->[$j][$i] = \"''";
727 $blob_cols[$j][$i] = $blob_val
728 unless $blob_val eq '';
729 }
730 }
731 }
732 }
733
734 return @blob_cols ? \@blob_cols : undef;
735}
736
d867eeda 737sub _update_blobs {
738 my ($self, $source, $blob_cols, $where) = @_;
739
740 my (@primary_cols) = $source->primary_columns;
741
742 $self->throw_exception('Cannot update TEXT/IMAGE column(s) without a primary key')
743 unless @primary_cols;
744
745# check if we're updating a single row by PK
746 my $pk_cols_in_where = 0;
747 for my $col (@primary_cols) {
748 $pk_cols_in_where++ if defined $where->{$col};
749 }
750 my @rows;
751
752 if ($pk_cols_in_where == @primary_cols) {
753 my %row_to_update;
754 @row_to_update{@primary_cols} = @{$where}{@primary_cols};
755 @rows = \%row_to_update;
756 } else {
757 my $cursor = $self->select ($source, \@primary_cols, $where, {});
758 @rows = map {
759 my %row; @row{@primary_cols} = @$_; \%row
760 } $cursor->all;
761 }
762
763 for my $row (@rows) {
764 $self->_insert_blobs($source, $blob_cols, $row);
765 }
766}
767
768sub _insert_blobs {
769 my ($self, $source, $blob_cols, $row) = @_;
770 my $dbh = $self->_get_dbh;
771
2baff5da 772 my $table = $source->name;
d867eeda 773
774 my %row = %$row;
775 my (@primary_cols) = $source->primary_columns;
776
777 $self->throw_exception('Cannot update TEXT/IMAGE column(s) without a primary key')
778 unless @primary_cols;
779
780 $self->throw_exception('Cannot update TEXT/IMAGE column(s) without primary key values')
781 if ((grep { defined $row{$_} } @primary_cols) != @primary_cols);
782
783 for my $col (keys %$blob_cols) {
784 my $blob = $blob_cols->{$col};
785
786 my %where = map { ($_, $row{$_}) } @primary_cols;
787
788 my $cursor = $self->select ($source, [$col], \%where, {});
789 $cursor->next;
790 my $sth = $cursor->sth;
791
2baff5da 792 if (not $sth) {
b561bb6f 793
794 $self->throw_exception(
795 "Could not find row in table '$table' for blob update:\n"
796 . $self->_pretty_print (\%where)
797 );
2baff5da 798 }
799
d867eeda 800 eval {
801 do {
802 $sth->func('CS_GET', 1, 'ct_data_info') or die $sth->errstr;
803 } while $sth->fetch;
804
805 $sth->func('ct_prepare_send') or die $sth->errstr;
806
807 my $log_on_update = $self->_blob_log_on_update;
808 $log_on_update = 1 if not defined $log_on_update;
809
810 $sth->func('CS_SET', 1, {
811 total_txtlen => length($blob),
812 log_on_update => $log_on_update
813 }, 'ct_data_info') or die $sth->errstr;
814
815 $sth->func($blob, length($blob), 'ct_send_data') or die $sth->errstr;
816
817 $sth->func('ct_finish_send') or die $sth->errstr;
818 };
819 my $exception = $@;
820 $sth->finish if $sth;
821 if ($exception) {
822 if ($self->using_freetds) {
823 $self->throw_exception (
824 'TEXT/IMAGE operation failed, probably because you are using FreeTDS: '
825 . $exception
826 );
827 } else {
828 $self->throw_exception($exception);
829 }
830 }
831 }
832}
833
0a9a9955 834sub _insert_blobs_array {
835 my ($self, $source, $blob_cols, $cols, $data) = @_;
836
837 for my $i (0..$#$data) {
838 my $datum = $data->[$i];
839
840 my %row;
841 @row{ @$cols } = @$datum;
842
843 my %blob_vals;
844 for my $j (0..$#$cols) {
845 if (exists $blob_cols->[$i][$j]) {
846 $blob_vals{ $cols->[$j] } = $blob_cols->[$i][$j];
847 }
848 }
849
850 $self->_insert_blobs ($source, \%blob_vals, \%row);
851 }
852}
853
d867eeda 854=head2 connect_call_datetime_setup
855
856Used as:
857
858 on_connect_call => 'datetime_setup'
859
860In L<DBIx::Class::Storage::DBI/connect_info> to set:
861
862 $dbh->syb_date_fmt('ISO_strict'); # output fmt: 2004-08-21T14:36:48.080Z
863 $dbh->do('set dateformat mdy'); # input fmt: 08/13/1979 18:08:55.080
864
865On connection for use with L<DBIx::Class::InflateColumn::DateTime>, using
866L<DateTime::Format::Sybase>, which you will need to install.
867
868This works for both C<DATETIME> and C<SMALLDATETIME> columns, although
869C<SMALLDATETIME> columns only have minute precision.
870
871=cut
872
873{
874 my $old_dbd_warned = 0;
875
876 sub connect_call_datetime_setup {
877 my $self = shift;
878 my $dbh = $self->_dbh;
879
880 if ($dbh->can('syb_date_fmt')) {
881 # amazingly, this works with FreeTDS
882 $dbh->syb_date_fmt('ISO_strict');
883 } elsif (not $old_dbd_warned) {
884 carp "Your DBD::Sybase is too old to support ".
885 "DBIx::Class::InflateColumn::DateTime, please upgrade!";
886 $old_dbd_warned = 1;
47d9646a 887 }
d867eeda 888
889 $dbh->do('SET DATEFORMAT mdy');
890
891 1;
892 }
893}
894
895sub datetime_parser_type { "DateTime::Format::Sybase" }
896
897# ->begin_work and such have no effect with FreeTDS but we run them anyway to
898# let the DBD keep any state it needs to.
899#
900# If they ever do start working, the extra statements will do no harm (because
901# Sybase supports nested transactions.)
902
903sub _dbh_begin_work {
904 my $self = shift;
0a9a9955 905
906# bulkLogin=1 connections are always in a transaction, and can only call BEGIN
907# TRAN once. However, we need to make sure there's a $dbh.
908 return if $self->_is_bulk_storage && $self->_dbh && $self->_began_bulk_work;
909
d867eeda 910 $self->next::method(@_);
0a9a9955 911
d867eeda 912 if ($self->using_freetds) {
913 $self->_get_dbh->do('BEGIN TRAN');
914 }
0a9a9955 915
916 $self->_began_bulk_work(1) if $self->_is_bulk_storage;
47d9646a 917}
918
d867eeda 919sub _dbh_commit {
920 my $self = shift;
921 if ($self->using_freetds) {
922 $self->_dbh->do('COMMIT');
923 }
924 return $self->next::method(@_);
925}
926
927sub _dbh_rollback {
928 my $self = shift;
929 if ($self->using_freetds) {
930 $self->_dbh->do('ROLLBACK');
931 }
932 return $self->next::method(@_);
933}
934
935# savepoint support using ASE syntax
936
937sub _svp_begin {
938 my ($self, $name) = @_;
939
940 $self->_get_dbh->do("SAVE TRANSACTION $name");
941}
942
943# A new SAVE TRANSACTION with the same name releases the previous one.
944sub _svp_release { 1 }
945
946sub _svp_rollback {
947 my ($self, $name) = @_;
948
949 $self->_get_dbh->do("ROLLBACK TRANSACTION $name");
a964a928 950}
951
f68f4d44 9521;
953
d867eeda 954=head1 Schema::Loader Support
f68f4d44 955
d867eeda 956There is an experimental branch of L<DBIx::Class::Schema::Loader> that will
957allow you to dump a schema from most (if not all) versions of Sybase.
f68f4d44 958
d867eeda 959It is available via subversion from:
960
961 http://dev.catalyst.perl.org/repos/bast/branches/DBIx-Class-Schema-Loader/current/
962
963=head1 FreeTDS
964
965This driver supports L<DBD::Sybase> compiled against FreeTDS
966(L<http://www.freetds.org/>) to the best of our ability, however it is
967recommended that you recompile L<DBD::Sybase> against the Sybase Open Client
968libraries. They are a part of the Sybase ASE distribution:
969
970The Open Client FAQ is here:
971L<http://www.isug.com/Sybase_FAQ/ASE/section7.html>.
972
973Sybase ASE for Linux (which comes with the Open Client libraries) may be
974downloaded here: L<http://response.sybase.com/forms/ASE_Linux_Download>.
975
976To see if you're using FreeTDS check C<< $schema->storage->using_freetds >>, or run:
977
978 perl -MDBI -le 'my $dbh = DBI->connect($dsn, $user, $pass); print $dbh->{syb_oc_version}'
979
980Some versions of the libraries involved will not support placeholders, in which
981case the storage will be reblessed to
982L<DBIx::Class::Storage::DBI::Sybase::NoBindVars>.
983
984In some configurations, placeholders will work but will throw implicit type
985conversion errors for anything that's not expecting a string. In such a case,
986the C<auto_cast> option from L<DBIx::Class::Storage::DBI::AutoCast> is
987automatically set, which you may enable on connection with
988L<DBIx::Class::Storage::DBI::AutoCast/connect_call_set_auto_cast>. The type info
989for the C<CAST>s is taken from the L<DBIx::Class::ResultSource/data_type>
990definitions in your Result classes, and are mapped to a Sybase type (if it isn't
991already) using a mapping based on L<SQL::Translator>.
992
993In other configurations, placeholers will work just as they do with the Sybase
994Open Client libraries.
995
996Inserts or updates of TEXT/IMAGE columns will B<NOT> work with FreeTDS.
997
998=head1 INSERTS WITH PLACEHOLDERS
999
1000With placeholders enabled, inserts are done in a transaction so that there are
1001no concurrency issues with getting the inserted identity value using
1002C<SELECT MAX(col)>, which is the only way to get the C<IDENTITY> value in this
1003mode.
1004
1005In addition, they are done on a separate connection so that it's possible to
1006have active cursors when doing an insert.
1007
1008When using C<DBIx::Class::Storage::DBI::Sybase::NoBindVars> transactions are
1009disabled, as there are no concurrency issues with C<SELECT @@IDENTITY> as it's a
1010session variable.
1011
1012=head1 TRANSACTIONS
1013
1014Due to limitations of the TDS protocol, L<DBD::Sybase>, or both; you cannot
d390bd3c 1015begin a transaction while there are active cursors; nor can you use multiple
1016active cursors within a transaction. An active cursor is, for example, a
1017L<ResultSet|DBIx::Class::ResultSet> that has been executed using C<next> or
1018C<first> but has not been exhausted or L<reset|DBIx::Class::ResultSet/reset>.
d867eeda 1019
1020For example, this will not work:
1021
1022 $schema->txn_do(sub {
1023 my $rs = $schema->resultset('Book');
1024 while (my $row = $rs->next) {
1025 $schema->resultset('MetaData')->create({
1026 book_id => $row->id,
1027 ...
1028 });
1029 }
1030 });
1031
d390bd3c 1032This won't either:
1033
1034 my $first_row = $large_rs->first;
1035 $schema->txn_do(sub { ... });
1036
d867eeda 1037Transactions done for inserts in C<AutoCommit> mode when placeholders are in use
1038are not affected, as they are done on an extra database handle.
1039
1040Some workarounds:
1041
1042=over 4
1043
1044=item * use L<DBIx::Class::Storage::DBI::Replicated>
1045
1046=item * L<connect|DBIx::Class::Schema/connect> another L<Schema|DBIx::Class::Schema>
1047
1048=item * load the data from your cursor with L<DBIx::Class::ResultSet/all>
1049
1050=back
1051
1052=head1 MAXIMUM CONNECTIONS
1053
1054The TDS protocol makes separate connections to the server for active statements
1055in the background. By default the number of such connections is limited to 25,
1056on both the client side and the server side.
1057
1058This is a bit too low for a complex L<DBIx::Class> application, so on connection
1059the client side setting is set to C<256> (see L<DBD::Sybase/maxConnect>.) You
1060can override it to whatever setting you like in the DSN.
1061
1062See
1063L<http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.help.ase_15.0.sag1/html/sag1/sag1272.htm>
1064for information on changing the setting on the server side.
1065
1066=head1 DATES
1067
1068See L</connect_call_datetime_setup> to setup date formats
1069for L<DBIx::Class::InflateColumn::DateTime>.
1070
1071=head1 TEXT/IMAGE COLUMNS
1072
1073L<DBD::Sybase> compiled with FreeTDS will B<NOT> allow you to insert or update
1074C<TEXT/IMAGE> columns.
1075
1076Setting C<< $dbh->{LongReadLen} >> will also not work with FreeTDS use either:
1077
1078 $schema->storage->dbh->do("SET TEXTSIZE $bytes");
f68f4d44 1079
d867eeda 1080or
f68f4d44 1081
d867eeda 1082 $schema->storage->set_textsize($bytes);
d4483998 1083
d867eeda 1084instead.
d4483998 1085
d867eeda 1086However, the C<LongReadLen> you pass in
1087L<DBIx::Class::Storage::DBI/connect_info> is used to execute the equivalent
1088C<SET TEXTSIZE> command on connection.
d4483998 1089
d867eeda 1090See L</connect_call_blob_setup> for a L<DBIx::Class::Storage::DBI/connect_info>
1091setting you need to work with C<IMAGE> columns.
f68f4d44 1092
0a9a9955 1093=head1 BULK API
1094
1095The experimental L<DBD::Sybase> Bulk API support is used for
1096L<populate|DBIx::Class::ResultSet/populate> in B<void> context, in a transaction
1097on a separate connection.
1098
1099To use this feature effectively, use a large number of rows for each
1100L<populate|DBIx::Class::ResultSet/populate> call, eg.:
1101
1102 while (my $rows = $data_source->get_100_rows()) {
1103 $rs->populate($rows);
1104 }
1105
1106B<NOTE:> the L<add_columns|DBIx::Class::ResultSource/add_columns>
1107calls in your C<Result> classes B<must> list columns in database order for this
1108to work. Also, you may have to unset the C<LANG> environment variable before
1109loading your app, if it doesn't match the character set of your database.
1110
1111When inserting IMAGE columns using this method, you'll need to use
1112L</connect_call_blob_setup> as well.
1113
d867eeda 1114=head1 AUTHOR
f68f4d44 1115
d867eeda 1116See L<DBIx::Class/CONTRIBUTORS>.
47d9646a 1117
f68f4d44 1118=head1 LICENSE
1119
1120You may distribute this code under the same terms as Perl itself.
1121
1122=cut
d867eeda 1123# vim:sts=2 sw=2: