Part one of the sybase work by Caelum (mostly reviewed)
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBI / Sybase.pm
CommitLineData
f68f4d44 1package DBIx::Class::Storage::DBI::Sybase;
2
3use strict;
4use warnings;
2ad62d97 5
eabab5d0 6use base qw/
d867eeda 7 DBIx::Class::Storage::DBI::Sybase::Common
8 DBIx::Class::Storage::DBI::AutoCast
eabab5d0 9/;
2ad62d97 10use mro 'c3';
d867eeda 11use Carp::Clan qw/^DBIx::Class/;
12use List::Util ();
13use Sub::Name ();
14
15__PACKAGE__->mk_group_accessors('simple' =>
16 qw/_identity _blob_log_on_update _writer_storage _is_writer_storage
17 _identity_method/
18);
19
20my @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
27DBIx::Class::Storage::DBI::Sybase - Sybase support for DBIx::Class
28
29=head1 SYNOPSIS
30
31This subclass supports L<DBD::Sybase> for real Sybase databases. If you are
32using an MSSQL database via L<DBD::Sybase>, your storage will be reblessed to
33L<DBIx::Class::Storage::DBI::Sybase::Microsoft_SQL_Server>.
34
35=head1 DESCRIPTION
36
37If your version of Sybase does not support placeholders, then your storage
38will be reblessed to L<DBIx::Class::Storage::DBI::Sybase::NoBindVars>. You can
39also enable that driver explicitly, see the documentation for more details.
40
41With this driver there is unfortunately no way to get the C<last_insert_id>
42without doing a C<SELECT MAX(col)>. This is done safely in a transaction
43(locking the table.) See L</INSERTS WITH PLACEHOLDERS>.
44
45A 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
f68f4d44 52
47d9646a 53sub _rebless {
d867eeda 54 my $self = shift;
d29565e0 55
d867eeda 56 if (ref($self) eq 'DBIx::Class::Storage::DBI::Sybase') {
ef131d82 57 my $dbtype = eval {
d867eeda 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
74You are using FreeTDS with Sybase.
75
76We will do our best to support this configuration, but please consider this
77support experimental.
78
79TEXT/IMAGE columns will definitely not work.
80
81You are encouraged to recompile DBD::Sybase with the Sybase Open Client libraries
82instead.
83
84See perldoc DBIx::Class::Storage::DBI::Sybase for more details.
85
86To turn off this warning set the DBIC_SYBASE_FREETDS_NOWARN environment
87variable.
88EOF
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;
d29565e0 95 $self->_rebless;
d867eeda 96 }
d29565e0 97 }
d867eeda 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
112sub _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
131for 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.
147sub _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
165Used as:
166
167 on_connect_call => [ [ 'blob_setup', log_on_update => 0 ] ]
168
169Does C<< $dbh->{syb_binary_images} = 1; >> to return C<IMAGE> data as raw binary
170instead of as a hex string.
171
172Recommended.
173
174Also sets the C<log_on_update> value for blob write operations. The default is
175C<1>, but C<0> is better if your database is configured for it.
176
177See
178L<DBD::Sybase/Handling_IMAGE/TEXT_data_with_syb_ct_get_data()/syb_ct_send_data()>.
179
180=cut
181
182sub 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
192sub _is_lob_type {
193 my $self = shift;
194 my $type = shift;
195 $type && $type =~ /(?:text|image|lob|bytea|binary|memo)/i;
196}
197
198sub _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.
242our %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
260sub _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
269sub _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
277sub _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
291sub last_insert_id { shift->_identity }
292
293# handles TEXT/IMAGE and transaction for last_insert_id
294sub 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
337sub _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
353sub 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
389sub _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
407sub _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
420sub 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
444sub _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
459sub _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
490sub _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
550Used as:
551
552 on_connect_call => 'datetime_setup'
553
554In 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
559On connection for use with L<DBIx::Class::InflateColumn::DateTime>, using
560L<DateTime::Format::Sybase>, which you will need to install.
561
562This works for both C<DATETIME> and C<SMALLDATETIME> columns, although
563C<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;
47d9646a 581 }
d867eeda 582
583 $dbh->do('SET DATEFORMAT mdy');
584
585 1;
586 }
587}
588
589sub 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
597sub _dbh_begin_work {
598 my $self = shift;
599 $self->next::method(@_);
600 if ($self->using_freetds) {
601 $self->_get_dbh->do('BEGIN TRAN');
602 }
47d9646a 603}
604
d867eeda 605sub _dbh_commit {
606 my $self = shift;
607 if ($self->using_freetds) {
608 $self->_dbh->do('COMMIT');
609 }
610 return $self->next::method(@_);
611}
612
613sub _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
623sub _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.
630sub _svp_release { 1 }
631
632sub _svp_rollback {
633 my ($self, $name) = @_;
634
635 $self->_get_dbh->do("ROLLBACK TRANSACTION $name");
a964a928 636}
637
f68f4d44 6381;
639
d867eeda 640=head1 Schema::Loader Support
f68f4d44 641
d867eeda 642There is an experimental branch of L<DBIx::Class::Schema::Loader> that will
643allow you to dump a schema from most (if not all) versions of Sybase.
f68f4d44 644
d867eeda 645It 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
651This driver supports L<DBD::Sybase> compiled against FreeTDS
652(L<http://www.freetds.org/>) to the best of our ability, however it is
653recommended that you recompile L<DBD::Sybase> against the Sybase Open Client
654libraries. They are a part of the Sybase ASE distribution:
655
656The Open Client FAQ is here:
657L<http://www.isug.com/Sybase_FAQ/ASE/section7.html>.
658
659Sybase ASE for Linux (which comes with the Open Client libraries) may be
660downloaded here: L<http://response.sybase.com/forms/ASE_Linux_Download>.
661
662To 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
666Some versions of the libraries involved will not support placeholders, in which
667case the storage will be reblessed to
668L<DBIx::Class::Storage::DBI::Sybase::NoBindVars>.
669
670In some configurations, placeholders will work but will throw implicit type
671conversion errors for anything that's not expecting a string. In such a case,
672the C<auto_cast> option from L<DBIx::Class::Storage::DBI::AutoCast> is
673automatically set, which you may enable on connection with
674L<DBIx::Class::Storage::DBI::AutoCast/connect_call_set_auto_cast>. The type info
675for the C<CAST>s is taken from the L<DBIx::Class::ResultSource/data_type>
676definitions in your Result classes, and are mapped to a Sybase type (if it isn't
677already) using a mapping based on L<SQL::Translator>.
678
679In other configurations, placeholers will work just as they do with the Sybase
680Open Client libraries.
681
682Inserts or updates of TEXT/IMAGE columns will B<NOT> work with FreeTDS.
683
684=head1 INSERTS WITH PLACEHOLDERS
685
686With placeholders enabled, inserts are done in a transaction so that there are
687no concurrency issues with getting the inserted identity value using
688C<SELECT MAX(col)>, which is the only way to get the C<IDENTITY> value in this
689mode.
690
691In addition, they are done on a separate connection so that it's possible to
692have active cursors when doing an insert.
693
694When using C<DBIx::Class::Storage::DBI::Sybase::NoBindVars> transactions are
695disabled, as there are no concurrency issues with C<SELECT @@IDENTITY> as it's a
696session variable.
697
698=head1 TRANSACTIONS
699
700Due to limitations of the TDS protocol, L<DBD::Sybase>, or both; you cannot
701begin a transaction while there are active cursors. An active cursor is, for
702example, a L<ResultSet|DBIx::Class::ResultSet> that has been executed using
703C<next> or C<first> but has not been exhausted or
704L<reset|DBIx::Class::ResultSet/reset>.
705
706For 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
718Transactions done for inserts in C<AutoCommit> mode when placeholders are in use
719are not affected, as they are done on an extra database handle.
720
721Some 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
735The TDS protocol makes separate connections to the server for active statements
736in the background. By default the number of such connections is limited to 25,
737on both the client side and the server side.
738
739This is a bit too low for a complex L<DBIx::Class> application, so on connection
740the client side setting is set to C<256> (see L<DBD::Sybase/maxConnect>.) You
741can override it to whatever setting you like in the DSN.
742
743See
744L<http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.help.ase_15.0.sag1/html/sag1/sag1272.htm>
745for information on changing the setting on the server side.
746
747=head1 DATES
748
749See L</connect_call_datetime_setup> to setup date formats
750for L<DBIx::Class::InflateColumn::DateTime>.
751
752=head1 TEXT/IMAGE COLUMNS
753
754L<DBD::Sybase> compiled with FreeTDS will B<NOT> allow you to insert or update
755C<TEXT/IMAGE> columns.
756
757Setting C<< $dbh->{LongReadLen} >> will also not work with FreeTDS use either:
758
759 $schema->storage->dbh->do("SET TEXTSIZE $bytes");
f68f4d44 760
d867eeda 761or
f68f4d44 762
d867eeda 763 $schema->storage->set_textsize($bytes);
d4483998 764
d867eeda 765instead.
d4483998 766
d867eeda 767However, the C<LongReadLen> you pass in
768L<DBIx::Class::Storage::DBI/connect_info> is used to execute the equivalent
769C<SET TEXTSIZE> command on connection.
d4483998 770
d867eeda 771See L</connect_call_blob_setup> for a L<DBIx::Class::Storage::DBI/connect_info>
772setting you need to work with C<IMAGE> columns.
f68f4d44 773
d867eeda 774=head1 AUTHOR
f68f4d44 775
d867eeda 776See L<DBIx::Class/CONTRIBUTORS>.
47d9646a 777
f68f4d44 778=head1 LICENSE
779
780You may distribute this code under the same terms as Perl itself.
781
782=cut
d867eeda 783# vim:sts=2 sw=2: