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