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