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