Fix syntax error in example
[dbsrgits/SQL-Abstract.git] / lib / SQL / Abstract.pm
index fdd6399..8150396 100644 (file)
@@ -2,7 +2,6 @@ package SQL::Abstract; # see doc at end of file
 
 use strict;
 use warnings;
-use Module::Runtime ();
 use Carp ();
 use List::Util ();
 use Scalar::Util ();
@@ -28,7 +27,7 @@ BEGIN {
 # GLOBALS
 #======================================================================
 
-our $VERSION  = '1.90_01';
+our $VERSION  = '2.000001';
 
 # This would confuse some packagers
 $VERSION = eval $VERSION if $VERSION =~ /_/; # numify for warning-free dev releases
@@ -155,6 +154,7 @@ our %Defaults = (
     (map +($_ => '_expand_op_is'), ('is', 'is_not')),
     (map +($_ => __PACKAGE__->make_unop_expander("_expand_${_}")),
       qw(ident value nest)),
+    bind => __PACKAGE__->make_unop_expander(sub { +{ -bind => $_[2] } }),
   },
   render => {
     (map +($_, "_render_$_"),
@@ -293,6 +293,19 @@ sub new {
         );
       };
     }
+    foreach my $type (qw(in between)) {
+      my $meth = "_where_field_".uc($type);
+      if (__PACKAGE__->can($meth) ne $class->can($meth)) {
+        my $exp = sub {
+          my ($self, $op, $v, $k) = @_;
+          $op = join ' ', split '_', $op;
+          return +{ -literal => [
+            $self->$meth($k, $op, $v)
+          ] };
+        };
+        $opt{expand_op}{$_} = $exp for $type, "not_${type}";
+      }
+    }
     if ($class->isa('DBIx::Class::SQLMaker')) {
       $opt{warn_once_on_nest} = 1;
       $opt{disable_old_special_ops} = 1;
@@ -301,17 +314,45 @@ sub new {
         s/\A\s+//, s/\s+\Z// for $sql;
         return [ $sql, @bind ];
       };
-      $opt{expand_op}{ident} = sub {
+      $opt{expand_op}{ident} = $class->make_unop_expander(sub {
         my ($self, undef, $body) = @_;
         $body = $body->from if Scalar::Util::blessed($body);
         $self->_expand_ident(ident => $body);
+      });
+    }
+    if ($class->isa('SQL::Abstract::More')) {
+      my $orig = $opt{expand_op}{or};
+      $opt{expand_op}{or} = sub {
+        my ($self, $logop, $v, $k) = @_;
+        if ($k and ref($v) eq 'ARRAY') {
+          my ($type, $val) = @$v;
+          my $op;
+          if (
+            ref($type) eq 'HASH' and ref($val) eq 'HASH'
+            and keys %$type == 1 and keys %$val == 1
+            and (keys %$type)[0] eq (keys %$val)[0]
+          ) {
+            ($op) = keys %$type;
+            ($type) = values %$type;
+            ($val) = values %$val;
+          }
+          if ($self->is_bind_value_with_type(my $v = [ $type, $val ])) {
+            return $self->expand_expr(
+              { $k, map +($op ? { $op => $_ } : $_), { -bind => $v } }
+            );
+          }
+        }
+        return $self->$orig($logop, $v, $k);
+      };
+      $opt{render}{bind} = sub {
+        return [ '?', map +(ref($_->[0]) ? $_ : $_->[1]), $_[2] ]
       };
     }
   }
 
   if ($opt{lazy_join_sql_parts}) {
-    my $mod = Module::Runtime::use_module('SQL::Abstract::Parts');
-    $opt{join_sql_parts} ||= sub { $mod->new(@_) };
+    require SQL::Abstract::Parts;
+    $opt{join_sql_parts} ||= sub { SQL::Abstract::Parts->new(@_) };
   }
 
   $opt{join_sql_parts} ||= sub { join $_[0], @_[1..$#_] };
@@ -846,7 +887,7 @@ sub render_aqt {
   die "Not a node type: $k" unless $k =~ s/^-//;
   if (my $meth = $self->{render}{$k}) {
     local our $Render_Top_Level = $top_level;
-    return $self->$meth($k, $v);
+    return $self->$meth($k, $v)||[];
   }
   die "notreached: $k";
 }
@@ -931,7 +972,7 @@ sub _expand_expr {
   if (ref($expr) eq 'HASH') {
     return undef unless my $kc = keys %$expr;
     if ($kc > 1) {
-      return $self->_expand_op_andor(and => $expr);
+      return $self->_expand_logop(and => $expr);
     }
     my ($key, $value) = %$expr;
     if ($key =~ /^-/ and $key =~ s/ [_\s]? \d+ $//x ) {
@@ -941,7 +982,7 @@ sub _expand_expr {
     return $self->_expand_hashpair($key, $value);
   }
   if (ref($expr) eq 'ARRAY') {
-    return $self->_expand_op_andor(lc($self->{logic}), $expr);
+    return $self->_expand_logop(lc($self->{logic}), $expr);
   }
   if (my $literal = is_literal_value($expr)) {
     return +{ -literal => $literal };
@@ -963,7 +1004,7 @@ sub _expand_hashpair {
   }
   if ($k =~ /^-./) {
     return $self->_expand_hashpair_op($k, $v);
-  } elsif ($k =~ /^[^\w]/i) {
+  } elsif ($k =~ /^\W+$/) {
     my ($lhs, @rhs) = ref($v) eq 'ARRAY' ? @$v : $v;
     return $self->_expand_op(
       -op, [ $k, $self->expand_expr($lhs, -ident), @rhs ]
@@ -980,7 +1021,7 @@ sub _expand_hashpair_ident {
   # hash with multiple or no elements is andor
 
   if (ref($v) eq 'HASH' and keys %$v != 1) {
-    return $self->_expand_op_andor(and => $v, $k);
+    return $self->_expand_logop(and => $v, $k);
   }
 
   # undef needs to be re-sent with cmp to achieve IS/IS NOT NULL
@@ -1007,11 +1048,11 @@ sub _expand_hashpair_ident {
     return $self->sqlfalse unless @$v;
     $self->_debug("ARRAY($k) means distribute over elements");
     my $logic = lc(
-      $v->[0] =~ /^-(and|or)$/i
+      ($v->[0]||'') =~ /^-(and|or)$/i
         ? (shift(@{$v = [ @$v ]}), $1)
         : lc($self->{logic} || 'OR')
     );
-    return $self->_expand_op_andor(
+    return $self->_expand_logop(
       $logic => $v, $k
     );
   }
@@ -1188,7 +1229,7 @@ sub _expand_hashtriple {
         "operator '%s' applied on an empty array (field '$k')"
       ) ? $self->sqlfalse : $self->sqltrue);
     }
-    return $self->_expand_op_andor($logic => \@values, $k);
+    return $self->_expand_logop($logic => \@values, $k);
   }
   if (is_undef_value($vv)) {
     my $is = ($self->_dwim_op_to_is($op,
@@ -1242,8 +1283,11 @@ sub _expand_ident {
   unless (defined($body) or (ref($body) and ref($body) eq 'ARRAY')) {
     puke "-ident requires a single plain scalar argument (a quotable identifier) or an arrayref of identifier parts";
   }
-  my @parts = map split(/\Q${\($self->{name_sep}||'.')}\E/, $_),
-                ref($body) ? @$body : $body;
+  my ($sep) = map +(defined() ? $_ : '.') , $self->{name_sep};
+  my @parts = map +($sep
+                     ? map split(/\Q${sep}\E/, $_), @$_
+                     : @$_
+                   ), ref($body) ? $body : [ $body ];
   return { -ident => $parts[-1] } if $self->{_dequalify_idents};
   unless ($self->{quote_char}) {
     $self->_assert_pass_injection_guard($_) for @parts;
@@ -1293,6 +1337,11 @@ sub _expand_list {
   ] };
 }
 
+sub _expand_logop {
+  my ($self, $logop, $v, $k) = @_;
+  $self->${\$self->{expand_op}{$logop}}($logop, $v, $k);
+}
+
 sub _expand_op_andor {
   my ($self, $logop, $v, $k) = @_;
   if (defined $k) {
@@ -1681,6 +1730,25 @@ sub _open_outer_paren {
   $sql;
 }
 
+sub _where_field_IN {
+  my ($self, $k, $op, $vals) = @_;
+  @{$self->_render_op_in(
+    $op,
+    [
+      $self->expand_expr($k, -ident),
+      map $self->expand_expr($_, -value),
+        ref($vals) eq 'ARRAY' ? @$vals : $vals
+    ]
+  )};
+}
+
+sub _where_field_BETWEEN {
+  my ($self, $k, $op, $vals) = @_;
+  @{$self->_render_op_between(
+    $op,
+    [ $self->expand_expr($k, -ident), ref($vals) eq 'ARRAY' ? @$vals : $vals ]
+  )};
+}
 
 #======================================================================
 # ORDER BY
@@ -2807,7 +2875,7 @@ into an C<AND> of its elements:
 
 To get an OR instead, you can combine it with the arrayref idea:
 
-    my %where => (
+    my %where = (
          user => 'nwiger',
          priority => [ { '=', 2 }, { '>', 5 } ]
     );