savepoint support
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBI / Sybase.pm
CommitLineData
3885cff6 1package DBIx::Class::Storage::DBI::Sybase;
2
3use strict;
4use warnings;
2ad62d97 5
eabab5d0 6use base qw/
64f4e691 7 DBIx::Class::Storage::DBI::Sybase::Base
b0b44f97 8 DBIx::Class::Storage::DBI
eabab5d0 9/;
2ad62d97 10use mro 'c3';
6b1f5ef7 11use Carp::Clan qw/^DBIx::Class/;
289877b0 12use List::Util ();
6b1f5ef7 13
98259fe4 14=head1 NAME
15
928f0af8 16DBIx::Class::Storage::DBI::Sybase - Sybase support for DBIx::Class
98259fe4 17
18=head1 SYNOPSIS
19
20This subclass supports L<DBD::Sybase> for real Sybase databases. If you are
21using an MSSQL database via L<DBD::Sybase>, your storage will be reblessed to
22L<DBIx::Class::Storage::DBI::Sybase::Microsoft_SQL_Server>.
23
24=head1 DESCRIPTION
25
26If your version of Sybase does not support placeholders, then your storage
27will be reblessed to L<DBIx::Class::Storage::DBI::Sybase::NoBindVars>. You can
28also enable that driver explicitly, see the documentation for more details.
29
30With this driver there is unfortunately no way to get the C<last_insert_id>
31without doing a C<select max(col)>.
32
33But your queries will be cached.
34
8c4b6c50 35You need a version of L<DBD::Sybase> compiled with the Sybase OpenClient
36libraries, B<NOT> FreeTDS, for placeholder support. Otherwise your storage will
37be automatically reblessed into C<::NoBindVars>.
5703eb14 38
61cfaef7 39A recommended L<DBIx::Class::Storage::DBI/connect_info> setting:
98259fe4 40
61cfaef7 41 on_connect_call => [['datetime_setup'], ['blob_setup', log_on_update => 0]]
98259fe4 42
43=head1 METHODS
44
45=cut
46
fd5a07e4 47__PACKAGE__->mk_group_accessors('simple' =>
48 qw/_blob_log_on_update/
49);
50
47d9646a 51sub _rebless {
b50a5275 52 my $self = shift;
c5ce7cd6 53
54 if (ref($self) eq 'DBIx::Class::Storage::DBI::Sybase') {
55 my $dbtype = eval {
56 @{$self->dbh->selectrow_arrayref(qq{sp_server_info \@attribute_id=1})}[2]
57 } || '';
58
59 my $exception = $@;
60 $dbtype =~ s/\W/_/gi;
61 my $subclass = "DBIx::Class::Storage::DBI::Sybase::${dbtype}";
62
63 if (!$exception && $dbtype && $self->load_optional_class($subclass)) {
64 bless $self, $subclass;
65 $self->_rebless;
5703eb14 66 } else { # real Sybase
67 my $no_bind_vars = 'DBIx::Class::Storage::DBI::Sybase::NoBindVars';
68
8c4b6c50 69 if ($self->_using_freetds) {
5703eb14 70 carp <<'EOF';
71
72Your version of Sybase potentially supports placeholders and query caching,
8c4b6c50 73however you seem to be using FreeTDS which does not (yet?) support this.
5703eb14 74
8c4b6c50 75Please recompile DBD::Sybase with the Sybase OpenClient libraries if you want
76these features.
77
78TEXT/IMAGE column support will also not work under FreeTDS.
5703eb14 79
80See perldoc DBIx::Class::Storage::DBI::Sybase for more details.
81EOF
82 $self->ensure_class_loaded($no_bind_vars);
83 bless $self, $no_bind_vars;
683f73ec 84 $self->_rebless;
85 }
61cfaef7 86
87 if (not $self->dbh->{syb_dynamic_supported}) {
88 $self->ensure_class_loaded($no_bind_vars);
89 bless $self, $no_bind_vars;
90 $self->_rebless;
91 }
92
41c93b1b 93 $self->_set_maxConnect;
47d9646a 94 }
c5ce7cd6 95 }
b50a5275 96}
97
61cfaef7 98sub _using_freetds {
99 my $self = shift;
8c4b6c50 100
61cfaef7 101 return $self->_dbh->{syb_oc_version} =~ /freetds/i;
8c4b6c50 102}
103
41c93b1b 104sub _set_maxConnect {
105 my $self = shift;
106
107 my $dsn = $self->_dbi_connect_info->[0];
108
109 return if ref($dsn) eq 'CODE';
110
111 if ($dsn !~ /maxConnect=/) {
112 $self->_dbi_connect_info->[0] = "$dsn;maxConnect=256";
41c93b1b 113 my $connected = defined $self->_dbh;
114 $self->disconnect;
115 $self->ensure_connected if $connected;
116 }
117}
118
63d46bb3 119=head2 connect_call_blob_setup
120
121Used as:
122
61cfaef7 123 on_connect_call => [ [ 'blob_setup', log_on_update => 0 ] ]
63d46bb3 124
125Does C<< $dbh->{syb_binary_images} = 1; >> to return C<IMAGE> data as raw binary
126instead of as a hex string.
127
6636ad53 128Recommended.
129
fd5a07e4 130Also sets the C<log_on_update> value for blob write operations. The default is
131C<1>, but C<0> is better if your database is configured for it.
132
133See
134L<DBD::Sybase/Handling_IMAGE/TEXT_data_with_syb_ct_get_data()/syb_ct_send_data()>.
135
63d46bb3 136=cut
137
138sub connect_call_blob_setup {
139 my $self = shift;
fd5a07e4 140 my %args = @_;
63d46bb3 141 my $dbh = $self->_dbh;
142 $dbh->{syb_binary_images} = 1;
fd5a07e4 143
144 $self->_blob_log_on_update($args{log_on_update})
145 if exists $args{log_on_update};
146}
147
148sub _is_lob_type {
149 my $self = shift;
5703eb14 150 my $type = shift;
078a332f 151 $type && $type =~ /(?:text|image|lob|bytea|binary|memo)/i;
fd5a07e4 152}
153
b5453fbb 154## This will be useful if we ever implement BLOB filehandle inflation and will
155## need to use the API, but for now it isn't.
5703eb14 156#
b5453fbb 157#sub order_columns_for_select {
61cfaef7 158# my ($self, $source, $columns) = @_;
5703eb14 159#
160# my (@non_blobs, @blobs);
161#
61cfaef7 162# for my $col (@$columns) {
b5453fbb 163# if ($self->_is_lob_type($source->column_info($col)->{data_type})) {
5703eb14 164# push @blobs, $col;
165# } else {
166# push @non_blobs, $col;
167# }
168# }
169#
b5453fbb 170# croak "cannot select more than a one TEXT/IMAGE column at a time"
5703eb14 171# if @blobs > 1;
172#
b5453fbb 173# return (@non_blobs, @blobs);
5703eb14 174#}
175
176# override to handle TEXT/IMAGE
fd5a07e4 177sub insert {
7d17f469 178 my ($self, $source, $to_insert) = splice @_, 0, 3;
289877b0 179 my $dbh = $self->_dbh;
7d17f469 180
181 my $blob_cols = $self->_remove_blob_cols($source, $to_insert);
182
289877b0 183# check if we need to set IDENTITY_INSERT
184 my $identity_insert = 0;
185 my %col_info = map { ($_, $source->column_info($_)) } keys %$to_insert;
186 my $table = $source->from;
187
188 if (List::Util::first { $_->{is_auto_increment} } (values %col_info)) {
189 $identity_insert = 1;
190 $dbh->do("SET IDENTITY_INSERT $table ON");
191 }
192
7d17f469 193 my $updated_cols = $self->next::method($source, $to_insert, @_);
194
1816be4f 195 $dbh->do("SET IDENTITY_INSERT $table OFF") if $identity_insert;
289877b0 196
078a332f 197 $self->_insert_blobs($source, $blob_cols, $to_insert) if %$blob_cols;
7d17f469 198
199 return $updated_cols;
200}
201
078a332f 202sub update {
203 my ($self, $source) = splice @_, 0, 2;
204 my ($fields, $where) = @_;
205 my $wantarray = wantarray;
206
207 my $blob_cols = $self->_remove_blob_cols($source, $fields);
208
209 my @res;
210 if ($wantarray) {
211 @res = $self->next::method($source, @_);
212 } else {
213 $res[0] = $self->next::method($source, @_);
214 }
215
216 $self->_update_blobs($source, $blob_cols, $where) if %$blob_cols;
217
218 return $wantarray ? @res : $res[0];
219}
7d17f469 220
221sub _remove_blob_cols {
222 my ($self, $source, $fields) = @_;
fd5a07e4 223
224 my %blob_cols;
225
7d17f469 226 for my $col (keys %$fields) {
9b3dabe0 227 if ($self->_is_lob_type($source->column_info($col)->{data_type})) {
228 $blob_cols{$col} = delete $fields->{$col};
229 $fields->{$col} = \"''";
230 }
fd5a07e4 231 }
232
7d17f469 233 return \%blob_cols;
fd5a07e4 234}
235
236sub _update_blobs {
078a332f 237 my ($self, $source, $blob_cols, $where) = @_;
238
239 my (@primary_cols) = $source->primary_columns;
240
241 croak "Cannot update TEXT/IMAGE column(s) without a primary key"
242 unless @primary_cols;
243
244# check if we're updating a single row by PK
245 my $pk_cols_in_where = 0;
246 for my $col (@primary_cols) {
247 $pk_cols_in_where++ if defined $where->{$col};
248 }
249 my @rows;
250
251 if ($pk_cols_in_where == @primary_cols) {
252 my %row_to_update;
253 @row_to_update{@primary_cols} = @{$where}{@primary_cols};
254 @rows = \%row_to_update;
255 } else {
256 my $rs = $source->resultset->search(
257 $where,
258 {
259 result_class => 'DBIx::Class::ResultClass::HashRefInflator',
260 select => \@primary_cols
261 }
262 );
263 @rows = $rs->all; # statement must finish
264 }
265
266 for my $row (@rows) {
267 $self->_insert_blobs($source, $blob_cols, $row);
268 }
269}
270
271sub _insert_blobs {
272 my ($self, $source, $blob_cols, $row) = @_;
fd5a07e4 273 my $dbh = $self->dbh;
274
275 my $table = $source->from;
276
078a332f 277 my %row = %$row;
fd5a07e4 278 my (@primary_cols) = $source->primary_columns;
279
9b3dabe0 280 croak "Cannot update TEXT/IMAGE column(s) without a primary key"
fd5a07e4 281 unless @primary_cols;
282
078a332f 283 if ((grep { defined $row{$_} } @primary_cols) != @primary_cols) {
9b3dabe0 284 if (@primary_cols == 1) {
285 my $col = $primary_cols[0];
078a332f 286 $row{$col} = $self->last_insert_id($source, $col);
9b3dabe0 287 } else {
288 croak "Cannot update TEXT/IMAGE column(s) without primary key values";
289 }
290 }
fd5a07e4 291
292 for my $col (keys %$blob_cols) {
293 my $blob = $blob_cols->{$col};
9b3dabe0 294 my $sth;
fd5a07e4 295
9b3dabe0 296 if (not $self->isa('DBIx::Class::Storage::DBI::NoBindVars')) {
297 my $search_cond = join ',' => map "$_ = ?", @primary_cols;
298
299 $sth = $self->sth(
300 "select $col from $table where $search_cond"
301 );
078a332f 302 $sth->execute(map $row{$_}, @primary_cols);
9b3dabe0 303 } else {
078a332f 304 my $search_cond = join ',' => map "$_ = $row{$_}", @primary_cols;
9b3dabe0 305
306 $sth = $dbh->prepare(
307 "select $col from $table where $search_cond"
308 );
309 $sth->execute;
310 }
fd5a07e4 311
312 eval {
313 while ($sth->fetch) {
314 $sth->func('CS_GET', 1, 'ct_data_info') or die $sth->errstr;
315 }
316 $sth->func('ct_prepare_send') or die $sth->errstr;
317
318 my $log_on_update = $self->_blob_log_on_update;
319 $log_on_update = 1 if not defined $log_on_update;
320
321 $sth->func('CS_SET', 1, {
322 total_txtlen => length($blob),
323 log_on_update => $log_on_update
324 }, 'ct_data_info') or die $sth->errstr;
325
326 $sth->func($blob, length($blob), 'ct_send_data') or die $sth->errstr;
327
328 $sth->func('ct_finish_send') or die $sth->errstr;
329 };
330 my $exception = $@;
331 $sth->finish;
332 croak $exception if $exception;
333 }
63d46bb3 334}
335
9539eeb1 336=head2 connect_call_datetime_setup
337
338Used as:
339
340 on_connect_call => 'datetime_setup'
341
342In L<DBIx::Class::Storage::DBI/connect_info> to set:
343
3abafb11 344 $dbh->syb_date_fmt('ISO_strict'); # output fmt: 2004-08-21T14:36:48.080Z
345 $dbh->do('set dateformat mdy'); # input fmt: 08/13/1979 18:08:55.080
9539eeb1 346
347On connection for use with L<DBIx::Class::InflateColumn::DateTime>, using
3abafb11 348L<DateTime::Format::Sybase>, which you will need to install.
349
350This works for both C<DATETIME> and C<SMALLDATETIME> columns, although
351C<SMALLDATETIME> columns only have minute precision.
9539eeb1 352
353=cut
354
9041a97a 355{
356 my $old_dbd_warned = 0;
357
9539eeb1 358 sub connect_call_datetime_setup {
6b1f5ef7 359 my $self = shift;
6b1f5ef7 360 my $dbh = $self->_dbh;
361
362 if ($dbh->can('syb_date_fmt')) {
363 $dbh->syb_date_fmt('ISO_strict');
364 } elsif (not $old_dbd_warned) {
365 carp "Your DBD::Sybase is too old to support ".
366 "DBIx::Class::InflateColumn::DateTime, please upgrade!";
367 $old_dbd_warned = 1;
368 }
369
370 $dbh->do('set dateformat mdy');
c5ce7cd6 371
6b1f5ef7 372 1;
c5ce7cd6 373 }
6b1f5ef7 374}
375
6636ad53 376sub datetime_parser_type { "DateTime::Format::Sybase" }
377
6b1f5ef7 378sub _dbh_last_insert_id {
379 my ($self, $dbh, $source, $col) = @_;
c5ce7cd6 380
381 # sorry, there's no other way!
23419345 382 my $sth = $self->sth("select max($col) from ".$source->from);
383 my ($id) = $dbh->selectrow_array($sth);
384 $sth->finish;
385
386 return $id;
a964a928 387}
388
1816be4f 389# savepoint support using ASE syntax
390
391sub _svp_begin {
392 my ($self, $name) = @_;
393
394 $self->dbh->do("SAVE TRANSACTION $name");
395}
396
397# A new SAVE TRANSACTION with the same name releases the previous one.
398sub _svp_release { 1 }
399
400sub _svp_rollback {
401 my ($self, $name) = @_;
402
403 $self->dbh->do("ROLLBACK TRANSACTION $name");
404}
405
3885cff6 4061;
407
41c93b1b 408=head1 MAXIMUM CONNECTIONS
409
410L<DBD::Sybase> makes separate connections to the server for active statements in
411the background. By default the number of such connections is limited to 25, on
412both the client side and the server side.
413
414This is a bit too low, so on connection the clientside setting is set to C<256>
415(see L<DBD::Sybase/maxConnect>.) You can override it to whatever setting you
416like in the DSN.
417
418See
419L<http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.help.ase_15.0.sag1/html/sag1/sag1272.htm>
420for information on changing the setting on the server side.
421
c5ce7cd6 422=head1 DATES
423
3abafb11 424See L</connect_call_datetime_setup> to setup date formats
425for L<DBIx::Class::InflateColumn::DateTime>.
c5ce7cd6 426
6636ad53 427=head1 IMAGE AND TEXT COLUMNS
63d46bb3 428
8c4b6c50 429L<DBD::Sybase> compiled with FreeTDS will B<NOT> work with C<TEXT/IMAGE>
430columns.
5703eb14 431
63d46bb3 432See L</connect_call_blob_setup> for a L<DBIx::Class::Storage::DBI/connect_info>
433setting you need to work with C<IMAGE> columns.
434
3885cff6 435=head1 AUTHORS
436
7e8cecc1 437See L<DBIx::Class/CONTRIBUTORS>.
c5ce7cd6 438
3885cff6 439=head1 LICENSE
440
441You may distribute this code under the same terms as Perl itself.
442
443=cut
c5ce7cd6 444# vim:sts=2 sw=2: