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