add quote_names connect_info option
[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
d7a58a29 413 my %bind_attributes;
5db2758d 414
d7a58a29 415 foreach my $column ($source->columns) {
07cda1c5 416 my $data_type = $source->column_info($column)->{data_type}
417 or next;
5db2758d 418
d7a58a29 419 my %column_bind_attrs = $self->bind_attribute_by_data_type($data_type);
5db2758d 420
d7a58a29 421 if ($data_type =~ /^[BC]LOB$/i) {
931e5d43 422 if ($DBD::Oracle::VERSION eq '1.23') {
423 $self->throw_exception(
424"BLOB/CLOB support in DBD::Oracle == 1.23 is broken, use an earlier or later ".
555cc3f4 425"version.\n\nSee: https://rt.cpan.org/Public/Bug/Display.html?id=46016\n"
931e5d43 426 );
427 }
5db2758d 428
d7a58a29 429 $column_bind_attrs{'ora_type'} = uc($data_type) eq 'CLOB'
430 ? DBD::Oracle::ORA_CLOB()
431 : DBD::Oracle::ORA_BLOB()
432 ;
433 $column_bind_attrs{'ora_field'} = $column;
434 }
5db2758d 435
d7a58a29 436 $bind_attributes{$column} = \%column_bind_attrs;
437 }
5db2758d 438
d7a58a29 439 return \%bind_attributes;
5db2758d 440}
441
1816be4f 442sub _svp_begin {
d7a58a29 443 my ($self, $name) = @_;
444 $self->_get_dbh->do("SAVEPOINT $name");
1816be4f 445}
446
281719d2 447# Oracle automatically releases a savepoint when you start another one with the
448# same name.
449sub _svp_release { 1 }
450
451sub _svp_rollback {
d7a58a29 452 my ($self, $name) = @_;
453 $self->_get_dbh->do("ROLLBACK TO SAVEPOINT $name")
281719d2 454}
455
6c0230de 456=head2 relname_to_table_alias
457
458L<DBIx::Class> uses L<DBIx::Class::Relationship> names as table aliases in
459queries.
460
461Unfortunately, Oracle doesn't support identifiers over 30 chars in length, so
af0edca1 462the L<DBIx::Class::Relationship> name is shortened and appended with half of an
463MD5 hash.
6c0230de 464
465See L<DBIx::Class::Storage/"relname_to_table_alias">.
466
467=cut
468
469sub relname_to_table_alias {
470 my $self = shift;
471 my ($relname, $join_count) = @_;
472
473 my $alias = $self->next::method(@_);
474
63ca94e1 475 return $self->sql_maker->_shorten_identifier($alias, [$relname]);
6c0230de 476}
477
6c0bb6a7 478=head2 with_deferred_fk_checks
479
480Runs a coderef between:
481
482 alter session set constraints = deferred
483 ...
484 alter session set constraints = immediate
485
b7b18f32 486to defer foreign key checks.
487
488Constraints must be declared C<DEFERRABLE> for this to work.
6c0bb6a7 489
490=cut
491
492sub with_deferred_fk_checks {
493 my ($self, $sub) = @_;
b7b18f32 494
495 my $txn_scope_guard = $self->txn_scope_guard;
496
6c0bb6a7 497 $self->_do_query('alter session set constraints = deferred');
54161a15 498
b7b18f32 499 my $sg = Scope::Guard->new(sub {
500 $self->_do_query('alter session set constraints = immediate');
501 });
281719d2 502
6298a324 503 return
504 preserve_context { $sub->() } after => sub { $txn_scope_guard->commit };
281719d2 505}
506
c0024355 507=head1 ATTRIBUTES
508
509Following additional attributes can be used in resultsets.
510
6b2fbbf0 511=head2 connect_by or connect_by_nocycle
c0024355 512
513=over 4
514
515=item Value: \%connect_by
516
517=back
518
519A hashref of conditions used to specify the relationship between parent rows
520and child rows of the hierarchy.
521
6b2fbbf0 522
c0024355 523 connect_by => { parentid => 'prior personid' }
524
525 # adds a connect by statement to the query:
526 # SELECT
527 # me.persionid me.firstname, me.lastname, me.parentid
528 # FROM
529 # person me
530 # CONNECT BY
531 # parentid = prior persionid
6b2fbbf0 532
c0024355 533
6b2fbbf0 534 connect_by_nocycle => { parentid => 'prior personid' }
2ba03b16 535
6b2fbbf0 536 # adds a connect by statement to the query:
537 # SELECT
538 # me.persionid me.firstname, me.lastname, me.parentid
539 # FROM
540 # person me
541 # CONNECT BY NOCYCLE
542 # parentid = prior persionid
2ba03b16 543
544
c0024355 545=head2 start_with
546
547=over 4
548
549=item Value: \%condition
550
551=back
552
553A hashref of conditions which specify the root row(s) of the hierarchy.
554
555It uses the same syntax as L<DBIx::Class::ResultSet/search>
556
557 start_with => { firstname => 'Foo', lastname => 'Bar' }
558
559 # SELECT
560 # me.persionid me.firstname, me.lastname, me.parentid
561 # FROM
562 # person me
563 # START WITH
564 # firstname = 'foo' and lastname = 'bar'
565 # CONNECT BY
566 # parentid = prior persionid
567
568=head2 order_siblings_by
569
570=over 4
571
572=item Value: ($order_siblings_by | \@order_siblings_by)
573
574=back
575
576Which column(s) to order the siblings by.
577
578It uses the same syntax as L<DBIx::Class::ResultSet/order_by>
579
580 'order_siblings_by' => 'firstname ASC'
581
582 # SELECT
583 # me.persionid me.firstname, me.lastname, me.parentid
584 # FROM
585 # person me
586 # CONNECT BY
587 # parentid = prior persionid
588 # ORDER SIBLINGS BY
589 # firstname ASC
590
7a84c41b 591=head1 AUTHOR
18360aed 592
7a84c41b 593See L<DBIx::Class/CONTRIBUTORS>.
18360aed 594
595=head1 LICENSE
596
597You may distribute this code under the same terms as Perl itself.
598
599=cut
7137528d 600
6011;