Use proper quote handling in _count_subq_rs
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / ResultSet.pm
index d9d8959..c82711a 100644 (file)
@@ -870,7 +870,7 @@ sub find {
     # relationship
   }
   else {
-    my (@unique_queries, %seen_column_combinations);
+    my (@unique_queries, %seen_column_combinations, $ci, @fc_exceptions);
 
     # no key was specified - fall down to heuristics mode:
     # run through all unique queries registered on the resultset, and
@@ -887,17 +887,25 @@ sub find {
         join "\x00", sort $rsrc->unique_constraint_columns($c_name)
       }++;
 
-      push @unique_queries, try {
-        $self->result_source->_minimal_valueset_satisfying_constraint(
-          constraint_name => $c_name,
-          values => ($self->_merge_with_rscond($call_cond))[0],
-        ),
-      } || ();
+      try {
+        push @unique_queries, $self->_qualify_cond_columns(
+          $self->result_source->_minimal_valueset_satisfying_constraint(
+            constraint_name => $c_name,
+            values => ($self->_merge_with_rscond($call_cond))[0],
+            columns_info => ($ci ||= $self->result_source->columns_info),
+          ),
+          $alias
+        );
+      }
+      catch {
+        push @fc_exceptions, $_ if $_ =~ /\bFilterColumn\b/;
+      };
     }
 
-    $final_cond = @unique_queries
-      ? [ map { $self->_qualify_cond_columns($_, $alias) } @unique_queries ]
-      : $self->_non_unique_find_fallback ($call_cond, $attrs)
+    $final_cond =
+        @unique_queries   ? \@unique_queries
+      : @fc_exceptions    ? $self->throw_exception(join "; ", map { $_ =~ /(.*) at .+ line \d+$/s } @fc_exceptions )
+      :                     $self->_non_unique_find_fallback ($call_cond, $attrs)
     ;
   }
 
@@ -1722,7 +1730,8 @@ sub _count_subq_rs {
         $sql_maker->{name_sep} = '';
       }
 
-      my ($lquote, $rquote, $sep) = map { quotemeta $_ } ($sql_maker->_quote_chars, $sql_maker->name_sep);
+      my $sep = quotemeta($sql_maker->name_sep);
+      my $ident_re = $sql_maker->_quoted_ident_re;
 
       my $having_sql = $sql_maker->_parse_rs_attrs ({ having => $attrs->{having} });
       my %seen_having;
@@ -1730,13 +1739,13 @@ sub _count_subq_rs {
       # search for both a proper quoted qualified string, for a naive unquoted scalarref
       # and if all fails for an utterly naive quoted scalar-with-function
       while ($having_sql =~ /
-        $rquote $sep $lquote (.+?) $rquote
+        $ident_re $sep ($ident_re)
           |
         [\s,] \w+ \. (\w+) [\s,]
           |
-        [\s,] $lquote (.+?) $rquote [\s,]
+        [\s,] ($ident_re) [\s,]
       /gx) {
-        my $part = $1 || $2 || $3;  # one of them matched if we got here
+        my $part = $sql_maker->_unquote($1 || $2 || $3);  # one of them matched if we got here
         unless ($seen_having{$part}++) {
           push @parts, $part;
         }
@@ -2220,36 +2229,42 @@ case there are obviously no benefits to using this method over L</create>.
 sub populate {
   my $self = shift;
 
-  my ($data, $guard);
-
   # this is naive and just a quick check
   # the types will need to be checked more thoroughly when the
   # multi-source populate gets added
-  if (ref $_[0] eq 'ARRAY') {
-    return unless @{$_[0]};
-
-    $data = $_[0] if (ref $_[0][0] eq 'HASH' or ref $_[0][0] eq 'ARRAY');
-  }
-
-  $self->throw_exception('Populate expects an arrayref of hashrefs or arrayref of arrayrefs')
-    unless $data;
+  my $data = (
+    ref $_[0] eq 'ARRAY'
+      and
+    ( @{$_[0]} or return )
+      and
+    ( ref $_[0][0] eq 'HASH' or ref $_[0][0] eq 'ARRAY' )
+      and
+    $_[0]
+  ) or $self->throw_exception('Populate expects an arrayref of hashrefs or arrayref of arrayrefs');
 
   # FIXME - no cref handling
   # At this point assume either hashes or arrays
 
   if(defined wantarray) {
-    my @results;
-
-    $guard = $self->result_source->schema->storage->txn_scope_guard
-      if ( @$data > 2 or ( @$data == 2 and ref $data->[0] eq 'ARRAY' ) );
+    my (@results, $guard);
 
     if (ref $data->[0] eq 'ARRAY') {
+      # column names only, nothing to do
+      return if @$data == 1;
+
+      $guard = $self->result_source->schema->storage->txn_scope_guard
+        if @$data > 2;
+
       @results = map
         { my $vals = $_; $self->new_result({ map { $data->[0][$_] => $vals->[$_] } 0..$#{$data->[0]} })->insert }
         @{$data}[1 .. $#$data]
       ;
     }
     else {
+
+      $guard = $self->result_source->schema->storage->txn_scope_guard
+        if @$data > 1;
+
       @results = map { $self->new_result($_)->insert } @$data;
     }
 
@@ -2278,6 +2293,8 @@ sub populate {
 
       # positional(!) explicit column list
       if ($i == 0) {
+        # column names only, nothing to do
+        return if @$data == 1;
 
         $colinfo->{$data->[0][$_]} = { pos => $_, name => $data->[0][$_] } and push @$colnames, $data->[0][$_]
           for 0 .. $#{$data->[0]};
@@ -2415,13 +2432,14 @@ sub populate {
   }
 
 ### start work
+  my $guard;
   $guard = $rsrc->schema->storage->txn_scope_guard
     if $slices_with_rels;
 
 ### main source data
   # FIXME - need to switch entirely to a coderef-based thing,
   # so that large sets aren't copied several times... I think
-  $rsrc->storage->insert_bulk(
+  $rsrc->storage->_insert_bulk(
     $rsrc,
     [ @$colnames, sort keys %$rs_data ],
     [ map {
@@ -4139,10 +4157,11 @@ names:
 
 B<NOTE:> You will almost always need a corresponding L</as> attribute when you
 use L</select>, to instruct DBIx::Class how to store the result of the column.
-Also note that the L</as> attribute has nothing to do with the SQL-side 'AS'
-identifier aliasing. You can however alias a function, so you can use it in
-e.g. an C<ORDER BY> clause. This is done via the C<-as> B<select function
-attribute> supplied as shown in the example above.
+
+Also note that the L</as> attribute has B<nothing to do> with the SQL-side
+C<AS> identifier aliasing. You B<can> alias a function (so you can use it e.g.
+in an C<ORDER BY> clause), however this is done via the C<-as> B<select
+function attribute> supplied as shown in the example above.
 
 =head2 +select
 
@@ -4172,8 +4191,10 @@ Indicates DBIC-side names for object inflation. That is L</as> indicates the
 slot name in which the column value will be stored within the
 L<Row|DBIx::Class::Row> object. The value will then be accessible via this
 identifier by the C<get_column> method (or via the object accessor B<if one
-with the same name already exists>) as shown below. The L</as> attribute has
-B<nothing to do> with the SQL-side C<AS>. See L</select> for details.
+with the same name already exists>) as shown below.
+
+The L</as> attribute has B<nothing to do> with the SQL-side identifier
+aliasing C<AS>. See L</select> for details.
 
   $rs = $schema->resultset('Employee')->search(undef, {
     select => [
@@ -4349,8 +4370,10 @@ For a more in-depth discussion, see L</PREFETCHING>.
 
 This attribute is a shorthand for specifying a L</join> spec, adding all
 columns from the joined related sources as L</+columns> and setting
-L</collapse> to a true value. For example, the following two queries are
-equivalent:
+L</collapse> to a true value. It can be thought of as a rough B<superset>
+of the L</join> attribute.
+
+For example, the following two queries are equivalent:
 
   my $rs = $schema->resultset('Artist')->search({}, {
     prefetch => { cds => ['genre', 'tracks' ] },
@@ -4527,15 +4550,20 @@ A arrayref of columns to group by. Can include columns of joined tables.
 
 =back
 
-HAVING is a select statement attribute that is applied between GROUP BY and
-ORDER BY. It is applied to the after the grouping calculations have been
-done.
+The HAVING operator specifies a B<secondary> condition applied to the set
+after the grouping calculations have been done. In other words it is a
+constraint just like L</where> (and accepting the same
+L<SQL::Abstract syntax|SQL::Abstract/WHERE CLAUSES>) applied to the data
+as it exists after GROUP BY has taken place. Specifying L</having> without
+L</group_by> is a logical mistake, and a fatal error on most RDBMS engines.
+
+E.g.
 
   having => { 'count_employee' => { '>=', 100 } }
 
 or with an in-place function in which case literal SQL is required:
 
-  having => \[ 'count(employee) >= ?', [ count => 100 ] ]
+  having => \[ 'count(employee) >= ?', 100 ]
 
 =head2 distinct