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