All expected evals converted to try, except where no test is done,
[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
38aead8e 43 my $oracle_version = eval { $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,
89 triggering_event => 'INSERT',
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
ed7ab0f4 116 my $rc = 1;
117 try {
ecdf1ac8 118 $dbh->do('select 1 from dual');
ed7ab0f4 119 } catch {
120 $rc = 0;
6dc4be0f 121 };
7ba7a57d 122
ed7ab0f4 123 return $rc;
c2481821 124}
125
d789fa99 126sub _dbh_execute {
127 my $self = shift;
128 my ($dbh, $op, $extra_bind, $ident, $bind_attributes, @args) = @_;
129
130 my $wantarray = wantarray;
d789fa99 131
c2d7baef 132 my (@res, $exception, $retried);
133
0f0abc97 134 RETRY: {
135 do {
ed7ab0f4 136 my $exception;
137 try {
0f0abc97 138 if ($wantarray) {
c3515436 139 @res = $self->next::method(@_);
0f0abc97 140 } else {
c3515436 141 $res[0] = $self->next::method(@_);
0f0abc97 142 }
ed7ab0f4 143 } catch {
144 $exception = shift;
0f0abc97 145 };
0f0abc97 146 if ($exception =~ /ORA-01003/) {
147 # ORA-01003: no statement parsed (someone changed the table somehow,
148 # invalidating your cursor.)
149 my ($sql, $bind) = $self->_prep_for_execute($op, $extra_bind, $ident, \@args);
150 delete $dbh->{CachedKids}{$sql};
d789fa99 151 } else {
0f0abc97 152 last RETRY;
d789fa99 153 }
0f0abc97 154 } while (not $retried++);
155 }
d789fa99 156
157 $self->throw_exception($exception) if $exception;
158
e1958268 159 $wantarray ? @res : $res[0]
d789fa99 160}
161
7137528d 162=head2 get_autoinc_seq
163
164Returns the sequence name for an autoincrement column
165
166=cut
167
18360aed 168sub get_autoinc_seq {
169 my ($self, $source, $col) = @_;
d4daee7b 170
373940e1 171 $self->dbh_do('_dbh_get_autoinc_seq', $source, $col);
18360aed 172}
173
7137528d 174=head2 columns_info_for
175
176This wraps the superclass version of this method to force table
177names to uppercase
178
179=cut
180
18360aed 181sub columns_info_for {
182 my ($self, $table) = @_;
183
dd2600c6 184 $self->next::method($table);
18360aed 185}
186
8f7e044c 187=head2 datetime_parser_type
188
189This sets the proper DateTime::Format module for use with
190L<DBIx::Class::InflateColumn::DateTime>.
191
192=cut
193
194sub datetime_parser_type { return "DateTime::Format::Oracle"; }
195
9900b569 196=head2 connect_call_datetime_setup
d2a3958e 197
198Used as:
199
9900b569 200 on_connect_call => 'datetime_setup'
d2a3958e 201
8384a713 202In L<connect_info|DBIx::Class::Storage::DBI/connect_info> to set the session nls
203date, and timestamp values for use with L<DBIx::Class::InflateColumn::DateTime>
204and the necessary environment variables for L<DateTime::Format::Oracle>, which
205is used by it.
d2a3958e 206
82f6f45f 207Maximum allowable precision is used, unless the environment variables have
208already been set.
d2a3958e 209
9900b569 210These are the defaults used:
211
212 $ENV{NLS_DATE_FORMAT} ||= 'YYYY-MM-DD HH24:MI:SS';
213 $ENV{NLS_TIMESTAMP_FORMAT} ||= 'YYYY-MM-DD HH24:MI:SS.FF';
214 $ENV{NLS_TIMESTAMP_TZ_FORMAT} ||= 'YYYY-MM-DD HH24:MI:SS.FF TZHTZM';
215
d9e53b85 216To get more than second precision with L<DBIx::Class::InflateColumn::DateTime>
217for your timestamps, use something like this:
218
219 use Time::HiRes 'time';
220 my $ts = DateTime->from_epoch(epoch => time);
221
d2a3958e 222=cut
223
9900b569 224sub connect_call_datetime_setup {
d2a3958e 225 my $self = shift;
d2a3958e 226
227 my $date_format = $ENV{NLS_DATE_FORMAT} ||= 'YYYY-MM-DD HH24:MI:SS';
228 my $timestamp_format = $ENV{NLS_TIMESTAMP_FORMAT} ||=
229 'YYYY-MM-DD HH24:MI:SS.FF';
230 my $timestamp_tz_format = $ENV{NLS_TIMESTAMP_TZ_FORMAT} ||=
231 'YYYY-MM-DD HH24:MI:SS.FF TZHTZM';
232
7a84c41b 233 $self->_do_query(
d7a58a29 234 "alter session set nls_date_format = '$date_format'"
235 );
7a84c41b 236 $self->_do_query(
d7a58a29 237 "alter session set nls_timestamp_format = '$timestamp_format'"
238 );
239 $self->_do_query(
240 "alter session set nls_timestamp_tz_format='$timestamp_tz_format'"
241 );
d2a3958e 242}
243
5db2758d 244=head2 source_bind_attributes
245
246Handle LOB types in Oracle. Under a certain size (4k?), you can get away
247with the driver assuming your input is the deprecated LONG type if you
248encode it as a hex string. That ain't gonna fly at larger values, where
249you'll discover you have to do what this does.
250
251This method had to be overridden because we need to set ora_field to the
252actual column, and that isn't passed to the call (provided by Storage) to
253bind_attribute_by_data_type.
254
255According to L<DBD::Oracle>, the ora_field isn't always necessary, but
256adding it doesn't hurt, and will save your bacon if you're modifying a
257table with more than one LOB column.
258
259=cut
260
e6dd7b42 261sub source_bind_attributes
5db2758d 262{
d7a58a29 263 require DBD::Oracle;
264 my $self = shift;
265 my($source) = @_;
5db2758d 266
d7a58a29 267 my %bind_attributes;
5db2758d 268
d7a58a29 269 foreach my $column ($source->columns) {
270 my $data_type = $source->column_info($column)->{data_type} || '';
271 next unless $data_type;
5db2758d 272
d7a58a29 273 my %column_bind_attrs = $self->bind_attribute_by_data_type($data_type);
5db2758d 274
d7a58a29 275 if ($data_type =~ /^[BC]LOB$/i) {
931e5d43 276 if ($DBD::Oracle::VERSION eq '1.23') {
277 $self->throw_exception(
278"BLOB/CLOB support in DBD::Oracle == 1.23 is broken, use an earlier or later ".
555cc3f4 279"version.\n\nSee: https://rt.cpan.org/Public/Bug/Display.html?id=46016\n"
931e5d43 280 );
281 }
282
d7a58a29 283 $column_bind_attrs{'ora_type'} = uc($data_type) eq 'CLOB'
284 ? DBD::Oracle::ORA_CLOB()
285 : DBD::Oracle::ORA_BLOB()
286 ;
287 $column_bind_attrs{'ora_field'} = $column;
288 }
5db2758d 289
d7a58a29 290 $bind_attributes{$column} = \%column_bind_attrs;
291 }
5db2758d 292
d7a58a29 293 return \%bind_attributes;
5db2758d 294}
295
1816be4f 296sub _svp_begin {
d7a58a29 297 my ($self, $name) = @_;
298 $self->_get_dbh->do("SAVEPOINT $name");
1816be4f 299}
300
281719d2 301# Oracle automatically releases a savepoint when you start another one with the
302# same name.
303sub _svp_release { 1 }
304
305sub _svp_rollback {
d7a58a29 306 my ($self, $name) = @_;
307 $self->_get_dbh->do("ROLLBACK TO SAVEPOINT $name")
281719d2 308}
309
6c0230de 310=head2 relname_to_table_alias
311
312L<DBIx::Class> uses L<DBIx::Class::Relationship> names as table aliases in
313queries.
314
315Unfortunately, Oracle doesn't support identifiers over 30 chars in length, so
af0edca1 316the L<DBIx::Class::Relationship> name is shortened and appended with half of an
317MD5 hash.
6c0230de 318
319See L<DBIx::Class::Storage/"relname_to_table_alias">.
320
321=cut
322
323sub relname_to_table_alias {
324 my $self = shift;
325 my ($relname, $join_count) = @_;
326
327 my $alias = $self->next::method(@_);
328
329 return $alias if length($alias) <= 30;
330
af0edca1 331 # get a base64 md5 of the alias with join_count
332 require Digest::MD5;
333 my $ctx = Digest::MD5->new;
334 $ctx->add($alias);
335 my $md5 = $ctx->b64digest;
6c0230de 336
f098ade6 337 # remove alignment mark just in case
338 $md5 =~ s/=*\z//;
339
af0edca1 340 # truncate and prepend to truncated relname without vowels
341 (my $devoweled = $relname) =~ s/[aeiou]//g;
909668fe 342 my $shortened = substr($devoweled, 0, 18);
6c0230de 343
909668fe 344 my $new_alias =
345 $shortened . '_' . substr($md5, 0, 30 - length($shortened) - 1);
346
347 return $new_alias;
6c0230de 348}
349
6c0bb6a7 350=head2 with_deferred_fk_checks
351
352Runs a coderef between:
353
354 alter session set constraints = deferred
355 ...
356 alter session set constraints = immediate
357
b7b18f32 358to defer foreign key checks.
359
360Constraints must be declared C<DEFERRABLE> for this to work.
6c0bb6a7 361
362=cut
363
364sub with_deferred_fk_checks {
365 my ($self, $sub) = @_;
b7b18f32 366
367 my $txn_scope_guard = $self->txn_scope_guard;
368
6c0bb6a7 369 $self->_do_query('alter session set constraints = deferred');
b7b18f32 370
371 my $sg = Scope::Guard->new(sub {
372 $self->_do_query('alter session set constraints = immediate');
373 });
374
375 return Context::Preserve::preserve_context(sub { $sub->() },
376 after => sub { $txn_scope_guard->commit });
6c0bb6a7 377}
378
7a84c41b 379=head1 AUTHOR
18360aed 380
7a84c41b 381See L<DBIx::Class/CONTRIBUTORS>.
18360aed 382
383=head1 LICENSE
384
385You may distribute this code under the same terms as Perl itself.
386
387=cut
7137528d 388
3891;