Part one of the sybase work by Caelum (mostly reviewed)
[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 _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   $self->throw_exception('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   $self->throw_exception('Cannot update TEXT/IMAGE column(s) without a primary key')
500     unless @primary_cols;
501
502   $self->throw_exception('Cannot update TEXT/IMAGE column(s) without primary key values')
503     if ((grep { defined $row{$_} } @primary_cols) != @primary_cols);
504
505   for my $col (keys %$blob_cols) {
506     my $blob = $blob_cols->{$col};
507
508     my %where = map { ($_, $row{$_}) } @primary_cols;
509
510     my $cursor = $self->select ($source, [$col], \%where, {});
511     $cursor->next;
512     my $sth = $cursor->sth;
513
514     eval {
515       do {
516         $sth->func('CS_GET', 1, 'ct_data_info') or die $sth->errstr;
517       } while $sth->fetch;
518
519       $sth->func('ct_prepare_send') or die $sth->errstr;
520
521       my $log_on_update = $self->_blob_log_on_update;
522       $log_on_update    = 1 if not defined $log_on_update;
523
524       $sth->func('CS_SET', 1, {
525         total_txtlen => length($blob),
526         log_on_update => $log_on_update
527       }, 'ct_data_info') or die $sth->errstr;
528
529       $sth->func($blob, length($blob), 'ct_send_data') or die $sth->errstr;
530
531       $sth->func('ct_finish_send') or die $sth->errstr;
532     };
533     my $exception = $@;
534     $sth->finish if $sth;
535     if ($exception) {
536       if ($self->using_freetds) {
537         $self->throw_exception (
538           'TEXT/IMAGE operation failed, probably because you are using FreeTDS: '
539           . $exception
540         );
541       } else {
542         $self->throw_exception($exception);
543       }
544     }
545   }
546 }
547
548 =head2 connect_call_datetime_setup
549
550 Used as:
551
552   on_connect_call => 'datetime_setup'
553
554 In L<DBIx::Class::Storage::DBI/connect_info> to set:
555
556   $dbh->syb_date_fmt('ISO_strict'); # output fmt: 2004-08-21T14:36:48.080Z
557   $dbh->do('set dateformat mdy');   # input fmt:  08/13/1979 18:08:55.080
558
559 On connection for use with L<DBIx::Class::InflateColumn::DateTime>, using
560 L<DateTime::Format::Sybase>, which you will need to install.
561
562 This works for both C<DATETIME> and C<SMALLDATETIME> columns, although
563 C<SMALLDATETIME> columns only have minute precision.
564
565 =cut
566
567 {
568   my $old_dbd_warned = 0;
569
570   sub connect_call_datetime_setup {
571     my $self = shift;
572     my $dbh = $self->_dbh;
573
574     if ($dbh->can('syb_date_fmt')) {
575       # amazingly, this works with FreeTDS
576       $dbh->syb_date_fmt('ISO_strict');
577     } elsif (not $old_dbd_warned) {
578       carp "Your DBD::Sybase is too old to support ".
579       "DBIx::Class::InflateColumn::DateTime, please upgrade!";
580       $old_dbd_warned = 1;
581     }
582
583     $dbh->do('SET DATEFORMAT mdy');
584
585     1;
586   }
587 }
588
589 sub datetime_parser_type { "DateTime::Format::Sybase" }
590
591 # ->begin_work and such have no effect with FreeTDS but we run them anyway to
592 # let the DBD keep any state it needs to.
593 #
594 # If they ever do start working, the extra statements will do no harm (because
595 # Sybase supports nested transactions.)
596
597 sub _dbh_begin_work {
598   my $self = shift;
599   $self->next::method(@_);
600   if ($self->using_freetds) {
601     $self->_get_dbh->do('BEGIN TRAN');
602   }
603 }
604
605 sub _dbh_commit {
606   my $self = shift;
607   if ($self->using_freetds) {
608     $self->_dbh->do('COMMIT');
609   }
610   return $self->next::method(@_);
611 }
612
613 sub _dbh_rollback {
614   my $self = shift;
615   if ($self->using_freetds) {
616     $self->_dbh->do('ROLLBACK');
617   }
618   return $self->next::method(@_);
619 }
620
621 # savepoint support using ASE syntax
622
623 sub _svp_begin {
624   my ($self, $name) = @_;
625
626   $self->_get_dbh->do("SAVE TRANSACTION $name");
627 }
628
629 # A new SAVE TRANSACTION with the same name releases the previous one.
630 sub _svp_release { 1 }
631
632 sub _svp_rollback {
633   my ($self, $name) = @_;
634
635   $self->_get_dbh->do("ROLLBACK TRANSACTION $name");
636 }
637
638 1;
639
640 =head1 Schema::Loader Support
641
642 There is an experimental branch of L<DBIx::Class::Schema::Loader> that will
643 allow you to dump a schema from most (if not all) versions of Sybase.
644
645 It is available via subversion from:
646
647   http://dev.catalyst.perl.org/repos/bast/branches/DBIx-Class-Schema-Loader/current/
648
649 =head1 FreeTDS
650
651 This driver supports L<DBD::Sybase> compiled against FreeTDS
652 (L<http://www.freetds.org/>) to the best of our ability, however it is
653 recommended that you recompile L<DBD::Sybase> against the Sybase Open Client
654 libraries. They are a part of the Sybase ASE distribution:
655
656 The Open Client FAQ is here:
657 L<http://www.isug.com/Sybase_FAQ/ASE/section7.html>.
658
659 Sybase ASE for Linux (which comes with the Open Client libraries) may be
660 downloaded here: L<http://response.sybase.com/forms/ASE_Linux_Download>.
661
662 To see if you're using FreeTDS check C<< $schema->storage->using_freetds >>, or run:
663
664   perl -MDBI -le 'my $dbh = DBI->connect($dsn, $user, $pass); print $dbh->{syb_oc_version}'
665
666 Some versions of the libraries involved will not support placeholders, in which
667 case the storage will be reblessed to
668 L<DBIx::Class::Storage::DBI::Sybase::NoBindVars>.
669
670 In some configurations, placeholders will work but will throw implicit type
671 conversion errors for anything that's not expecting a string. In such a case,
672 the C<auto_cast> option from L<DBIx::Class::Storage::DBI::AutoCast> is
673 automatically set, which you may enable on connection with
674 L<DBIx::Class::Storage::DBI::AutoCast/connect_call_set_auto_cast>. The type info
675 for the C<CAST>s is taken from the L<DBIx::Class::ResultSource/data_type>
676 definitions in your Result classes, and are mapped to a Sybase type (if it isn't
677 already) using a mapping based on L<SQL::Translator>.
678
679 In other configurations, placeholers will work just as they do with the Sybase
680 Open Client libraries.
681
682 Inserts or updates of TEXT/IMAGE columns will B<NOT> work with FreeTDS.
683
684 =head1 INSERTS WITH PLACEHOLDERS
685
686 With placeholders enabled, inserts are done in a transaction so that there are
687 no concurrency issues with getting the inserted identity value using
688 C<SELECT MAX(col)>, which is the only way to get the C<IDENTITY> value in this
689 mode.
690
691 In addition, they are done on a separate connection so that it's possible to
692 have active cursors when doing an insert.
693
694 When using C<DBIx::Class::Storage::DBI::Sybase::NoBindVars> transactions are
695 disabled, as there are no concurrency issues with C<SELECT @@IDENTITY> as it's a
696 session variable.
697
698 =head1 TRANSACTIONS
699
700 Due to limitations of the TDS protocol, L<DBD::Sybase>, or both; you cannot
701 begin a transaction while there are active cursors. An active cursor is, for
702 example, a L<ResultSet|DBIx::Class::ResultSet> that has been executed using
703 C<next> or C<first> but has not been exhausted or
704 L<reset|DBIx::Class::ResultSet/reset>.
705
706 For example, this will not work:
707
708   $schema->txn_do(sub {
709     my $rs = $schema->resultset('Book');
710     while (my $row = $rs->next) {
711       $schema->resultset('MetaData')->create({
712         book_id => $row->id,
713         ...
714       });
715     }
716   });
717
718 Transactions done for inserts in C<AutoCommit> mode when placeholders are in use
719 are not affected, as they are done on an extra database handle.
720
721 Some workarounds:
722
723 =over 4
724
725 =item * use L<DBIx::Class::Storage::DBI::Replicated>
726
727 =item * L<connect|DBIx::Class::Schema/connect> another L<Schema|DBIx::Class::Schema>
728
729 =item * load the data from your cursor with L<DBIx::Class::ResultSet/all>
730
731 =back
732
733 =head1 MAXIMUM CONNECTIONS
734
735 The TDS protocol makes separate connections to the server for active statements
736 in the background. By default the number of such connections is limited to 25,
737 on both the client side and the server side.
738
739 This is a bit too low for a complex L<DBIx::Class> application, so on connection
740 the client side setting is set to C<256> (see L<DBD::Sybase/maxConnect>.) You
741 can override it to whatever setting you like in the DSN.
742
743 See
744 L<http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.help.ase_15.0.sag1/html/sag1/sag1272.htm>
745 for information on changing the setting on the server side.
746
747 =head1 DATES
748
749 See L</connect_call_datetime_setup> to setup date formats
750 for L<DBIx::Class::InflateColumn::DateTime>.
751
752 =head1 TEXT/IMAGE COLUMNS
753
754 L<DBD::Sybase> compiled with FreeTDS will B<NOT> allow you to insert or update
755 C<TEXT/IMAGE> columns.
756
757 Setting C<< $dbh->{LongReadLen} >> will also not work with FreeTDS use either:
758
759   $schema->storage->dbh->do("SET TEXTSIZE $bytes");
760
761 or
762
763   $schema->storage->set_textsize($bytes);
764
765 instead.
766
767 However, the C<LongReadLen> you pass in
768 L<DBIx::Class::Storage::DBI/connect_info> is used to execute the equivalent
769 C<SET TEXTSIZE> command on connection.
770
771 See L</connect_call_blob_setup> for a L<DBIx::Class::Storage::DBI/connect_info>
772 setting you need to work with C<IMAGE> columns.
773
774 =head1 AUTHOR
775
776 See L<DBIx::Class/CONTRIBUTORS>.
777
778 =head1 LICENSE
779
780 You may distribute this code under the same terms as Perl itself.
781
782 =cut
783 # vim:sts=2 sw=2: