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