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