Fixed typo
[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' },
47 'connect_by' => { 'parentid' => 'prior persionid'},
48 'order_siblings_by' => 'firstname ASC',
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;
c2d7baef 160
6dc4be0f 161 eval {
162 $dbh->do("select 1 from dual");
163 };
7ba7a57d 164
6dc4be0f 165 return $@ ? 0 : 1;
c2481821 166}
167
d789fa99 168sub _dbh_execute {
169 my $self = shift;
170 my ($dbh, $op, $extra_bind, $ident, $bind_attributes, @args) = @_;
171
172 my $wantarray = wantarray;
d789fa99 173
c2d7baef 174 my (@res, $exception, $retried);
175
0f0abc97 176 RETRY: {
177 do {
178 eval {
179 if ($wantarray) {
c3515436 180 @res = $self->next::method(@_);
0f0abc97 181 } else {
c3515436 182 $res[0] = $self->next::method(@_);
0f0abc97 183 }
184 };
185 $exception = $@;
186 if ($exception =~ /ORA-01003/) {
187 # ORA-01003: no statement parsed (someone changed the table somehow,
188 # invalidating your cursor.)
189 my ($sql, $bind) = $self->_prep_for_execute($op, $extra_bind, $ident, \@args);
190 delete $dbh->{CachedKids}{$sql};
d789fa99 191 } else {
0f0abc97 192 last RETRY;
d789fa99 193 }
0f0abc97 194 } while (not $retried++);
195 }
d789fa99 196
197 $self->throw_exception($exception) if $exception;
198
e1958268 199 $wantarray ? @res : $res[0]
d789fa99 200}
201
7137528d 202=head2 get_autoinc_seq
203
204Returns the sequence name for an autoincrement column
205
206=cut
207
18360aed 208sub get_autoinc_seq {
209 my ($self, $source, $col) = @_;
d4daee7b 210
373940e1 211 $self->dbh_do('_dbh_get_autoinc_seq', $source, $col);
18360aed 212}
213
7137528d 214=head2 columns_info_for
215
216This wraps the superclass version of this method to force table
217names to uppercase
218
219=cut
220
18360aed 221sub columns_info_for {
222 my ($self, $table) = @_;
223
dd2600c6 224 $self->next::method($table);
18360aed 225}
226
8f7e044c 227=head2 datetime_parser_type
228
229This sets the proper DateTime::Format module for use with
230L<DBIx::Class::InflateColumn::DateTime>.
231
232=cut
233
234sub datetime_parser_type { return "DateTime::Format::Oracle"; }
235
9900b569 236=head2 connect_call_datetime_setup
d2a3958e 237
238Used as:
239
9900b569 240 on_connect_call => 'datetime_setup'
d2a3958e 241
82f6f45f 242In L<DBIx::Class::Storage::DBI/connect_info> to set the session nls date, and
243timestamp values for use with L<DBIx::Class::InflateColumn::DateTime> and the
244necessary environment variables for L<DateTime::Format::Oracle>, which is used
245by it.
d2a3958e 246
82f6f45f 247Maximum allowable precision is used, unless the environment variables have
248already been set.
d2a3958e 249
9900b569 250These are the defaults used:
251
252 $ENV{NLS_DATE_FORMAT} ||= 'YYYY-MM-DD HH24:MI:SS';
253 $ENV{NLS_TIMESTAMP_FORMAT} ||= 'YYYY-MM-DD HH24:MI:SS.FF';
254 $ENV{NLS_TIMESTAMP_TZ_FORMAT} ||= 'YYYY-MM-DD HH24:MI:SS.FF TZHTZM';
255
d9e53b85 256To get more than second precision with L<DBIx::Class::InflateColumn::DateTime>
257for your timestamps, use something like this:
258
259 use Time::HiRes 'time';
260 my $ts = DateTime->from_epoch(epoch => time);
261
d2a3958e 262=cut
263
9900b569 264sub connect_call_datetime_setup {
d2a3958e 265 my $self = shift;
d2a3958e 266
267 my $date_format = $ENV{NLS_DATE_FORMAT} ||= 'YYYY-MM-DD HH24:MI:SS';
268 my $timestamp_format = $ENV{NLS_TIMESTAMP_FORMAT} ||=
269 'YYYY-MM-DD HH24:MI:SS.FF';
270 my $timestamp_tz_format = $ENV{NLS_TIMESTAMP_TZ_FORMAT} ||=
271 'YYYY-MM-DD HH24:MI:SS.FF TZHTZM';
272
7a84c41b 273 $self->_do_query(
d7a58a29 274 "alter session set nls_date_format = '$date_format'"
275 );
7a84c41b 276 $self->_do_query(
d7a58a29 277 "alter session set nls_timestamp_format = '$timestamp_format'"
278 );
7a84c41b 279 $self->_do_query(
d7a58a29 280 "alter session set nls_timestamp_tz_format='$timestamp_tz_format'"
281 );
d2a3958e 282}
283
5db2758d 284=head2 source_bind_attributes
285
286Handle LOB types in Oracle. Under a certain size (4k?), you can get away
287with the driver assuming your input is the deprecated LONG type if you
288encode it as a hex string. That ain't gonna fly at larger values, where
289you'll discover you have to do what this does.
290
291This method had to be overridden because we need to set ora_field to the
292actual column, and that isn't passed to the call (provided by Storage) to
293bind_attribute_by_data_type.
294
295According to L<DBD::Oracle>, the ora_field isn't always necessary, but
296adding it doesn't hurt, and will save your bacon if you're modifying a
297table with more than one LOB column.
298
299=cut
300
e6dd7b42 301sub source_bind_attributes
5db2758d 302{
d7a58a29 303 require DBD::Oracle;
304 my $self = shift;
305 my($source) = @_;
5db2758d 306
d7a58a29 307 my %bind_attributes;
5db2758d 308
d7a58a29 309 foreach my $column ($source->columns) {
310 my $data_type = $source->column_info($column)->{data_type} || '';
311 next unless $data_type;
5db2758d 312
d7a58a29 313 my %column_bind_attrs = $self->bind_attribute_by_data_type($data_type);
5db2758d 314
d7a58a29 315 if ($data_type =~ /^[BC]LOB$/i) {
931e5d43 316 if ($DBD::Oracle::VERSION eq '1.23') {
317 $self->throw_exception(
318"BLOB/CLOB support in DBD::Oracle == 1.23 is broken, use an earlier or later ".
555cc3f4 319"version.\n\nSee: https://rt.cpan.org/Public/Bug/Display.html?id=46016\n"
931e5d43 320 );
321 }
5db2758d 322
d7a58a29 323 $column_bind_attrs{'ora_type'} = uc($data_type) eq 'CLOB'
324 ? DBD::Oracle::ORA_CLOB()
325 : DBD::Oracle::ORA_BLOB()
326 ;
327 $column_bind_attrs{'ora_field'} = $column;
328 }
5db2758d 329
d7a58a29 330 $bind_attributes{$column} = \%column_bind_attrs;
331 }
5db2758d 332
d7a58a29 333 return \%bind_attributes;
5db2758d 334}
335
1816be4f 336sub _svp_begin {
d7a58a29 337 my ($self, $name) = @_;
338 $self->_get_dbh->do("SAVEPOINT $name");
1816be4f 339}
340
281719d2 341# Oracle automatically releases a savepoint when you start another one with the
342# same name.
343sub _svp_release { 1 }
344
345sub _svp_rollback {
d7a58a29 346 my ($self, $name) = @_;
347 $self->_get_dbh->do("ROLLBACK TO SAVEPOINT $name")
281719d2 348}
349
6c0230de 350=head2 relname_to_table_alias
351
352L<DBIx::Class> uses L<DBIx::Class::Relationship> names as table aliases in
353queries.
354
355Unfortunately, Oracle doesn't support identifiers over 30 chars in length, so
af0edca1 356the L<DBIx::Class::Relationship> name is shortened and appended with half of an
357MD5 hash.
6c0230de 358
359See L<DBIx::Class::Storage/"relname_to_table_alias">.
360
361=cut
362
363sub relname_to_table_alias {
364 my $self = shift;
365 my ($relname, $join_count) = @_;
366
367 my $alias = $self->next::method(@_);
368
369 return $alias if length($alias) <= 30;
370
af0edca1 371 # get a base64 md5 of the alias with join_count
372 require Digest::MD5;
373 my $ctx = Digest::MD5->new;
374 $ctx->add($alias);
375 my $md5 = $ctx->b64digest;
6c0230de 376
f098ade6 377 # remove alignment mark just in case
378 $md5 =~ s/=*\z//;
379
af0edca1 380 # truncate and prepend to truncated relname without vowels
381 (my $devoweled = $relname) =~ s/[aeiou]//g;
909668fe 382 my $shortened = substr($devoweled, 0, 18);
6c0230de 383
909668fe 384 my $new_alias =
385 $shortened . '_' . substr($md5, 0, 30 - length($shortened) - 1);
386
387 return $new_alias;
6c0230de 388}
389
6c0bb6a7 390=head2 with_deferred_fk_checks
391
392Runs a coderef between:
393
394 alter session set constraints = deferred
395 ...
396 alter session set constraints = immediate
397
b7b18f32 398to defer foreign key checks.
399
400Constraints must be declared C<DEFERRABLE> for this to work.
6c0bb6a7 401
402=cut
403
404sub with_deferred_fk_checks {
405 my ($self, $sub) = @_;
b7b18f32 406
407 my $txn_scope_guard = $self->txn_scope_guard;
408
6c0bb6a7 409 $self->_do_query('alter session set constraints = deferred');
54161a15 410
b7b18f32 411 my $sg = Scope::Guard->new(sub {
412 $self->_do_query('alter session set constraints = immediate');
413 });
281719d2 414
b7b18f32 415 return Context::Preserve::preserve_context(sub { $sub->() },
416 after => sub { $txn_scope_guard->commit });
281719d2 417}
418
c0024355 419sub _select_args {
420 my ($self, $ident, $select, $where, $attrs) = @_;
421
422 my $connect_by_args = {};
423 if ( $attrs->{connect_by} || $attrs->{start_with} || $attrs->{order_siblings_by} ) {
424 $connect_by_args = {
425 connect_by => $attrs->{connect_by},
426 start_with => $attrs->{start_with},
427 order_siblings_by => $attrs->{order_siblings_by},
428 }
429 }
430
431 my @rv = $self->next::method($ident, $select, $where, $attrs);
432
433 return (@rv, $connect_by_args);
434}
435
436=head1 ATTRIBUTES
437
438Following additional attributes can be used in resultsets.
439
440=head2 connect_by
441
442=over 4
443
444=item Value: \%connect_by
445
446=back
447
448A hashref of conditions used to specify the relationship between parent rows
449and child rows of the hierarchy.
450
451 connect_by => { parentid => 'prior personid' }
452
453 # adds a connect by statement to the query:
454 # SELECT
455 # me.persionid me.firstname, me.lastname, me.parentid
456 # FROM
457 # person me
458 # CONNECT BY
459 # parentid = prior persionid
460
461=head2 start_with
462
463=over 4
464
465=item Value: \%condition
466
467=back
468
469A hashref of conditions which specify the root row(s) of the hierarchy.
470
471It uses the same syntax as L<DBIx::Class::ResultSet/search>
472
473 start_with => { firstname => 'Foo', lastname => 'Bar' }
474
475 # SELECT
476 # me.persionid me.firstname, me.lastname, me.parentid
477 # FROM
478 # person me
479 # START WITH
480 # firstname = 'foo' and lastname = 'bar'
481 # CONNECT BY
482 # parentid = prior persionid
483
484=head2 order_siblings_by
485
486=over 4
487
488=item Value: ($order_siblings_by | \@order_siblings_by)
489
490=back
491
492Which column(s) to order the siblings by.
493
494It uses the same syntax as L<DBIx::Class::ResultSet/order_by>
495
496 'order_siblings_by' => 'firstname ASC'
497
498 # SELECT
499 # me.persionid me.firstname, me.lastname, me.parentid
500 # FROM
501 # person me
502 # CONNECT BY
503 # parentid = prior persionid
504 # ORDER SIBLINGS BY
505 # firstname ASC
506
7a84c41b 507=head1 AUTHOR
18360aed 508
7a84c41b 509See L<DBIx::Class/CONTRIBUTORS>.
18360aed 510
511=head1 LICENSE
512
513You may distribute this code under the same terms as Perl itself.
514
515=cut
7137528d 516
5171;