better in handling
[scpubgit/Q-Branch.git] / lib / SQL / Abstract.pm
index 2661082..40cb08c 100644 (file)
@@ -39,6 +39,7 @@ our $AUTOLOAD;
 my @BUILTIN_SPECIAL_OPS = (
   {regex => qr/^ (?: not \s )? between $/ix, handler => sub { die "NOPE" }},
   {regex => qr/^ is (?: \s+ not )?     $/ix, handler => sub { die "NOPE" }},
+  {regex => qr/^ (?: not \s )? in      $/ix, handler => sub { die "NOPE" }},
 );
 
 #======================================================================
@@ -155,8 +156,8 @@ sub new {
   $opt{equality_op}   = qr/^( \Q$opt{cmp}\E | \= )$/ix;
   $opt{inequality_op} = qr/^( != | <> )$/ix;
 
-  $opt{like_op}       = qr/^ (is\s+)? r?like $/xi;
-  $opt{not_like_op}   = qr/^ (is\s+)? not \s+ r?like $/xi;
+  $opt{like_op}       = qr/^ (is_)?r?like $/xi;
+  $opt{not_like_op}   = qr/^ (is_)?not_r?like $/xi;
 
   # SQL booleans
   $opt{sqltrue}  ||= '1=1';
@@ -201,16 +202,18 @@ sub new {
     -or => '_expand_op_andor',
     -nest => '_expand_nest',
     -bind => sub { shift; +{ @_ } },
+    -in => '_expand_in',
+    -not_in => '_expand_in',
   };
 
   $opt{expand_op} = {
     'between' => '_expand_between',
-    'not between' => '_expand_between',
+    'not_between' => '_expand_between',
     'in' => '_expand_in',
-    'not in' => '_expand_in',
+    'not_in' => '_expand_in',
     'nest' => '_expand_nest',
     (map +($_ => '_expand_op_andor'), ('and', 'or')),
-    (map +($_ => '_expand_op_is'), ('is', 'is not')),
+    (map +($_ => '_expand_op_is'), ('is', 'is_not')),
   };
 
   # placeholder for _expand_unop system
@@ -234,10 +237,10 @@ sub new {
   };
 
   $opt{render_op} = {
-    (map +($_ => '_render_op_between'), 'between', 'not between'),
-    (map +($_ => '_render_op_in'), 'in', 'not in'),
+    (map +($_ => '_render_op_between'), 'between', 'not_between'),
+    (map +($_ => '_render_op_in'), 'in', 'not_in'),
     (map +($_ => '_render_unop_postfix'),
-      'is null', 'is not null', 'asc', 'desc',
+      'is_null', 'is_not_null', 'asc', 'desc',
     ),
     (not => '_render_op_not'),
     (map +($_ => '_render_op_andor'), qw(and or)),
@@ -578,6 +581,12 @@ sub render_expr {
   $self->render_aqt($self->expand_expr($expr));
 }
 
+sub _normalize_op {
+  my ($self, $raw) = @_;
+  s/^-(?=.)//, s/\s+/_/g for my $op = lc $raw;
+  $op;
+}
+
 sub _expand_expr {
   my ($self, $expr) = @_;
   our $Expand_Depth ||= 0; local $Expand_Depth = $Expand_Depth + 1;
@@ -702,22 +711,23 @@ sub _expand_expr_hashpair_scalar {
 sub _expand_expr_hashpair_op {
   my ($self, $k, $v) = @_;
 
-  my $op = $k;
-  $op =~ s/^-// if length($op) > 1;
-  $self->_assert_pass_injection_guard($op);
+  $self->_assert_pass_injection_guard($k =~ /\A-(.*)\Z/s);
+
+  my $op = $self->_normalize_op($k);
 
   # Ops prefixed with -not_ get converted
 
-  if (my ($rest) = $op =~/^not[_ ](.*)$/) {
+  if (my ($rest) = $op =~/^not_(.*)$/) {
     return +{ -op => [
       'not',
       $self->_expand_expr({ "-${rest}", $v })
-  ] };
+    ] };
   }
 
-
   { # Old SQLA compat
 
+    my $op = join(' ', split '_', $op);
+
     # the old special op system requires illegality for top-level use
 
     if (
@@ -776,9 +786,10 @@ sub _expand_expr_hashtriple {
 
   my $ik = $self->_expand_ident(-ident => $k);
 
-  my $op = join ' ', split '_', (map lc, $vk =~ /^-?(.*)$/)[0];
+  my $op = $self->_normalize_op($vk);
   $self->_assert_pass_injection_guard($op);
-  if ($op =~ s/ [_\s]? \d+ $//x ) {
+
+  if ($op =~ s/ _? \d+ $//x ) {
     return $self->_expand_expr($k, { $vk, $vv });
   }
   if (my $x = $self->{expand_op}{$op}) {
@@ -786,6 +797,9 @@ sub _expand_expr_hashtriple {
     return $self->$x($op, $vv, $k);
   }
   { # Old SQLA compat
+
+    my $op = join(' ', split '_', $op);
+
     if (my $us = List::Util::first { $op =~ $_->{regex} } @{$self->{special_ops}}) {
       return { -op => [ $op, $ik, $vv ] };
     }
@@ -807,7 +821,7 @@ sub _expand_expr_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($op)}' "
+        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)"
         ;
@@ -839,7 +853,10 @@ sub _expand_expr_hashtriple {
 }
 
 sub _dwim_op_to_is {
-  my ($self, $op, $empty, $fail) = @_;
+  my ($self, $raw, $empty, $fail) = @_;
+
+  my $op = $self->_normalize_op($raw);
+
   if ($op =~ /^not$/i) {
     return 0;
   }
@@ -847,14 +864,14 @@ sub _dwim_op_to_is {
     return 1;
   }
   if ($op =~ $self->{like_op}) {
-    belch(sprintf $empty, uc($op));
+    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($op));
+    belch(sprintf $empty, uc(join ' ', split '_', $op));
     return 0;
   }
   puke(sprintf $fail, $op);
@@ -954,7 +971,7 @@ sub _expand_op_is {
          and exists($vv->{-value})
          and !defined($vv->{-value})
        );
-  return +{ -op => [ $op.' null', $self->_expand_ident(-ident => $k) ] };
+  return +{ -op => [ $op.'_null', $self->_expand_ident(-ident => $k) ] };
 }
 
 sub _expand_between {
@@ -977,7 +994,10 @@ sub _expand_between {
 }
 
 sub _expand_in {
-  my ($self, $op, $vv, $k) = @_;
+  my ($self, $raw, $vv, $k) = @_;
+  $k = shift @{$vv = [ @$vv ]} unless defined $k;
+  local our $Cur_Col_Meta = $k;
+  my $op = $self->_normalize_op($raw);
   if (my $literal = is_literal_value($vv)) {
     my ($sql, @bind) = @$literal;
     my $opened_sql = $self->_open_outer_paren($sql);
@@ -1090,6 +1110,8 @@ sub _render_op {
 
   { # Old SQLA compat
 
+    my $op = join(' ', split '_', $op);
+
     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"
@@ -1128,7 +1150,11 @@ sub _render_op_between {
   };
   my ($lhsql, @lhbind) = $self->render_aqt($left);
   return (
-    join(' ', '(', $lhsql, $self->_sqlcase($op), $rhsql, ')'),
+    join(' ',
+      '(', $lhsql,
+       $self->_sqlcase(join ' ', split '_', $op),
+      $rhsql, ')'
+    ),
     @lhbind, @rhbind
   );
 }
@@ -1144,7 +1170,7 @@ sub _render_op_in {
   } @$rhs;
   my ($lhsql, @lbind) = $self->render_aqt($lhs);
   return (
-    $lhsql.' '.$self->_sqlcase($op).' ( '
+    $lhsql.' '.$self->_sqlcase(join ' ', split '_', $op).' ( '
     .join(', ', @in_sql)
     .' )',
     @lbind, @in_bind
@@ -1166,7 +1192,7 @@ sub _render_op_multop {
   return '' unless @parts;
   return @{$parts[0]} if @parts == 1;
   my ($final_sql) = join(
-    ' '.$self->_sqlcase($op).' ',
+    ' '.$self->_sqlcase(join ' ', split '_', $op).' ',
     map $_->[0], @parts
   );
   return (
@@ -1183,14 +1209,15 @@ sub _render_op_not {
 sub _render_unop_prefix {
   my ($self, $op, $v) = @_;
   my ($expr_sql, @bind) = $self->render_aqt($v->[0]);
-  my $op_sql = $self->_sqlcase($op);
+
+  my $op_sql = $self->_sqlcase($op); # join ' ', split '_', $op);
   return ("${op_sql} ${expr_sql}", @bind);
 }
 
 sub _render_unop_postfix {
   my ($self, $op, $v) = @_;
   my ($expr_sql, @bind) = $self->render_aqt($v->[0]);
-  my $op_sql = $self->_sqlcase($op);
+  my $op_sql = $self->_sqlcase(join ' ', split '_', $op);
   return ($expr_sql.' '.$op_sql, @bind);
 }