1 package # Hide from PAUSE
2 DBIx::Class::SQLAHacks;
4 use base qw/SQL::Abstract::Limit/;
7 use Carp::Clan qw/^DBIx::Class|^SQL::Abstract/;
10 # reinstall the carp()/croak() functions imported into SQL::Abstract
11 # as Carp and Carp::Clan do not like each other much
12 no warnings qw/redefine/;
14 for my $f (qw/carp croak/) {
15 my $orig = \&{"SQL::Abstract::$f"};
16 *{"SQL::Abstract::$f"} = sub {
18 local $Carp::CarpLevel = 1; # even though Carp::Clan ignores this, $orig will not
20 if (Carp::longmess() =~ /DBIx::Class::SQLAHacks::[\w]+\(\) called/) {
21 __PACKAGE__->can($f)->(@_);
31 my $self = shift->SUPER::new(@_);
33 # This prevents the caching of $dbh in S::A::L, I believe
34 # If limit_dialect is a ref (like a $dbh), go ahead and replace
35 # it with what it resolves to:
36 $self->{limit_dialect} = $self->_find_syntax($self->{limit_dialect})
37 if ref $self->{limit_dialect};
43 # Some databases (sqlite) do not handle multiple parenthesis
44 # around in/between arguments. A tentative x IN ( ( 1, 2 ,3) )
45 # is interpreted as x IN 1 or something similar.
47 # Since we currently do not have access to the SQLA AST, resort
48 # to barbaric mutilation of any SQL supplied in literal form
50 sub _strip_outer_paren {
51 my ($self, $arg) = @_;
53 return $self->_SWITCH_refkind ($arg, {
55 $$arg->[0] = __strip_outer_paren ($$arg->[0]);
59 return \__strip_outer_paren( $$arg );
67 sub __strip_outer_paren {
70 if ($sql and not ref $sql) {
71 while ($sql =~ /^ \s* \( (.*) \) \s* $/x ) {
80 my ($self, $lhs, $op, $rhs) = @_;
81 $rhs = $self->_strip_outer_paren ($rhs);
82 return $self->SUPER::_where_field_IN ($lhs, $op, $rhs);
85 sub _where_field_BETWEEN {
86 my ($self, $lhs, $op, $rhs) = @_;
87 $rhs = $self->_strip_outer_paren ($rhs);
88 return $self->SUPER::_where_field_BETWEEN ($lhs, $op, $rhs);
91 # Slow but ANSI standard Limit/Offset support. DB2 uses this
93 my ($self, $sql, $order, $rows, $offset ) = @_;
96 my $last = $rows + $offset - 1;
97 my ( $order_by ) = $self->_order_by( $order );
102 SELECT Q1.*, ROW_NUMBER() OVER( ) AS ROW_NUM FROM (
107 WHERE ROW_NUM BETWEEN $offset AND $last
114 # Crappy Top based Limit/Offset support. MSSQL uses this currently,
115 # but may have to switch to RowNumberOver one day
117 my ( $self, $sql, $order, $rows, $offset ) = @_;
119 # mangle the input sql so it can be properly aliased in the outer queries
120 $sql =~ s/^ \s* SELECT \s+ (.+?) \s+ (?=FROM)//ix
121 or croak "Unrecognizable SELECT: $sql";
123 my @sql_select = split (/\s*,\s*/, $sql_select);
125 # we can't support subqueries (in fact MSSQL can't) - croak
126 if (@sql_select != @{$self->{_dbic_rs_attrs}{select}}) {
128 'SQL SELECT did not parse cleanly - retrieved %d comma separated elements, while '
129 . 'the resultset select attribure contains %d elements: %s',
131 scalar @{$self->{_dbic_rs_attrs}{select}},
136 my $name_sep = $self->name_sep || '.';
137 $name_sep = "\Q$name_sep\E";
138 my $col_re = qr/ ^ (?: (.+) $name_sep )? ([^$name_sep]+) $ /x;
140 # construct the new select lists, rename(alias) some columns if necessary
141 my (@outer_select, @inner_select, %seen_names, %col_aliases, %outer_col_aliases);
143 for (@{$self->{_dbic_rs_attrs}{select}}) {
145 my ($table, $orig_colname) = ( $_ =~ $col_re );
147 $seen_names{$orig_colname}++;
150 for my $i (0 .. $#sql_select) {
152 my $colsel_arg = $self->{_dbic_rs_attrs}{select}[$i];
153 my $colsel_sql = $sql_select[$i];
155 # this may or may not work (in case of a scalarref or something)
156 my ($table, $orig_colname) = ( $colsel_arg =~ $col_re );
159 # do not attempt to understand non-scalar selects - alias numerically
160 if (ref $colsel_arg) {
161 $quoted_alias = $self->_quote ('column_' . (@inner_select + 1) );
163 # column name seen more than once - alias it
164 elsif ($orig_colname && ($seen_names{$orig_colname} > 1) ) {
165 $quoted_alias = $self->_quote ("${table}__${orig_colname}");
168 # we did rename - make a record and adjust
171 push @inner_select, "$colsel_sql AS $quoted_alias";
173 # push alias to outer
174 push @outer_select, $quoted_alias;
176 # Any aliasing accumulated here will be considered
177 # both for inner and outer adjustments of ORDER BY
178 $self->__record_alias (
182 $table ? $orig_colname : undef,
186 # otherwise just leave things intact inside, and use the abbreviated one outside
187 # (as we do not have table names anymore)
189 push @inner_select, $colsel_sql;
191 my $outer_quoted = $self->_quote ($orig_colname); # it was not a duplicate so should just work
192 push @outer_select, $outer_quoted;
193 $self->__record_alias (
197 $table ? $orig_colname : undef,
202 my $outer_select = join (', ', @outer_select );
203 my $inner_select = join (', ', @inner_select );
205 %outer_col_aliases = (%outer_col_aliases, %col_aliases);
208 croak '$order supplied to SQLAHacks limit emulators must be a hash'
209 if (ref $order ne 'HASH');
211 $order = { %$order }; #copy
213 my $req_order = [ $self->_order_by_chunks ($order->{order_by}) ];
214 my $limit_order = [ @$req_order ? @$req_order : $self->_order_by_chunks ($order->{_virtual_order_by}) ];
216 my ( $order_by_inner, $order_by_outer ) = $self->_order_directions($limit_order);
217 my $order_by_requested = $self->_order_by ($req_order);
220 delete $order->{$_} for qw/order_by _virtual_order_by/;
221 my $grpby_having = $self->_order_by ($order);
223 # short circuit for counts - the ordering complexity is needless
224 if ($self->{_dbic_rs_attrs}{-for_count_only}) {
225 return "SELECT TOP $rows $inner_select $sql $grpby_having $order_by_outer";
229 # we can't really adjust the order_by columns, as introspection is lacking
230 # resort to simple substitution
231 for my $col (keys %outer_col_aliases) {
232 for ($order_by_requested, $order_by_outer) {
233 $_ =~ s/\s+$col\s+/ $outer_col_aliases{$col} /g;
236 for my $col (keys %col_aliases) {
237 $order_by_inner =~ s/\s+$col\s+/$col_aliases{$col}/g;
240 my $inner_lim = $rows + $offset;
242 $sql = "SELECT TOP $inner_lim $inner_select $sql $grpby_having $order_by_inner";
247 SELECT TOP $rows $outer_select FROM
256 if ($order_by_requested) {
259 SELECT $outer_select FROM
260 ( $sql ) AS outer_sel
269 # action at a distance to shorten Top code above
271 my ($self, $register, $alias, $fqcol, $col) = @_;
273 # record qualified name
274 $register->{$fqcol} = $alias;
275 $register->{$self->_quote($fqcol)} = $alias;
279 # record unqialified name, undef (no adjustment) if a duplicate is found
280 if (exists $register->{$col}) {
281 $register->{$col} = undef;
284 $register->{$col} = $alias;
287 $register->{$self->_quote($col)} = $register->{$col};
292 # While we're at it, this should make LIMIT queries more efficient,
293 # without digging into things too deeply
295 my ($self, $syntax) = @_;
296 return $self->{_cached_syntax} ||= $self->SUPER::_find_syntax($syntax);
300 update => 'FOR UPDATE',
301 shared => 'FOR SHARE',
304 my ($self, $table, $fields, $where, $order, @rest) = @_;
306 $self->{"${_}_bind"} = [] for (qw/having from order/);
308 if (ref $table eq 'SCALAR') {
311 elsif (not ref $table) {
312 $table = $self->_quote($table);
314 local $self->{rownum_hack_count} = 1
315 if (defined $rest[0] && $self->{limit_dialect} eq 'RowNum');
316 @rest = (-1) unless defined $rest[0];
317 croak "LIMIT 0 Does Not Compute" if $rest[0] == 0;
318 # and anyway, SQL::Abstract::Limit will cause a barf if we don't first
319 my ($sql, @where_bind) = $self->SUPER::select(
320 $table, $self->_recurse_fields($fields), $where, $order, @rest
322 if (my $for = delete $self->{_dbic_rs_attrs}{for}) {
323 $sql .= " $for_syntax->{$for}" if $for_syntax->{$for};
326 return wantarray ? ($sql, @{$self->{from_bind}}, @where_bind, @{$self->{having_bind}}, @{$self->{order_bind}} ) : $sql;
332 $table = $self->_quote($table) unless ref($table);
334 # SQLA will emit INSERT INTO $table ( ) VALUES ( )
335 # which is sadly understood only by MySQL. Change default behavior here,
336 # until SQLA2 comes with proper dialect support
337 if (! $_[0] or (ref $_[0] eq 'HASH' and !keys %{$_[0]} ) ) {
338 return "INSERT INTO ${table} DEFAULT VALUES"
341 $self->SUPER::insert($table, @_);
347 $table = $self->_quote($table) unless ref($table);
348 $self->SUPER::update($table, @_);
354 $table = $self->_quote($table) unless ref($table);
355 $self->SUPER::delete($table, @_);
361 return $_[1].$self->_order_by($_[2]);
363 return $self->SUPER::_emulate_limit(@_);
367 sub _recurse_fields {
368 my ($self, $fields, $params) = @_;
369 my $ref = ref $fields;
370 return $self->_quote($fields) unless $ref;
371 return $$fields if $ref eq 'SCALAR';
373 if ($ref eq 'ARRAY') {
374 return join(', ', map {
375 $self->_recurse_fields($_)
376 .(exists $self->{rownum_hack_count} && !($params && $params->{no_rownum_hack})
377 ? ' AS col'.$self->{rownum_hack_count}++
380 } elsif ($ref eq 'HASH') {
381 foreach my $func (keys %$fields) {
382 if ($func eq 'distinct') {
383 my $_fields = $fields->{$func};
384 if (ref $_fields eq 'ARRAY' && @{$_fields} > 1) {
386 'The select => { distinct => ... } syntax is not supported for multiple columns.'
387 .' Instead please use { group_by => [ qw/' . (join ' ', @$_fields) . '/ ] }'
388 .' or { select => [ qw/' . (join ' ', @$_fields) . '/ ], distinct => 1 }'
392 $_fields = @{$_fields}[0] if ref $_fields eq 'ARRAY';
394 'The select => { distinct => ... } syntax will be deprecated in DBIC version 0.09,'
395 ." please use { group_by => '${_fields}' } or { select => '${_fields}', distinct => 1 }"
399 return $self->_sqlcase($func)
400 .'( '.$self->_recurse_fields($fields->{$func}).' )';
403 # Is the second check absolutely necessary?
404 elsif ( $ref eq 'REF' and ref($$fields) eq 'ARRAY' ) {
405 return $self->_fold_sqlbind( $fields );
408 croak($ref . qq{ unexpected in _recurse_fields()})
413 my ($self, $arg) = @_;
415 if (ref $arg eq 'HASH' and keys %$arg and not grep { $_ =~ /^-(?:desc|asc)/i } keys %$arg ) {
419 if (defined $arg->{group_by}) {
420 $ret = $self->_sqlcase(' group by ')
421 .$self->_recurse_fields($arg->{group_by}, { no_rownum_hack => 1 });
424 if (defined $arg->{having}) {
425 my ($frag, @bind) = $self->_recurse_where($arg->{having});
426 push(@{$self->{having_bind}}, @bind);
427 $ret .= $self->_sqlcase(' having ').$frag;
430 if (defined $arg->{order_by}) {
431 my ($frag, @bind) = $self->SUPER::_order_by($arg->{order_by});
432 push(@{$self->{order_bind}}, @bind);
439 my ($sql, @bind) = $self->SUPER::_order_by ($arg);
440 push(@{$self->{order_bind}}, @bind);
445 sub _order_directions {
446 my ($self, $order) = @_;
448 # strip bind values - none of the current _order_directions users support them
449 return $self->SUPER::_order_directions( [ map
450 { ref $_ ? $_->[0] : $_ }
451 $self->_order_by_chunks ($order)
456 my ($self, $from) = @_;
457 if (ref $from eq 'ARRAY') {
458 return $self->_recurse_from(@$from);
459 } elsif (ref $from eq 'HASH') {
460 return $self->_make_as($from);
462 return $from; # would love to quote here but _table ends up getting called
463 # twice during an ->select without a limit clause due to
464 # the way S::A::Limit->select works. should maybe consider
465 # bypassing this and doing S::A::select($self, ...) in
466 # our select method above. meantime, quoting shims have
467 # been added to select/insert/update/delete here
472 my ($self, $from, @join) = @_;
474 push(@sqlf, $self->_make_as($from));
475 foreach my $j (@join) {
478 # check whether a join type exists
479 my $join_clause = '';
480 my $to_jt = ref($to) eq 'ARRAY' ? $to->[0] : $to;
481 if (ref($to_jt) eq 'HASH' and exists($to_jt->{-join_type})) {
482 $join_clause = ' '.uc($to_jt->{-join_type}).' JOIN ';
484 $join_clause = ' JOIN ';
486 push(@sqlf, $join_clause);
488 if (ref $to eq 'ARRAY') {
489 push(@sqlf, '(', $self->_recurse_from(@$to), ')');
491 push(@sqlf, $self->_make_as($to));
493 push(@sqlf, ' ON ', $self->_join_condition($on));
495 return join('', @sqlf);
499 my ($self, $sqlbind) = @_;
501 my @sqlbind = @$$sqlbind; # copy
502 my $sql = shift @sqlbind;
503 push @{$self->{from_bind}}, @sqlbind;
509 my ($self, $from) = @_;
510 return join(' ', map { (ref $_ eq 'SCALAR' ? $$_
511 : ref $_ eq 'REF' ? $self->_fold_sqlbind($_)
513 } reverse each %{$self->_skip_options($from)});
517 my ($self, $hash) = @_;
519 $clean_hash->{$_} = $hash->{$_}
520 for grep {!/^-/} keys %$hash;
524 sub _join_condition {
525 my ($self, $cond) = @_;
526 if (ref $cond eq 'HASH') {
531 croak (ref($v) . qq{ reference arguments are not supported in JOINS - try using \"..." instead'})
532 if ref($v) ne 'SCALAR';
536 my $x = '= '.$self->_quote($v); $j{$_} = \$x;
539 return scalar($self->_recurse_where(\%j));
540 } elsif (ref $cond eq 'ARRAY') {
541 return join(' OR ', map { $self->_join_condition($_) } @$cond);
543 die "Can't handle this yet!";
548 my ($self, $label) = @_;
549 return '' unless defined $label;
550 return "*" if $label eq '*';
551 return $label unless $self->{quote_char};
552 if(ref $self->{quote_char} eq "ARRAY"){
553 return $self->{quote_char}->[0] . $label . $self->{quote_char}->[1]
554 if !defined $self->{name_sep};
555 my $sep = $self->{name_sep};
556 return join($self->{name_sep},
557 map { $self->{quote_char}->[0] . $_ . $self->{quote_char}->[1] }
558 split(/\Q$sep\E/,$label));
560 return $self->SUPER::_quote($label);
565 $self->{limit_dialect} = shift if @_;
566 return $self->{limit_dialect};
571 $self->{quote_char} = shift if @_;
572 return $self->{quote_char};
577 $self->{name_sep} = shift if @_;
578 return $self->{name_sep};
589 DBIx::Class::SQLAHacks - This module is a subclass of SQL::Abstract::Limit
590 and includes a number of DBIC-specific workarounds, not yet suitable for
591 inclusion into SQLA proper.
597 Tries to determine limit dialect.
601 Quotes table names, handles "limit" dialects (e.g. where rownum between x and
602 y), supports SELECT ... FOR UPDATE and SELECT ... FOR SHARE.
604 =head2 insert update delete
606 Just quotes table names.
610 Specifies the dialect of used for implementing an SQL "limit" clause for
611 restricting the number of query results returned. Valid values are: RowNum.
613 See L<DBIx::Class::Storage::DBI/connect_info> for details.
617 Character separating quoted table names.
619 See L<DBIx::Class::Storage::DBI/connect_info> for details.
623 Set to an array-ref to specify separate left and right quotes for table names.
625 See L<DBIx::Class::Storage::DBI/connect_info> for details.