File Coverage

blib/lib/DBIx/Class/Storage/DBI/Oracle/Generic.pm
Criterion Covered Total %
statement 27 197 13.7
branch 0 76 0.0
condition 0 47 0.0
subroutine 9 32 28.1
pod 6 6 100.0
total 42 358 11.7


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