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   1175 use strict;
  3         7  
  3         81  
4 3     3   14 use warnings;
  3         6  
  3         77  
5 3     3   15 use base qw/DBIx::Class::Storage::DBI/;
  3         5  
  3         304  
6 3     3   19 use mro 'c3';
  3         8  
  3         22  
7 3     3   89 use DBIx::Class::Carp;
  3         8  
  3         34  
8 3     3   21 use Scope::Guard ();
  3         15  
  3         62  
9 3     3   15 use Context::Preserve 'preserve_context';
  3         13  
  3         146  
10 3     3   19 use Try::Tiny;
  3         6  
  3         163  
11 3     3   21 use List::Util 'first';
  3         7  
  3         144  
12 3     3   35 use namespace::clean;
  3         6  
  3         16  
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<DBIx::Class::Storage::DBI::Oracle::WhereJoins> 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<DBIx::Class::InflateColumn::DateTime>.
352              
353             =head2 connect_call_datetime_setup
354              
355             Used as:
356              
357             on_connect_call => 'datetime_setup'
358              
359             In L<connect_info|DBIx::Class::Storage::DBI/connect_info> to set the session nls
360             date, and timestamp values for use with L<DBIx::Class::InflateColumn::DateTime>
361             and the necessary environment variables for L<DateTime::Format::Oracle>, 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<DBIx::Class::InflateColumn::DateTime>
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 <quinnfazigu@gmail.org>
402             ### https://github.com/Perl5/DBIx-Class/commit/5db2758de6
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<DBD::Oracle>, 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<DBIx::Class> uses L<DBIx::Class::Relationship> names as table aliases in
638             queries.
639              
640             Unfortunately, Oracle doesn't support identifiers over 30 chars in length, so
641             the L<DBIx::Class::Relationship> name is shortened and appended with half of an
642             MD5 hash.
643              
644             See L<DBIx::Class::Storage::DBI/relname_to_table_alias>.
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<DEFERRABLE> 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<DBIx::Class::ResultSet/search>
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<DBIx::Class::ResultSet/order_by>
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<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>.
775              
776             =head1 COPYRIGHT AND LICENSE
777              
778             This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE>
779             by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can
780             redistribute it and/or modify it under the same terms as the
781             L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>.
782              
783             =cut
784              
785             1;
786             # vim:sts=2 sw=2: