minor cleanups, test update of blob to NULL
[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
0a9a9955 513 { $source->column_info ($_)->{is_auto_increment} }
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
528array inserts.
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
650 my $bind_attributes = $self->source_bind_attributes($source);
651
652 foreach my $slice_idx (0..$#source_columns) {
653 my $col = $source_columns[$slice_idx];
654
655 my $attributes = $bind_attributes->{$col}
656 if $bind_attributes && defined $bind_attributes->{$col};
657
658 my @slice = map $_->[$slice_idx], @new_data;
659
660 $sth->bind_param_array(($slice_idx + 1), \@slice, $attributes);
661 }
662
663 $bulk->_query_start($sql);
664
665# this is stolen from DBI::insert_bulk
666 my $tuple_status = [];
667 my $rv = eval { $sth->execute_array({ArrayTupleStatus => $tuple_status}) };
668
669 if (my $err = $@ || $sth->errstr) {
670 my $i = 0;
671 ++$i while $i <= $#$tuple_status && !ref $tuple_status->[$i];
672
673 $self->throw_exception("Unexpected populate error: $err")
674 if ($i > $#$tuple_status);
675
676 $self->throw_exception(sprintf "%s for populate slice:\n%s",
677 ($tuple_status->[$i][1] || $err),
678 $self->_pretty_print ({
679 map { $source_columns[$_] => $new_data[$i][$_] } (0 .. $#$cols)
680 }),
681 );
682 }
683
684 $guard->commit;
685 $sth->finish;
686
687 $bulk->_query_end($sql);
688 };
689 my $exception = $@;
690 if ($exception =~ /-Y option/) {
691 carp <<"EOF";
692
693Sybase bulk API operation failed due to character set incompatibility, reverting
694to regular array inserts:
695
696*** Try unsetting the LANG environment variable.
697
698$@
699EOF
700 $self->_bulk_storage(undef);
701 DBD::Sybase::set_cslib_cb($orig_cslib_cb);
702 unshift @_, $self;
703 goto \&insert_bulk;
704 }
705 elsif ($exception) {
706 DBD::Sybase::set_cslib_cb($orig_cslib_cb);
707# rollback makes the bulkLogin connection unusable
708 $self->_bulk_storage->disconnect;
709 $self->throw_exception($exception);
710 }
711
712 DBD::Sybase::set_cslib_cb($orig_cslib_cb);
713}
d867eeda 714
2baff5da 715# Make sure blobs are not bound as placeholders, and return any non-empty ones
716# as a hash.
d867eeda 717sub _remove_blob_cols {
718 my ($self, $source, $fields) = @_;
719
720 my %blob_cols;
721
722 for my $col (keys %$fields) {
723 if ($self->_is_lob_type($source->column_info($col)->{data_type})) {
2baff5da 724 my $blob_val = delete $fields->{$col};
725 if (not defined $blob_val) {
726 $fields->{$col} = \'NULL';
727 }
728 else {
729 $fields->{$col} = \"''";
730 $blob_cols{$col} = $blob_val unless $blob_val eq '';
731 }
d867eeda 732 }
733 }
734
735 return keys %blob_cols ? \%blob_cols : undef;
736}
737
0a9a9955 738# same for insert_bulk
739sub _remove_blob_cols_array {
740 my ($self, $source, $cols, $data) = @_;
741
742 my @blob_cols;
743
744 for my $i (0..$#$cols) {
745 my $col = $cols->[$i];
746
747 if ($self->_is_lob_type($source->column_info($col)->{data_type})) {
748 for my $j (0..$#$data) {
749 my $blob_val = delete $data->[$j][$i];
750 if (not defined $blob_val) {
751 $data->[$j][$i] = \'NULL';
752 }
753 else {
754 $data->[$j][$i] = \"''";
755 $blob_cols[$j][$i] = $blob_val
756 unless $blob_val eq '';
757 }
758 }
759 }
760 }
761
762 return @blob_cols ? \@blob_cols : undef;
763}
764
d867eeda 765sub _update_blobs {
766 my ($self, $source, $blob_cols, $where) = @_;
767
768 my (@primary_cols) = $source->primary_columns;
769
770 $self->throw_exception('Cannot update TEXT/IMAGE column(s) without a primary key')
771 unless @primary_cols;
772
773# check if we're updating a single row by PK
774 my $pk_cols_in_where = 0;
775 for my $col (@primary_cols) {
776 $pk_cols_in_where++ if defined $where->{$col};
777 }
778 my @rows;
779
780 if ($pk_cols_in_where == @primary_cols) {
781 my %row_to_update;
782 @row_to_update{@primary_cols} = @{$where}{@primary_cols};
783 @rows = \%row_to_update;
784 } else {
785 my $cursor = $self->select ($source, \@primary_cols, $where, {});
786 @rows = map {
787 my %row; @row{@primary_cols} = @$_; \%row
788 } $cursor->all;
789 }
790
791 for my $row (@rows) {
792 $self->_insert_blobs($source, $blob_cols, $row);
793 }
794}
795
796sub _insert_blobs {
797 my ($self, $source, $blob_cols, $row) = @_;
798 my $dbh = $self->_get_dbh;
799
2baff5da 800 my $table = $source->name;
d867eeda 801
802 my %row = %$row;
803 my (@primary_cols) = $source->primary_columns;
804
805 $self->throw_exception('Cannot update TEXT/IMAGE column(s) without a primary key')
806 unless @primary_cols;
807
808 $self->throw_exception('Cannot update TEXT/IMAGE column(s) without primary key values')
809 if ((grep { defined $row{$_} } @primary_cols) != @primary_cols);
810
811 for my $col (keys %$blob_cols) {
812 my $blob = $blob_cols->{$col};
813
814 my %where = map { ($_, $row{$_}) } @primary_cols;
815
816 my $cursor = $self->select ($source, [$col], \%where, {});
817 $cursor->next;
818 my $sth = $cursor->sth;
819
2baff5da 820 if (not $sth) {
b561bb6f 821
822 $self->throw_exception(
823 "Could not find row in table '$table' for blob update:\n"
824 . $self->_pretty_print (\%where)
825 );
2baff5da 826 }
827
d867eeda 828 eval {
829 do {
830 $sth->func('CS_GET', 1, 'ct_data_info') or die $sth->errstr;
831 } while $sth->fetch;
832
833 $sth->func('ct_prepare_send') or die $sth->errstr;
834
835 my $log_on_update = $self->_blob_log_on_update;
836 $log_on_update = 1 if not defined $log_on_update;
837
838 $sth->func('CS_SET', 1, {
839 total_txtlen => length($blob),
840 log_on_update => $log_on_update
841 }, 'ct_data_info') or die $sth->errstr;
842
843 $sth->func($blob, length($blob), 'ct_send_data') or die $sth->errstr;
844
845 $sth->func('ct_finish_send') or die $sth->errstr;
846 };
847 my $exception = $@;
848 $sth->finish if $sth;
849 if ($exception) {
850 if ($self->using_freetds) {
851 $self->throw_exception (
852 'TEXT/IMAGE operation failed, probably because you are using FreeTDS: '
853 . $exception
854 );
855 } else {
856 $self->throw_exception($exception);
857 }
858 }
859 }
860}
861
0a9a9955 862sub _insert_blobs_array {
863 my ($self, $source, $blob_cols, $cols, $data) = @_;
864
865 for my $i (0..$#$data) {
866 my $datum = $data->[$i];
867
868 my %row;
869 @row{ @$cols } = @$datum;
870
871 my %blob_vals;
872 for my $j (0..$#$cols) {
873 if (exists $blob_cols->[$i][$j]) {
874 $blob_vals{ $cols->[$j] } = $blob_cols->[$i][$j];
875 }
876 }
877
878 $self->_insert_blobs ($source, \%blob_vals, \%row);
879 }
880}
881
d867eeda 882=head2 connect_call_datetime_setup
883
884Used as:
885
886 on_connect_call => 'datetime_setup'
887
888In L<DBIx::Class::Storage::DBI/connect_info> to set:
889
890 $dbh->syb_date_fmt('ISO_strict'); # output fmt: 2004-08-21T14:36:48.080Z
891 $dbh->do('set dateformat mdy'); # input fmt: 08/13/1979 18:08:55.080
892
893On connection for use with L<DBIx::Class::InflateColumn::DateTime>, using
894L<DateTime::Format::Sybase>, which you will need to install.
895
896This works for both C<DATETIME> and C<SMALLDATETIME> columns, although
897C<SMALLDATETIME> columns only have minute precision.
898
899=cut
900
901{
902 my $old_dbd_warned = 0;
903
904 sub connect_call_datetime_setup {
905 my $self = shift;
906 my $dbh = $self->_dbh;
907
908 if ($dbh->can('syb_date_fmt')) {
909 # amazingly, this works with FreeTDS
910 $dbh->syb_date_fmt('ISO_strict');
911 } elsif (not $old_dbd_warned) {
912 carp "Your DBD::Sybase is too old to support ".
913 "DBIx::Class::InflateColumn::DateTime, please upgrade!";
914 $old_dbd_warned = 1;
47d9646a 915 }
d867eeda 916
917 $dbh->do('SET DATEFORMAT mdy');
918
919 1;
920 }
921}
922
923sub datetime_parser_type { "DateTime::Format::Sybase" }
924
925# ->begin_work and such have no effect with FreeTDS but we run them anyway to
926# let the DBD keep any state it needs to.
927#
928# If they ever do start working, the extra statements will do no harm (because
929# Sybase supports nested transactions.)
930
931sub _dbh_begin_work {
932 my $self = shift;
0a9a9955 933
934# bulkLogin=1 connections are always in a transaction, and can only call BEGIN
935# TRAN once. However, we need to make sure there's a $dbh.
936 return if $self->_is_bulk_storage && $self->_dbh && $self->_began_bulk_work;
937
d867eeda 938 $self->next::method(@_);
0a9a9955 939
d867eeda 940 if ($self->using_freetds) {
941 $self->_get_dbh->do('BEGIN TRAN');
942 }
0a9a9955 943
944 $self->_began_bulk_work(1) if $self->_is_bulk_storage;
47d9646a 945}
946
d867eeda 947sub _dbh_commit {
948 my $self = shift;
949 if ($self->using_freetds) {
950 $self->_dbh->do('COMMIT');
951 }
952 return $self->next::method(@_);
953}
954
955sub _dbh_rollback {
956 my $self = shift;
957 if ($self->using_freetds) {
958 $self->_dbh->do('ROLLBACK');
959 }
960 return $self->next::method(@_);
961}
962
963# savepoint support using ASE syntax
964
965sub _svp_begin {
966 my ($self, $name) = @_;
967
968 $self->_get_dbh->do("SAVE TRANSACTION $name");
969}
970
971# A new SAVE TRANSACTION with the same name releases the previous one.
972sub _svp_release { 1 }
973
974sub _svp_rollback {
975 my ($self, $name) = @_;
976
977 $self->_get_dbh->do("ROLLBACK TRANSACTION $name");
a964a928 978}
979
f68f4d44 9801;
981
d867eeda 982=head1 Schema::Loader Support
f68f4d44 983
d867eeda 984There is an experimental branch of L<DBIx::Class::Schema::Loader> that will
985allow you to dump a schema from most (if not all) versions of Sybase.
f68f4d44 986
d867eeda 987It is available via subversion from:
988
989 http://dev.catalyst.perl.org/repos/bast/branches/DBIx-Class-Schema-Loader/current/
990
991=head1 FreeTDS
992
993This driver supports L<DBD::Sybase> compiled against FreeTDS
994(L<http://www.freetds.org/>) to the best of our ability, however it is
995recommended that you recompile L<DBD::Sybase> against the Sybase Open Client
996libraries. They are a part of the Sybase ASE distribution:
997
998The Open Client FAQ is here:
999L<http://www.isug.com/Sybase_FAQ/ASE/section7.html>.
1000
1001Sybase ASE for Linux (which comes with the Open Client libraries) may be
1002downloaded here: L<http://response.sybase.com/forms/ASE_Linux_Download>.
1003
1004To see if you're using FreeTDS check C<< $schema->storage->using_freetds >>, or run:
1005
1006 perl -MDBI -le 'my $dbh = DBI->connect($dsn, $user, $pass); print $dbh->{syb_oc_version}'
1007
1008Some versions of the libraries involved will not support placeholders, in which
1009case the storage will be reblessed to
1010L<DBIx::Class::Storage::DBI::Sybase::NoBindVars>.
1011
1012In some configurations, placeholders will work but will throw implicit type
1013conversion errors for anything that's not expecting a string. In such a case,
1014the C<auto_cast> option from L<DBIx::Class::Storage::DBI::AutoCast> is
1015automatically set, which you may enable on connection with
1016L<DBIx::Class::Storage::DBI::AutoCast/connect_call_set_auto_cast>. The type info
1017for the C<CAST>s is taken from the L<DBIx::Class::ResultSource/data_type>
1018definitions in your Result classes, and are mapped to a Sybase type (if it isn't
1019already) using a mapping based on L<SQL::Translator>.
1020
1021In other configurations, placeholers will work just as they do with the Sybase
1022Open Client libraries.
1023
1024Inserts or updates of TEXT/IMAGE columns will B<NOT> work with FreeTDS.
1025
1026=head1 INSERTS WITH PLACEHOLDERS
1027
1028With placeholders enabled, inserts are done in a transaction so that there are
1029no concurrency issues with getting the inserted identity value using
1030C<SELECT MAX(col)>, which is the only way to get the C<IDENTITY> value in this
1031mode.
1032
1033In addition, they are done on a separate connection so that it's possible to
1034have active cursors when doing an insert.
1035
1036When using C<DBIx::Class::Storage::DBI::Sybase::NoBindVars> transactions are
1037disabled, as there are no concurrency issues with C<SELECT @@IDENTITY> as it's a
1038session variable.
1039
1040=head1 TRANSACTIONS
1041
1042Due to limitations of the TDS protocol, L<DBD::Sybase>, or both; you cannot
d390bd3c 1043begin a transaction while there are active cursors; nor can you use multiple
1044active cursors within a transaction. An active cursor is, for example, a
1045L<ResultSet|DBIx::Class::ResultSet> that has been executed using C<next> or
1046C<first> but has not been exhausted or L<reset|DBIx::Class::ResultSet/reset>.
d867eeda 1047
1048For example, this will not work:
1049
1050 $schema->txn_do(sub {
1051 my $rs = $schema->resultset('Book');
1052 while (my $row = $rs->next) {
1053 $schema->resultset('MetaData')->create({
1054 book_id => $row->id,
1055 ...
1056 });
1057 }
1058 });
1059
d390bd3c 1060This won't either:
1061
1062 my $first_row = $large_rs->first;
1063 $schema->txn_do(sub { ... });
1064
d867eeda 1065Transactions done for inserts in C<AutoCommit> mode when placeholders are in use
1066are not affected, as they are done on an extra database handle.
1067
1068Some workarounds:
1069
1070=over 4
1071
1072=item * use L<DBIx::Class::Storage::DBI::Replicated>
1073
1074=item * L<connect|DBIx::Class::Schema/connect> another L<Schema|DBIx::Class::Schema>
1075
1076=item * load the data from your cursor with L<DBIx::Class::ResultSet/all>
1077
1078=back
1079
1080=head1 MAXIMUM CONNECTIONS
1081
1082The TDS protocol makes separate connections to the server for active statements
1083in the background. By default the number of such connections is limited to 25,
1084on both the client side and the server side.
1085
1086This is a bit too low for a complex L<DBIx::Class> application, so on connection
1087the client side setting is set to C<256> (see L<DBD::Sybase/maxConnect>.) You
1088can override it to whatever setting you like in the DSN.
1089
1090See
1091L<http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.help.ase_15.0.sag1/html/sag1/sag1272.htm>
1092for information on changing the setting on the server side.
1093
1094=head1 DATES
1095
1096See L</connect_call_datetime_setup> to setup date formats
1097for L<DBIx::Class::InflateColumn::DateTime>.
1098
1099=head1 TEXT/IMAGE COLUMNS
1100
1101L<DBD::Sybase> compiled with FreeTDS will B<NOT> allow you to insert or update
1102C<TEXT/IMAGE> columns.
1103
1104Setting C<< $dbh->{LongReadLen} >> will also not work with FreeTDS use either:
1105
1106 $schema->storage->dbh->do("SET TEXTSIZE $bytes");
f68f4d44 1107
d867eeda 1108or
f68f4d44 1109
d867eeda 1110 $schema->storage->set_textsize($bytes);
d4483998 1111
d867eeda 1112instead.
d4483998 1113
d867eeda 1114However, the C<LongReadLen> you pass in
1115L<DBIx::Class::Storage::DBI/connect_info> is used to execute the equivalent
1116C<SET TEXTSIZE> command on connection.
d4483998 1117
d867eeda 1118See L</connect_call_blob_setup> for a L<DBIx::Class::Storage::DBI/connect_info>
1119setting you need to work with C<IMAGE> columns.
f68f4d44 1120
0a9a9955 1121=head1 BULK API
1122
1123The experimental L<DBD::Sybase> Bulk API support is used for
1124L<populate|DBIx::Class::ResultSet/populate> in B<void> context, in a transaction
1125on a separate connection.
1126
1127To use this feature effectively, use a large number of rows for each
1128L<populate|DBIx::Class::ResultSet/populate> call, eg.:
1129
1130 while (my $rows = $data_source->get_100_rows()) {
1131 $rs->populate($rows);
1132 }
1133
1134B<NOTE:> the L<add_columns|DBIx::Class::ResultSource/add_columns>
1135calls in your C<Result> classes B<must> list columns in database order for this
1136to work. Also, you may have to unset the C<LANG> environment variable before
1137loading your app, if it doesn't match the character set of your database.
1138
1139When inserting IMAGE columns using this method, you'll need to use
1140L</connect_call_blob_setup> as well.
1141
d867eeda 1142=head1 AUTHOR
f68f4d44 1143
d867eeda 1144See L<DBIx::Class/CONTRIBUTORS>.
47d9646a 1145
f68f4d44 1146=head1 LICENSE
1147
1148You may distribute this code under the same terms as Perl itself.
1149
1150=cut
d867eeda 1151# vim:sts=2 sw=2: