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