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