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