Switch cursor accessor to CAG's component_class type for autoloading
[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 ();
6298a324 6use Context::Preserve 'preserve_context';
ed7ab0f4 7use Try::Tiny;
fd323bf1 8use namespace::clean;
18360aed 9
6a247f33 10__PACKAGE__->sql_limit_dialect ('RowNum');
2b8cc2f2 11__PACKAGE__->sql_quote_char ('"');
6a247f33 12
7137528d 13=head1 NAME
14
7a84c41b 15DBIx::Class::Storage::DBI::Oracle::Generic - Oracle Support for DBIx::Class
7137528d 16
17=head1 SYNOPSIS
18
d88ecca6 19 # In your result (table) classes
20 use base 'DBIx::Class::Core';
2e46b6eb 21 __PACKAGE__->add_columns({ id => { sequence => 'mysequence', auto_nextval => 1 } });
7137528d 22 __PACKAGE__->set_primary_key('id');
7137528d 23
c0024355 24 # Somewhere in your Code
25 # add some data to a table with a hierarchical relationship
26 $schema->resultset('Person')->create ({
27 firstname => 'foo',
28 lastname => 'bar',
29 children => [
30 {
31 firstname => 'child1',
32 lastname => 'bar',
33 children => [
34 {
35 firstname => 'grandchild',
36 lastname => 'bar',
37 }
38 ],
39 },
40 {
41 firstname => 'child2',
42 lastname => 'bar',
43 },
44 ],
45 });
46
47 # select from the hierarchical relationship
48 my $rs = $schema->resultset('Person')->search({},
49 {
50 'start_with' => { 'firstname' => 'foo', 'lastname' => 'bar' },
e6600283 51 'connect_by' => { 'parentid' => { '-prior' => { -ident => 'personid' } },
25ca709b 52 'order_siblings_by' => { -asc => 'name' },
c0024355 53 };
54 );
55
56 # this will select the whole tree starting from person "foo bar", creating
57 # following query:
58 # SELECT
59 # me.persionid me.firstname, me.lastname, me.parentid
60 # FROM
61 # person me
62 # START WITH
63 # firstname = 'foo' and lastname = 'bar'
64 # CONNECT BY
e6600283 65 # parentid = prior personid
c0024355 66 # ORDER SIBLINGS BY
67 # firstname ASC
68
7137528d 69=head1 DESCRIPTION
70
6c0230de 71This class implements base Oracle support. The subclass
72L<DBIx::Class::Storage::DBI::Oracle::WhereJoins> is for C<(+)> joins in Oracle
73versions before 9.
7137528d 74
75=head1 METHODS
76
77=cut
78
db56cf3d 79use base qw/DBIx::Class::Storage::DBI/;
2ad62d97 80use mro 'c3';
18360aed 81
d5dedbd6 82__PACKAGE__->sql_maker_class('DBIx::Class::SQLMaker::Oracle');
c0024355 83
bf51641f 84sub _determine_supports_insert_returning {
85 my $self = shift;
86
87# TODO find out which version supports the RETURNING syntax
88# 8i has it and earlier docs are a 404 on oracle.com
89
90 return 1
91 if $self->_server_info->{normalized_dbms_version} >= 8.001;
92
93 return 0;
94}
95
96__PACKAGE__->_use_insert_returning_bound (1);
97
dd2600c6 98sub deployment_statements {
99 my $self = shift;;
100 my ($schema, $type, $version, $dir, $sqltargs, @rest) = @_;
101
102 $sqltargs ||= {};
032b2366 103 my $quote_char = $self->schema->storage->sql_maker->quote_char;
104 $sqltargs->{quote_table_names} = $quote_char ? 1 : 0;
105 $sqltargs->{quote_field_names} = $quote_char ? 1 : 0;
dd2600c6 106
96736321 107 if (
108 ! exists $sqltargs->{producer_args}{oracle_version}
109 and
110 my $dver = $self->_server_info->{dbms_version}
111 ) {
112 $sqltargs->{producer_args}{oracle_version} = $dver;
113 }
a4433d8e 114
38aead8e 115 $self->next::method($schema, $type, $version, $dir, $sqltargs, @rest);
dd2600c6 116}
117
18360aed 118sub _dbh_last_insert_id {
2e46b6eb 119 my ($self, $dbh, $source, @columns) = @_;
120 my @ids = ();
121 foreach my $col (@columns) {
122 my $seq = ($source->column_info($col)->{sequence} ||= $self->get_autoinc_seq($source,$col));
07cda1c5 123 my $id = $self->_sequence_fetch( 'CURRVAL', $seq );
2e46b6eb 124 push @ids, $id;
125 }
126 return @ids;
18360aed 127}
128
129sub _dbh_get_autoinc_seq {
130 my ($self, $dbh, $source, $col) = @_;
131
032b2366 132 my $sql_maker = $self->sql_maker;
07cda1c5 133 my ($ql, $qr) = map { $_ ? (quotemeta $_) : '' } $sql_maker->_quote_chars;
cb464582 134
e6dd7b42 135 my $source_name;
032b2366 136 if ( ref $source->name eq 'SCALAR' ) {
137 $source_name = ${$source->name};
07cda1c5 138
139 # the ALL_TRIGGERS match further on is case sensitive - thus uppercase
140 # stuff unless it is already quoted
141 $source_name = uc ($source_name) if $source_name !~ /\"/;
e6dd7b42 142 }
143 else {
032b2366 144 $source_name = $source->name;
07cda1c5 145 $source_name = uc($source_name) unless $ql;
e6dd7b42 146 }
38aead8e 147
032b2366 148 # trigger_body is a LONG
149 local $dbh->{LongReadLen} = 64 * 1024 if ($dbh->{LongReadLen} < 64 * 1024);
150
151 # disable default bindtype
152 local $sql_maker->{bindtype} = 'normal';
153
154 # look up the correct sequence automatically
07cda1c5 155 my ( $schema, $table ) = $source_name =~ /( (?:${ql})? \w+ (?:${qr})? ) \. ( (?:${ql})? \w+ (?:${qr})? )/x;
a6646e1b 156
157 # if no explicit schema was requested - use the default schema (which in the case of Oracle is the db user)
158 $schema ||= uc( ($self->_dbi_connect_info||[])->[1] || '');
159
032b2366 160 my ($sql, @bind) = $sql_maker->select (
161 'ALL_TRIGGERS',
07cda1c5 162 [qw/TRIGGER_BODY TABLE_OWNER TRIGGER_NAME/],
032b2366 163 {
07cda1c5 164 $schema ? (OWNER => $schema) : (),
165 TABLE_NAME => $table || $source_name,
166 TRIGGERING_EVENT => { -like => '%INSERT%' }, # this will also catch insert_or_update
167 TRIGGER_TYPE => { -like => '%BEFORE%' }, # we care only about 'before' triggers
168 STATUS => 'ENABLED',
032b2366 169 },
170 );
e6dd7b42 171
6f5f880d 172 # to find all the triggers that mention the column in question a simple
173 # regex grep since the trigger_body above is a LONG and hence not searchable
174 my @triggers = ( map
175 { my %inf; @inf{qw/body schema name/} = @$_; \%inf }
176 ( grep
07cda1c5 177 { $_->[0] =~ /\:new\.${ql}${col}${qr} | \:new\.$col/xi }
6f5f880d 178 @{ $dbh->selectall_arrayref( $sql, {}, @bind ) }
179 )
180 );
181
182 # extract all sequence names mentioned in each trigger
183 for (@triggers) {
184 $_->{sequences} = [ $_->{body} =~ / ( "? [\.\w\"\-]+ "? ) \. nextval /xig ];
185 }
186
187 my $chosen_trigger;
188
189 # if only one trigger matched things are easy
190 if (@triggers == 1) {
191
192 if ( @{$triggers[0]{sequences}} == 1 ) {
193 $chosen_trigger = $triggers[0];
194 }
195 else {
196 $self->throw_exception( sprintf (
197 "Unable to introspect trigger '%s' for column %s.%s (references multiple sequences). "
198 . "You need to specify the correct 'sequence' explicitly in '%s's column_info.",
199 $triggers[0]{name},
200 $source_name,
201 $col,
202 $col,
203 ) );
204 }
205 }
206 # got more than one matching trigger - see if we can narrow it down
207 elsif (@triggers > 1) {
df6e3f5c 208
6f5f880d 209 my @candidates = grep
210 { $_->{body} =~ / into \s+ \:new\.$col /xi }
211 @triggers
212 ;
df6e3f5c 213
6f5f880d 214 if (@candidates == 1 && @{$candidates[0]{sequences}} == 1) {
215 $chosen_trigger = $candidates[0];
df6e3f5c 216 }
6f5f880d 217 else {
218 $self->throw_exception( sprintf (
219 "Unable to reliably select a BEFORE INSERT trigger for column %s.%s (possibilities: %s). "
220 . "You need to specify the correct 'sequence' explicitly in '%s's column_info.",
221 $source_name,
222 $col,
223 ( join ', ', map { "'$_->{name}'" } @triggers ),
224 $col,
225 ) );
226 }
227 }
228
229 if ($chosen_trigger) {
230 my $seq_name = $chosen_trigger->{sequences}[0];
231
232 $seq_name = "$chosen_trigger->{schema}.$seq_name"
233 unless $seq_name =~ /\./;
df6e3f5c 234
07cda1c5 235 return \$seq_name if $seq_name =~ /\"/; # may already be quoted in-trigger
df6e3f5c 236 return $seq_name;
18360aed 237 }
6f5f880d 238
239 $self->throw_exception( sprintf (
240 "No suitable BEFORE INSERT triggers found for column %s.%s. "
241 . "You need to specify the correct 'sequence' explicitly in '%s's column_info.",
242 $source_name,
243 $col,
244 $col,
245 ));
18360aed 246}
247
2e46b6eb 248sub _sequence_fetch {
249 my ( $self, $type, $seq ) = @_;
07cda1c5 250
251 # use the maker to leverage quoting settings
252 my $sql_maker = $self->sql_maker;
253 my ($id) = $self->_get_dbh->selectrow_array ($sql_maker->select('DUAL', [ ref $seq ? \"$$seq.$type" : "$seq.$type" ] ) );
2e46b6eb 254 return $id;
255}
256
6dc4be0f 257sub _ping {
c2481821 258 my $self = shift;
7ba7a57d 259
6dc4be0f 260 my $dbh = $self->_dbh or return 0;
7ba7a57d 261
6dc4be0f 262 local $dbh->{RaiseError} = 1;
ecdf1ac8 263 local $dbh->{PrintError} = 0;
c2d7baef 264
52b420dd 265 return try {
ecdf1ac8 266 $dbh->do('select 1 from dual');
52b420dd 267 1;
ed7ab0f4 268 } catch {
52b420dd 269 0;
6dc4be0f 270 };
c2481821 271}
272
d789fa99 273sub _dbh_execute {
274 my $self = shift;
275 my ($dbh, $op, $extra_bind, $ident, $bind_attributes, @args) = @_;
276
87560ef9 277 my (@res, $tried);
cca282b6 278 my $want = wantarray;
4f661051 279 my $next = $self->next::can;
87560ef9 280 do {
52b420dd 281 try {
dd415de8 282 my $exec = sub { $self->$next($dbh, $op, $extra_bind, $ident, $bind_attributes, @args) };
283
cca282b6 284 if (!defined $want) {
dd415de8 285 $exec->();
286 }
cca282b6 287 elsif (! $want) {
dd415de8 288 $res[0] = $exec->();
289 }
290 else {
291 @res = $exec->();
292 }
87560ef9 293
294 $tried++;
52b420dd 295 }
296 catch {
87560ef9 297 if (! $tried and $_ =~ /ORA-01003/) {
0f0abc97 298 # ORA-01003: no statement parsed (someone changed the table somehow,
299 # invalidating your cursor.)
300 my ($sql, $bind) = $self->_prep_for_execute($op, $extra_bind, $ident, \@args);
301 delete $dbh->{CachedKids}{$sql};
d789fa99 302 }
52b420dd 303 else {
304 $self->throw_exception($_);
305 }
306 };
87560ef9 307 } while (! $tried++);
d789fa99 308
cca282b6 309 return wantarray ? @res : $res[0];
d789fa99 310}
311
a5a27e7a 312sub _dbh_execute_array {
313 #my ($self, $sth, $tuple_status, @extra) = @_;
314
315 # DBD::Oracle warns loudly on partial execute_array failures
316 local $_[1]->{PrintWarn} = 0;
317
318 shift->next::method(@_);
319}
320
7137528d 321=head2 get_autoinc_seq
322
323Returns the sequence name for an autoincrement column
324
325=cut
326
18360aed 327sub get_autoinc_seq {
328 my ($self, $source, $col) = @_;
d4daee7b 329
373940e1 330 $self->dbh_do('_dbh_get_autoinc_seq', $source, $col);
18360aed 331}
332
8f7e044c 333=head2 datetime_parser_type
334
335This sets the proper DateTime::Format module for use with
336L<DBIx::Class::InflateColumn::DateTime>.
337
338=cut
339
340sub datetime_parser_type { return "DateTime::Format::Oracle"; }
341
9900b569 342=head2 connect_call_datetime_setup
d2a3958e 343
344Used as:
345
9900b569 346 on_connect_call => 'datetime_setup'
d2a3958e 347
8384a713 348In L<connect_info|DBIx::Class::Storage::DBI/connect_info> to set the session nls
349date, and timestamp values for use with L<DBIx::Class::InflateColumn::DateTime>
350and the necessary environment variables for L<DateTime::Format::Oracle>, which
351is used by it.
d2a3958e 352
82f6f45f 353Maximum allowable precision is used, unless the environment variables have
354already been set.
d2a3958e 355
9900b569 356These are the defaults used:
357
358 $ENV{NLS_DATE_FORMAT} ||= 'YYYY-MM-DD HH24:MI:SS';
359 $ENV{NLS_TIMESTAMP_FORMAT} ||= 'YYYY-MM-DD HH24:MI:SS.FF';
360 $ENV{NLS_TIMESTAMP_TZ_FORMAT} ||= 'YYYY-MM-DD HH24:MI:SS.FF TZHTZM';
361
d9e53b85 362To get more than second precision with L<DBIx::Class::InflateColumn::DateTime>
363for your timestamps, use something like this:
364
365 use Time::HiRes 'time';
366 my $ts = DateTime->from_epoch(epoch => time);
367
d2a3958e 368=cut
369
9900b569 370sub connect_call_datetime_setup {
d2a3958e 371 my $self = shift;
d2a3958e 372
373 my $date_format = $ENV{NLS_DATE_FORMAT} ||= 'YYYY-MM-DD HH24:MI:SS';
374 my $timestamp_format = $ENV{NLS_TIMESTAMP_FORMAT} ||=
375 'YYYY-MM-DD HH24:MI:SS.FF';
376 my $timestamp_tz_format = $ENV{NLS_TIMESTAMP_TZ_FORMAT} ||=
377 'YYYY-MM-DD HH24:MI:SS.FF TZHTZM';
378
7a84c41b 379 $self->_do_query(
d7a58a29 380 "alter session set nls_date_format = '$date_format'"
381 );
7a84c41b 382 $self->_do_query(
d7a58a29 383 "alter session set nls_timestamp_format = '$timestamp_format'"
384 );
7a84c41b 385 $self->_do_query(
d7a58a29 386 "alter session set nls_timestamp_tz_format='$timestamp_tz_format'"
387 );
d2a3958e 388}
389
5db2758d 390=head2 source_bind_attributes
391
392Handle LOB types in Oracle. Under a certain size (4k?), you can get away
393with the driver assuming your input is the deprecated LONG type if you
394encode it as a hex string. That ain't gonna fly at larger values, where
395you'll discover you have to do what this does.
396
397This method had to be overridden because we need to set ora_field to the
398actual column, and that isn't passed to the call (provided by Storage) to
399bind_attribute_by_data_type.
400
401According to L<DBD::Oracle>, the ora_field isn't always necessary, but
402adding it doesn't hurt, and will save your bacon if you're modifying a
403table with more than one LOB column.
404
405=cut
406
e6dd7b42 407sub source_bind_attributes
5db2758d 408{
d7a58a29 409 require DBD::Oracle;
410 my $self = shift;
411 my($source) = @_;
5db2758d 412
8138f96a 413 my %bind_attributes = %{ $self->next::method(@_) };
5db2758d 414
d7a58a29 415 foreach my $column ($source->columns) {
8138f96a 416 my %column_bind_attrs = %{ $bind_attributes{$column} || {} };
5db2758d 417
8138f96a 418 my $data_type = $source->column_info($column)->{data_type};
5db2758d 419
8138f96a 420 if ($self->_is_lob_type($data_type)) {
931e5d43 421 if ($DBD::Oracle::VERSION eq '1.23') {
422 $self->throw_exception(
423"BLOB/CLOB support in DBD::Oracle == 1.23 is broken, use an earlier or later ".
555cc3f4 424"version.\n\nSee: https://rt.cpan.org/Public/Bug/Display.html?id=46016\n"
931e5d43 425 );
426 }
5db2758d 427
8138f96a 428 $column_bind_attrs{'ora_type'} = $self->_is_text_lob_type($data_type)
d7a58a29 429 ? DBD::Oracle::ORA_CLOB()
430 : DBD::Oracle::ORA_BLOB()
431 ;
432 $column_bind_attrs{'ora_field'} = $column;
433 }
5db2758d 434
d7a58a29 435 $bind_attributes{$column} = \%column_bind_attrs;
436 }
5db2758d 437
d7a58a29 438 return \%bind_attributes;
5db2758d 439}
440
1816be4f 441sub _svp_begin {
d7a58a29 442 my ($self, $name) = @_;
443 $self->_get_dbh->do("SAVEPOINT $name");
1816be4f 444}
445
281719d2 446# Oracle automatically releases a savepoint when you start another one with the
447# same name.
448sub _svp_release { 1 }
449
450sub _svp_rollback {
d7a58a29 451 my ($self, $name) = @_;
452 $self->_get_dbh->do("ROLLBACK TO SAVEPOINT $name")
281719d2 453}
454
6c0230de 455=head2 relname_to_table_alias
456
457L<DBIx::Class> uses L<DBIx::Class::Relationship> names as table aliases in
458queries.
459
460Unfortunately, Oracle doesn't support identifiers over 30 chars in length, so
af0edca1 461the L<DBIx::Class::Relationship> name is shortened and appended with half of an
462MD5 hash.
6c0230de 463
464See L<DBIx::Class::Storage/"relname_to_table_alias">.
465
466=cut
467
468sub relname_to_table_alias {
469 my $self = shift;
470 my ($relname, $join_count) = @_;
471
472 my $alias = $self->next::method(@_);
473
63ca94e1 474 return $self->sql_maker->_shorten_identifier($alias, [$relname]);
6c0230de 475}
476
6c0bb6a7 477=head2 with_deferred_fk_checks
478
479Runs a coderef between:
480
481 alter session set constraints = deferred
482 ...
483 alter session set constraints = immediate
484
b7b18f32 485to defer foreign key checks.
486
487Constraints must be declared C<DEFERRABLE> for this to work.
6c0bb6a7 488
489=cut
490
491sub with_deferred_fk_checks {
492 my ($self, $sub) = @_;
b7b18f32 493
494 my $txn_scope_guard = $self->txn_scope_guard;
495
6c0bb6a7 496 $self->_do_query('alter session set constraints = deferred');
54161a15 497
b7b18f32 498 my $sg = Scope::Guard->new(sub {
499 $self->_do_query('alter session set constraints = immediate');
500 });
281719d2 501
6298a324 502 return
503 preserve_context { $sub->() } after => sub { $txn_scope_guard->commit };
281719d2 504}
505
c0024355 506=head1 ATTRIBUTES
507
508Following additional attributes can be used in resultsets.
509
6b2fbbf0 510=head2 connect_by or connect_by_nocycle
c0024355 511
512=over 4
513
514=item Value: \%connect_by
515
516=back
517
518A hashref of conditions used to specify the relationship between parent rows
519and child rows of the hierarchy.
520
6b2fbbf0 521
c0024355 522 connect_by => { parentid => 'prior personid' }
523
524 # adds a connect by statement to the query:
525 # SELECT
526 # me.persionid me.firstname, me.lastname, me.parentid
527 # FROM
528 # person me
529 # CONNECT BY
530 # parentid = prior persionid
6b2fbbf0 531
c0024355 532
6b2fbbf0 533 connect_by_nocycle => { parentid => 'prior personid' }
2ba03b16 534
6b2fbbf0 535 # adds a connect by statement to the query:
536 # SELECT
537 # me.persionid me.firstname, me.lastname, me.parentid
538 # FROM
539 # person me
540 # CONNECT BY NOCYCLE
541 # parentid = prior persionid
2ba03b16 542
543
c0024355 544=head2 start_with
545
546=over 4
547
548=item Value: \%condition
549
550=back
551
552A hashref of conditions which specify the root row(s) of the hierarchy.
553
554It uses the same syntax as L<DBIx::Class::ResultSet/search>
555
556 start_with => { firstname => 'Foo', lastname => 'Bar' }
557
558 # SELECT
559 # me.persionid me.firstname, me.lastname, me.parentid
560 # FROM
561 # person me
562 # START WITH
563 # firstname = 'foo' and lastname = 'bar'
564 # CONNECT BY
565 # parentid = prior persionid
566
567=head2 order_siblings_by
568
569=over 4
570
571=item Value: ($order_siblings_by | \@order_siblings_by)
572
573=back
574
575Which column(s) to order the siblings by.
576
577It uses the same syntax as L<DBIx::Class::ResultSet/order_by>
578
579 'order_siblings_by' => 'firstname ASC'
580
581 # SELECT
582 # me.persionid me.firstname, me.lastname, me.parentid
583 # FROM
584 # person me
585 # CONNECT BY
586 # parentid = prior persionid
587 # ORDER SIBLINGS BY
588 # firstname ASC
589
7a84c41b 590=head1 AUTHOR
18360aed 591
7a84c41b 592See L<DBIx::Class/CONTRIBUTORS>.
18360aed 593
594=head1 LICENSE
595
596You may distribute this code under the same terms as Perl itself.
597
598=cut
7137528d 599
6001;