Simplify oracle retrial logic
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBI / Oracle / Generic.pm
CommitLineData
18360aed 1package DBIx::Class::Storage::DBI::Oracle::Generic;
2
3use strict;
4use warnings;
b7b18f32 5use Scope::Guard ();
6use Context::Preserve ();
ed7ab0f4 7use Try::Tiny;
18360aed 8
7137528d 9=head1 NAME
10
7a84c41b 11DBIx::Class::Storage::DBI::Oracle::Generic - Oracle Support for DBIx::Class
7137528d 12
13=head1 SYNOPSIS
14
d88ecca6 15 # In your result (table) classes
16 use base 'DBIx::Class::Core';
2e46b6eb 17 __PACKAGE__->add_columns({ id => { sequence => 'mysequence', auto_nextval => 1 } });
7137528d 18 __PACKAGE__->set_primary_key('id');
19 __PACKAGE__->sequence('mysequence');
20
21=head1 DESCRIPTION
22
6c0230de 23This class implements base Oracle support. The subclass
24L<DBIx::Class::Storage::DBI::Oracle::WhereJoins> is for C<(+)> joins in Oracle
25versions before 9.
7137528d 26
27=head1 METHODS
28
29=cut
30
db56cf3d 31use base qw/DBIx::Class::Storage::DBI/;
2ad62d97 32use mro 'c3';
18360aed 33
dd2600c6 34sub deployment_statements {
35 my $self = shift;;
36 my ($schema, $type, $version, $dir, $sqltargs, @rest) = @_;
37
38 $sqltargs ||= {};
032b2366 39 my $quote_char = $self->schema->storage->sql_maker->quote_char;
40 $sqltargs->{quote_table_names} = $quote_char ? 1 : 0;
41 $sqltargs->{quote_field_names} = $quote_char ? 1 : 0;
dd2600c6 42
9780718f 43 my $oracle_version = try { $self->_get_dbh->get_info(18) };
a4433d8e 44
45 $sqltargs->{producer_args}{oracle_version} = $oracle_version;
46
38aead8e 47 $self->next::method($schema, $type, $version, $dir, $sqltargs, @rest);
dd2600c6 48}
49
18360aed 50sub _dbh_last_insert_id {
2e46b6eb 51 my ($self, $dbh, $source, @columns) = @_;
52 my @ids = ();
53 foreach my $col (@columns) {
54 my $seq = ($source->column_info($col)->{sequence} ||= $self->get_autoinc_seq($source,$col));
55 my $id = $self->_sequence_fetch( 'currval', $seq );
56 push @ids, $id;
57 }
58 return @ids;
18360aed 59}
60
61sub _dbh_get_autoinc_seq {
62 my ($self, $dbh, $source, $col) = @_;
63
032b2366 64 my $sql_maker = $self->sql_maker;
cb464582 65
e6dd7b42 66 my $source_name;
032b2366 67 if ( ref $source->name eq 'SCALAR' ) {
68 $source_name = ${$source->name};
e6dd7b42 69 }
70 else {
032b2366 71 $source_name = $source->name;
e6dd7b42 72 }
3963bf4c 73 $source_name = uc($source_name) unless $sql_maker->quote_char;
38aead8e 74
032b2366 75 # trigger_body is a LONG
76 local $dbh->{LongReadLen} = 64 * 1024 if ($dbh->{LongReadLen} < 64 * 1024);
77
78 # disable default bindtype
79 local $sql_maker->{bindtype} = 'normal';
80
81 # look up the correct sequence automatically
82 my ( $schema, $table ) = $source_name =~ /(\w+)\.(\w+)/;
83 my ($sql, @bind) = $sql_maker->select (
84 'ALL_TRIGGERS',
85 ['trigger_body'],
86 {
87 $schema ? (owner => $schema) : (),
88 table_name => $table || $source_name,
cc42fa9a 89 triggering_event => { -like => '%INSERT%' },
032b2366 90 status => 'ENABLED',
91 },
92 );
93 my $sth = $dbh->prepare($sql);
94 $sth->execute (@bind);
e6dd7b42 95
18360aed 96 while (my ($insert_trigger) = $sth->fetchrow_array) {
852a66f6 97 return $1 if $insert_trigger =~ m!("?\w+"?)\.nextval!i; # col name goes here???
18360aed 98 }
4f2b3017 99 $self->throw_exception("Unable to find a sequence INSERT trigger on table '$source_name'.");
18360aed 100}
101
2e46b6eb 102sub _sequence_fetch {
103 my ( $self, $type, $seq ) = @_;
9ae966b9 104 my ($id) = $self->_get_dbh->selectrow_array("SELECT ${seq}.${type} FROM DUAL");
2e46b6eb 105 return $id;
106}
107
6dc4be0f 108sub _ping {
c2481821 109 my $self = shift;
7ba7a57d 110
6dc4be0f 111 my $dbh = $self->_dbh or return 0;
7ba7a57d 112
6dc4be0f 113 local $dbh->{RaiseError} = 1;
ecdf1ac8 114 local $dbh->{PrintError} = 0;
c2d7baef 115
52b420dd 116 return try {
ecdf1ac8 117 $dbh->do('select 1 from dual');
52b420dd 118 1;
ed7ab0f4 119 } catch {
52b420dd 120 0;
6dc4be0f 121 };
c2481821 122}
123
d789fa99 124sub _dbh_execute {
125 my $self = shift;
126 my ($dbh, $op, $extra_bind, $ident, $bind_attributes, @args) = @_;
127
66efa982 128 my $retried;
4f661051 129 my $next = $self->next::can;
66efa982 130 while (1) {
52b420dd 131 try {
66efa982 132 return $self->$next($dbh, $op, $extra_bind, $ident, $bind_attributes, @args);
52b420dd 133 }
134 catch {
66efa982 135 if (!$retried++ and $_ =~ /ORA-01003/) {
0f0abc97 136 # ORA-01003: no statement parsed (someone changed the table somehow,
137 # invalidating your cursor.)
138 my ($sql, $bind) = $self->_prep_for_execute($op, $extra_bind, $ident, \@args);
139 delete $dbh->{CachedKids}{$sql};
d789fa99 140 }
52b420dd 141 else {
142 $self->throw_exception($_);
143 }
144 };
66efa982 145 }
d789fa99 146}
147
7137528d 148=head2 get_autoinc_seq
149
150Returns the sequence name for an autoincrement column
151
152=cut
153
18360aed 154sub get_autoinc_seq {
155 my ($self, $source, $col) = @_;
d4daee7b 156
373940e1 157 $self->dbh_do('_dbh_get_autoinc_seq', $source, $col);
18360aed 158}
159
8f7e044c 160=head2 datetime_parser_type
161
162This sets the proper DateTime::Format module for use with
163L<DBIx::Class::InflateColumn::DateTime>.
164
165=cut
166
167sub datetime_parser_type { return "DateTime::Format::Oracle"; }
168
9900b569 169=head2 connect_call_datetime_setup
d2a3958e 170
171Used as:
172
9900b569 173 on_connect_call => 'datetime_setup'
d2a3958e 174
8384a713 175In L<connect_info|DBIx::Class::Storage::DBI/connect_info> to set the session nls
176date, and timestamp values for use with L<DBIx::Class::InflateColumn::DateTime>
177and the necessary environment variables for L<DateTime::Format::Oracle>, which
178is used by it.
d2a3958e 179
82f6f45f 180Maximum allowable precision is used, unless the environment variables have
181already been set.
d2a3958e 182
9900b569 183These are the defaults used:
184
185 $ENV{NLS_DATE_FORMAT} ||= 'YYYY-MM-DD HH24:MI:SS';
186 $ENV{NLS_TIMESTAMP_FORMAT} ||= 'YYYY-MM-DD HH24:MI:SS.FF';
187 $ENV{NLS_TIMESTAMP_TZ_FORMAT} ||= 'YYYY-MM-DD HH24:MI:SS.FF TZHTZM';
188
d9e53b85 189To get more than second precision with L<DBIx::Class::InflateColumn::DateTime>
190for your timestamps, use something like this:
191
192 use Time::HiRes 'time';
193 my $ts = DateTime->from_epoch(epoch => time);
194
d2a3958e 195=cut
196
9900b569 197sub connect_call_datetime_setup {
d2a3958e 198 my $self = shift;
d2a3958e 199
200 my $date_format = $ENV{NLS_DATE_FORMAT} ||= 'YYYY-MM-DD HH24:MI:SS';
201 my $timestamp_format = $ENV{NLS_TIMESTAMP_FORMAT} ||=
202 'YYYY-MM-DD HH24:MI:SS.FF';
203 my $timestamp_tz_format = $ENV{NLS_TIMESTAMP_TZ_FORMAT} ||=
204 'YYYY-MM-DD HH24:MI:SS.FF TZHTZM';
205
7a84c41b 206 $self->_do_query(
d7a58a29 207 "alter session set nls_date_format = '$date_format'"
208 );
7a84c41b 209 $self->_do_query(
d7a58a29 210 "alter session set nls_timestamp_format = '$timestamp_format'"
211 );
212 $self->_do_query(
213 "alter session set nls_timestamp_tz_format='$timestamp_tz_format'"
214 );
d2a3958e 215}
216
5db2758d 217=head2 source_bind_attributes
218
219Handle LOB types in Oracle. Under a certain size (4k?), you can get away
220with the driver assuming your input is the deprecated LONG type if you
221encode it as a hex string. That ain't gonna fly at larger values, where
222you'll discover you have to do what this does.
223
224This method had to be overridden because we need to set ora_field to the
225actual column, and that isn't passed to the call (provided by Storage) to
226bind_attribute_by_data_type.
227
228According to L<DBD::Oracle>, the ora_field isn't always necessary, but
229adding it doesn't hurt, and will save your bacon if you're modifying a
230table with more than one LOB column.
231
232=cut
233
e6dd7b42 234sub source_bind_attributes
5db2758d 235{
d7a58a29 236 require DBD::Oracle;
237 my $self = shift;
238 my($source) = @_;
5db2758d 239
d7a58a29 240 my %bind_attributes;
5db2758d 241
d7a58a29 242 foreach my $column ($source->columns) {
243 my $data_type = $source->column_info($column)->{data_type} || '';
244 next unless $data_type;
5db2758d 245
d7a58a29 246 my %column_bind_attrs = $self->bind_attribute_by_data_type($data_type);
5db2758d 247
d7a58a29 248 if ($data_type =~ /^[BC]LOB$/i) {
931e5d43 249 if ($DBD::Oracle::VERSION eq '1.23') {
250 $self->throw_exception(
251"BLOB/CLOB support in DBD::Oracle == 1.23 is broken, use an earlier or later ".
555cc3f4 252"version.\n\nSee: https://rt.cpan.org/Public/Bug/Display.html?id=46016\n"
931e5d43 253 );
254 }
255
d7a58a29 256 $column_bind_attrs{'ora_type'} = uc($data_type) eq 'CLOB'
257 ? DBD::Oracle::ORA_CLOB()
258 : DBD::Oracle::ORA_BLOB()
259 ;
260 $column_bind_attrs{'ora_field'} = $column;
261 }
5db2758d 262
d7a58a29 263 $bind_attributes{$column} = \%column_bind_attrs;
264 }
5db2758d 265
d7a58a29 266 return \%bind_attributes;
5db2758d 267}
268
1816be4f 269sub _svp_begin {
d7a58a29 270 my ($self, $name) = @_;
271 $self->_get_dbh->do("SAVEPOINT $name");
1816be4f 272}
273
281719d2 274# Oracle automatically releases a savepoint when you start another one with the
275# same name.
276sub _svp_release { 1 }
277
278sub _svp_rollback {
d7a58a29 279 my ($self, $name) = @_;
280 $self->_get_dbh->do("ROLLBACK TO SAVEPOINT $name")
281719d2 281}
282
6c0230de 283=head2 relname_to_table_alias
284
285L<DBIx::Class> uses L<DBIx::Class::Relationship> names as table aliases in
286queries.
287
288Unfortunately, Oracle doesn't support identifiers over 30 chars in length, so
af0edca1 289the L<DBIx::Class::Relationship> name is shortened and appended with half of an
290MD5 hash.
6c0230de 291
292See L<DBIx::Class::Storage/"relname_to_table_alias">.
293
294=cut
295
296sub relname_to_table_alias {
297 my $self = shift;
298 my ($relname, $join_count) = @_;
299
300 my $alias = $self->next::method(@_);
301
302 return $alias if length($alias) <= 30;
303
af0edca1 304 # get a base64 md5 of the alias with join_count
305 require Digest::MD5;
306 my $ctx = Digest::MD5->new;
307 $ctx->add($alias);
308 my $md5 = $ctx->b64digest;
6c0230de 309
f098ade6 310 # remove alignment mark just in case
311 $md5 =~ s/=*\z//;
312
af0edca1 313 # truncate and prepend to truncated relname without vowels
314 (my $devoweled = $relname) =~ s/[aeiou]//g;
909668fe 315 my $shortened = substr($devoweled, 0, 18);
6c0230de 316
909668fe 317 my $new_alias =
318 $shortened . '_' . substr($md5, 0, 30 - length($shortened) - 1);
319
320 return $new_alias;
6c0230de 321}
322
6c0bb6a7 323=head2 with_deferred_fk_checks
324
325Runs a coderef between:
326
327 alter session set constraints = deferred
328 ...
329 alter session set constraints = immediate
330
b7b18f32 331to defer foreign key checks.
332
333Constraints must be declared C<DEFERRABLE> for this to work.
6c0bb6a7 334
335=cut
336
337sub with_deferred_fk_checks {
338 my ($self, $sub) = @_;
b7b18f32 339
340 my $txn_scope_guard = $self->txn_scope_guard;
341
6c0bb6a7 342 $self->_do_query('alter session set constraints = deferred');
b7b18f32 343
344 my $sg = Scope::Guard->new(sub {
345 $self->_do_query('alter session set constraints = immediate');
346 });
347
348 return Context::Preserve::preserve_context(sub { $sub->() },
349 after => sub { $txn_scope_guard->commit });
6c0bb6a7 350}
351
7a84c41b 352=head1 AUTHOR
18360aed 353
7a84c41b 354See L<DBIx::Class/CONTRIBUTORS>.
18360aed 355
356=head1 LICENSE
357
358You may distribute this code under the same terms as Perl itself.
359
360=cut
7137528d 361
3621;