Use proper quote handling in _extract_order_criteria
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBIHacks.pm
index ae04942..e3d7e8a 100644 (file)
@@ -15,8 +15,8 @@ use mro 'c3';
 
 use List::Util 'first';
 use Scalar::Util 'blessed';
-use Sub::Name 'subname';
-use DBIx::Class::_Util qw(is_plain_value is_literal_value UNRESOLVABLE_CONDITION);
+use DBIx::Class::_Util 'UNRESOLVABLE_CONDITION';
+use SQL::Abstract qw(is_plain_value is_literal_value);
 use namespace::clean;
 
 #
@@ -111,8 +111,8 @@ sub _adjust_select_args_for_complex_prefetch {
   my $outer_attrs = { %$attrs };
   delete @{$outer_attrs}{qw(from bind rows offset group_by _grouped_by_distinct having)};
 
-  my $inner_attrs = { %$attrs };
-  delete @{$inner_attrs}{qw(for collapse select as _related_results_construction)};
+  my $inner_attrs = { %$attrs, _simple_passthrough_construction => 1 };
+  delete @{$inner_attrs}{qw(for collapse select as)};
 
   # there is no point of ordering the insides if there is no limit
   delete $inner_attrs->{order_by} if (
@@ -402,10 +402,12 @@ sub _resolve_aliastypes_from_select_args {
   # name_sep, otherwise sorry nasty legacy syntax like
   # { 'count(foo.id)' => { '>' => 3 } } will stop working >:(
   local $sql_maker->{quote_char} = $sql_maker->{quote_char};
+  local $sql_maker->{escape_char} = $sql_maker->{escape_char};
   local $sql_maker->{name_sep} = $sql_maker->{name_sep};
 
   unless (defined $sql_maker->{quote_char} and length $sql_maker->{quote_char}) {
     $sql_maker->{quote_char} = ["\x00", "\xFF"];
+    $sql_maker->{escape_char} = "\xFF";
     # if we don't unset it we screw up retarded but unfortunately working
     # 'MAX(foo.bar)' => { '>', 3 }
     $sql_maker->{name_sep} = '';
@@ -482,16 +484,17 @@ sub _resolve_aliastypes_from_select_args {
 
   # now loop through all fully qualified columns and get the corresponding
   # alias (should work even if they are in scalarrefs)
+  my $ident_re = $sql_maker->_quoted_ident_re;
   for my $alias (keys %$alias_list) {
     my $al_re = qr/
-      $lquote $alias $rquote $sep (?: $lquote ([^$rquote]+) $rquote )?
+      $lquote \Q$alias\E $rquote $sep ($ident_re)?
         |
-      \b $alias \. ([^\s\)\($rquote]+)?
+      \b \Q$alias\E \. ([^\s\)\($rquote]+)?
     /x;
 
     for my $type (keys %$to_scan) {
       for my $piece (@{$to_scan->{$type}}) {
-        if (my @matches = $piece =~ /$al_re/g) {
+        if (my @matches = map { $sql_maker->_unquote($_) } $piece =~ /$al_re/g) {
           $aliases_by_type->{$type}{$alias} ||= { -parents => $alias_list->{$alias}{-join_path}||[] };
           $aliases_by_type->{$type}{$alias}{-seen_columns}{"$alias.$_"} = "$alias.$_"
             for grep { defined $_ } @matches;
@@ -505,7 +508,7 @@ sub _resolve_aliastypes_from_select_args {
   for my $col (keys %$colinfo) {
     next if $col =~ / \. /x;   # if column is qualified it was caught by the above
 
-    my $col_re = qr/ $lquote ($col) $rquote /x;
+    my $col_re = qr/ $lquote (\Q$col\E) $rquote /x;
 
     for my $type (keys %$to_scan) {
       for my $piece (@{$to_scan->{$type}}) {
@@ -846,10 +849,14 @@ sub _extract_order_criteria {
     return scalar $sql_maker->_order_by_chunks ($order_by)
       unless wantarray;
 
-    my ($lq, $rq, $sep) = map { quotemeta($_) } (
-      ($orig_quote_chars ? @$orig_quote_chars : $sql_maker->_quote_chars),
-      $sql_maker->name_sep
-    );
+    my $sep = quotemeta($sql_maker->name_sep);
+
+    my @quotes = map { quotemeta($_) }
+        $orig_quote_chars
+          ? @$orig_quote_chars
+          : ($sql_maker->_quote_chars, $sql_maker->_escape_char);
+
+    my $quoted_ident_re = $sql_maker->_quoted_ident_re(@quotes);
 
     my @chunks;
     for ($sql_maker->_order_by_chunks ($order_by) ) {
@@ -858,8 +865,9 @@ sub _extract_order_criteria {
 
       # order criteria may have come back pre-quoted (literals and whatnot)
       # this is fragile, but the best we can currently do
-      $chunk->[0] =~ s/^ $lq (.+?) $rq $sep $lq (.+?) $rq $/"$1.$2"/xe
-        or $chunk->[0] =~ s/^ $lq (.+) $rq $/$1/x;
+      if (my @quoted = $chunk->[0] =~ /\A ($quoted_ident_re) (?: $sep ($quoted_ident_re) )? \z/x) {
+        $chunk->[0] = join('.', map { $sql_maker->_unquote($_, @quotes) } grep { defined } @quoted);
+      }
 
       push @chunks, $chunk;
     }
@@ -875,7 +883,7 @@ sub _extract_order_criteria {
 
     # pass these in to deal with literals coming from
     # the user or the deep guts of prefetch
-    my $orig_quote_chars = [$sql_maker->_quote_chars];
+    my $orig_quote_chars = [$sql_maker->_quote_chars, $sql_maker->_escape_char];
 
     local $sql_maker->{quote_char};
     return $parser->($sql_maker, $order_by, $orig_quote_chars);
@@ -1120,25 +1128,36 @@ sub _collapse_cond_unroll_pairs {
       if (ref $rhs eq 'HASH' and ! keys %$rhs) {
         # FIXME - SQLA seems to be doing... nothing...?
       }
+      elsif (ref $rhs eq 'HASH' and keys %$rhs == 1 and exists $rhs->{-ident}) {
+        push @conds, { $lhs => { '=', $rhs } };
+      }
+      elsif (ref $rhs eq 'HASH' and keys %$rhs == 1 and exists $rhs->{-value} and is_plain_value $rhs->{-value}) {
+        push @conds, { $lhs => $rhs->{-value} };
+      }
       elsif (ref $rhs eq 'HASH' and keys %$rhs == 1 and exists $rhs->{'='}) {
-        for my $p ($self->_collapse_cond_unroll_pairs([ [ $lhs => $rhs->{'='} ] ])) {
-
-          # extra sanity check
-          if (keys %$p > 1) {
-            require Data::Dumper::Concise;
-            local $Data::Dumper::Deepcopy = 1;
-            $self->throw_exception(
-              "Internal error: unexpected collapse unroll:"
-            . Data::Dumper::Concise::Dumper { in => { $lhs => $rhs }, out => $p }
-            );
-          }
+        if( is_literal_value $rhs->{'='}) {
+          push @conds, { $lhs => $rhs };
+        }
+        else {
+          for my $p ($self->_collapse_cond_unroll_pairs([ [ $lhs => $rhs->{'='} ] ])) {
+
+            # extra sanity check
+            if (keys %$p > 1) {
+              require Data::Dumper::Concise;
+              local $Data::Dumper::Deepcopy = 1;
+              $self->throw_exception(
+                "Internal error: unexpected collapse unroll:"
+              . Data::Dumper::Concise::Dumper { in => { $lhs => $rhs }, out => $p }
+              );
+            }
 
-          my ($l, $r) = %$p;
+            my ($l, $r) = %$p;
 
-          push @conds, ( ! length ref $r or is_plain_value($r) )
-            ? { $l => $r }
-            : { $l => { '=' => $r } }
-          ;
+            push @conds, ( ! length ref $r or is_plain_value($r) )
+              ? { $l => $r }
+              : { $l => { '=' => $r } }
+            ;
+          }
         }
       }
       elsif (ref $rhs eq 'ARRAY') {
@@ -1208,23 +1227,29 @@ sub _extract_fixed_condition_columns {
       $vals->{$undef_marker} = $v if $consider_nulls
     }
     elsif (
-      ! length ref $v
-        or
-      is_plain_value ($v)
-    ) {
-      $vals->{$v} = $v;
-    }
-    elsif (
       ref $v eq 'HASH'
         and
       keys %$v == 1
-        and
-      ref $v->{'='}
-        and
+    ) {
+      if (exists $v->{-value}) {
+        if (defined $v->{-value}) {
+          $vals->{$v->{-value}} = $v->{-value}
+        }
+        elsif( $consider_nulls ) {
+          $vals->{$undef_marker} = $v->{-value};
+        }
+      }
       # do not need to check for plain values - _collapse_cond did it for us
-      is_literal_value($v->{'='})
+      elsif(ref $v->{'='} and is_literal_value($v->{'='}) ) {
+        $vals->{$v->{'='}} = $v->{'='};
+      }
+    }
+    elsif (
+      ! length ref $v
+        or
+      is_plain_value ($v)
     ) {
-      $vals->{$v->{'='}} = $v->{'='};
+      $vals->{$v} = $v;
     }
     elsif (ref $v eq 'ARRAY' and ($v->[0]||'') eq '-and') {
       for ( @{$v}[1..$#$v] ) {