File Coverage

blib/lib/DBIx/Class/Storage/DBI/Oracle/Generic.pm
Criterion Covered Total %
statement 30 203 14.7
branch 0 72 0.0
condition 0 47 0.0
subroutine 10 35 28.5
pod 6 6 100.0
total 46 363 12.6


line stmt bran cond sub pod time code
1             package DBIx::Class::Storage::DBI::Oracle::Generic;
2              
3 3     3   2115 use strict;
  3         5  
  3         91  
4 3     3   38 use warnings;
  3         6  
  3         93  
5 3     3   12 use base qw/DBIx::Class::Storage::DBI/;
  3         4  
  3         261  
6 3     3   21 use mro 'c3';
  3         5  
  3         17  
7 3     3   79 use DBIx::Class::Carp;
  3         4  
  3         22  
8 3     3   15 use Scope::Guard ();
  3         4  
  3         61  
9 3     3   13 use Context::Preserve 'preserve_context';
  3         3  
  3         150  
10 3     3   12 use Try::Tiny;
  3         5  
  3         168  
11 3     3   13 use List::Util 'first';
  3         4  
  3         163  
12 3     3   15 use namespace::clean;
  3         5  
  3         18  
13              
14             __PACKAGE__->sql_limit_dialect ('RowNum');
15             __PACKAGE__->sql_quote_char ('"');
16             __PACKAGE__->sql_maker_class('DBIx::Class::SQLMaker::Oracle');
17             __PACKAGE__->datetime_parser_type('DateTime::Format::Oracle');
18              
19 0     0     sub __cache_queries_with_max_lob_parts { 2 }
20              
21             =head1 NAME
22              
23             DBIx::Class::Storage::DBI::Oracle::Generic - Oracle Support for DBIx::Class
24              
25             =head1 SYNOPSIS
26              
27             # In your result (table) classes
28             use base 'DBIx::Class::Core';
29             __PACKAGE__->add_columns({ id => { sequence => 'mysequence', auto_nextval => 1 } });
30             __PACKAGE__->set_primary_key('id');
31              
32             # Somewhere in your Code
33             # add some data to a table with a hierarchical relationship
34             $schema->resultset('Person')->create ({
35             firstname => 'foo',
36             lastname => 'bar',
37             children => [
38             {
39             firstname => 'child1',
40             lastname => 'bar',
41             children => [
42             {
43             firstname => 'grandchild',
44             lastname => 'bar',
45             }
46             ],
47             },
48             {
49             firstname => 'child2',
50             lastname => 'bar',
51             },
52             ],
53             });
54              
55             # select from the hierarchical relationship
56             my $rs = $schema->resultset('Person')->search({},
57             {
58             'start_with' => { 'firstname' => 'foo', 'lastname' => 'bar' },
59             'connect_by' => { 'parentid' => { '-prior' => { -ident => 'personid' } },
60             'order_siblings_by' => { -asc => 'name' },
61             };
62             );
63              
64             # this will select the whole tree starting from person "foo bar", creating
65             # following query:
66             # SELECT
67             # me.persionid me.firstname, me.lastname, me.parentid
68             # FROM
69             # person me
70             # START WITH
71             # firstname = 'foo' and lastname = 'bar'
72             # CONNECT BY
73             # parentid = prior personid
74             # ORDER SIBLINGS BY
75             # firstname ASC
76              
77             =head1 DESCRIPTION
78              
79             This class implements base Oracle support. The subclass
80             L is for C<(+)> joins in Oracle
81             versions before 9.0.
82              
83             =head1 METHODS
84              
85             =cut
86              
87             sub _determine_supports_insert_returning {
88 0     0     my $self = shift;
89              
90             # TODO find out which version supports the RETURNING syntax
91             # 8i has it and earlier docs are a 404 on oracle.com
92              
93             return 1
94 0 0         if $self->_server_info->{normalized_dbms_version} >= 8.001;
95              
96 0           return 0;
97             }
98              
99             __PACKAGE__->_use_insert_returning_bound (1);
100              
101             sub deployment_statements {
102 0     0 1   my $self = shift;;
103 0           my ($schema, $type, $version, $dir, $sqltargs, @rest) = @_;
104              
105 0   0       $sqltargs ||= {};
106              
107 0 0 0       if (
108             ! exists $sqltargs->{producer_args}{oracle_version}
109             and
110             my $dver = $self->_server_info->{dbms_version}
111             ) {
112 0           $sqltargs->{producer_args}{oracle_version} = $dver;
113             }
114              
115 0           $self->next::method($schema, $type, $version, $dir, $sqltargs, @rest);
116             }
117              
118             sub _dbh_last_insert_id {
119 0     0     my ($self, $dbh, $source, @columns) = @_;
120 0           my @ids = ();
121 0           foreach my $col (@columns) {
122 0   0       my $seq = ($source->column_info($col)->{sequence} ||= $self->get_autoinc_seq($source,$col));
123 0           my $id = $self->_sequence_fetch( 'CURRVAL', $seq );
124 0           push @ids, $id;
125             }
126 0           return @ids;
127             }
128              
129             sub _dbh_get_autoinc_seq {
130 0     0     my ($self, $dbh, $source, $col) = @_;
131              
132 0           my $sql_maker = $self->sql_maker;
133 0 0         my ($ql, $qr) = map { $_ ? (quotemeta $_) : '' } $sql_maker->_quote_chars;
  0            
134              
135 0           my $source_name;
136 0 0         if ( ref $source->name eq 'SCALAR' ) {
137 0           $source_name = ${$source->name};
  0            
138              
139             # the ALL_TRIGGERS match further on is case sensitive - thus uppercase
140             # stuff unless it is already quoted
141 0 0         $source_name = uc ($source_name) if $source_name !~ /\"/;
142             }
143             else {
144 0           $source_name = $source->name;
145 0 0         $source_name = uc($source_name) unless $ql;
146             }
147              
148             # trigger_body is a LONG
149 0 0         local $dbh->{LongReadLen} = 64 * 1024 if ($dbh->{LongReadLen} < 64 * 1024);
150              
151             # disable default bindtype
152 0           local $sql_maker->{bindtype} = 'normal';
153              
154             # look up the correct sequence automatically
155 0           my ( $schema, $table ) = $source_name =~ /( (?:${ql})? \w+ (?:${qr})? ) \. ( (?:${ql})? \w+ (?:${qr})? )/x;
156              
157             # if no explicit schema was requested - use the default schema (which in the case of Oracle is the db user)
158 0   0       $schema ||= \'= USER';
159              
160 0   0       my ($sql, @bind) = $sql_maker->select (
161             'ALL_TRIGGERS',
162             [qw/TRIGGER_BODY TABLE_OWNER TRIGGER_NAME/],
163             {
164             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',
169             },
170             );
171              
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             # via -like
175             my @triggers = ( map
176 0           { my %inf; @inf{qw/body schema name/} = @$_; \%inf }
  0            
  0            
177             ( grep
178 0           { $_->[0] =~ /\:new\.${ql}${col}${qr} | \:new\.$col/xi }
179 0           @{ $dbh->selectall_arrayref( $sql, {}, @bind ) }
  0            
180             )
181             );
182              
183             # extract all sequence names mentioned in each trigger, throw away
184             # triggers without apparent sequences
185             @triggers = map {
186 0           my @seqs = $_->{body} =~ / ( [\.\w\"\-]+ ) \. nextval /xig;
  0            
187             @seqs
188 0 0         ? { %$_, sequences => \@seqs }
189             : ()
190             ;
191             } @triggers;
192              
193 0           my $chosen_trigger;
194              
195             # if only one trigger matched things are easy
196 0 0         if (@triggers == 1) {
    0          
197              
198 0 0         if ( @{$triggers[0]{sequences}} == 1 ) {
  0            
199 0           $chosen_trigger = $triggers[0];
200             }
201             else {
202             $self->throw_exception( sprintf (
203             "Unable to introspect trigger '%s' for column '%s.%s' (references multiple sequences). "
204             . "You need to specify the correct 'sequence' explicitly in '%s's column_info.",
205             $triggers[0]{name},
206 0           $source_name,
207             $col,
208             $col,
209             ) );
210             }
211             }
212             # got more than one matching trigger - see if we can narrow it down
213             elsif (@triggers > 1) {
214              
215             my @candidates = grep
216 0           { $_->{body} =~ / into \s+ \:new\.$col /xi }
  0            
217             @triggers
218             ;
219              
220 0 0 0       if (@candidates == 1 && @{$candidates[0]{sequences}} == 1) {
  0            
221 0           $chosen_trigger = $candidates[0];
222             }
223             else {
224             $self->throw_exception( sprintf (
225             "Unable to reliably select a BEFORE INSERT trigger for column '%s.%s' (possibilities: %s). "
226             . "You need to specify the correct 'sequence' explicitly in '%s's column_info.",
227             $source_name,
228             $col,
229 0           ( join ', ', map { "'$_->{name}'" } @triggers ),
  0            
230             $col,
231             ) );
232             }
233             }
234              
235 0 0         if ($chosen_trigger) {
236 0           my $seq_name = $chosen_trigger->{sequences}[0];
237              
238 0 0         $seq_name = "$chosen_trigger->{schema}.$seq_name"
239             unless $seq_name =~ /\./;
240              
241 0 0         return \$seq_name if $seq_name =~ /\"/; # may already be quoted in-trigger
242 0           return $seq_name;
243             }
244              
245 0           $self->throw_exception( sprintf (
246             "No suitable BEFORE INSERT triggers found for column '%s.%s'. "
247             . "You need to specify the correct 'sequence' explicitly in '%s's column_info.",
248             $source_name,
249             $col,
250             $col,
251             ));
252             }
253              
254             sub _sequence_fetch {
255 0     0     my ( $self, $type, $seq ) = @_;
256              
257             # use the maker to leverage quoting settings
258 0 0         my $sth = $self->_dbh->prepare_cached(
259             $self->sql_maker->select('DUAL', [ ref $seq ? \"$$seq.$type" : "$seq.$type" ] )
260             );
261 0           $sth->execute;
262 0           my ($id) = $sth->fetchrow_array;
263 0           $sth->finish;
264 0           return $id;
265             }
266              
267             sub _ping {
268 0     0     my $self = shift;
269              
270 0 0         my $dbh = $self->_dbh or return 0;
271              
272 0           local $dbh->{RaiseError} = 1;
273 0           local $dbh->{PrintError} = 0;
274              
275             return try {
276 0     0     $dbh->do('select 1 from dual');
277 0           1;
278             } catch {
279 0     0     0;
280 0           };
281             }
282              
283             sub _dbh_execute {
284             #my ($self, $dbh, $sql, $bind, $bind_attrs) = @_;
285 0     0     my ($self, $sql, $bind) = @_[0,2,3];
286              
287             # Turn off sth caching for multi-part LOBs. See _prep_for_execute below
288             local $self->{disable_sth_caching} = 1 if first {
289 0   0 0     ($_->[0]{_ora_lob_autosplit_part}||0)
290             >
291             (__cache_queries_with_max_lob_parts - 1)
292 0 0         } @$bind;
293              
294 0           my $next = $self->next::can;
295              
296             # if we are already in a txn we can't retry anything
297 0 0         return shift->$next(@_)
298             if $self->transaction_depth;
299              
300             # cheat the blockrunner we are just about to create
301             # we do want to rerun things regardless of outer state
302 0           local $self->{_in_do_block};
303              
304             return DBIx::Class::Storage::BlockRunner->new(
305             storage => $self,
306             wrap_txn => 0,
307             retry_handler => sub {
308             # ORA-01003: no statement parsed (someone changed the table somehow,
309             # invalidating your cursor.)
310 0 0 0 0     if (
      0        
311             $_[0]->failed_attempt_count == 1
312             and
313             $_[0]->last_exception =~ /ORA-01003/
314             and
315             my $dbh = $_[0]->storage->_dbh
316             ) {
317 0           delete $dbh->{CachedKids}{$sql};
318 0           return 1;
319             }
320             else {
321 0           return 0;
322             }
323             },
324 0           )->run( $next, @_ );
325             }
326              
327             sub _dbh_execute_for_fetch {
328             #my ($self, $sth, $tuple_status, @extra) = @_;
329              
330             # DBD::Oracle warns loudly on partial execute_for_fetch failures
331 0     0     local $_[1]->{PrintWarn} = 0;
332              
333 0           shift->next::method(@_);
334             }
335              
336             =head2 get_autoinc_seq
337              
338             Returns the sequence name for an autoincrement column
339              
340             =cut
341              
342             sub get_autoinc_seq {
343 0     0 1   my ($self, $source, $col) = @_;
344              
345 0           $self->dbh_do('_dbh_get_autoinc_seq', $source, $col);
346             }
347              
348             =head2 datetime_parser_type
349              
350             This sets the proper DateTime::Format module for use with
351             L.
352              
353             =head2 connect_call_datetime_setup
354              
355             Used as:
356              
357             on_connect_call => 'datetime_setup'
358              
359             In L to set the session nls
360             date, and timestamp values for use with L
361             and the necessary environment variables for L, which
362             is used by it.
363              
364             Maximum allowable precision is used, unless the environment variables have
365             already been set.
366              
367             These are the defaults used:
368              
369             $ENV{NLS_DATE_FORMAT} ||= 'YYYY-MM-DD HH24:MI:SS';
370             $ENV{NLS_TIMESTAMP_FORMAT} ||= 'YYYY-MM-DD HH24:MI:SS.FF';
371             $ENV{NLS_TIMESTAMP_TZ_FORMAT} ||= 'YYYY-MM-DD HH24:MI:SS.FF TZHTZM';
372              
373             To get more than second precision with L
374             for your timestamps, use something like this:
375              
376             use Time::HiRes 'time';
377             my $ts = DateTime->from_epoch(epoch => time);
378              
379             =cut
380              
381             sub connect_call_datetime_setup {
382 0     0 1   my $self = shift;
383              
384 0   0       my $date_format = $ENV{NLS_DATE_FORMAT} ||= 'YYYY-MM-DD HH24:MI:SS';
385             my $timestamp_format = $ENV{NLS_TIMESTAMP_FORMAT} ||=
386 0   0       'YYYY-MM-DD HH24:MI:SS.FF';
387             my $timestamp_tz_format = $ENV{NLS_TIMESTAMP_TZ_FORMAT} ||=
388 0   0       'YYYY-MM-DD HH24:MI:SS.FF TZHTZM';
389              
390 0           $self->_do_query(
391             "alter session set nls_date_format = '$date_format'"
392             );
393 0           $self->_do_query(
394             "alter session set nls_timestamp_format = '$timestamp_format'"
395             );
396 0           $self->_do_query(
397             "alter session set nls_timestamp_tz_format='$timestamp_tz_format'"
398             );
399             }
400              
401             ### Note originally by Ron "Quinn" Straight
402             ### http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=dbsrgits/DBIx-Class.git;a=commitdiff;h=5db2758de644d53e07cd3e05f0e9037bf40116fc
403             #
404             # Handle LOB types in Oracle. Under a certain size (4k?), you can get away
405             # with the driver assuming your input is the deprecated LONG type if you
406             # encode it as a hex string. That ain't gonna fly at larger values, where
407             # you'll discover you have to do what this does.
408             #
409             # This method had to be overridden because we need to set ora_field to the
410             # actual column, and that isn't passed to the call (provided by Storage) to
411             # bind_attribute_by_data_type.
412             #
413             # According to L, the ora_field isn't always necessary, but
414             # adding it doesn't hurt, and will save your bacon if you're modifying a
415             # table with more than one LOB column.
416             #
417             sub _dbi_attrs_for_bind {
418 0     0     my ($self, $ident, $bind) = @_;
419              
420 0           my $attrs = $self->next::method($ident, $bind);
421              
422             # Push the column name into all bind attrs, make sure to *NOT* write into
423             # the existing $attrs->[$idx]{..} hashref, as it is cached by the call to
424             # next::method above.
425             $attrs->[$_]
426             and
427 0           keys %{ $attrs->[$_] }
428             and
429             $bind->[$_][0]{dbic_colname}
430             and
431 0           $attrs->[$_] = { %{$attrs->[$_]}, ora_field => $bind->[$_][0]{dbic_colname} }
432 0   0       for 0 .. $#$attrs;
      0        
      0        
433              
434 0           $attrs;
435             }
436              
437             sub bind_attribute_by_data_type {
438 0     0 1   my ($self, $dt) = @_;
439              
440 0 0         if ($self->_is_lob_type($dt)) {
441              
442             # this is a hot-ish codepath, store an escape-flag in the DBD namespace, so that
443             # things like Class::Unload work (unlikely but possible)
444 0 0         unless ($DBD::Oracle::__DBIC_DBD_VERSION_CHECK_OK__) {
445              
446             # no earlier - no later
447 0 0         if ($DBD::Oracle::VERSION eq '1.23') {
448 0           $self->throw_exception(
449             "BLOB/CLOB support in DBD::Oracle == 1.23 is broken, use an earlier or later ".
450             "version (https://rt.cpan.org/Public/Bug/Display.html?id=46016)"
451             );
452             }
453              
454 0           $DBD::Oracle::__DBIC_DBD_VERSION_CHECK_OK__ = 1;
455             }
456              
457             return {
458 0 0         ora_type => $self->_is_text_lob_type($dt)
459             ? DBD::Oracle::ORA_CLOB()
460             : DBD::Oracle::ORA_BLOB()
461             };
462             }
463             else {
464 0           return undef;
465             }
466             }
467              
468             # Handle blob columns in WHERE.
469             #
470             # For equality comparisons:
471             #
472             # We split data intended for comparing to a LOB into 2000 character chunks and
473             # compare them using dbms_lob.substr on the LOB column.
474             #
475             # We turn off DBD::Oracle LOB binds for these partial LOB comparisons by passing
476             # dbd_attrs => undef, because these are regular varchar2 comparisons and
477             # otherwise the query will fail.
478             #
479             # Since the most common comparison size is likely to be under 4000 characters
480             # (TEXT comparisons previously deployed to other RDBMSes) we disable
481             # prepare_cached for queries with more than two part comparisons to a LOB
482             # column. This is done in _dbh_execute (above) which was previously overridden
483             # to gracefully recover from an Oracle error. This is to be careful to not
484             # exhaust your application's open cursor limit.
485             #
486             # See:
487             # http://itcareershift.com/blog1/2011/02/21/oracle-max-number-of-open-cursors-complete-reference-for-the-new-oracle-dba/
488             # on the open_cursor limit.
489             #
490             # For everything else:
491             #
492             # We assume that everything that is not a LOB comparison, will most likely be a
493             # LIKE query or some sort of function invocation. This may prove to be a naive
494             # assumption in the future, but for now it should cover the two most likely
495             # things users would want to do with a BLOB or CLOB, an equality test or a LIKE
496             # query (on a CLOB.)
497             #
498             # For these expressions, the bind must NOT have the attributes of a LOB bind for
499             # DBD::Oracle, otherwise the query will fail. This is done by passing
500             # dbd_attrs => undef.
501              
502             sub _prep_for_execute {
503 0     0     my $self = shift;
504 0           my ($op) = @_;
505              
506 0 0         return $self->next::method(@_)
507             if $op eq 'insert';
508              
509 0           my ($sql, $bind) = $self->next::method(@_);
510              
511             my $lob_bind_indices = { map {
512 0           (
513             $bind->[$_][0]{sqlt_datatype}
514             and
515             $self->_is_lob_type($bind->[$_][0]{sqlt_datatype})
516 0 0 0       ) ? ( $_ => 1 ) : ()
517             } ( 0 .. $#$bind ) };
518              
519 0 0         return ($sql, $bind) unless %$lob_bind_indices;
520              
521 0           my ($final_sql, @final_binds);
522 0 0 0       if ($op eq 'update') {
    0          
523 0 0         $self->throw_exception('Update with complex WHERE clauses involving BLOB columns currently not supported')
524             if $sql =~ /\bWHERE\b .+ \bWHERE\b/xs;
525              
526 0           my $where_sql;
527 0           ($final_sql, $where_sql) = $sql =~ /^ (.+?) ( \bWHERE\b .+) /xs;
528              
529 0 0         if (my $set_bind_count = $final_sql =~ y/?//) {
530              
531 0           delete $lob_bind_indices->{$_} for (0 .. ($set_bind_count - 1));
532              
533             # bail if only the update part contains blobs
534 0 0         return ($sql, $bind) unless %$lob_bind_indices;
535              
536 0           @final_binds = splice @$bind, 0, $set_bind_count;
537             $lob_bind_indices = { map
538 0           { $_ - $set_bind_count => $lob_bind_indices->{$_} }
  0            
539             keys %$lob_bind_indices
540             };
541             }
542              
543             # if we got that far - assume the where SQL is all we got
544             # (the first part is already shoved into $final_sql)
545 0           $sql = $where_sql;
546             }
547             elsif ($op ne 'select' and $op ne 'delete') {
548 0           $self->throw_exception("Unsupported \$op: $op");
549             }
550              
551 0           my @sql_parts = split /\?/, $sql;
552              
553 0           my $col_equality_re = qr/ (?<=\s) ([\w."]+) (\s*=\s*) $/x;
554              
555 0           for my $b_idx (0 .. $#$bind) {
556 0           my $bound = $bind->[$b_idx];
557              
558 0 0 0       if (
559             $lob_bind_indices->{$b_idx}
560             and
561             my ($col, $eq) = $sql_parts[0] =~ $col_equality_re
562             ) {
563 0           my $data = $bound->[1];
564              
565 0 0         $data = "$data" if ref $data;
566              
567 0           my @parts = unpack '(a2000)*', $data;
568              
569 0           my @sql_frag;
570              
571 0           for my $idx (0..$#parts) {
572 0           push @sql_frag, sprintf (
573             'UTL_RAW.CAST_TO_VARCHAR2(RAWTOHEX(DBMS_LOB.SUBSTR(%s, 2000, %d))) = ?',
574             $col, ($idx*2000 + 1),
575             );
576             }
577              
578 0           my $sql_frag = '( ' . (join ' AND ', @sql_frag) . ' )';
579              
580 0           $sql_parts[0] =~ s/$col_equality_re/$sql_frag/;
581              
582 0           $final_sql .= shift @sql_parts;
583              
584 0           for my $idx (0..$#parts) {
585             push @final_binds, [
586             {
587 0           %{ $bound->[0] },
  0            
588             _ora_lob_autosplit_part => $idx,
589             dbd_attrs => undef,
590             },
591             $parts[$idx]
592             ];
593             }
594             }
595             else {
596 0           $final_sql .= shift(@sql_parts) . '?';
597             push @final_binds, $lob_bind_indices->{$b_idx}
598             ? [
599             {
600 0 0         %{ $bound->[0] },
  0            
601             dbd_attrs => undef,
602             },
603             $bound->[1],
604             ] : $bound
605             ;
606             }
607             }
608              
609 0 0         if (@sql_parts > 1) {
610 0           carp "There are more placeholders than binds, this should not happen!";
611 0           @sql_parts = join ('?', @sql_parts);
612             }
613              
614 0           $final_sql .= $sql_parts[0];
615              
616 0           return ($final_sql, \@final_binds);
617             }
618              
619             # Savepoints stuff.
620              
621             sub _exec_svp_begin {
622 0     0     my ($self, $name) = @_;
623 0           $self->_dbh->do("SAVEPOINT $name");
624             }
625              
626             # Oracle automatically releases a savepoint when you start another one with the
627             # same name.
628 0     0     sub _exec_svp_release { 1 }
629              
630             sub _exec_svp_rollback {
631 0     0     my ($self, $name) = @_;
632 0           $self->_dbh->do("ROLLBACK TO SAVEPOINT $name")
633             }
634              
635             =head2 relname_to_table_alias
636              
637             L uses L names as table aliases in
638             queries.
639              
640             Unfortunately, Oracle doesn't support identifiers over 30 chars in length, so
641             the L name is shortened and appended with half of an
642             MD5 hash.
643              
644             See L.
645              
646             =cut
647              
648             sub relname_to_table_alias {
649 0     0 1   my $self = shift;
650 0           my ($relname, $join_count) = @_;
651              
652 0           my $alias = $self->next::method(@_);
653              
654             # we need to shorten here in addition to the shortening in SQLA itself,
655             # since the final relnames are crucial for the join optimizer
656 0           return $self->sql_maker->_shorten_identifier($alias);
657             }
658              
659             =head2 with_deferred_fk_checks
660              
661             Runs a coderef between:
662              
663             alter session set constraints = deferred
664             ...
665             alter session set constraints = immediate
666              
667             to defer foreign key checks.
668              
669             Constraints must be declared C for this to work.
670              
671             =cut
672              
673             sub with_deferred_fk_checks {
674 0     0 1   my ($self, $sub) = @_;
675              
676 0           my $txn_scope_guard = $self->txn_scope_guard;
677              
678 0           $self->_do_query('alter session set constraints = deferred');
679              
680             my $sg = Scope::Guard->new(sub {
681 0     0     $self->_do_query('alter session set constraints = immediate');
682 0           });
683              
684             return
685 0     0     preserve_context { $sub->() } after => sub { $txn_scope_guard->commit };
  0            
  0            
686             }
687              
688             =head1 ATTRIBUTES
689              
690             Following additional attributes can be used in resultsets.
691              
692             =head2 connect_by or connect_by_nocycle
693              
694             =over 4
695              
696             =item Value: \%connect_by
697              
698             =back
699              
700             A hashref of conditions used to specify the relationship between parent rows
701             and child rows of the hierarchy.
702              
703              
704             connect_by => { parentid => 'prior personid' }
705              
706             # adds a connect by statement to the query:
707             # SELECT
708             # me.persionid me.firstname, me.lastname, me.parentid
709             # FROM
710             # person me
711             # CONNECT BY
712             # parentid = prior persionid
713              
714              
715             connect_by_nocycle => { parentid => 'prior personid' }
716              
717             # adds a connect by statement to the query:
718             # SELECT
719             # me.persionid me.firstname, me.lastname, me.parentid
720             # FROM
721             # person me
722             # CONNECT BY NOCYCLE
723             # parentid = prior persionid
724              
725              
726             =head2 start_with
727              
728             =over 4
729              
730             =item Value: \%condition
731              
732             =back
733              
734             A hashref of conditions which specify the root row(s) of the hierarchy.
735              
736             It uses the same syntax as L
737              
738             start_with => { firstname => 'Foo', lastname => 'Bar' }
739              
740             # SELECT
741             # me.persionid me.firstname, me.lastname, me.parentid
742             # FROM
743             # person me
744             # START WITH
745             # firstname = 'foo' and lastname = 'bar'
746             # CONNECT BY
747             # parentid = prior persionid
748              
749             =head2 order_siblings_by
750              
751             =over 4
752              
753             =item Value: ($order_siblings_by | \@order_siblings_by)
754              
755             =back
756              
757             Which column(s) to order the siblings by.
758              
759             It uses the same syntax as L
760              
761             'order_siblings_by' => 'firstname ASC'
762              
763             # SELECT
764             # me.persionid me.firstname, me.lastname, me.parentid
765             # FROM
766             # person me
767             # CONNECT BY
768             # parentid = prior persionid
769             # ORDER SIBLINGS BY
770             # firstname ASC
771              
772             =head1 FURTHER QUESTIONS?
773              
774             Check the list of L.
775              
776             =head1 COPYRIGHT AND LICENSE
777              
778             This module is free software L
779             by the L. You can
780             redistribute it and/or modify it under the same terms as the
781             L.
782              
783             =cut
784              
785             1;
786             # vim:sts=2 sw=2: