]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 828344: "contains all of the words" no longer looks for all words within the...
authorByron Jones <bjones@mozilla.com>
Mon, 20 May 2013 17:54:06 +0000 (01:54 +0800)
committerByron Jones <bjones@mozilla.com>
Mon, 20 May 2013 17:54:06 +0000 (01:54 +0800)
r=LpSolit, a=LpSolit

Bugzilla/DB.pm
Bugzilla/DB/Oracle.pm
Bugzilla/Search.pm
Bugzilla/Search/Clause.pm
Bugzilla/Search/Condition.pm
Bugzilla/Search/Quicksearch.pm
xt/lib/Bugzilla/Test/Search/Constants.pm
xt/lib/Bugzilla/Test/Search/FieldTest.pm

index 0c841632f517d9b1678602f36046d31e6ab85ea5..1f9c31518a04546a66bae8a5cd25ee3c581c9658 100644 (file)
@@ -405,8 +405,10 @@ sub sql_string_until {
 }
 
 sub sql_in {
-    my ($self, $column_name, $in_list_ref) = @_;
-    return " $column_name IN (" . join(',', @$in_list_ref) . ") ";
+    my ($self, $column_name, $in_list_ref, $negate) = @_;
+    return " $column_name "
+             . ($negate ? "NOT " : "")
+             . "IN (" . join(',', @$in_list_ref) . ") ";
 }
 
 sub sql_fulltext_search {
index 1427bcedd7f086303b8b96f37050b08aa31a34d6..20eb0e5503db4dceae3476dc3b692d0d3619e24d 100644 (file)
@@ -216,16 +216,16 @@ sub sql_position {
 }
 
 sub sql_in {
-    my ($self, $column_name, $in_list_ref) = @_;
+    my ($self, $column_name, $in_list_ref, $negate) = @_;
     my @in_list = @$in_list_ref;
-    return $self->SUPER::sql_in($column_name, $in_list_ref) if $#in_list < 1000;
+    return $self->SUPER::sql_in($column_name, $in_list_ref, $negate) if $#in_list < 1000;
     my @in_str;
     while (@in_list) {
         my $length = $#in_list + 1;
         my $splice = $length > 1000 ? 1000 : $length;
         my @sub_in_list = splice(@in_list, 0, $splice);
         push(@in_str, 
-             $self->SUPER::sql_in($column_name, \@sub_in_list)); 
+             $self->SUPER::sql_in($column_name, \@sub_in_list, $negate));
     }
     return "( " . join(" OR ", @in_str) . " )";
 }
index 656d163ea82b4aa101fcec8389e291169b5dd80b..8e419c0ee84de7ae86092821cd9fb5eee6e532d9 100644 (file)
@@ -290,12 +290,14 @@ use constant OPERATOR_FIELD_OVERRIDE => {
     },
     dependson        => MULTI_SELECT_OVERRIDE,
     keywords         => MULTI_SELECT_OVERRIDE,
-    'flagtypes.name' => MULTI_SELECT_OVERRIDE,
+    'flagtypes.name' => {
+        _non_changed => \&_flagtypes_nonchanged,
+    },
     longdesc => {
-        %{ MULTI_SELECT_OVERRIDE() },
         changedby     => \&_long_desc_changedby,
         changedbefore => \&_long_desc_changedbefore_after,
         changedafter  => \&_long_desc_changedbefore_after,
+        _non_changed  => \&_long_desc_nonchanged,
     },
     'longdescs.count' => {
         changedby     => \&_long_desc_changedby,
@@ -690,8 +692,16 @@ sub sql {
     my ($self) = @_;
     return $self->{sql} if $self->{sql};
     my $dbh = Bugzilla->dbh;
-    
+
     my ($joins, $clause) = $self->_charts_to_conditions();
+
+    if (!$clause->as_string
+        && !Bugzilla->params->{'search_allow_no_criteria'}
+        && !$self->{allow_unlimited})
+    {
+        ThrowUserError('buglist_parameters_required');
+    }
+
     my $select = join(', ', $self->_sql_select);
     my $from = $self->_sql_from($joins);
     my $where = $self->_sql_where($clause);
@@ -1191,14 +1201,7 @@ sub _sql_where {
     # SQL a bit more readable for debugging.
     my $where = join("\n   AND ", $self->_standard_where);
     my $clause_sql = $main_clause->as_string;
-    if ($clause_sql) {
-        $where .= "\n   AND " . $clause_sql;
-    }
-    elsif (!Bugzilla->params->{'search_allow_no_criteria'}
-           && !$self->{allow_unlimited})
-    {
-        ThrowUserError('buglist_parameters_required');
-    }
+    $where .= "\n   AND " . $clause_sql if $clause_sql;
     return $where;
 }
 
@@ -1689,6 +1692,7 @@ sub _handle_chart {
         value      => $string_value,
         all_values => $value,
         joins      => [],
+        condition  => $condition,
     );
     $search_args{quoted} = $self->_quote_unless_numeric(\%search_args);
     # This should add a "term" selement to %search_args.
@@ -1861,8 +1865,14 @@ sub _quote_unless_numeric {
 }
 
 sub build_subselect {
-    my ($outer, $inner, $table, $cond) = @_;
-    return "$outer IN (SELECT $inner FROM $table WHERE $cond)";
+    my ($outer, $inner, $table, $cond, $negate) = @_;
+    # Execute subselects immediately to avoid dependent subqueries, which are
+    # large performance hits on MySql
+    my $q = "SELECT DISTINCT $inner FROM $table WHERE $cond";
+    my $dbh = Bugzilla->dbh;
+    my $list = $dbh->selectcol_arrayref($q);
+    return $negate ? "1=1" : "1=2" unless @$list;
+    return $dbh->sql_in($outer, $list, $negate);
 }
 
 # Used by anyexact to get the list of input values. This allows us to
@@ -2327,14 +2337,50 @@ sub _long_desc_changedbefore_after {
     }
 }
 
+sub _long_desc_nonchanged {
+    my ($self, $args) = @_;
+    my ($chart_id, $operator, $value, $joins) =
+        @$args{qw(chart_id operator value joins)};
+    my $dbh = Bugzilla->dbh;
+
+    my $table = "longdescs_$chart_id";
+    my $join_args = {
+        chart_id   => $chart_id,
+        sequence   => $chart_id,
+        field      => 'longdesc',
+        full_field => "$table.thetext",
+        operator   => $operator,
+        value      => $value,
+        all_values => $value,
+        quoted     => $dbh->quote($value),
+        joins      => [],
+    };
+    $self->_do_operator_function($join_args);
+
+    # If the user is not part of the insiders group, they cannot see
+    # private comments
+    if (!$self->_user->is_insider) {
+        $join_args->{term} .= " AND $table.isprivate = 0";
+    }
+
+    my $join = {
+        table => 'longdescs',
+        as    => $table,
+        extra => [ $join_args->{term} ],
+    };
+    push(@$joins, $join);
+
+    $args->{term} =  "$table.comment_id IS NOT NULL";
+}
+
 sub _content_matches {
     my ($self, $args) = @_;
     my ($chart_id, $joins, $fields, $operator, $value) =
         @$args{qw(chart_id joins fields operator value)};
     my $dbh = Bugzilla->dbh;
-    
+
     # "content" is an alias for columns containing text for which we
-    # can search a full-text index and retrieve results by relevance, 
+    # can search a full-text index and retrieve results by relevance,
     # currently just bug comments (and summaries to some degree).
     # There's only one way to search a full-text index, so we only
     # accept the "matches" operator, which is specific to full-text
@@ -2582,6 +2628,53 @@ sub _multiselect_multiple {
     }
 }
 
+sub _flagtypes_nonchanged {
+    my ($self, $args) = @_;
+    my ($chart_id, $operator, $value, $joins, $condition) =
+        @$args{qw(chart_id operator value joins condition)};
+    my $dbh = Bugzilla->dbh;
+
+    # For 'not' operators, we need to negate the whole term.
+    # If you search for "Flags" (does not contain) "approval+" we actually want
+    # to return *bugs* that don't contain an approval+ flag.  Without rewriting
+    # the negation we'll search for *flags* which don't contain approval+.
+    if ($operator =~ s/^not//) {
+        $args->{operator} = $operator;
+        $condition->operator($operator);
+        $condition->negate(1);
+    }
+
+    my $subselect_args = {
+        chart_id   => $chart_id,
+        sequence   => $chart_id,
+        field      => 'flagtypes.name',
+        full_field =>  $dbh->sql_string_concat("flagtypes_$chart_id.name", "flags_$chart_id.status"),
+        operator   => $operator,
+        value      => $value,
+        all_values => $value,
+        quoted     => $dbh->quote($value),
+        joins      => [],
+    };
+    $self->_do_operator_function($subselect_args);
+    my $subselect_term = $subselect_args->{term};
+
+    # don't call build_subselect as this must run as a true sub-select
+    $args->{term} = "EXISTS (
+        SELECT 1
+          FROM bugs bugs_$chart_id
+          LEFT JOIN attachments AS attachments_$chart_id
+                    ON bugs_$chart_id.bug_id = attachments_$chart_id.bug_id
+          LEFT JOIN flags AS flags_$chart_id
+                    ON bugs_$chart_id.bug_id = flags_$chart_id.bug_id
+                       AND (flags_$chart_id.attach_id = attachments_$chart_id.attach_id
+                            OR flags_$chart_id.attach_id IS NULL)
+          LEFT JOIN flagtypes AS flagtypes_$chart_id
+                    ON flags_$chart_id.type_id = flagtypes_$chart_id.id
+     WHERE bugs_$chart_id.bug_id = bugs.bug_id
+           AND $subselect_term
+    )";
+}
+
 sub _multiselect_nonchanged {
     my ($self, $args) = @_;
     my ($chart_id, $joins, $field, $operator) =
@@ -2659,8 +2752,7 @@ sub _multiselect_term {
     my $term = $args->{term};
     $term .= $args->{_extra_where} || '';
     my $select = $args->{_select_field} || 'bug_id';
-    my $not_sql = $not ? "NOT " : '';
-    return "bugs.bug_id ${not_sql}IN (SELECT $select FROM $table WHERE $term)";
+    return build_subselect("bugs.bug_id", $select, $table, $term, $not);
 }
 
 ###############################
index a068ce5ed4a5f636de5690e70d19646c3edb5bcd..5f5ea5b507bd8ac9b83b76e377c2c85912268109 100644 (file)
@@ -93,25 +93,29 @@ sub walk_conditions {
 
 sub as_string {
     my ($self) = @_;
-    my @strings;
-    foreach my $child (@{ $self->children }) {
-        next if $child->isa(__PACKAGE__) && !$child->has_translated_conditions;
-        next if $child->isa('Bugzilla::Search::Condition')
-                && !$child->translated;
+    if (!$self->{sql}) {
+        my @strings;
+        foreach my $child (@{ $self->children }) {
+            next if $child->isa(__PACKAGE__) && !$child->has_translated_conditions;
+            next if $child->isa('Bugzilla::Search::Condition')
+                    && !$child->translated;
 
-        my $string = $child->as_string;
-        if ($self->joiner eq 'AND') {
-            $string = "( $string )" if $string =~ /OR/;
-        }
-        else {
-            $string = "( $string )" if $string =~ /AND/;
+            my $string = $child->as_string;
+            next unless $string;
+            if ($self->joiner eq 'AND') {
+                $string = "( $string )" if $string =~ /OR/;
+            }
+            else {
+                $string = "( $string )" if $string =~ /AND/;
+            }
+            push(@strings, $string);
         }
-        push(@strings, $string);
+
+        my $sql = join(' ' . $self->joiner . ' ', @strings);
+        $sql = "NOT( $sql )" if $sql && $self->negate;
+        $self->{sql} = $sql;
     }
-    
-    my $sql = join(' ' . $self->joiner . ' ', @strings);
-    $sql = "NOT( $sql )" if $sql && $self->negate;
-    return $sql;
+    return $self->{sql};
 }
 
 # Search.pm converts URL parameters to Clause objects. This helps do the
index 2268da19743d122aa6a95aac16902e3db1a780e6..167b4f01ec3bc8fcd779ba874b8b09ec965582c7 100644 (file)
@@ -32,9 +32,16 @@ sub new {
 }
 
 sub field    { return $_[0]->{field}    }
-sub operator { return $_[0]->{operator} }
 sub value    { return $_[0]->{value}    }
 
+sub operator {
+    my ($self, $value) = @_;
+    if (@_ == 2) {
+        $self->{operator} = $value;
+    }
+    return $self->{operator};
+}
+
 sub fov {
     my ($self) = @_;
     return ($self->field, $self->operator, $self->value);
index 7424f831f16edcdc9637b3760fdad21df5f169c4..fd9d796d1609b0bf65c964499bcf28cd617b495a 100644 (file)
@@ -377,9 +377,14 @@ sub _handle_field_names {
 
     # Flag and requestee shortcut
     if ($or_operand =~ /^(?:flag:)?([^\?]+\?)([^\?]*)$/) {
-        addChart('flagtypes.name', 'substring', $1, $negate);
-        $chart++; $and = $or = 0; # Next chart for boolean AND
-        addChart('requestees.login_name', 'substring', $2, $negate);
+        my ($flagtype, $requestee) = ($1, $2);
+        addChart('flagtypes.name', 'substring', $flagtype, $negate);
+        if ($requestee) {
+            # AND
+            $chart++;
+            $and = $or = 0;
+            addChart('requestees.login_name', 'substring', $requestee, $negate);
+        }
         return 1;
     }
 
index 512d180d919aebccb9c1085be7b5452f6b8b3631..051570ff8bb3a3c1416c8bff126d7f548f5cf40d 100644 (file)
@@ -197,11 +197,14 @@ use constant GREATERTHAN_BROKEN => (
 );
 
 # allwords and allwordssubstr have these broken tests in common.
-#
-# allwordssubstr on cc fields matches against a single cc,
-# instead of matching against all ccs on a bug.
 use constant ALLWORDS_BROKEN => (
+    # allwordssubstr on cc fields matches against a single cc,
+    # instead of matching against all ccs on a bug.
     cc        => { contains => [1] },
+    # bug 828344 changed how these searches operate to revert back to the 4.0
+    # behavour, so these tests need to be updated (bug 849117).
+    'flagtypes.name' => { contains => [1] },
+    longdesc         => { contains => [1] },
 );
 
 # Fields that don't generally work at all with changed* searches, but
@@ -330,6 +333,24 @@ use constant KNOWN_BROKEN => {
         # This should probably search the reporter.
         creation_ts => { contains => [1] },
     },
+    notequals => {
+        'flagtypes.name' => { contains => [1, 5] },
+        longdesc         => { contains => [1] },
+    },
+    notregexp => {
+        'flagtypes.name' => { contains => [1, 5] },
+        longdesc         => { contains => [1] },
+    },
+    notsubstring => {
+        'flagtypes.name' => { contains => [5] },
+        longdesc         => { contains => [1] },
+    },
+    nowords => {
+        'flagtypes.name' => { contains => [1, 5] },
+    },
+    nowordssubstr => {
+        'flagtypes.name' => { contains => [5] },
+    },
 };
 
 ###################
@@ -360,17 +381,34 @@ use constant CHANGED_FROM_TO_BROKEN_NOT => (
 
 # These are field/operator combinations that are broken when run under NOT().
 use constant BROKEN_NOT => {
-    allwords       => {
-        cc => { contains => [1] },
+    allwords => {
+        cc               => { contains => [1] },
+        'flagtypes.name' => { contains => [1, 5] },
+        longdesc         => { contains => [1] },
     },
     'allwords-<1> <2>' => {
         cc => { },
     },
     allwordssubstr => {
-        cc => { contains => [1] },
+        cc               => { contains => [1] },
+        'flagtypes.name' => { contains => [5, 6] },
+        longdesc         => { contains => [1] },
     },
     'allwordssubstr-<1>,<2>' => {
-        cc => { },
+        cc               => { },
+        longdesc         => { contains => [1] },
+    },
+    anyexact => {
+        'flagtypes.name' => { contains => [1, 2, 5] },
+    },
+    anywords => {
+        'flagtypes.name' => { contains => [1, 2, 5] },
+    },
+    anywordssubstr => {
+        'flagtypes.name' => { contains => [5] },
+    },
+    casesubstring => {
+        'flagtypes.name' => { contains => [5] },
     },
     changedafter => {
         "attach_data.thedata"   => { contains => [2, 3, 4] },
@@ -397,7 +435,6 @@ use constant BROKEN_NOT => {
         dependson       => { contains => [1, 3] },
         work_time       => { contains => [1] },
         FIELD_TYPE_BUG_ID, { contains => [1 .. 4] },
-        
     },
     changedto => {
         CHANGED_BROKEN_NOT,
@@ -406,10 +443,38 @@ use constant BROKEN_NOT => {
         "remaining_time" => { contains => [1] },
     },
     greaterthan => {
-        cc        => { contains => [1] },
+        cc               => { contains => [1] },
+        'flagtypes.name' => { contains => [5] },
     },
     greaterthaneq => {
         cc               => { contains => [1] },
+        'flagtypes.name' => { contains => [2, 5] },
+    },
+    equals => {
+        'flagtypes.name' => { contains => [1, 5] },
+    },
+    notequals => {
+        longdesc         => { contains => [1] },
+    },
+    notregexp => {
+        longdesc         => { contains => [1] },
+    },
+    notsubstring => {
+        longdesc         => { contains => [1] },
+    },
+    lessthan => {
+        'flagtypes.name' => { contains => [5] },
+    },
+    lessthaneq => {
+        'flagtypes.name' => { contains => [1, 5] },
+    },
+    regexp => {
+        'flagtypes.name' => { contains => [1, 5] },
+        longdesc         => { contains => [1] },
+    },
+    substring => {
+        'flagtypes.name' => { contains => [5] },
+        longdesc         => { contains => [1] },
     },
 };
 
index 283a90d16290523e20c54a60946d3dbe48a3814c..ee25f2dc6efd206e3c574a03c8df7b7645b9edf0 100644 (file)
@@ -28,6 +28,7 @@ use strict;
 use warnings;
 use Bugzilla::Search;
 use Bugzilla::Test::Search::Constants;
+use Bugzilla::Util qw(trim);
 
 use Data::Dumper;
 use Scalar::Util qw(blessed);
@@ -72,6 +73,13 @@ sub bug {
     my $self = shift;
     return $self->search_test->bug(@_);
 }
+sub number {
+    my ($self, $id) = @_;
+    foreach my $number (1..NUM_BUGS) {
+        return $number if $self->search_test->bug($number)->id == $id;
+    }
+    return 0;
+}
 
 # The name displayed for this test by Test::More. Used in test descriptions.
 sub name {
@@ -147,9 +155,18 @@ sub translated_value {
     return $self->{translated_value};
 }
 # Used in failure diagnostic messages.
-sub debug_value {
-    my ($self) = @_;
-    return "Value: '" . $self->translated_value . "'";
+sub debug_fail {
+    my ($self, $number, $results, $sql) = @_;
+    my @expected = @{ $self->test->{contains} };
+    my @results = sort
+                  map { $self->number($_) }
+                  map { $_->[0] }
+                  @$results;
+    return
+        "   Value: '" . $self->translated_value . "'\n" .
+        "Expected: [" . join(',', @expected) . "]\n" .
+        " Results: [" . join(',', @results) . "]\n" .
+        trim($sql) . "\n";
 }
 
 # True for a bug if we ran the "transform" function on it and the
@@ -184,6 +201,7 @@ sub bug_is_contained {
 # The tests we know are broken for this operator/field combination.
 sub _known_broken {
     my ($self, $constant, $skip_pg_check) = @_;
+
     $constant ||= KNOWN_BROKEN;
     my $field = $self->field;
     my $type = $self->field_object->type;
@@ -192,8 +210,8 @@ sub _known_broken {
     my $value_name = "$operator-$value";
     if (my $extra_name = $self->test->{extra_name}) {
         $value_name .= "-$extra_name";
-    }    
-    
+    }
+
     my $value_broken = $constant->{$value_name}->{$field};
     $value_broken ||= $constant->{$value_name}->{$type};
     return $value_broken if $value_broken;
@@ -601,12 +619,12 @@ sub _test_content_for_bug {
         if ($self->bug_is_contained($number)) {
             ok($result_ids{$bug_id},
                "$name: contains bug $number ($bug_id)")
-                or diag Dumper($results) . $self->debug_value . "\n\nSQL: $sql";
+                or diag $self->debug_fail($number, $results, $sql);
         }
         else {
             ok(!$result_ids{$bug_id},
                "$name: does not contain bug $number ($bug_id)")
-                or diag Dumper($results) . $self->debug_value . "\n\nSQL: $sql";
+                or diag $self->debug_fail($number, $results, $sql);
         }
     }
 }