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