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