remove a literal "" in favour of undef
[dbsrgits/SQL-Abstract.git] / lib / SQL / Abstract.pm
index 50eb48b..092598b 100644 (file)
@@ -160,6 +160,13 @@ sub new {
   # regexes are applied in order, thus push after user-defines
   push @{$opt{special_ops}}, @BUILTIN_SPECIAL_OPS;
 
+  if ($class->isa('DBIx::Class::SQLMaker')) {
+    push @{$opt{special_ops}}, our $DBIC_Compat_Op ||= {
+      regex => qr/^(?:ident|value)$/i, handler => sub { die "NOPE" }
+    };
+    $opt{is_dbic_sqlmaker} = 1;
+  }
+
   # unary operators
   $opt{unary_ops} ||= [];
 
@@ -179,7 +186,7 @@ sub new {
 
   $opt{node_types} = +{
     map +("-$_" => '_render_'.$_),
-      qw(op func value bind ident literal)
+      qw(op func bind ident literal list)
   };
 
   $opt{expand_unary} = {};
@@ -499,6 +506,7 @@ sub where {
 sub _expand_expr {
   my ($self, $expr, $logic, $default_scalar_to) = @_;
   local our $Default_Scalar_To = $default_scalar_to if $default_scalar_to;
+  our $Expand_Depth ||= 0; local $Expand_Depth = $Expand_Depth + 1;
   return undef unless defined($expr);
   if (ref($expr) eq 'HASH') {
     if (keys %$expr > 1) {
@@ -509,14 +517,19 @@ sub _expand_expr {
           sort keys %$expr
       ] };
     }
-    return unless %$expr;
+    return { -literal => [ '' ] } unless keys %$expr;
     return $self->_expand_expr_hashpair(%$expr, $logic);
   }
   if (ref($expr) eq 'ARRAY') {
     my $logic = lc($logic || $self->{logic});
     $logic eq 'and' or $logic eq 'or' or puke "unknown logic: $logic";
 
-    my @expr = @$expr;
+    #my @expr = @$expr;
+    my @expr = grep {
+      (ref($_) eq 'ARRAY' and @$_)
+      or (ref($_) eq 'HASH' and %$_)
+      or 1
+    } @$expr;
 
     my @res;
 
@@ -525,13 +538,15 @@ sub _expand_expr {
         unless defined($el) and length($el);
       my $elref = ref($el);
       if (!$elref) {
+        local $Expand_Depth = 0;
         push(@res, $self->_expand_expr({ $el, shift(@expr) }));
       } elsif ($elref eq 'ARRAY') {
         push(@res, $self->_expand_expr($el)) if @$el;
       } elsif (my $l = is_literal_value($el)) {
         push @res, { -literal => $l };
       } elsif ($elref eq 'HASH') {
-        push @res, $self->_expand_expr($el);
+        local $Expand_Depth = 0;
+        push @res, $self->_expand_expr($el) if %$el;
       } else {
         die "notreached";
       }
@@ -543,12 +558,12 @@ sub _expand_expr {
   }
   if (!ref($expr) or Scalar::Util::blessed($expr)) {
     if (my $d = $Default_Scalar_To) {
-      return +{ $d => $expr };
+      return $self->_expand_expr({ $d => $expr });
     }
     if (my $m = our $Cur_Col_Meta) {
       return +{ -bind => [ $m, $expr ] };
     }
-    return +{ -value => $expr };
+    return +{ -bind => [ undef, $expr ] };
   }
   die "notreached";
 }
@@ -569,6 +584,17 @@ sub _expand_expr_hashpair {
           . "You probably wanted ...-and => [ $k => COND1, $k => COND2 ... ]";
     }
     if ($k eq '-nest') {
+      # DBIx::Class requires a nest warning to be emitted once but the private
+      # method it overrode to do so no longer exists
+      if ($self->{is_dbic_sqlmaker}) {
+        unless (our $Nest_Warned) {
+          belch(
+            "-nest in search conditions is deprecated, you most probably wanted:\n"
+            .q|{..., -and => [ \%cond0, \@cond1, \'cond2', \[ 'cond3', [ col => bind ] ], etc. ], ... }|
+          );
+          $Nest_Warned = 1;
+        }
+      }
       return $self->_expand_expr($v);
     }
     if ($k eq '-bool') {
@@ -600,14 +626,21 @@ sub _expand_expr_hashpair {
       $op =~ s/^-// if length($op) > 1;
     
       # top level special ops are illegal in general
-      puke "Illegal use of top-level '-$op'"
-        if List::Util::first { $op =~ $_->{regex} } @{$self->{special_ops}};
+      # note that, arguably, if it makes no sense at top level, it also
+      # makes no sense on the other side of an = sign or similar but DBIC
+      # gets disappointingly upset if I disallow it
+      if (
+        (our $Expand_Depth) == 1
+        and List::Util::first { $op =~ $_->{regex} } @{$self->{special_ops}}
+      ) {
+        puke "Illegal use of top-level '-$op'"
+      }
       if (my $us = List::Util::first { $op =~ $_->{regex} } @{$self->{unary_ops}}) {
         return { -op => [ $op, $v ] };
       }
     }
-    if ($k eq '-value' and my $m = our $Cur_Col_Meta) {
-      return +{ -bind => [ $m, $v ] };
+    if ($k eq '-value') {
+      return +{ -bind => [ our $Cur_Col_Meta, $v ] };
     }
     if (my $custom = $self->{expand_unary}{$k}) {
       return $self->$custom($v);
@@ -621,6 +654,9 @@ sub _expand_expr_hashpair {
       and (keys %$v)[0] =~ /^-/
     ) {
       my ($func) = $k =~ /^-(.*)$/;
+      if (List::Util::first { $func =~ $_->{regex} } @{$self->{special_ops}}) {
+        return +{ -op => [ $func, $self->_expand_expr($v) ] };
+      }
       return +{ -func => [ $func, $self->_expand_expr($v) ] };
     }
     if (!ref($v) or is_literal_value($v)) {
@@ -655,6 +691,7 @@ sub _expand_expr_hashpair {
           sort keys %$v
       ] };
     }
+    return undef unless keys %$v;
     my ($vk, $vv) = %$v;
     $vk =~ s/^-//;
     $vk = lc($vk);
@@ -837,9 +874,7 @@ sub _expand_expr_hashpair {
     my ($sql, @bind) = @$literal;
     if ($self->{bindtype} eq 'columns') {
       for (@bind) {
-        if (!defined $_ || ref($_) ne 'ARRAY' || @$_ != 2) {
-          puke "bindtype 'columns' selected, you need to pass: [column_name => bind_value]"
-        }
+        $self->_assert_bindval_matches_bindtype($_);
       }
     }
     return +{ -literal => [ $self->_quote($k).' '.$sql, @bind ] };
@@ -860,23 +895,15 @@ sub _render_expr {
 sub _recurse_where {
   my ($self, $where, $logic) = @_;
 
-#print STDERR Data::Dumper::Concise::Dumper([ $where, $logic ]);
-
   # Special case: top level simple string treated as literal
 
   my $where_exp = (ref($where)
                     ? $self->_expand_expr($where, $logic)
                     : { -literal => [ $where ] });
 
-#print STDERR Data::Dumper::Concise::Dumper([ EXP => $where_exp ]);
-
-  # dispatch on appropriate method according to refkind of $where
-#  my $method = $self->_METHOD_FOR_refkind("_where", $where_exp);
-
-#  my ($sql, @bind) =  $self->$method($where_exp, $logic);
+  # dispatch expanded expression
 
   my ($sql, @bind) = defined($where_exp) ? $self->_render_expr($where_exp) : (undef);
-
   # DBIx::Class used to call _recurse_where in scalar context
   # something else might too...
   if (wantarray) {
@@ -894,12 +921,6 @@ sub _render_ident {
   return $self->_convert($self->_quote($ident));
 }
 
-sub _render_value {
-  my ($self, $value) = @_;
-
-  return ($self->_convert('?'), $self->_bindtype(undef, $value));
-}
-
 my %unop_postfix = map +($_ => 1),
   'is null', 'is not null',
   'asc', 'desc',
@@ -959,9 +980,11 @@ sub _render_op {
   if (my $h = $special{$op}) {
     return $self->$h(\@args);
   }
-  if (my $us = List::Util::first { $op =~ $_->{regex} } @{$self->{special_ops}}) {
+  my $us = List::Util::first { $op =~ $_->{regex} } @{$self->{special_ops}};
+  if ($us and @args > 1) {
     puke "Special op '${op}' requires first value to be identifier"
       unless my ($k) = map $_->{-ident}, grep ref($_) eq 'HASH', $args[0];
+    local our $Expand_Depth = 1;
     return $self->${\($us->{handler})}($k, $op, $args[1]);
   }
   if (my $us = List::Util::first { $op =~ $_->{regex} } @{$self->{unary_ops}}) {
@@ -976,11 +999,16 @@ sub _render_op {
         ? "${expr_sql} ${op_sql}"
         : "${op_sql} ${expr_sql}"
     );
-    return (($op eq 'not' ? '('.$final_sql.')' : $final_sql), @bind);
+    return (($op eq 'not' || $us ? '('.$final_sql.')' : $final_sql), @bind);
+  #} elsif (@args == 0) {
+  #  return '';
   } else {
-     my @parts = map [ $self->_render_expr($_) ], @args;
-     my ($final_sql) = map +($op =~ /^(and|or)$/ ? "(${_})" : $_), join(
-       ($final_op eq ',' ? '' : ' ').$self->_sqlcase($final_op).' ',
+     my @parts = grep length($_->[0]), map [ $self->_render_expr($_) ], @args;
+     return '' unless @parts;
+     my $is_andor = !!($op =~ /^(and|or)$/);
+     return @{$parts[0]} if $is_andor and @parts == 1;
+     my ($final_sql) = map +($is_andor ? "( ${_} )" : $_), join(
+       ' '.$self->_sqlcase($final_op).' ',
        map $_->[0], @parts
      );
      return (
@@ -991,6 +1019,12 @@ sub _render_op {
   die "unhandled";
 }
 
+sub _render_list {
+  my ($self, $list) = @_;
+  my @parts = grep length($_->[0]), map [ $self->_render_expr($_) ], @$list;
+  return join(', ', map $_->[0], @parts), map @{$_}[1..$#$_], @parts;
+}
+
 sub _render_func {
   my ($self, $rest) = @_;
   my ($func, @args) = @$rest;
@@ -1067,8 +1101,9 @@ sub _expand_order_by {
       }
     }
     my @exp = map +(defined($dir) ? { -op => [ $dir => $_ ] } : $_),
-                map $self->_expand_expr($_, undef, -ident), @to_expand;
-    return (@exp > 1 ? { -op => [ ',', @exp ] } : $exp[0]);
+                map $self->_expand_expr($_, undef, -ident),
+                map ref($_) eq 'ARRAY' ? @$_ : $_, @to_expand;
+    return (@exp > 1 ? { -list => \@exp } : $exp[0]);
   };
 
   local @{$self->{expand_unary}}{qw(-asc -desc)} = (
@@ -1086,21 +1121,32 @@ sub _order_by {
 
   my ($sql, @bind) = $self->_render_expr($expanded);
 
+  return '' unless length($sql);
+
   my $final_sql = $self->_sqlcase(' order by ').$sql;
 
   return wantarray ? ($final_sql, @bind) : $final_sql;
 }
 
+# _order_by no longer needs to call this so doesn't but DBIC uses it.
+
 sub _order_by_chunks {
   my ($self, $arg) = @_;
 
   return () unless defined(my $expanded = $self->_expand_order_by($arg));
 
+  return $self->_chunkify_order_by($expanded);
+}
+
+sub _chunkify_order_by {
+  my ($self, $expanded) = @_;
+
+  return grep length, $self->_render_expr($expanded)
+    if $expanded->{-ident} or @{$expanded->{-literal}||[]} == 1;
+
   for ($expanded) {
-    if (ref() eq 'HASH' and my $op = $_->{-op}) {
-      if ($op->[0] eq ',') {
-        return map [ $self->_render_expr($_) ], @{$op}[1..$#$op];
-      }
+    if (ref() eq 'HASH' and my $l = $_->{-list}) {
+      return map $self->_chunkify_order_by($_), @$l;
     }
     return [ $self->_render_expr($_) ];
   }
@@ -1127,8 +1173,8 @@ sub _expand_maybe_list_expr {
   my ($self, $expr, $logic, $default) = @_;
   my $e = do {
     if (ref($expr) eq 'ARRAY') {
-      return { -op => [
-        ',', map $self->_expand_expr($_, $logic, $default), @$expr
+      return { -list => [
+        map $self->_expand_expr($_, $logic, $default), @$expr
       ] } if @$expr > 1;
       $expr->[0]
     } else {