Commit 08fd93d5 authored by Max Kanat-Alexander's avatar Max Kanat-Alexander

Bug 578888: Search.pm: Add and store joins as data structures instead of

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