]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 578888: Search.pm: Add and store joins as data structures instead of
authorMax Kanat-Alexander <mkanat@bugzilla.org>
Thu, 15 Jul 2010 05:33:40 +0000 (22:33 -0700)
committerMax Kanat-Alexander <mkanat@bugzilla.org>
Thu, 15 Jul 2010 05:33:40 +0000 (22:33 -0700)
raw SQL.
r=mkanat, a=mkanat (module owner)

Bugzilla/Search.pm
xt/lib/Bugzilla/Test/Search/Constants.pm

index d4b8f5c856498e9407a4520f5a8101e7394b0f8c..abdd67d209187dd65a3f8671939a84982b092c57 100644 (file)
@@ -52,6 +52,7 @@ use Bugzilla::Field;
 use Bugzilla::Status;
 use Bugzilla::Keyword;
 
+use Data::Dumper;
 use Date::Format;
 use Date::Parse;
 use List::MoreUtils qw(uniq);
@@ -287,7 +288,7 @@ use constant SPECIAL_ORDER => {
             table => 'milestones',
             from  => 'target_milestone',
             to    => 'value',
-            extra => ' AND bugs.product_id = map_target_milestone.product_id',
+            extra => ['bugs.product_id = map_target_milestone.product_id'],
             join  => 'INNER',
         }
     },
@@ -345,11 +346,11 @@ use constant COLUMN_JOINS => {
         join   => 'INNER',
     },
     'flagtypes.name' => {
-        name  => 'map_flags',
+        as    => 'map_flags',
         table => 'flags',
-        extra => ' AND attach_id IS NULL',
+        extra => ['attach_id IS NULL'],
         then_to => {
-            name  => 'map_flagtypes',
+            as    => 'map_flagtypes',
             table => 'flagtypes',
             from  => 'map_flags.type_id',
             to    => 'id',
@@ -358,7 +359,7 @@ use constant COLUMN_JOINS => {
     keywords => {
         table => 'keywords',
         then_to => {
-            name  => 'map_keyworddefs',
+            as    => 'map_keyworddefs',
             table => 'keyworddefs',
             from  => 'map_keywords.keywordid',
             to    => 'id',
@@ -669,7 +670,7 @@ sub _select_order_joins {
     foreach my $field ($self->_input_order_columns) {
         my $join_info = $self->_special_order->{$field}->{join};
         if ($join_info) {
-            my @join_sql = $self->_translate_join($field, $join_info);
+            my @join_sql = $self->_translate_join($join_info, $field);
             push(@joins, @join_sql);
         }
     }
@@ -995,17 +996,18 @@ sub _charts_to_conditions {
                 push(@joins, @{ $or_item->{joins} });
                 push(@having, @{ $or_item->{having} });
             }
-            my $or_sql = join(' OR ', map { "($_)" } @or_terms);
-            push(@and_terms, $or_sql) if $or_sql ne '';
-        }
-        @and_terms = map { "($_)" } @and_terms;
-        foreach my $and_term (@and_terms) {
-            # Clean up the SQL a bit by removing extra parens.
-            while ($and_term =~ /^\(\(/ and $and_term =~ /\)\)$/) {
-                $and_term =~ s/^\(//;
-                $and_term =~ s/\)$//;
+
+            if (@or_terms) {
+                # If a term contains ANDs, we need to put parens around the
+                # condition. This is a pretty weak test, but it's actually OK
+                # to put parens around too many things.
+                @or_terms = map { $_ =~ /\bAND\b/i ? "($_)" : $_ } @or_terms;
+                my $or_sql = join(' OR ', @or_terms);
+                push(@and_terms, $or_sql);
             }
         }
+        # And here we need to paren terms that contain ORs.
+        @and_terms = map { $_ =~ /\bOR\b/i ? "($_)" : $_ } @and_terms;
         my $and_sql = join(' AND ', @and_terms);
         if ($negate and $and_sql ne '') {
             $and_sql = "NOT ($and_sql)";
@@ -1120,11 +1122,11 @@ sub _column_join {
     my $join_info = COLUMN_JOINS->{$field};
     if (!$join_info) {
         if ($self->_multi_select_fields->{$field}) {
-            return $self->_translate_join($field, { table => "bug_$field" });
+            return $self->_translate_join({ table => "bug_$field" }, $field);
         }
         return ();
     }
-    return $self->_translate_join($field, $join_info);
+    return $self->_translate_join($join_info, $field);
 }
 
 sub _valid_values {
@@ -1142,7 +1144,13 @@ sub _valid_values {
 }
 
 sub _translate_join {
-    my ($self, $field, $join_info) = @_;
+    my ($self, $join_info, $field) = @_;
+    
+    die "join with no table: " . Dumper($join_info, $field)
+        if !$join_info->{table};
+    die "join with no name: " . Dumper($join_info, $field)
+        if (!$join_info->{as} and !$field);
+        
     my $from_table = "bugs";
     my $from  = $join_info->{from} || "bug_id";
     if ($from =~ /^(\w+)\.(\w+)$/) {
@@ -1151,15 +1159,23 @@ sub _translate_join {
     my $to    = $join_info->{to}    || "bug_id";
     my $join  = $join_info->{join}  || 'LEFT';
     my $table = $join_info->{table};
-    die "$field requires a table in COLUMN_JOINS" if !$table;
-    my $extra = $join_info->{extra} || '';
-    my $name  = $join_info->{name}  || "map_$field";
+    my @extra = @{ $join_info->{extra} || [] };
+    my $name  = $join_info->{as}    || "map_$field";
     $name =~ s/\./_/g;
+    
+    # If a term contains ORs, we need to put parens around the condition.
+    # This is a pretty weak test, but it's actually OK to put parens
+    # around too many things.
+    @extra = map { $_ =~ /\bOR\b/i ? "($_)" : $_ } @extra;
+    my $extra_condition = join(' AND ', uniq @extra);
+    if ($extra_condition) {
+        $extra_condition = " AND $extra_condition";
+    }
 
     my @join_sql = "$join JOIN $table AS $name"
-                        . " ON $from_table.$from = $name.$to$extra";
+                        . " ON $from_table.$from = $name.$to$extra_condition";
     if (my $then_to = $join_info->{then_to}) {
-        push(@join_sql, $self->_translate_join($field, $then_to));
+        push(@join_sql, $self->_translate_join($then_to));
     }
     return @join_sql;
 }
@@ -1298,7 +1314,8 @@ sub init {
     my %suppseen = ("bugs" => 1);
     my $suppstring = "bugs";
     my @supplist = (" ");
-    foreach my $str ($self->_select_order_joins, @$joins) {
+    my @join_sql = map { $self->_translate_join($_) } @$joins;
+    foreach my $str ($self->_select_order_joins, @join_sql) {
 
         if ($str =~ /^(LEFT|INNER|RIGHT)\s+JOIN/i) {
             $str =~ /^(.*?)\s+ON\s+(.*)$/i;
@@ -1668,12 +1685,15 @@ sub _contact_exact_group {
     $group->check_members_are_visible();
     my $group_ids = Bugzilla::Group->flatten_group_membership($group->id);
     my $table = "user_group_map_$chart_id";
-    my $join_sql =
-        "LEFT JOIN user_group_map AS $table"
-        .     " ON $table.user_id = bugs.$field"
-        .        " AND " . $dbh->sql_in("$table.group_id", $group_ids)
-        .        " AND $table.isbless = 0";
-    push(@$joins, $join_sql);
+    my $join = {
+        table => 'user_group_map',
+        as    => $table,
+        from  => $field,
+        to    => 'user_id',
+        extra => [$dbh->sql_in("$table.group_id", $group_ids),
+                  "$table.isbless = 0"],
+    };
+    push(@$joins, $join);
     if ($operator =~ /^not/) {
         $args->{term} = "$table.group_id IS NULL";
     }
@@ -1694,10 +1714,9 @@ sub _contact_nonchanged {
 
 sub _qa_contact_nonchanged {
     my ($self, $args) = @_;
-    my $joins = $args->{joins};
-    
-    push(@$joins, "LEFT JOIN profiles AS map_qa_contact " .
-                         "ON bugs.qa_contact = map_qa_contact.userid");
+
+    # This will join in map_qa_contact for us.    
+    $self->_add_extra_column('qa_contact');
     $args->{full_field} = "COALESCE(map_qa_contact.login_name,'')";
 }
 
@@ -1734,16 +1753,19 @@ sub _cc_exact_group {
         $args->{sequence}++;
     }
     
-    my $group_table = "user_group_map_$chart_id";
     my $cc_table = "cc_$chart_id";
-    push(@$joins, "LEFT JOIN cc AS $cc_table " .
-                         "ON bugs.bug_id = $cc_table.bug_id");
-    my $join_sql =
-        "LEFT JOIN user_group_map AS $group_table"
-        .     " ON $group_table.user_id = $cc_table.who"
-        .        " AND " . $dbh->sql_in("$group_table.group_id", $all_groups)
-        .        " AND $group_table.isbless = 0 ";
-    push(@$joins, $join_sql);
+    push(@$joins, { table => 'cc', as => $cc_table });
+    my $group_table = "user_group_map_$chart_id";
+    my $group_join = {
+        table => 'user_group_map',
+        as    => $group_table,
+        from  => "$cc_table.who",
+        to    => 'user_id',
+        extra => [$dbh->sql_in("$group_table.group_id", $all_groups),
+                  "$group_table.isbless = 0"],
+    };
+    push(@$joins, $group_join);
+
     if ($operator =~ /^not/) {
         $args->{term} = "$group_table.group_id IS NULL";
     }
@@ -1773,11 +1795,12 @@ sub _cc_nonchanged {
     
     my $term = $args->{term};
     my $table = "cc_$chart_id";
-    my $join_sql =
-        "LEFT JOIN cc AS $table"
-        .     " ON bugs.bug_id = $table.bug_id"
-         .       " AND $table.who IN (SELECT userid FROM profiles WHERE $term)";
-    push(@$joins, $join_sql);
+    my $join = {
+        table => 'cc',
+        as    => $table,
+        extra => ["$table.who IN (SELECT userid FROM profiles WHERE $term)"],
+    };
+    push(@$joins, $join);
     
     $args->{term} = "$table.who IS NOT NULL";
 }
@@ -1788,8 +1811,7 @@ sub _long_desc_changedby {
     my ($chart_id, $joins, $value) = @$args{qw(chart_id joins value)};
     
     my $table = "longdescs_$chart_id";
-    push(@$joins, "LEFT JOIN longdescs AS $table " .
-                         "ON $table.bug_id = bugs.bug_id");
+    push(@$joins, { table => 'longdescs', as => $table });
     my $user_id = login_to_id($value, THROW_ERROR);
     $args->{term} = "$table.who = $user_id";
 }
@@ -1803,11 +1825,12 @@ sub _long_desc_changedbefore_after {
     my $sql_operator = ($operator =~ /before/) ? '<=' : '>=';
     my $table = "longdescs_$chart_id";
     my $sql_date = $dbh->quote(SqlifyDate($value));
-    my $join_sql =
-        "LEFT JOIN longdescs AS $table "
-        .        " ON $table.bug_id = bugs.bug_id"
-        .           " AND $table.bug_when $sql_operator $sql_date";
-    push(@$joins, $join_sql);
+    my $join = {
+        table => 'longdescs',
+        as    => $table,
+        extra => ["$table.bug_when $sql_operator $sql_date"],
+    };
+    push(@$joins, $join);
     $args->{term} = "$table.bug_when IS NOT NULL";
 }
 
@@ -1828,8 +1851,7 @@ sub _content_matches {
     my $table = "bugs_fulltext_$chart_id";
     my $comments_col = "comments";
     $comments_col = "comments_noprivate" unless $self->{'user'}->is_insider;
-    push(@$joins, "LEFT JOIN bugs_fulltext AS $table " .
-                         "ON bugs.bug_id = $table.bug_id");
+    push(@$joins, { table => 'bugs_fulltext', as => $table });
     
     # Create search terms to add to the SELECT and WHERE clauses.
     my ($term1, $rterm1) =
@@ -1903,11 +1925,12 @@ sub _commenter {
     }
     $self->_do_operator_function($args);
     my $term = $args->{term};
-    my $join_sql =
-        "LEFT JOIN longdescs AS $table"
-        . " ON $table.bug_id = bugs.bug_id $extra"
-        .    " AND $table.who IN (SELECT userid FROM profiles WHERE $term)";
-    push(@$joins, $join_sql);
+    my $join = {
+        table => 'longdescs',
+        as    => $table,
+        extra => ["$table.who IN (SELECT userid FROM profiles WHERE $term)"],
+    };
+    push(@$joins, $join);
     $args->{term} = "$table.who IS NOT NULL";
 }
 
@@ -1916,9 +1939,13 @@ sub _long_desc {
     my ($chart_id, $joins) = @$args{qw(chart_id joins)};
     
     my $table = "longdescs_$chart_id";
-    my $extra = $self->{'user'}->is_insider ? "" : "AND $table.isprivate = 0";
-    push(@$joins, "LEFT JOIN longdescs AS $table " .
-                         "ON $table.bug_id = bugs.bug_id $extra");
+    my $extra = $self->{'user'}->is_insider ? [] : ["$table.isprivate = 0"];
+    my $join = {
+        table => 'longdescs',
+        as    => $table,
+        extra => $extra,
+    };
+    push(@$joins, $join);
     $args->{full_field} = "$table.thetext";
 }
 
@@ -1927,9 +1954,13 @@ sub _longdescs_isprivate {
     my ($chart_id, $joins) = @$args{qw(chart_id joins)};
     
     my $table = "longdescs_$chart_id";
-    my $extra = $self->{'user'}->is_insider ? "" : "AND $table.isprivate = 0";
-    push(@$joins, "LEFT JOIN longdescs AS $table " .
-                         "ON $table.bug_id = bugs.bug_id $extra");
+    my $extra = $self->{'user'}->is_insider ? [] : ["$table.isprivate = 0"];
+    my $join = {
+        table => 'longdescs',
+        as    => $table,
+        extra => $extra,
+    };
+    push(@$joins, $join);
     $args->{full_field} = "$table.isprivate";
 }
 
@@ -1938,8 +1969,7 @@ sub _work_time_changedby {
     my ($chart_id, $joins, $value) = @$args{qw(chart_id joins value)};
     
     my $table = "longdescs_$chart_id";
-    push(@$joins, "LEFT JOIN longdescs AS $table " .
-                         "ON $table.bug_id = bugs.bug_id");
+    push(@$joins, { table => 'longdescs', as => $table });
     my $user_id = login_to_id($value, THROW_ERROR);
     $args->{term} = "$table.who = $user_id AND $table.work_time != 0";
 }
@@ -1953,12 +1983,13 @@ sub _work_time_changedbefore_after {
     my $table = "longdescs_$chart_id";
     my $sql_operator = ($operator =~ /before/) ? '<=' : '>=';
     my $sql_date = $dbh->quote(SqlifyDate($value));
-    my $join_sql =
-        "LEFT JOIN longdescs AS $table"
-        .     " ON $table.bug_id = bugs.bug_id"
-        .        " AND $table.work_time != 0"
-        .        " AND $table.bug_when $sql_operator $sql_date";
-    push(@$joins, $join_sql);
+    my $join = {
+        table => 'longdescs',
+        as    => $table,
+        extra => ["$table.work_time != 0",
+                  "$table.bug_when $sql_operator $sql_date"],
+    };
+    push(@$joins, $join);
     
     $args->{term} = "$table.bug_when IS NOT NULL";
 }
@@ -1968,8 +1999,7 @@ sub _work_time {
     my ($chart_id, $joins) = @$args{qw(chart_id joins)};
     
     my $table = "longdescs_$chart_id";
-    push(@$joins, "LEFT JOIN longdescs AS $table " .
-                         "ON $table.bug_id = bugs.bug_id");
+    push(@$joins, { table => 'longdescs', as => $table });
     $args->{full_field} = "$table.work_time";
 }
 
@@ -1987,8 +2017,7 @@ sub _percentage_complete {
     my $expression = COLUMNS->{percentage_complete}->{name};
     $expression =~ s/\bldtime\b/$table/g;
     $args->{full_field} = "($expression)";
-    push(@$joins, "LEFT JOIN longdescs AS $table " .
-                         "ON $table.bug_id = bugs.bug_id");
+    push(@$joins, { table => 'longdescs', as => $table });
 
     # We need remaining_time in _select_columns, otherwise we can't use
     # it in the expression for creating percentage_complete.
@@ -2007,18 +2036,22 @@ sub _bug_group_nonchanged {
     my ($chart_id, $joins, $field) = @$args{qw(chart_id joins field)};
     
     my $map_table = "bug_group_map_$chart_id";
-    push(@$joins,
-        "LEFT JOIN bug_group_map AS $map_table " .
-               "ON bugs.bug_id = $map_table.bug_id");
+    
+    push(@$joins, { table => 'bug_group_map', as => $map_table });
     
     my $groups_table = "groups_$chart_id";
     my $full_field = "$groups_table.name";
     $args->{full_field} = $full_field;
     $self->_do_operator_function($args);
     my $term = $args->{term};
-    push(@$joins,
-        "LEFT JOIN groups AS $groups_table " .
-               "ON $groups_table.id = $map_table.group_id AND $term");
+    my $groups_join = {
+        table => 'groups',
+        as    => $groups_table,
+        from  => "$map_table.group_id",
+        to    => 'id',
+        extra => [$term],
+    };
+    push(@$joins, $groups_join);
     $args->{term} = "$full_field IS NOT NULL";
 }
 
@@ -2029,11 +2062,19 @@ sub _attach_data_thedata {
     my $attach_table = "attachments_$chart_id";
     my $data_table = "attachdata_$chart_id";
     my $extra = $self->{'user'}->is_insider
-                ? "" : "AND $attach_table.isprivate = 0";
-    push(@$joins, "LEFT JOIN attachments AS $attach_table " .
-                         "ON bugs.bug_id = $attach_table.bug_id $extra");
-    push(@$joins, "LEFT JOIN attach_data AS $data_table " .
-                       "ON $data_table.id = $attach_table.attach_id");
+                ? [] : ["$attach_table.isprivate = 0"];
+    my $attachments_join = {
+        table => 'attachments',
+        as    => $attach_table,
+        extra => $extra,
+    };
+    my $data_join = {
+        table => 'attach_data',
+        as    => $data_table,
+        from  => "$attach_table.attach_id",
+        to    => "id",
+    };
+    push(@$joins, $attachments_join, $data_join);
     $args->{full_field} = "$data_table.thedata";
 }
 
@@ -2041,16 +2082,24 @@ sub _attachments_submitter {
     my ($self, $args) = @_;
     my ($chart_id, $joins) = @$args{qw(chart_id joins)};
     
-    my $attach_table = "attachment_submitter_$chart_id";
+    my $attach_table = "attachments_$chart_id";
+    my $profiles_table = "map_attachment_submitter_$chart_id";    
     my $extra = $self->{'user'}->is_insider
-                ? "" : "AND $attach_table.isprivate = 0";
-    push(@$joins, "LEFT JOIN attachments AS $attach_table " .
-                         "ON bugs.bug_id = $attach_table.bug_id $extra");
+                ? [] : ["$attach_table.isprivate = 0"];
+    my $attachments_join = {
+        table => 'attachments',
+        as    => $attach_table,
+        extra => $extra,
+    };
+    my $profiles_join = {
+        table => 'profiles',
+        as    => $profiles_table,
+        from  => "$attach_table.submitter_id",
+        to    => 'userid',
+    };
+    push(@$joins, $attachments_join, $profiles_join);
     
-    my $map_table = "map_attachment_submitter_$chart_id";
-    push(@$joins, "LEFT JOIN profiles AS $map_table " .
-                         "ON $attach_table.submitter_id = $map_table.userid");
-    $args->{full_field} = "$map_table.login_name";
+    $args->{full_field} = "$profiles_table.login_name";
 }
 
 sub _attachments {
@@ -2060,10 +2109,13 @@ sub _attachments {
     my $dbh = Bugzilla->dbh;
     
     my $table = "attachments_$chart_id";
-    my $extra = $self->{'user'}->is_insider ? "" : "AND $table.isprivate = 0";
-    push(@$joins, "LEFT JOIN attachments AS $table " .
-                         "ON bugs.bug_id = $table.bug_id $extra");
-    
+    my $extra = $self->{'user'}->is_insider? [] : ["$table.isprivate = 0"];
+    my $join = {
+        table => 'attachments',
+        as    => $table,
+        extra => $extra,
+    };
+    push(@$joins, $join);
     $field =~ /^attachments\.(.+)$/;
     my $attach_field = $1;
     
@@ -2074,19 +2126,25 @@ sub _join_flag_tables {
     my ($self, $args) = @_;
     my ($joins, $chart_id) = @$args{qw(joins chart_id)};
     
-    my $attachments = "attachments_$chart_id";
+    my $attach_table = "attachments_$chart_id";
+    my $flags_table = "flags_$chart_id";
     my $extra = $self->{'user'}->is_insider
-                ? "" : "AND $attachments.isprivate = 0";
-    push(@$joins, "LEFT JOIN attachments AS $attachments " .
-                         "ON bugs.bug_id = $attachments.bug_id $extra");
-    my $flags = "flags_$chart_id";
+                ? [] : ["$attach_table.isprivate = 0"];
+    my $attachments_join = {
+        table => 'attachments',
+        as    => $attach_table,
+        extra => $extra,
+    };
     # We join both the bugs and the attachments table in separately,
     # and then the join code will later combine the terms.
-    push(@$joins, "LEFT JOIN flags AS $flags " . 
-                         "ON bugs.bug_id = $flags.bug_id ");
-    push(@$joins, "LEFT JOIN flags AS $flags " .
-                         "ON $flags.attach_id = $attachments.attach_id " .
-                             "OR $flags.attach_id IS NULL");
+    my $flags_join = {
+        table => 'flags',
+        as    => $flags_table,
+        extra => ["($flags_table.attach_id = $attach_table.attach_id "
+                  . " OR $flags_table.attach_id IS NULL)"],
+    };
+    
+    push(@$joins, $attachments_join, $flags_join);
 }
 
 sub _flagtypes_name {
@@ -2110,8 +2168,13 @@ sub _flagtypes_name {
     $self->_join_flag_tables($args);
     my $flags = "flags_$chart_id";
     my $flagtypes = "flagtypes_$chart_id";
-    push(@$joins, "LEFT JOIN flagtypes AS $flagtypes " . 
-                         "ON $flags.type_id = $flagtypes.id");
+    my $flagtypes_join = {
+        table => 'flagtypes',
+        as    => $flagtypes,
+        from  => "$flags.type_id",
+        to    => 'id',
+    };
+    push(@$joins, $flagtypes_join);
     
     # Generate the condition by running the operator-specific
     # function. Afterwards the condition resides in the $args->{term}
@@ -2146,8 +2209,13 @@ sub _requestees_login_name {
     $self->_join_flag_tables($args);
     my $flags = "flags_$chart_id";
     my $map_table = "map_flag_requestees_$chart_id";
-    push(@$joins, "LEFT JOIN profiles AS $map_table " .
-                         "ON $flags.requestee_id = $map_table.userid");
+    my $profiles_join = {
+        table => 'profiles',
+        as    => $map_table,
+        from  => "$flags.requestee_id",
+        to    => 'userid',
+    };
+    push(@$joins, $profiles_join);
 
     $args->{full_field} = "$map_table.login_name";
 }
@@ -2159,9 +2227,13 @@ sub _setters_login_name {
     $self->_join_flag_tables($args);
     my $flags = "flags_$chart_id";
     my $map_table = "map_flag_setters_$chart_id";
-    push(@$joins, "LEFT JOIN profiles AS $map_table " .
-                         "ON $flags.setter_id = $map_table.userid");
-
+    my $profiles_join = {
+        table => 'profiles',
+        as    => $map_table,
+        from  => "$flags.setter_id",
+        to    => 'userid',
+    };
+    push(@$joins, $profiles_join);
     $args->{full_field} = "$map_table.login_name";
 }
 
@@ -2198,9 +2270,10 @@ sub _classification_nonchanged {
     my ($self, $args) = @_;
     my $joins = $args->{joins};
     
-    # Generate the restriction condition
-    push(@$joins, "INNER JOIN products AS map_product " .
-                          "ON bugs.product_id = map_product.id");
+    # This joins the right tables for us.
+    $self->_add_extra_column('product');
+    
+    # Generate the restriction condition    
     $args->{full_field} = "classifications.name";
     $self->_do_operator_function($args);
     my $term = $args->{term};
@@ -2228,13 +2301,12 @@ sub _keywords_exact {
         return;
     }
     
-    # This is an optimization for anywords, since we already know
+    # This is an optimization for anywords and anyexact, since we already know
     # the keyword id from having checked it above.
-    if ($operator eq 'anywords') {
+    if ($operator eq 'anywords' or $operator eq 'anyexact') {
         my $table = "keywords_$chart_id";
         $args->{term} = $dbh->sql_in("$table.keywordid", \@keyword_ids);
-        push(@$joins, "LEFT JOIN keywords AS $table"
-                      .     " ON $table.bug_id = bugs.bug_id");
+        push(@$joins, { table => 'keywords', as => $table });
         return;
     }
     
@@ -2249,10 +2321,14 @@ sub _keywords_nonchanged {
     my $k_table = "keywords_$chart_id";
     my $kd_table = "keyworddefs_$chart_id";
     
-    push(@$joins, "LEFT JOIN keywords AS $k_table " .
-                         "ON $k_table.bug_id = bugs.bug_id");
-    push(@$joins, "LEFT JOIN keyworddefs AS $kd_table " .
-                         "ON $kd_table.id = $k_table.keywordid");
+    push(@$joins, { table => 'keywords', as => $k_table });
+    my $defs_join = {
+        table => 'keyworddefs',
+        as    => $kd_table,
+        from  => "$k_table.keywordid",
+        to    => 'id',
+    };
+    push(@$joins, $defs_join);
     
     $args->{full_field} = "$kd_table.name";
 }
@@ -2268,8 +2344,13 @@ sub _dependson_nonchanged {
     $args->{full_field} = $full_field;
     $self->_do_operator_function($args);
     my $term = $args->{term};
-    push(@$joins, "LEFT JOIN dependencies AS $table " .
-                         "ON $table.blocked = bugs.bug_id AND ($term)");
+    my $dep_join = {
+        table => 'dependencies',
+        as    => $table,
+        to    => 'blocked',
+        extra => [$term],
+    };
+    push(@$joins, $dep_join);
     $args->{term} = "$full_field IS NOT NULL";
 }
 
@@ -2283,8 +2364,13 @@ sub _blocked_nonchanged {
     $args->{full_field} = $full_field;
     $self->_do_operator_function($args);
     my $term = $args->{term};
-    push(@$joins, "LEFT JOIN dependencies AS $table " .
-                         "ON $table.dependson = bugs.bug_id AND ($term)");
+    my $dep_join = {
+        table => 'dependencies',
+        as    => $table,
+        to    => 'dependson',
+        extra => [$term],
+    };
+    push(@$joins, $dep_join);
     $args->{term} = "$full_field IS NOT NULL";
 }
 
@@ -2304,29 +2390,27 @@ sub _owner_idle_time_greater_less {
     my $quoted = $dbh->quote(SqlifyDate($value));
     
     my $ld_table = "comment_$table";
-    my $comments_join =
-        "LEFT JOIN longdescs AS $ld_table"
-        .     " ON $ld_table.who = bugs.assigned_to"
-        .        " AND $ld_table.bug_id = bugs.bug_id"
-        .        " AND $ld_table.bug_when > $quoted";
-    push(@$joins, $comments_join);
-
-    my $act_table = "activity_$table";
-    my $assigned_fieldid = $self->_chart_fields->{'assigned_to'}->id;
-
-    # XXX Why are we joining using $assignedto_fieldid here? It shouldn't
-    #     matter when or if the assignee changed.
-    my $activity_join =
-        "LEFT JOIN bugs_activity AS $act_table"
-        .     " ON ( $act_table.who = bugs.assigned_to"
-        .         "  OR $act_table.fieldid = $assigned_fieldid )"
-        .        " AND $act_table.bug_id = bugs.bug_id"
-        .        " AND $act_table.bug_when > $quoted";
-    push(@$joins, $activity_join);
+    my $act_table = "activity_$table";    
+    my $comments_join = {
+        table => 'longdescs',
+        as    => $ld_table,
+        from  => 'assigned_to',
+        to    => 'who',
+        extra => ["$ld_table.bug_when > $quoted"],
+    };
+    my $activity_join = {
+        table => 'bugs_activity',
+        as    => $act_table,
+        from  => 'assigned_to',
+        to    => 'who',
+        extra => ["$act_table.bug_when > $quoted"]
+    };
+    
+    push(@$joins, $comments_join, $activity_join);
     
     if ($operator =~ /greater/) {
         $args->{term} =
-            "$ld_table.who IS NULL AND $act_table.who IS NULL)";
+            "$ld_table.who IS NULL AND $act_table.who IS NULL";
     } else {
          $args->{term} =
             "$ld_table.who IS NOT NULL OR $act_table.who IS NOT NULL";
@@ -2356,8 +2440,8 @@ sub _multiselect_negative {
 
 sub _multiselect_multiple {
     my ($self, $args) = @_;
-    my ($chart_id, $joins, $field, $operator, $value)
-        = @$args{qw(chart_id joins field operator value)};
+    my ($chart_id, $field, $operator, $value)
+        = @$args{qw(chart_id field operator value)};
     my $dbh = Bugzilla->dbh;
     
     my $table = "bug_$field";
@@ -2387,8 +2471,7 @@ sub _multiselect_nonchanged {
 
     my $table = "${field}_$chart_id";
     $args->{full_field} = "$table.value";
-    push(@$joins, "LEFT JOIN bug_$field AS $table " .
-                         "ON $table.bug_id = bugs.bug_id ");
+    push(@$joins, { table => "bug_$field", as => $table });
 }
 
 sub _simple_operator {
@@ -2527,11 +2610,13 @@ sub _changedbefore_changedafter {
     my $table = "act_${field_id}_$chart_id";
 
     my $sql_date = $dbh->quote(SqlifyDate($value));
-    push(@$joins,
-        "LEFT JOIN bugs_activity AS $table"
-        .     " ON $table.bug_id = bugs.bug_id"
-        .         " AND $table.fieldid = $field_id"
-        .         " AND $table.bug_when $sql_operator $sql_date");
+    my $join = {
+        table => 'bugs_activity',
+        as    => $table,
+        extra => ["$table.fieldid = $field_id",
+                  "$table.bug_when $sql_operator $sql_date"],
+    };
+    push(@$joins, $join);
     $args->{term} = "$table.bug_when IS NOT NULL";
 }
 
@@ -2545,11 +2630,14 @@ sub _changedfrom_changedto {
         || ThrowCodeError("invalid_field_name", { field => $field });
     my $field_id = $field_object->id;
     my $table = "act_${field_id}_$chart_id";
-    push(@$joins,
-        "LEFT JOIN bugs_activity AS $table"
-        .     " ON $table.bug_id = bugs.bug_id"
-        .        " AND $table.fieldid = $field_id"
-        .        " AND $table.$column = $quoted");
+    my $join = {
+        table => 'bugs_activity',
+        as    => $table,
+        extra => ["$table.fieldid = $field_id",
+                  "$table.$column = $quoted"],
+    };
+    push(@$joins, $join);
+
     $args->{term} = "$table.bug_when IS NOT NULL";
 }
 
@@ -2563,11 +2651,13 @@ sub _changedby {
     my $field_id = $field_object->id;
     my $table = "act_${field_id}_$chart_id";
     my $user_id  = login_to_id($value, THROW_ERROR);
-    push(@$joins,
-        "LEFT JOIN bugs_activity AS $table"
-        .     " ON $table.bug_id = bugs.bug_id"
-        .        " AND $table.fieldid = $field_id"
-        .        " AND $table.who = $user_id");
+    my $join = {
+        table => 'bugs_activity',
+        as    => $table,
+        extra => ["$table.fieldid = $field_id",
+                  "$table.who = $user_id"],
+    };
+    push(@$joins, $join);
     $args->{term} = "$table.bug_when IS NOT NULL";
 }
 
index 9e70caf5ad60e3ab70d23d8a6be648da36dd48fd..11a7760e2f84393c4f27d2fb6454564a1e3f3414 100644 (file)
@@ -363,10 +363,8 @@ use constant KNOWN_BROKEN => {
     anyexact => {
         percentage_complete => { contains => [2] },
     },
-    # bug_group anywordssubstr returns all our bugs. Not sure why.
     anywordssubstr => {
         percentage_complete => { contains => [2] },
-        bug_group => { contains => [3,4,5] },
     },
 
     'allwordssubstr-<1>' => { ALLWORDS_BROKEN },
@@ -389,16 +387,13 @@ use constant KNOWN_BROKEN => {
     },
 
     # anywords searches don't work on decimal values.
-    # bug_group anywords returns all bugs.
     # attach_data doesn't work (perhaps because it's the entire
     # data, or some problem with the regex?).
     anywords => {
         'attach_data.thedata' => { contains => [1] },
-        bug_group => { contains => [2,3,4,5] },
         work_time => { contains => [1] },
     },
     'anywords-<1> <2>' => {
-        bug_group => { contains => [3,4,5] },
         percentage_complete => { contains => [2] },
         'attach_data.thedata' => { contains => [1,2] },
         work_time => { contains => [1,2] },