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