modify belch and puke to operate as method calls
[dbsrgits/SQL-Abstract.git] / lib / SQL / Abstract.pm
index 4fef402..1016433 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
@@ -56,11 +55,13 @@ sub _debug {
 }
 
 sub belch (@) {
+  Scalar::Util::blessed($_[0]) and $_[0]->isa(__PACKAGE__) and shift;
   my($func) = (caller(1))[3];
   Carp::carp "[$func] Warning: ", @_;
 }
 
 sub puke (@) {
+  Scalar::Util::blessed($_[0]) and $_[0]->isa(__PACKAGE__) and shift;
   my($func) = (caller(1))[3];
   Carp::croak "[$func] Fatal: ", @_;
 }
@@ -155,6 +156,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 +295,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 +316,45 @@ sub new {
         s/\A\s+//, s/\s+\Z// for $sql;
         return [ $sql, @bind ];
       };
-      $opt{expand_op}{ident} = __PACKAGE__->make_unop_expander(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..$#_] };
@@ -445,9 +488,9 @@ sub sqlfalse { +{ -literal => [ $_[0]->{sqlfalse} ] } }
 sub _assert_pass_injection_guard {
   if ($_[1] =~ $_[0]->{injection_guard}) {
     my $class = ref $_[0];
-    puke "Possible SQL injection attempt '$_[1]'. If this is indeed a part of the "
+    $_[0]->puke("Possible SQL injection attempt '$_[1]'. If this is indeed a part of the "
      . "desired SQL use literal SQL ( \'...' or \[ '...' ] ) or supply your own "
-     . "{injection_guard} attribute to ${class}->new()"
+     . "{injection_guard} attribute to ${class}->new()")
   }
 }
 
@@ -511,7 +554,7 @@ sub _expand_insert_values {
 
     # no names (arrayref) means can't generate bindtype
     !($fields) && $self->{bindtype} eq 'columns'
-      && belch "can't do 'columns' bindtype when called with arrayref";
+      && $self->belch("can't do 'columns' bindtype when called with arrayref");
 
     +(
       (@$fields
@@ -577,7 +620,7 @@ sub _expand_insert_value {
   }
   if (ref($v) eq 'HASH') {
     if (grep !/^-/, keys %$v) {
-      belch "HASH ref as bind value in insert is not supported";
+      $self->belch("HASH ref as bind value in insert is not supported");
       return +{ -bind => [ $k, $v ] };
     }
   }
@@ -602,7 +645,7 @@ sub update {
     } else {
       my %clauses;
       @clauses{qw(target set where)} = ($table, $set, $where);
-      puke "Unsupported data type specified to \$sql->update"
+      $self->puke("Unsupported data type specified to \$sql->update")
         unless ref($clauses{set}) eq 'HASH';
       @clauses{keys %$options} = values %$options;
       \%clauses;
@@ -846,7 +889,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,17 +974,17 @@ 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 ) {
-      belch 'Use of [and|or|nest]_N modifiers is deprecated and will be removed in SQLA v2.0. '
-          . "You probably wanted ...-and => [ $key => COND1, $key => COND2 ... ]";
+      $self->belch('Use of [and|or|nest]_N modifiers is deprecated and will be removed in SQLA v2.0. '
+          . "You probably wanted ...-and => [ $key => COND1, $key => COND2 ... ]");
     }
     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 };
@@ -956,14 +999,14 @@ sub _expand_hashpair {
   my ($self, $k, $v) = @_;
   unless (defined($k) and length($k)) {
     if (defined($k) and my $literal = is_literal_value($v)) {
-      belch 'Hash-pairs consisting of an empty string with a literal are deprecated, and will be removed in 2.0: use -and => [ $literal ] instead';
+      $self->belch('Hash-pairs consisting of an empty string with a literal are deprecated, and will be removed in 2.0: use -and => [ $literal ] instead');
       return { -literal => $literal };
     }
-    puke "Supplying an empty left hand side argument is not supported";
+    $self->puke("Supplying an empty left hand side argument is not supported");
   }
   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 +1023,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,18 +1050,18 @@ 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
     );
   }
 
   if (my $literal = is_literal_value($v)) {
     unless (length $k) {
-      belch 'Hash-pairs consisting of an empty string with a literal are deprecated, and will be removed in 2.0: use -and => [ $literal ] instead';
+      $self->belch('Hash-pairs consisting of an empty string with a literal are deprecated, and will be removed in 2.0: use -and => [ $literal ] instead');
       return \$literal;
     }
     my ($sql, @bind) = @$literal;
@@ -1072,7 +1115,7 @@ sub _expand_hashpair_op {
         )
       )
     ) {
-      puke "Illegal use of top-level '-$wsop'"
+      $self->puke("Illegal use of top-level '-$wsop'")
     }
   }
 
@@ -1175,10 +1218,10 @@ sub _expand_hashtriple {
       or $op =~ $self->{not_like_op}
     ) {
       if (lc($logic) eq 'or' and @values > 1) {
-        belch "A multi-element arrayref as an argument to the inequality op '${\uc(join ' ', split '_', $op)}' "
+        $self->belch("A multi-element arrayref as an argument to the inequality op '${\uc(join ' ', split '_', $op)}' "
             . 'is technically equivalent to an always-true 1=1 (you probably wanted '
             . "to say ...{ \$inequality_op => [ -and => \@values ] }... instead)"
-        ;
+        );
       }
     }
     unless (@values) {
@@ -1188,7 +1231,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,
@@ -1218,17 +1261,17 @@ sub _dwim_op_to_is {
     return 1;
   }
   if ($op =~ $self->{like_op}) {
-    belch(sprintf $empty, uc(join ' ', split '_', $op));
+    $self->belch(sprintf $empty, uc(join ' ', split '_', $op));
     return 1;
   }
   if ($op =~ $self->{inequality_op}) {
     return 0;
   }
   if ($op =~ $self->{not_like_op}) {
-    belch(sprintf $empty, uc(join ' ', split '_', $op));
+    $self->belch(sprintf $empty, uc(join ' ', split '_', $op));
     return 0;
   }
-  puke(sprintf $fail, $op);
+  $self->puke(sprintf $fail, $op);
 }
 
 sub _expand_func {
@@ -1240,10 +1283,13 @@ sub _expand_func {
 sub _expand_ident {
   my ($self, undef, $body) = @_;
   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";
+    $self->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;
@@ -1277,7 +1323,7 @@ sub _expand_bool {
   if (ref($v)) {
     return $self->_expand_expr($v);
   }
-  puke "-bool => undef not supported" unless defined($v);
+  $self->puke("-bool => undef not supported") unless defined($v);
   return $self->_expand_expr({ -ident => $v });
 }
 
@@ -1293,6 +1339,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) {
@@ -1311,7 +1362,7 @@ sub _expand_op_andor {
     ] };
   }
   if (ref($v) eq 'ARRAY') {
-    $logop eq 'and' or $logop eq 'or' or puke "unknown logic: $logop";
+    $logop eq 'and' or $logop eq 'or' or $self->puke("unknown logic: $logop");
 
     my @expr = grep {
       (ref($_) eq 'ARRAY' and @$_)
@@ -1322,7 +1373,7 @@ sub _expand_op_andor {
     my @res;
 
     while (my ($el) = splice @expr, 0, 1) {
-      puke "Supplying an empty left hand side argument is not supported in array-pairs"
+      $self->puke("Supplying an empty left hand side argument is not supported in array-pairs")
         unless defined($el) and length($el);
       my $elref = ref($el);
       if (!$elref) {
@@ -1349,7 +1400,7 @@ sub _expand_op_andor {
 sub _expand_op_is {
   my ($self, $op, $vv, $k) = @_;
   ($k, $vv) = @$vv unless defined $k;
-  puke "$op can only take undef as argument"
+  $self->puke("$op can only take undef as argument")
     if defined($vv)
        and not (
          ref($vv) eq 'HASH'
@@ -1368,7 +1419,7 @@ sub _expand_between {
     or
     (@rhs == 2 and defined($rhs[0]) and defined($rhs[1]))
   ) {
-    puke "Operator '${\uc($op)}' requires either an arrayref with two defined values or expressions, or a single literal scalarref/arrayref-ref";
+    $self->puke("Operator '${\uc($op)}' requires either an arrayref with two defined values or expressions, or a single literal scalarref/arrayref-ref");
   }
   return +{ -op => [
     $op,
@@ -1393,10 +1444,10 @@ sub _expand_in {
   . 'AND DATA!!! (the upcoming Data::Query-based version of SQL::Abstract '
   . 'will emit the logically correct SQL instead of raising this exception)'
   ;
-  puke("Argument passed to the '${\uc($op)}' operator can not be undefined")
+  $self->puke("Argument passed to the '${\uc($op)}' operator can not be undefined")
     if !defined($vv);
   my @rhs = map $self->expand_expr($_, -value),
-              map { defined($_) ? $_: puke($undef_err) }
+              map { defined($_) ? $_: $self->puke($undef_err) }
                 (ref($vv) eq 'ARRAY' ? @$vv : $vv);
   return $self->${\($op =~ /^not/ ? 'sqltrue' : 'sqlfalse')} unless @rhs;
 
@@ -1413,7 +1464,7 @@ sub _expand_nest {
   # method it overrode to do so no longer exists
   if ($self->{warn_once_on_nest}) {
     unless (our $Nest_Warned) {
-      belch(
+      $self->belch(
         "-nest in search conditions is deprecated, you most probably wanted:\n"
         .q|{..., -and => [ \%cond0, \@cond1, \'cond2', \[ 'cond3', [ col => bind ] ], etc. ], ... }|
       );
@@ -1452,7 +1503,7 @@ sub _recurse_where {
     return ($sql, @bind);
   }
   else {
-    belch "Calling _recurse_where in scalar context is deprecated and will go away before 2.0";
+    $self->belch("Calling _recurse_where in scalar context is deprecated and will go away before 2.0");
     return $sql;
   }
 }
@@ -1516,7 +1567,7 @@ sub _render_op {
 
     my $ss = List::Util::first { $op =~ $_->{regex} } @{$self->{special_ops}};
     if ($ss and @args > 1) {
-      puke "Special op '${op}' requires first value to be identifier"
+      $self->puke("Special op '${op}' requires first value to be identifier")
         unless my ($ident) = map $_->{-ident}, grep ref($_) eq 'HASH', $args[0];
       my $k = join(($self->{name_sep}||'.'), @$ident);
       local our $Expand_Depth = 1;
@@ -1543,7 +1594,7 @@ sub _render_op_between {
   my ($left, $low, $high) = @$args;
   my @rh = do {
     if (@$args == 2) {
-      puke "Single arg to between must be a literal"
+      $self->puke("Single arg to between must be a literal")
         unless $low->{-literal};
       $low;
     } else {
@@ -1681,6 +1732,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
@@ -1703,7 +1773,7 @@ sub _expand_order_by {
         and keys %$arg > 1
         and grep /^-(asc|desc)$/, keys %$arg
       ) {
-        puke "ordering direction hash passed to order by must have exactly one key (-asc or -desc)";
+        $self->puke("ordering direction hash passed to order by must have exactly one key (-asc or -desc)");
       }
     }
     my @exp = map +(
@@ -1787,7 +1857,7 @@ sub _quote {
 
   return '' unless defined $_[1];
   return ${$_[1]} if ref($_[1]) eq 'SCALAR';
-  puke 'Identifier cannot be hashref' if ref($_[1]) eq 'HASH';
+  $_[0]->puke('Identifier cannot be hashref') if ref($_[1]) eq 'HASH';
 
   unless ($_[0]->{quote_char}) {
     if (ref($_[1]) eq 'ARRAY') {
@@ -1802,7 +1872,7 @@ sub _quote {
   my ($l, $r) =
       !$qref             ? ($_[0]->{quote_char}, $_[0]->{quote_char})
     : ($qref eq 'ARRAY') ? @{$_[0]->{quote_char}}
-    : puke "Unsupported quote_char format: $_[0]->{quote_char}";
+    : $_[0]->puke("Unsupported quote_char format: $_[0]->{quote_char}");
 
   my $esc = $_[0]->{escape_char} || $r;
 
@@ -1856,7 +1926,7 @@ sub _assert_bindval_matches_bindtype {
   if ($self->{bindtype} eq 'columns') {
     for (@_) {
       if (!defined $_ || ref($_) ne 'ARRAY' || @$_ != 2) {
-        puke "bindtype 'columns' selected, you need to pass: [column_name => bind_value]"
+        $self->puke("bindtype 'columns' selected, you need to pass: [column_name => bind_value]")
       }
     }
   }
@@ -1910,7 +1980,7 @@ sub _METHOD_FOR_refkind {
       and last;
   }
 
-  return $method || puke "cannot dispatch on '$meth_prefix' for ".$self->_refkind($data);
+  return $method || $self->puke("cannot dispatch on '$meth_prefix' for ".$self->_refkind($data));
 }
 
 
@@ -1923,7 +1993,7 @@ sub _SWITCH_refkind {
       and last;
   }
 
-  puke "no dispatch entry for ".$self->_refkind($data)
+  $self->puke("no dispatch entry for ".$self->_refkind($data))
     unless $coderef;
 
   $coderef->();
@@ -1943,7 +2013,7 @@ sub _SWITCH_refkind {
 sub values {
     my $self = shift;
     my $data = shift || return;
-    puke "Argument to ", __PACKAGE__, "->values must be a \\%hash"
+    $self->puke("Argument to ", __PACKAGE__, "->values must be a \\%hash")
         unless ref $data eq 'HASH';
 
     my @all_bind;
@@ -2051,7 +2121,7 @@ sub AUTOLOAD {
     # This allows us to check for a local, then _form, attr
     my $self = shift;
     my($name) = $AUTOLOAD =~ /.*::(.+)/;
-    puke "AUTOLOAD invoked for method name ${name} and allow_autoload option not set" unless $self->{allow_autoload};
+    $self->puke("AUTOLOAD invoked for method name ${name} and allow_autoload option not set") unless $self->{allow_autoload};
     return $self->generate($name, @_);
 }
 
@@ -2807,7 +2877,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 } ]
     );