X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FSQLAHacks%2FOracle.pm;h=23c12857337c5c2a4ac823bcf520d713b0c95607;hb=aa9bcbfb851c89bdedd331806beb0c66ddf11709;hp=a4a815ed51faf9654a48fe0d32c6ceb45781b85b;hpb=2df1857d287a0481224caef360f29b9eeb1421ae;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/SQLAHacks/Oracle.pm b/lib/DBIx/Class/SQLAHacks/Oracle.pm index a4a815e..23c1285 100644 --- a/lib/DBIx/Class/SQLAHacks/Oracle.pm +++ b/lib/DBIx/Class/SQLAHacks/Oracle.pm @@ -1,41 +1,44 @@ package # Hide from PAUSE DBIx::Class::SQLAHacks::Oracle; +use warnings; +use strict; + use base qw( DBIx::Class::SQLAHacks ); use Carp::Clan qw/^DBIx::Class|^SQL::Abstract/; -# -# TODO: -# - Check the parameter syntax of connect_by -# - Review by experienced DBIC/SQL:A developers :-) -# - Check NOCYCLE parameter -# http://download.oracle.com/docs/cd/B19306_01/server.102/b14200/pseudocolumns001.htm#i1009434 -# +sub new { + my $self = shift; + my %opts = (ref $_[0] eq 'HASH') ? %{$_[0]} : @_; + push @{$opts{special_ops}}, { + regex => qr/^prior$/i, + handler => '_where_field_PRIOR', + }; -sub select { - my ($self, $table, $fields, $where, $order, @rest) = @_; + $self->SUPER::new (\%opts); +} - $self->{_db_specific_attrs} = pop @rest; +sub _assemble_binds { + my $self = shift; + return map { @{ (delete $self->{"${_}_bind"}) || [] } } (qw/from where oracle_connect_by having order/); +} - my ($sql, @bind) = $self->SUPER::select($table, $fields, $where, $order, @rest); - push @bind, @{$self->{_oracle_connect_by_binds}}; - return wantarray ? ($sql, @bind) : $sql; -} +sub _parse_rs_attrs { + my $self = shift; + my ($rs_attrs) = @_; -sub _emulate_limit { - my ( $self, $syntax, $sql, $order, $rows, $offset ) = @_; + my ($cb_sql, @cb_bind) = $self->_connect_by($rs_attrs); + push @{$self->{oracle_connect_by_bind}}, @cb_bind; - my ($cb_sql, @cb_bind) = $self->_connect_by(); - $sql .= $cb_sql; - $self->{_oracle_connect_by_binds} = \@cb_bind; + my $sql = $self->SUPER::_parse_rs_attrs(@_); - return $self->SUPER::_emulate_limit($syntax, $sql, $order, $rows, $offset); + return "$cb_sql $sql"; } sub _connect_by { - my ($self) = @_; - my $attrs = $self->{_db_specific_attrs}; + my ($self, $attrs) = @_; + my $sql = ''; my @bind; @@ -45,17 +48,14 @@ sub _connect_by { $sql .= $self->_sqlcase(' start with ') . $ws; push @bind, @wb; } - if ( my $connect_by = $attrs->{'connect_by'}) { - my ($connect_by_sql, @connect_by_sql_bind) = $self->_recurse_where( $attrs->{'connect_by'} ); + if ( my $connect_by = $attrs->{'connect_by'} || $attrs->{'connect_by_nocycle'} ) { + my ($connect_by_sql, @connect_by_sql_bind) = $self->_recurse_where( $connect_by ); $sql .= sprintf(" %s %s", - $self->_sqlcase('connect by'), + ( $attrs->{'connect_by_nocycle'} ) ? $self->_sqlcase('connect by nocycle') + : $self->_sqlcase('connect by'), $connect_by_sql, ); push @bind, @connect_by_sql_bind; - # $sql .= $self->_sqlcase(' connect by'); - # foreach my $key ( keys %$connect_by ) { - # $sql .= " $key = " . $connect_by->{$key}; - # } } if ( $attrs->{'order_siblings_by'} ) { $sql .= $self->_order_siblings_by( $attrs->{'order_siblings_by'} ); @@ -66,16 +66,121 @@ sub _connect_by { } sub _order_siblings_by { - my $self = shift; - my $ref = ref $_[0]; + my ( $self, $arg ) = @_; + + my ( @sql, @bind ); + for my $c ( $self->_order_by_chunks($arg) ) { + $self->_SWITCH_refkind( + $c, + { + SCALAR => sub { push @sql, $c }, + ARRAYREF => sub { push @sql, shift @$c; push @bind, @$c }, + } + ); + } + + my $sql = + @sql + ? sprintf( '%s %s', $self->_sqlcase(' order siblings by'), join( ', ', @sql ) ) + : ''; + + return wantarray ? ( $sql, @bind ) : $sql; +} + +# we need to add a '=' only when PRIOR is used against a column diretly +# i.e. when it is invoked by a special_op callback +sub _where_field_PRIOR { + my ($self, $lhs, $op, $rhs) = @_; + my ($sql, @bind) = $self->_recurse_where ($rhs); + + $sql = sprintf ('%s = %s %s ', + $self->_convert($self->_quote($lhs)), + $self->_sqlcase ($op), + $sql + ); + + return ($sql, @bind); +} + +# this takes an identifier and shortens it if necessary +# optionally keywords can be passed as an arrayref to generate useful +# identifiers +sub _shorten_identifier { + my ($self, $to_shorten, $keywords) = @_; + + # 30 characters is the identifier limit for Oracle + my $max_len = 30; + # we want at least 10 characters of the base36 md5 + my $min_entropy = 10; + + my $max_trunc = $max_len - $min_entropy - 1; + + return $to_shorten + if length($to_shorten) <= $max_len; + + croak 'keywords needs to be an arrayref' + if defined $keywords && ref $keywords ne 'ARRAY'; + + # if no keywords are passed use the identifier as one + my @keywords = @{$keywords || []}; + @keywords = $to_shorten unless @keywords; + + # get a base36 md5 of the identifier + require Digest::MD5; + require Math::BigInt; + require Math::Base36; + my $b36sum = Math::Base36::encode_base36( + Math::BigInt->from_hex ( + '0x' . Digest::MD5::md5_hex ($to_shorten) + ) + ); + + # switch from perl to java + # get run-length + my ($concat_len, @lengths); + for (@keywords) { + $_ = ucfirst (lc ($_)); + $_ =~ s/\_+(\w)/uc ($1)/eg; + + push @lengths, length ($_); + $concat_len += $lengths[-1]; + } + + # if we are still too long - try to disemvowel non-capitals (not keyword starts) + if ($concat_len > $max_trunc) { + $concat_len = 0; + @lengths = (); + + for (@keywords) { + $_ =~ s/[aeiou]//g; + + push @lengths, length ($_); + $concat_len += $lengths[-1]; + } + } + + # still too long - just start cuting proportionally + if ($concat_len > $max_trunc) { + my $trim_ratio = $max_trunc / $concat_len; + + for my $i (0 .. $#keywords) { + $keywords[$i] = substr ($keywords[$i], 0, int ($trim_ratio * $lengths[$i] ) ); + } + } + + my $fin = join ('', @keywords); + my $fin_len = length $fin; + + return sprintf ('%s_%s', + $fin, + substr ($b36sum, 0, $max_len - $fin_len - 1), + ); +} - my @vals = $ref eq 'ARRAY' ? @{$_[0]} : - $ref eq 'SCALAR' ? ${$_[0]} : - $ref eq '' ? $_[0] : - puke( "Unsupported data struct $ref for ORDER SIBILINGS BY" ); +sub _unqualify_colname { + my ($self, $fqcn) = @_; - my $val = join ', ', map { $self->_quote($_) } @vals; - return $val ? $self->_sqlcase(' order siblings by')." $val" : ''; + return $self->_shorten_identifier($self->next::method($fqcn)); } 1; @@ -90,7 +195,7 @@ DBIx::Class::SQLAHacks::Oracle - adds hierarchical query support for Oracle to S =head1 DESCRIPTION -See L for more informations about +See L for more information about how to use hierarchical queries with DBIx::Class. =cut