test multi-row blob update
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBI / Sybase.pm
1 package DBIx::Class::Storage::DBI::Sybase;
2
3 use strict;
4 use warnings;
5
6 use base qw/
7     DBIx::Class::Storage::DBI::Sybase::Common
8     DBIx::Class::Storage::DBI::AutoCast
9 /;
10 use mro 'c3';
11 use Carp::Clan qw/^DBIx::Class/;
12 use List::Util ();
13 use Sub::Name ();
14
15 __PACKAGE__->mk_group_accessors('simple' =>
16     qw/_identity _blob_log_on_update _insert_dbh _identity_method/
17 );
18
19 =head1 NAME
20
21 DBIx::Class::Storage::DBI::Sybase - Sybase support for DBIx::Class
22
23 =head1 SYNOPSIS
24
25 This subclass supports L<DBD::Sybase> for real Sybase databases.  If you are
26 using an MSSQL database via L<DBD::Sybase>, your storage will be reblessed to
27 L<DBIx::Class::Storage::DBI::Sybase::Microsoft_SQL_Server>.
28
29 =head1 DESCRIPTION
30
31 If your version of Sybase does not support placeholders, then your storage
32 will be reblessed to L<DBIx::Class::Storage::DBI::Sybase::NoBindVars>. You can
33 also enable that driver explicitly, see the documentation for more details.
34
35 With this driver there is unfortunately no way to get the C<last_insert_id>
36 without doing a C<SELECT MAX(col)>. This is done safely in a transaction
37 (locking the table.) See L</INSERTS WITH PLACEHOLDERS>.
38
39 A recommended L<DBIx::Class::Storage::DBI/connect_info> setting:
40
41   on_connect_call => [['datetime_setup'], ['blob_setup', log_on_update => 0]]
42
43 =head1 METHODS
44
45 =cut
46
47 sub _rebless {
48   my $self = shift;
49
50   if (ref($self) eq 'DBIx::Class::Storage::DBI::Sybase') {
51     my $dbtype = eval {
52       @{$self->_get_dbh->selectrow_arrayref(qq{sp_server_info \@attribute_id=1})}[2]
53     } || '';
54
55     my $exception = $@;
56     $dbtype =~ s/\W/_/gi;
57     my $subclass = "DBIx::Class::Storage::DBI::Sybase::${dbtype}";
58
59     if (!$exception && $dbtype && $self->load_optional_class($subclass)) {
60       bless $self, $subclass;
61       $self->_rebless;
62     } else { # real Sybase
63       my $no_bind_vars = 'DBIx::Class::Storage::DBI::Sybase::NoBindVars';
64
65       if ($self->using_freetds) {
66         carp <<'EOF' unless $ENV{DBIC_SYBASE_FREETDS_NOWARN};
67
68 You are using FreeTDS with Sybase.
69
70 We will do our best to support this configuration, but please consider this
71 support experimental.
72
73 TEXT/IMAGE columns will definitely not work.
74
75 You are encouraged to recompile DBD::Sybase with the Sybase Open Client libraries
76 instead.
77
78 See perldoc DBIx::Class::Storage::DBI::Sybase for more details.
79
80 To turn off this warning set the DBIC_SYBASE_FREETDS_NOWARN environment
81 variable.
82 EOF
83         if (not $self->_typeless_placeholders_supported) {
84           if ($self->_placeholders_supported) {
85             $self->auto_cast(1);
86           } else {
87             $self->ensure_class_loaded($no_bind_vars);
88             bless $self, $no_bind_vars;
89             $self->_rebless;
90           }
91         }
92       }
93       elsif (not $self->_get_dbh->{syb_dynamic_supported}) {
94         # not necessarily FreeTDS, but no placeholders nevertheless
95         $self->ensure_class_loaded($no_bind_vars);
96         bless $self, $no_bind_vars;
97         $self->_rebless;
98       } elsif (not $self->_typeless_placeholders_supported) {
99 # this is highly unlikely, but we check just in case
100         $self->auto_cast(1);
101       }
102     }
103   }
104 }
105
106 sub _init {
107   my $self = shift;
108   $self->_set_max_connect(256);
109
110   # based on LongReadLen in connect_info
111   $self->set_textsize if $self->using_freetds;
112 }
113
114 # Make sure we have CHAINED mode turned on if AutoCommit is off in non-FreeTDS
115 # DBD::Sybase (since we don't know how DBD::Sybase was compiled.) If however
116 # we're using FreeTDS, CHAINED mode turns on an implicit transaction which we
117 # only want when AutoCommit is off.
118 sub _populate_dbh {
119   my $self = shift;
120
121   $self->next::method(@_);
122
123   if (not $self->using_freetds) {
124     $self->_dbh->{syb_chained_txn} = 1;
125   } else {
126     if ($self->_dbh_autocommit) {
127       $self->_dbh->do('SET CHAINED OFF');
128     } else {
129       $self->_dbh->do('SET CHAINED ON');
130     }
131   }
132 }
133
134 =head2 connect_call_blob_setup
135
136 Used as:
137
138   on_connect_call => [ [ 'blob_setup', log_on_update => 0 ] ]
139
140 Does C<< $dbh->{syb_binary_images} = 1; >> to return C<IMAGE> data as raw binary
141 instead of as a hex string.
142
143 Recommended.
144
145 Also sets the C<log_on_update> value for blob write operations. The default is
146 C<1>, but C<0> is better if your database is configured for it.
147
148 See
149 L<DBD::Sybase/Handling_IMAGE/TEXT_data_with_syb_ct_get_data()/syb_ct_send_data()>.
150
151 =cut
152
153 sub connect_call_blob_setup {
154   my $self = shift;
155   my %args = @_;
156   my $dbh = $self->_dbh;
157   $dbh->{syb_binary_images} = 1;
158
159   $self->_blob_log_on_update($args{log_on_update})
160     if exists $args{log_on_update};
161 }
162
163 sub _is_lob_type {
164   my $self = shift;
165   my $type = shift;
166   $type && $type =~ /(?:text|image|lob|bytea|binary|memo)/i;
167 }
168
169 sub _prep_for_execute {
170   my $self = shift;
171   my ($op, $extra_bind, $ident, $args) = @_;
172
173   my ($sql, $bind) = $self->next::method (@_);
174
175   if ($op eq 'insert') {
176     my $table = $ident->from;
177
178     my $bind_info = $self->_resolve_column_info(
179       $ident, [map $_->[0], @{$bind}]
180     );
181     my $identity_col = List::Util::first
182       { $bind_info->{$_}{is_auto_increment} }
183       (keys %$bind_info)
184     ;
185
186     if ($identity_col) {
187       $sql = join ("\n",
188         "SET IDENTITY_INSERT $table ON",
189         $sql,
190         "SET IDENTITY_INSERT $table OFF",
191       );
192     }
193     else {
194       $identity_col = List::Util::first
195         { $ident->column_info($_)->{is_auto_increment} }
196         $ident->columns
197       ;
198     }
199
200     if ($identity_col) {
201       $sql =
202         "$sql\n" .
203         $self->_fetch_identity_sql($ident, $identity_col);
204     }
205   }
206
207   return ($sql, $bind);
208 }
209
210 # Stolen from SQLT, with some modifications. This is a makeshift
211 # solution before a sane type-mapping library is available, thus
212 # the 'our' for easy overrides.
213 our %TYPE_MAPPING  = (
214     number    => 'numeric',
215     money     => 'money',
216     varchar   => 'varchar',
217     varchar2  => 'varchar',
218     timestamp => 'datetime',
219     text      => 'varchar',
220     real      => 'double precision',
221     comment   => 'text',
222     bit       => 'bit',
223     tinyint   => 'smallint',
224     float     => 'double precision',
225     serial    => 'numeric',
226     bigserial => 'numeric',
227     boolean   => 'varchar',
228     long      => 'varchar',
229 );
230
231 sub _native_data_type {
232   my ($self, $type) = @_;
233
234   $type = lc $type;
235   $type =~ s/\s* identity//x;
236
237   return uc($TYPE_MAPPING{$type} || $type);
238 }
239
240 sub _fetch_identity_sql {
241   my ($self, $source, $col) = @_;
242
243   return sprintf ("SELECT MAX(%s) FROM %s",
244     map { $self->sql_maker->_quote ($_) } ($col, $source->from)
245   );
246 }
247
248 sub _execute {
249   my $self = shift;
250   my ($op) = @_;
251
252   my ($rv, $sth, @bind) = $self->dbh_do($self->can('_dbh_execute'), @_);
253
254   if ($op eq 'insert') {
255     $self->_identity($sth->fetchrow_array);
256     $sth->finish;
257   }
258
259   return wantarray ? ($rv, $sth, @bind) : $rv;
260 }
261
262 sub last_insert_id { shift->_identity }
263
264 # handles TEXT/IMAGE and transaction for last_insert_id
265 sub insert {
266   my $self = shift;
267   my ($source, $to_insert) = @_;
268
269   my $blob_cols = $self->_remove_blob_cols($source, $to_insert);
270
271   my $identity_col = List::Util::first
272     { $source->column_info($_)->{is_auto_increment} }
273     $source->columns;
274
275   # do we need the horrific SELECT MAX(COL) hack?
276   my $dumb_last_insert_id =
277     (    $identity_col
278       && (not exists $to_insert->{$identity_col})
279       && ($self->_identity_method||'') ne '@@IDENTITY'
280     ) ? 1 : 0;
281
282   # we are already in a transaction, or there are no blobs
283   # and we don't need the PK - just (try to) do it
284   if ($self->{transaction_depth}
285         || (!$blob_cols && !$dumb_last_insert_id) 
286   ) {
287     return $self->_insert ($source, $to_insert, $blob_cols, $identity_col);
288   }
289
290   # this is tricky: a transaction needs to take place if we need
291   # either a last_insert_id or we will be doing blob insert.
292   # This will happen on a *seperate* connection, thus the local()
293   # acrobatics
294
295   local $self->{_dbh};
296
297   # localize so it appears right if we blow out with an exception
298   local $self->{transaction_depth} = 0;
299
300   $self->_insert_dbh($self->_connect(@{ $self->_dbi_connect_info }))
301     unless $self->_insert_dbh;
302
303   $self->{_dbh} = $self->_insert_dbh;
304   my $guard = $self->txn_scope_guard;
305
306   # _dbh_begin_work in the guard may reconnect,
307   # so we update the accessor just in case
308   $self->_insert_dbh($self->_dbh);
309
310   my $updated_cols = $self->_insert ($source, $to_insert, $blob_cols, $identity_col);
311
312   $guard->commit;
313
314   return $updated_cols;
315
316 }
317
318 sub _insert {
319   my ($self, $source, $to_insert, $blob_cols, $identity_col) = @_;
320
321   my $updated_cols =
322     (Sub::Name::subname insert =>
323       sub { $self->next::method ($source, $to_insert) })->();
324
325   my $final_row = {
326     $identity_col => $self->last_insert_id($source, $identity_col),
327     %$to_insert,
328     %$updated_cols,
329   };
330
331   $self->_insert_blobs ($source, $blob_cols, $final_row) if $blob_cols;
332
333   return $updated_cols;
334 }
335
336 sub update {
337   my $self = shift;
338   my ($source, $fields, $where) = @_;
339
340   my $wantarray = wantarray;
341
342   my $blob_cols = $self->_remove_blob_cols($source, $fields);
343
344 # update+blob update(s) done atomically
345   my $guard = $self->txn_scope_guard if $blob_cols;
346
347   my @res;
348   if ($wantarray) {
349     @res    = $self->next::method(@_);
350   }
351   elsif (defined $wantarray) {
352     $res[0] = $self->next::method(@_);
353   }
354   else {
355     $self->next::method(@_);
356   }
357
358   $self->_update_blobs($source, $blob_cols, $where) if $blob_cols;
359
360   $guard->commit if $guard;
361
362   return $wantarray ? @res : $res[0];
363 }
364
365 sub _remove_blob_cols {
366   my ($self, $source, $fields) = @_;
367
368   my %blob_cols;
369
370   for my $col (keys %$fields) {
371     if ($self->_is_lob_type($source->column_info($col)->{data_type})) {
372       $blob_cols{$col} = delete $fields->{$col};
373       $fields->{$col} = \"''";
374     }
375   }
376
377   return keys %blob_cols ? \%blob_cols : undef;
378 }
379
380 sub _update_blobs {
381   my ($self, $source, $blob_cols, $where) = @_;
382
383   my (@primary_cols) = $source->primary_columns;
384
385   croak "Cannot update TEXT/IMAGE column(s) without a primary key"
386     unless @primary_cols;
387
388 # check if we're updating a single row by PK
389   my $pk_cols_in_where = 0;
390   for my $col (@primary_cols) {
391     $pk_cols_in_where++ if defined $where->{$col};
392   }
393   my @rows;
394
395   if ($pk_cols_in_where == @primary_cols) {
396     my %row_to_update;
397     @row_to_update{@primary_cols} = @{$where}{@primary_cols};
398     @rows = \%row_to_update;
399   } else {
400     my $rs = $source->resultset->search(
401       $where,
402       {
403         result_class => 'DBIx::Class::ResultClass::HashRefInflator',
404         columns => \@primary_cols
405       }
406     );
407     @rows = $rs->all; # statement must finish
408   }
409
410   for my $row (@rows) {
411     $self->_insert_blobs($source, $blob_cols, $row);
412   }
413 }
414
415 sub _insert_blobs {
416   my ($self, $source, $blob_cols, $row) = @_;
417   my $dbh = $self->_get_dbh;
418
419   my $table = $source->from;
420
421   my %row = %$row;
422   my (@primary_cols) = $source->primary_columns;
423
424   croak "Cannot update TEXT/IMAGE column(s) without a primary key"
425     unless @primary_cols;
426
427   if ((grep { defined $row{$_} } @primary_cols) != @primary_cols) {
428     croak "Cannot update TEXT/IMAGE column(s) without primary key values";
429   }
430
431   for my $col (keys %$blob_cols) {
432     my $blob = $blob_cols->{$col};
433
434     my %where = map { ($_, $row{$_}) } @primary_cols;
435     my $cursor = $source->resultset->search(\%where, {
436       select => [$col]
437     })->cursor;
438     $cursor->next;
439     my $sth = $cursor->sth;
440
441     eval {
442       do {
443         $sth->func('CS_GET', 1, 'ct_data_info') or die $sth->errstr;
444       } while $sth->fetch;
445
446       $sth->func('ct_prepare_send') or die $sth->errstr;
447
448       my $log_on_update = $self->_blob_log_on_update;
449       $log_on_update    = 1 if not defined $log_on_update;
450
451       $sth->func('CS_SET', 1, {
452         total_txtlen => length($blob),
453         log_on_update => $log_on_update
454       }, 'ct_data_info') or die $sth->errstr;
455
456       $sth->func($blob, length($blob), 'ct_send_data') or die $sth->errstr;
457
458       $sth->func('ct_finish_send') or die $sth->errstr;
459     };
460     my $exception = $@;
461     $sth->finish if $sth;
462     if ($exception) {
463       if ($self->using_freetds) {
464         croak (
465           'TEXT/IMAGE operation failed, probably because you are using FreeTDS: '
466           . $exception
467         );
468       } else {
469         croak $exception;
470       }
471     }
472   }
473 }
474
475 =head2 connect_call_datetime_setup
476
477 Used as:
478
479   on_connect_call => 'datetime_setup'
480
481 In L<DBIx::Class::Storage::DBI/connect_info> to set:
482
483   $dbh->syb_date_fmt('ISO_strict'); # output fmt: 2004-08-21T14:36:48.080Z
484   $dbh->do('set dateformat mdy');   # input fmt:  08/13/1979 18:08:55.080
485
486 On connection for use with L<DBIx::Class::InflateColumn::DateTime>, using
487 L<DateTime::Format::Sybase>, which you will need to install.
488
489 This works for both C<DATETIME> and C<SMALLDATETIME> columns, although
490 C<SMALLDATETIME> columns only have minute precision.
491
492 =cut
493
494 {
495   my $old_dbd_warned = 0;
496
497   sub connect_call_datetime_setup {
498     my $self = shift;
499     my $dbh = $self->_dbh;
500
501     if ($dbh->can('syb_date_fmt')) {
502       # amazingly, this works with FreeTDS
503       $dbh->syb_date_fmt('ISO_strict');
504     } elsif (not $old_dbd_warned) {
505       carp "Your DBD::Sybase is too old to support ".
506       "DBIx::Class::InflateColumn::DateTime, please upgrade!";
507       $old_dbd_warned = 1;
508     }
509
510     $dbh->do('SET DATEFORMAT mdy');
511
512     1;
513   }
514 }
515
516 sub datetime_parser_type { "DateTime::Format::Sybase" }
517
518 # ->begin_work and such have no effect with FreeTDS but we run them anyway to
519 # let the DBD keep any state it needs to.
520 #
521 # If they ever do start working, the extra statements will do no harm (because
522 # Sybase supports nested transactions.)
523
524 sub _dbh_begin_work {
525   my $self = shift;
526   $self->next::method(@_);
527   if ($self->using_freetds) {
528     $self->_get_dbh->do('BEGIN TRAN');
529   }
530 }
531
532 sub _dbh_commit {
533   my $self = shift;
534   if ($self->using_freetds) {
535     $self->_dbh->do('COMMIT');
536   }
537   return $self->next::method(@_);
538 }
539
540 sub _dbh_rollback {
541   my $self = shift;
542   if ($self->using_freetds) {
543     $self->_dbh->do('ROLLBACK');
544   }
545   return $self->next::method(@_);
546 }
547
548 # savepoint support using ASE syntax
549
550 sub _svp_begin {
551   my ($self, $name) = @_;
552
553   $self->_get_dbh->do("SAVE TRANSACTION $name");
554 }
555
556 # A new SAVE TRANSACTION with the same name releases the previous one.
557 sub _svp_release { 1 }
558
559 sub _svp_rollback {
560   my ($self, $name) = @_;
561
562   $self->_get_dbh->do("ROLLBACK TRANSACTION $name");
563 }
564
565 1;
566
567 =head1 Schema::Loader Support
568
569 There is an experimental branch of L<DBIx::Class::Schema::Loader> that will
570 allow you to dump a schema from most (if not all) versions of Sybase.
571
572 It is available via subversion from:
573
574   http://dev.catalyst.perl.org/repos/bast/branches/DBIx-Class-Schema-Loader/current/
575
576 =head1 FreeTDS
577
578 This driver supports L<DBD::Sybase> compiled against FreeTDS
579 (L<http://www.freetds.org/>) to the best of our ability, however it is
580 recommended that you recompile L<DBD::Sybase> against the Sybase Open Client
581 libraries. They are a part of the Sybase ASE distribution:
582
583 The Open Client FAQ is here:
584 L<http://www.isug.com/Sybase_FAQ/ASE/section7.html>.
585
586 Sybase ASE for Linux (which comes with the Open Client libraries) may be
587 downloaded here: L<http://response.sybase.com/forms/ASE_Linux_Download>.
588
589 To see if you're using FreeTDS check C<< $schema->storage->using_freetds >>, or run:
590
591   perl -MDBI -le 'my $dbh = DBI->connect($dsn, $user, $pass); print $dbh->{syb_oc_version}'
592
593 Some versions of the libraries involved will not support placeholders, in which
594 case the storage will be reblessed to
595 L<DBIx::Class::Storage::DBI::Sybase::NoBindVars>.
596
597 In some configurations, placeholders will work but will throw implicit type
598 conversion errors for anything that's not expecting a string. In such a case,
599 the C<auto_cast> option from L<DBIx::Class::Storage::DBI::AutoCast> is
600 automatically set, which you may enable on connection with
601 L<DBIx::Class::Storage::DBI::AutoCast/connect_call_set_auto_cast>. The type info
602 for the C<CAST>s is taken from the L<DBIx::Class::ResultSource/data_type>
603 definitions in your Result classes, and are mapped to a Sybase type (if it isn't
604 already) using a mapping based on L<SQL::Translator>.
605
606 In other configurations, placeholers will work just as they do with the Sybase
607 Open Client libraries.
608
609 Inserts or updates of TEXT/IMAGE columns will B<NOT> work with FreeTDS.
610
611 =head1 INSERTS WITH PLACEHOLDERS
612
613 With placeholders enabled, inserts are done in a transaction so that there are
614 no concurrency issues with getting the inserted identity value using
615 C<SELECT MAX(col)>, which is the only way to get the C<IDENTITY> value in this
616 mode.
617
618 When using C<DBIx::Class::Storage::DBI::Sybase::NoBindVars> transactions are
619 disabled, as there are no concurrency issues with C<SELECT @@IDENTITY> as it's a
620 session variable.
621
622 =head1 TRANSACTIONS
623
624 Due to limitations of the TDS protocol, L<DBD::Sybase>, or both; you cannot
625 begin a transaction while there are active cursors. An active cursor is, for
626 example, a L<ResultSet|DBIx::Class::ResultSet> that has been executed using
627 C<next> or C<first> but has not been exhausted or
628 L<reset|DBIx::Class::ResultSet/reset>.
629
630 For example, this will not work:
631
632   $schema->txn_do(sub {
633     my $rs = $schema->resultset('Book');
634     while (my $row = $rs->next) {
635       $schema->resultset('MetaData')->create({
636         book_id => $row->id,
637         ...
638       });
639     }
640   });
641
642 Transactions done for inserts in C<AutoCommit> mode when placeholders are in use
643 are not affected, as they use an extra database handle to do the insert.
644
645 Some workarounds:
646
647 =over 4
648
649 =item * use L<DBIx::Class::Storage::DBI::Replicated>
650
651 =item * L<connect|DBIx::Class::Schema/connect> another L<Schema|DBIx::Class::Schema>
652
653 =item * load the data from your cursor with L<DBIx::Class::ResultSet/all>
654
655 =back
656
657 =head1 MAXIMUM CONNECTIONS
658
659 The TDS protocol makes separate connections to the server for active statements
660 in the background. By default the number of such connections is limited to 25,
661 on both the client side and the server side.
662
663 This is a bit too low for a complex L<DBIx::Class> application, so on connection
664 the client side setting is set to C<256> (see L<DBD::Sybase/maxConnect>.) You
665 can override it to whatever setting you like in the DSN.
666
667 See
668 L<http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.help.ase_15.0.sag1/html/sag1/sag1272.htm>
669 for information on changing the setting on the server side.
670
671 =head1 DATES
672
673 See L</connect_call_datetime_setup> to setup date formats
674 for L<DBIx::Class::InflateColumn::DateTime>.
675
676 =head1 TEXT/IMAGE COLUMNS
677
678 L<DBD::Sybase> compiled with FreeTDS will B<NOT> allow you to insert or update
679 C<TEXT/IMAGE> columns.
680
681 Setting C<< $dbh->{LongReadLen} >> will also not work with FreeTDS use either:
682
683   $schema->storage->dbh->do("SET TEXTSIZE $bytes");
684
685 or
686
687   $schema->storage->set_textsize($bytes);
688
689 instead.
690
691 However, the C<LongReadLen> you pass in
692 L<DBIx::Class::Storage::DBI/connect_info> is used to execute the equivalent
693 C<SET TEXTSIZE> command on connection.
694
695 See L</connect_call_blob_setup> for a L<DBIx::Class::Storage::DBI/connect_info>
696 setting you need to work with C<IMAGE> columns.
697
698 =head1 AUTHOR
699
700 See L<DBIx::Class/CONTRIBUTORS>.
701
702 =head1 LICENSE
703
704 You may distribute this code under the same terms as Perl itself.
705
706 =cut
707 # vim:sts=2 sw=2: