use _resolve_column_info in NoBindVars
[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
285baccb 14__PACKAGE__->mk_group_accessors('simple' =>
15 qw/_identity _blob_log_on_update/
16);
17
98259fe4 18=head1 NAME
19
928f0af8 20DBIx::Class::Storage::DBI::Sybase - Sybase support for DBIx::Class
98259fe4 21
22=head1 SYNOPSIS
23
24This subclass supports L<DBD::Sybase> for real Sybase databases. If you are
25using an MSSQL database via L<DBD::Sybase>, your storage will be reblessed to
26L<DBIx::Class::Storage::DBI::Sybase::Microsoft_SQL_Server>.
27
28=head1 DESCRIPTION
29
30If your version of Sybase does not support placeholders, then your storage
31will be reblessed to L<DBIx::Class::Storage::DBI::Sybase::NoBindVars>. You can
32also enable that driver explicitly, see the documentation for more details.
33
34With this driver there is unfortunately no way to get the C<last_insert_id>
35without doing a C<select max(col)>.
36
37But your queries will be cached.
38
8c4b6c50 39You need a version of L<DBD::Sybase> compiled with the Sybase OpenClient
40libraries, B<NOT> FreeTDS, for placeholder support. Otherwise your storage will
41be automatically reblessed into C<::NoBindVars>.
5703eb14 42
61cfaef7 43A recommended L<DBIx::Class::Storage::DBI/connect_info> setting:
98259fe4 44
61cfaef7 45 on_connect_call => [['datetime_setup'], ['blob_setup', log_on_update => 0]]
98259fe4 46
47=head1 METHODS
48
49=cut
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
285baccb 176# the select-piggybacking-on-insert trick stolen from odbc/mssql
177sub _prep_for_execute {
178 my $self = shift;
179 my ($op, $extra_bind, $ident, $args) = @_;
180
181 my ($sql, $bind) = $self->next::method (@_);
182
183 if ($op eq 'insert') {
285baccb 184 my $table = $ident->from;
185
186 my $bind_info = $self->_resolve_column_info($ident, [map $_->[0], @{$bind}]);
ec15b3fe 187 my $identity_col =
285baccb 188List::Util::first { $bind_info->{$_}{is_auto_increment} } (keys %$bind_info);
189
190 if ($identity_col) {
ec15b3fe 191 $sql =
192"SET IDENTITY_INSERT $table ON\n" .
193"$sql\n" .
194"SET IDENTITY_INSERT $table OFF"
285baccb 195 } else {
196 $identity_col = List::Util::first {
197 $ident->column_info($_)->{is_auto_increment}
198 } $ident->columns;
199 }
200
201 if ($identity_col) {
202# Sybase has nested transactions, only the outermost is actually committed
203 $sql =
204 "BEGIN TRANSACTION\n" .
285baccb 205 "$sql\n" .
285baccb 206 $self->_fetch_identity_sql($ident, $identity_col) . "\n" .
207 "COMMIT";
208 }
209 }
210
211 return ($sql, $bind);
212}
213
214sub _fetch_identity_sql {
215 my ($self, $source, $col) = @_;
216
217 return "SELECT MAX($col) FROM ".$source->from;
218}
219
220sub _execute {
221 my $self = shift;
222 my ($op) = @_;
223
224 my ($rv, $sth, @bind) = $self->dbh_do($self->can('_dbh_execute'), @_);
225
226 if ($op eq 'insert') {
227 $self->_identity($sth->fetchrow_array);
228 $sth->finish;
229 }
230
231 return wantarray ? ($rv, $sth, @bind) : $rv;
232}
233
234sub last_insert_id { shift->_identity }
235
5703eb14 236# override to handle TEXT/IMAGE
fd5a07e4 237sub insert {
7d17f469 238 my ($self, $source, $to_insert) = splice @_, 0, 3;
289877b0 239 my $dbh = $self->_dbh;
7d17f469 240
241 my $blob_cols = $self->_remove_blob_cols($source, $to_insert);
242
243 my $updated_cols = $self->next::method($source, $to_insert, @_);
244
078a332f 245 $self->_insert_blobs($source, $blob_cols, $to_insert) if %$blob_cols;
7d17f469 246
247 return $updated_cols;
248}
249
078a332f 250sub update {
251 my ($self, $source) = splice @_, 0, 2;
252 my ($fields, $where) = @_;
253 my $wantarray = wantarray;
254
255 my $blob_cols = $self->_remove_blob_cols($source, $fields);
256
257 my @res;
258 if ($wantarray) {
259 @res = $self->next::method($source, @_);
260 } else {
261 $res[0] = $self->next::method($source, @_);
262 }
263
264 $self->_update_blobs($source, $blob_cols, $where) if %$blob_cols;
265
266 return $wantarray ? @res : $res[0];
267}
7d17f469 268
269sub _remove_blob_cols {
270 my ($self, $source, $fields) = @_;
fd5a07e4 271
272 my %blob_cols;
273
7d17f469 274 for my $col (keys %$fields) {
9b3dabe0 275 if ($self->_is_lob_type($source->column_info($col)->{data_type})) {
276 $blob_cols{$col} = delete $fields->{$col};
277 $fields->{$col} = \"''";
278 }
fd5a07e4 279 }
280
7d17f469 281 return \%blob_cols;
fd5a07e4 282}
283
284sub _update_blobs {
078a332f 285 my ($self, $source, $blob_cols, $where) = @_;
286
287 my (@primary_cols) = $source->primary_columns;
288
289 croak "Cannot update TEXT/IMAGE column(s) without a primary key"
290 unless @primary_cols;
291
292# check if we're updating a single row by PK
293 my $pk_cols_in_where = 0;
294 for my $col (@primary_cols) {
295 $pk_cols_in_where++ if defined $where->{$col};
296 }
297 my @rows;
298
299 if ($pk_cols_in_where == @primary_cols) {
300 my %row_to_update;
301 @row_to_update{@primary_cols} = @{$where}{@primary_cols};
302 @rows = \%row_to_update;
303 } else {
304 my $rs = $source->resultset->search(
305 $where,
306 {
307 result_class => 'DBIx::Class::ResultClass::HashRefInflator',
308 select => \@primary_cols
309 }
310 );
311 @rows = $rs->all; # statement must finish
312 }
313
314 for my $row (@rows) {
315 $self->_insert_blobs($source, $blob_cols, $row);
316 }
317}
318
319sub _insert_blobs {
320 my ($self, $source, $blob_cols, $row) = @_;
fd5a07e4 321 my $dbh = $self->dbh;
322
323 my $table = $source->from;
324
078a332f 325 my %row = %$row;
fd5a07e4 326 my (@primary_cols) = $source->primary_columns;
327
9b3dabe0 328 croak "Cannot update TEXT/IMAGE column(s) without a primary key"
fd5a07e4 329 unless @primary_cols;
330
078a332f 331 if ((grep { defined $row{$_} } @primary_cols) != @primary_cols) {
9b3dabe0 332 if (@primary_cols == 1) {
333 my $col = $primary_cols[0];
078a332f 334 $row{$col} = $self->last_insert_id($source, $col);
9b3dabe0 335 } else {
336 croak "Cannot update TEXT/IMAGE column(s) without primary key values";
337 }
338 }
fd5a07e4 339
340 for my $col (keys %$blob_cols) {
341 my $blob = $blob_cols->{$col};
9b3dabe0 342 my $sth;
fd5a07e4 343
9b3dabe0 344 if (not $self->isa('DBIx::Class::Storage::DBI::NoBindVars')) {
345 my $search_cond = join ',' => map "$_ = ?", @primary_cols;
346
347 $sth = $self->sth(
348 "select $col from $table where $search_cond"
349 );
078a332f 350 $sth->execute(map $row{$_}, @primary_cols);
9b3dabe0 351 } else {
078a332f 352 my $search_cond = join ',' => map "$_ = $row{$_}", @primary_cols;
9b3dabe0 353
354 $sth = $dbh->prepare(
355 "select $col from $table where $search_cond"
356 );
357 $sth->execute;
358 }
fd5a07e4 359
360 eval {
361 while ($sth->fetch) {
362 $sth->func('CS_GET', 1, 'ct_data_info') or die $sth->errstr;
363 }
364 $sth->func('ct_prepare_send') or die $sth->errstr;
365
366 my $log_on_update = $self->_blob_log_on_update;
367 $log_on_update = 1 if not defined $log_on_update;
368
369 $sth->func('CS_SET', 1, {
370 total_txtlen => length($blob),
371 log_on_update => $log_on_update
372 }, 'ct_data_info') or die $sth->errstr;
373
374 $sth->func($blob, length($blob), 'ct_send_data') or die $sth->errstr;
375
376 $sth->func('ct_finish_send') or die $sth->errstr;
377 };
378 my $exception = $@;
379 $sth->finish;
380 croak $exception if $exception;
381 }
63d46bb3 382}
383
9539eeb1 384=head2 connect_call_datetime_setup
385
386Used as:
387
388 on_connect_call => 'datetime_setup'
389
390In L<DBIx::Class::Storage::DBI/connect_info> to set:
391
3abafb11 392 $dbh->syb_date_fmt('ISO_strict'); # output fmt: 2004-08-21T14:36:48.080Z
393 $dbh->do('set dateformat mdy'); # input fmt: 08/13/1979 18:08:55.080
9539eeb1 394
395On connection for use with L<DBIx::Class::InflateColumn::DateTime>, using
3abafb11 396L<DateTime::Format::Sybase>, which you will need to install.
397
398This works for both C<DATETIME> and C<SMALLDATETIME> columns, although
399C<SMALLDATETIME> columns only have minute precision.
9539eeb1 400
401=cut
402
9041a97a 403{
404 my $old_dbd_warned = 0;
405
9539eeb1 406 sub connect_call_datetime_setup {
6b1f5ef7 407 my $self = shift;
6b1f5ef7 408 my $dbh = $self->_dbh;
409
410 if ($dbh->can('syb_date_fmt')) {
411 $dbh->syb_date_fmt('ISO_strict');
412 } elsif (not $old_dbd_warned) {
413 carp "Your DBD::Sybase is too old to support ".
414 "DBIx::Class::InflateColumn::DateTime, please upgrade!";
415 $old_dbd_warned = 1;
416 }
417
418 $dbh->do('set dateformat mdy');
c5ce7cd6 419
6b1f5ef7 420 1;
c5ce7cd6 421 }
6b1f5ef7 422}
423
6636ad53 424sub datetime_parser_type { "DateTime::Format::Sybase" }
425
1816be4f 426# savepoint support using ASE syntax
427
428sub _svp_begin {
429 my ($self, $name) = @_;
430
431 $self->dbh->do("SAVE TRANSACTION $name");
432}
433
434# A new SAVE TRANSACTION with the same name releases the previous one.
435sub _svp_release { 1 }
436
437sub _svp_rollback {
438 my ($self, $name) = @_;
439
440 $self->dbh->do("ROLLBACK TRANSACTION $name");
441}
442
3885cff6 4431;
444
41c93b1b 445=head1 MAXIMUM CONNECTIONS
446
447L<DBD::Sybase> makes separate connections to the server for active statements in
448the background. By default the number of such connections is limited to 25, on
449both the client side and the server side.
450
451This is a bit too low, so on connection the clientside setting is set to C<256>
452(see L<DBD::Sybase/maxConnect>.) You can override it to whatever setting you
453like in the DSN.
454
455See
456L<http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.help.ase_15.0.sag1/html/sag1/sag1272.htm>
457for information on changing the setting on the server side.
458
c5ce7cd6 459=head1 DATES
460
3abafb11 461See L</connect_call_datetime_setup> to setup date formats
462for L<DBIx::Class::InflateColumn::DateTime>.
c5ce7cd6 463
6636ad53 464=head1 IMAGE AND TEXT COLUMNS
63d46bb3 465
8c4b6c50 466L<DBD::Sybase> compiled with FreeTDS will B<NOT> work with C<TEXT/IMAGE>
467columns.
5703eb14 468
63d46bb3 469See L</connect_call_blob_setup> for a L<DBIx::Class::Storage::DBI/connect_info>
470setting you need to work with C<IMAGE> columns.
471
3885cff6 472=head1 AUTHORS
473
7e8cecc1 474See L<DBIx::Class/CONTRIBUTORS>.
c5ce7cd6 475
3885cff6 476=head1 LICENSE
477
478You may distribute this code under the same terms as Perl itself.
479
480=cut
c5ce7cd6 481# vim:sts=2 sw=2: