Closed Bug 286235 Opened 19 years ago Closed 19 years ago

Implicit joins should be replaced by explicit joins - installment A

Categories

(Bugzilla :: Bugzilla-General, defect)

defect
Not set
normal

Tracking

()

RESOLVED FIXED
Bugzilla 2.20

People

(Reporter: Tomas.Kopal, Assigned: Tomas.Kopal)

References

Details

Attachments

(2 files, 4 obsolete files)

After removing implicit joins using the comma operator from Search.pm, there is
still a lot of places in Bugzilla where implicit joins are used. Postgres choke
up on many of them, so probably the cleanest solution is not to use them at all.
It would improve readibility of the code and avoid problems where the implicit
join can do different thing on different DBs (MySQL understands it as INNER
JOIN, Postgres as CROSS JOIN).
Attached patch V1 (obsolete) — Splinter Review
Sorry that the patch is such a big chunk, but most of the changes are simple
replacement of the comma join with inner join, so hopefully it shouldn't be too
dificult to go through.
I have also done some formatting changes (before I knew the patch will be so
big and I didn't want to spend more time un-doing them later), if that should
be a problem, I don't mind removing them from the patch.
This should hopefully be the last big change neccessary to get Postgres support
(except for the case-sensitivity problem at bug 285695).
It's tested on MySQL (although I admit I for sure didn't test all the changed
queries, there are simply too many of them, I tried to excercise the more
complex ones).
Attachment #177484 - Flags: review?(bugreport)
Status: NEW → ASSIGNED
Target Milestone: --- → Bugzilla 2.20
Summary: Impicit joins should be replaced by explicit joins → Implicit joins should be replaced by explicit joins
Fred, if you want to do some more advanced testing on this patch, it would of
course be appreciated. :-)
Attached patch V1.1 (obsolete) — Splinter Review
Unbitrotted, no other change.
Attachment #177484 - Attachment is obsolete: true
Attachment #177666 - Flags: review?(bugreport)
Attachment #177484 - Flags: review?(bugreport)

         q{SELECT DISTINCT groups.name, groups.id
-            FROM groups, user_group_map, group_group_map AS ggm
+            FROM groups
+            LEFT JOIN user_group_map
+              ON groups.id = user_group_map.group_id
+            LEFT JOIN group_group_map AS ggm
+              ON groups.id = ggm.grantor_id
            WHERE user_group_map.user_id = ?
-             AND ((user_group_map.isbless = 1
-                   AND groups.id=user_group_map.group_id)
-                  OR (groups.id = ggm.grantor_id
-                      AND ggm.grant_type = } . GROUP_BLESS .
-                   q{ AND user_group_map.group_id = ggm.member_id
+             AND ((user_group_map.isbless = 1)
+                  OR (ggm.grant_type = } . GROUP_BLESS . q{ 
+                      AND user_group_map.group_id = ggm.member_id
                       AND user_group_map.isbless = 0))},
          { Columns=>[1,2] }, $self->{id});

Why are you changing this to a LEFT JOIN instead of an INNER JOIN?

Also, if you have to edit this for any reason, please try breaking it up into
about 5 smaller bugs.  This is a lot to review all at once and there is no
reason that any file's changes have to land at the same time as any other's.

Aside from that, I'm half-way through (up to editcomponents) so far and it looks
good so far.


(In reply to comment #4)
> 
>          q{SELECT DISTINCT groups.name, groups.id
> -            FROM groups, user_group_map, group_group_map AS ggm
> +            FROM groups
> +            LEFT JOIN user_group_map
> +              ON groups.id = user_group_map.group_id
> +            LEFT JOIN group_group_map AS ggm
> +              ON groups.id = ggm.grantor_id
>             WHERE user_group_map.user_id = ?
> -             AND ((user_group_map.isbless = 1
> -                   AND groups.id=user_group_map.group_id)
> -                  OR (groups.id = ggm.grantor_id
> -                      AND ggm.grant_type = } . GROUP_BLESS .
> -                   q{ AND user_group_map.group_id = ggm.member_id
> +             AND ((user_group_map.isbless = 1)
> +                  OR (ggm.grant_type = } . GROUP_BLESS . q{ 
> +                      AND user_group_map.group_id = ggm.member_id
>                        AND user_group_map.isbless = 0))},
>           { Columns=>[1,2] }, $self->{id});
> 
> Why are you changing this to a LEFT JOIN instead of an INNER JOIN?

Because the parts I took from the original WHERE condition were connected by OR,
if I do both INNER JOINs, its AND.
How does MySQL handle the comma operator? You can't do INNER JOIN without
anything to join on, is it really INNER JOIN? Can't it be a CROSS JOIN? MySQL
documentation is a bit vague on this :-(.
If it is a CROSS JOIN, then we would have all combinations of rows and the WHERE
would limit the result (which is what I think is actually happening). As the
conditions are OR-ed, we'll get even rows when one of the maps is NULL.
I think that in order to get the same behaviour with explicit JOINs, we need
LEFT JOINs. Do I miss something?

> 
> Also, if you have to edit this for any reason, please try breaking it up into
> about 5 smaller bugs.  This is a lot to review all at once and there is no
> reason that any file's changes have to land at the same time as any other's.

Yeah, I was thinking about that before, but could not find any rule how to break
it. I will just split it to even parts next time. It's really a huge patch,
sorry :-).

> 
> Aside from that, I'm half-way through (up to editcomponents) so far and it looks
> good so far.
> 

Ahh, excellent. I was a bit afraid you'll run away screaming when you see the
size of the patch :-).
(In reply to comment #4)
>          q{SELECT DISTINCT groups.name, groups.id
> -            FROM groups, user_group_map, group_group_map AS ggm
> +            FROM groups
> +            LEFT JOIN user_group_map
> +              ON groups.id = user_group_map.group_id
> +            LEFT JOIN group_group_map AS ggm
> +              ON groups.id = ggm.grantor_id
>             WHERE user_group_map.user_id = ?
> -             AND ((user_group_map.isbless = 1
> -                   AND groups.id=user_group_map.group_id)
> -                  OR (groups.id = ggm.grantor_id
> -                      AND ggm.grant_type = } . GROUP_BLESS .
> -                   q{ AND user_group_map.group_id = ggm.member_id
> +             AND ((user_group_map.isbless = 1)
> +                  OR (ggm.grant_type = } . GROUP_BLESS . q{ 
> +                      AND user_group_map.group_id = ggm.member_id
>                        AND user_group_map.isbless = 0))},
>           { Columns=>[1,2] }, $self->{id});

  Yeah, I wrote this, and this change does not look correct to me.

  I am fairly certain that you need to move a lot of those WHERE conditions up
into the JOINs, particularly the isbless ones.
OK, looking at bug 279693 (where I wrote that statement), and I'm remembering
what the heck this code does. :-)

I think that the LEFT JOINs are *probably* OK, but I'd have to test with bless
group that was only inherited to be certain.

Note the "user_group_map.group_id = ggm.member_id" that I have in there, also.
Should that be in the join? I think with that change it makes the joins an INNER
JOIN, although I'm not certain.
OK, for that User.pm part, I re-derived it again and this is what I ended up
with. Is this looking more correct than the previous one?

SELECT DISTINCT groups.name, groups.id
FROM      groups
LEFT JOIN user_group_map
       ON (groups.id = user_group_map.group_id
      AND  user_group_map.isbless = 1)
LEFT JOIN group_group_map AS ggm
       ON (groups.id = ggm.grantor_id
      AND  ggm.grant_type = GROUP_BLESS
      AND  user_group_map.group_id = ggm.member_id
      AND  user_group_map.isbless = 0)
WHERE user_group_map.user_id = ?
  AND ((user_group_map.group_id IS NOT NULL) OR (ggm.member_id IS NOT NULL))
OK, this *should* be correct and is even simpler to understand :-). Comments?

SELECT DISTINCT groups.name, groups.id
FROM       groups
INNER JOIN user_group_map
      ON   (groups.id = user_group_map.group_id
      AND   user_group_map.user_id = ?)
LEFT JOIN  group_group_map AS ggm
      ON   (user_group_map.group_id = ggm.member_id
      AND   groups.id = ggm.grantor_id
      AND   ggm.grant_type = GROUP_BLESS)
WHERE (user_group_map.isbless = 1) OR (ggm.member_id IS NOT NULL)
(In reply to comment #9)
> WHERE (user_group_map.isbless = 1) OR (ggm.member_id IS NOT NULL)

Actually, isbless is a boolean, so we don't even need the ' = 1' part :-).
No!! We have no real booleans in PostgreSQL!! We have only smallints. You must
use the = 1.
Comment on attachment 177666 [details] [diff] [review]
V1.1

Based on the last few comments, I presume a new version is coming
Attachment #177666 - Flags: review?(bugreport)
(In reply to comment #12)
> (From update of attachment 177666 [details] [diff] [review] [edit])
> Based on the last few comments, I presume a new version is coming
> 

Actually, I was waiting for you to finish the review and incorporate all your
comments in one shot, so you don't have to go through the whole lot again.
But if you prefer updated version (split into few pieces), I can do the update
first.
Attached patch V1.2 part 1 (obsolete) — Splinter Review
Here is an updated version, split into smaller pieces.
Attachment #177666 - Attachment is obsolete: true
Attachment #178343 - Flags: review?(bugreport)
Attached patch V1.2 part 2Splinter Review
Attachment #178344 - Flags: review?(bugreport)
Attached patch V1.2 part 3 (obsolete) — Splinter Review
Attachment #178345 - Flags: review?(bugreport)
Attached patch V1.2 part 4Splinter Review
Attachment #178346 - Flags: review?(bugreport)
Comment on attachment 178343 [details] [diff] [review]
V1.2 part 1

     # for this to function properly in all circumstances.
     my $bless_groups = $dbh->selectcol_arrayref(
	 q{SELECT DISTINCT groups.name, groups.id
-	     FROM groups, user_group_map, group_group_map AS ggm
-	    WHERE user_group_map.user_id = ?
-	      AND ((user_group_map.isbless = 1
-		    AND groups.id=user_group_map.group_id)
-		   OR (groups.id = ggm.grantor_id
-		       AND ggm.grant_type = } . GROUP_BLESS .
-		    q{ AND user_group_map.group_id = ggm.member_id
-		       AND user_group_map.isbless = 0))},
+	     FROM groups
+      INNER JOIN user_group_map
+	       ON groups.id = user_group_map.group_id
+	      AND user_group_map.user_id = ?
+	LEFT JOIN group_group_map AS ggm
+	       ON user_group_map.group_id = ggm.member_id
+	      AND groups.id = ggm.grantor_id
+	      AND ggm.grant_type = } . GROUP_BLESS . q{
+	    WHERE user_group_map.isbless = 1
+	       OR ggm.member_id IS NOT NULL},
	  { Columns=>[1,2] }, $self->{id});

This is a logic change, please explain
Comment on attachment 178344 [details] [diff] [review]
V1.2 part 2

r=joel with or without a change to the nit below

One of these days (soon) we should expunge the remaining SendSQL calls, but
that is another bug.

nit:  
-	     $dbh->sql_to_days('creation_ts') . " != 'NULL' " .
+	     $dbh->sql_to_days('creation_ts') . " != 'NULL'" .
	     $and_product .
-	     "ORDER BY start " . $dbh->sql_limit(1));

We're in double-quotes here, why not just stay there and then we wont have to
worry about someone forgetting to start $and_product with whitespace.
Attachment #178344 - Flags: review?(bugreport) → review+
Comment on attachment 178346 [details] [diff] [review]
V1.2 part 4

    # the user should not have access.
-    " FROM	 flags 
-		 LEFT JOIN attachments ON ($attach_join_clause), 
-		 flagtypes, 
-		 profiles AS requesters
-		 LEFT JOIN profiles AS requestees 
-		   ON flags.requestee_id  = requestees.userid, 
-		 bugs 
-		 LEFT JOIN products ON bugs.product_id = products.id
-		 LEFT JOIN components ON bugs.component_id = components.id
-		 LEFT JOIN bug_group_map AS bgmap 
-		   ON bgmap.bug_id = bugs.bug_id 
-		 LEFT JOIN user_group_map AS ugmap 
-		   ON bgmap.group_id = ugmap.group_id 
-		   AND ugmap.user_id = $::userid 
+    " FROM	 flags
+		 LEFT JOIN attachments
+		   ON ($attach_join_clause)
+		 INNER JOIN flagtypes
+		   ON flags.type_id = flagtypes.id
+		 INNER JOIN profiles AS requesters
+		   ON flags.setter_id = requesters.userid
+		 LEFT JOIN profiles AS requestees
+		   ON flags.requestee_id  = requestees.userid
+		 INNER JOIN bugs
+		   ON flags.bug_id = bugs.bug_id
+		 LEFT JOIN products
+		   ON bugs.product_id = products.id
+		 LEFT JOIN components
+		   ON bugs.component_id = components.id
+		 LEFT JOIN bug_group_map AS bgmap
+		   ON bgmap.bug_id = bugs.bug_id
+		 LEFT JOIN user_group_map AS ugmap
+		   ON bgmap.group_id = ugmap.group_id
+		   AND ugmap.user_id = $::userid
		   AND ugmap.isbless = 0
-		 LEFT JOIN cc AS ccmap 
-		 ON ccmap.who = $::userid AND ccmap.bug_id = bugs.bug_id 
-    " . 
-    # All of these are inner join clauses.  Actual match criteria are added
-    # in the code below.
-    " WHERE	 flags.type_id	     = flagtypes.id
-      AND	 flags.setter_id     = requesters.userid
-      AND	 flags.bug_id	     = bugs.bug_id
+		 LEFT JOIN cc AS ccmap
+		   ON ccmap.who = $::userid
+		   AND ccmap.bug_id = bugs.bug_id
     ";

please explain logic change

Aside from that, the rest of this patch is OK.
Comment on attachment 178346 [details] [diff] [review]
V1.2 part 4

disregard comment 20...  got it
r=joel
Attachment #178346 - Flags: review?(bugreport) → review+
Thomas - I suggest you land part 2 and 4 and then try for part 1 and 3 under
another bug.  I started to review part 3 and saw a bunch of apparrent logic
changes.  I'll leave those for another time after we finish part 1.

Comment on attachment 178343 [details] [diff] [review]
V1.2 part 1

cancelling extra review request...  will land in stages
Attachment #178343 - Flags: review?(bugreport)
Attachment #178345 - Flags: review?(bugreport)
I'm requesting approval for parts 2 and 4 only.  
Flags: approval?
Summary: Implicit joins should be replaced by explicit joins → Implicit joins should be replaced by explicit joins - installment A
Flags: approval? → approval+
Attachment #178343 - Attachment is obsolete: true
Attachment #178345 - Attachment is obsolete: true
Checking in CGI.pl;
/cvsroot/mozilla/webtools/bugzilla/CGI.pl,v  <--  CGI.pl
new revision: 1.236; previous revision: 1.235
done
Checking in attachment.cgi;
/cvsroot/mozilla/webtools/bugzilla/attachment.cgi,v  <--  attachment.cgi
new revision: 1.79; previous revision: 1.78
done
Checking in buglist.cgi;
/cvsroot/mozilla/webtools/bugzilla/buglist.cgi,v  <--  buglist.cgi
new revision: 1.289; previous revision: 1.288
done
Checking in checksetup.pl;
/cvsroot/mozilla/webtools/bugzilla/checksetup.pl,v  <--  checksetup.pl
new revision: 1.383; previous revision: 1.382
done
Checking in collectstats.pl;
/cvsroot/mozilla/webtools/bugzilla/collectstats.pl,v  <--  collectstats.pl
new revision: 1.43; previous revision: 1.42
done
Checking in editclassifications.cgi;
/cvsroot/mozilla/webtools/bugzilla/editclassifications.cgi,v  <-- 
editclassifications.cgi
new revision: 1.11; previous revision: 1.10
done
Checking in editcomponents.cgi;
/cvsroot/mozilla/webtools/bugzilla/editcomponents.cgi,v  <--  editcomponents.cgi
new revision: 1.53; previous revision: 1.52
done
Checking in editflagtypes.cgi;
/cvsroot/mozilla/webtools/bugzilla/editflagtypes.cgi,v  <--  editflagtypes.cgi
new revision: 1.17; previous revision: 1.16
done
Checking in editgroups.cgi;
/cvsroot/mozilla/webtools/bugzilla/editgroups.cgi,v  <--  editgroups.cgi
new revision: 1.52; previous revision: 1.51
done
Checking in editmilestones.cgi;
/cvsroot/mozilla/webtools/bugzilla/editmilestones.cgi,v  <--  editmilestones.cgi
new revision: 1.36; previous revision: 1.35
done
Checking in process_bug.cgi;
/cvsroot/mozilla/webtools/bugzilla/process_bug.cgi,v  <--  process_bug.cgi
new revision: 1.245; previous revision: 1.244
done
Checking in request.cgi;
/cvsroot/mozilla/webtools/bugzilla/request.cgi,v  <--  request.cgi
new revision: 1.21; previous revision: 1.20
done
Checking in sanitycheck.cgi;
/cvsroot/mozilla/webtools/bugzilla/sanitycheck.cgi,v  <--  sanitycheck.cgi
new revision: 1.92; previous revision: 1.91
done
Checking in showdependencytree.cgi;
/cvsroot/mozilla/webtools/bugzilla/showdependencytree.cgi,v  <-- 
showdependencytree.cgi
new revision: 1.32; previous revision: 1.31
done
Checking in summarize_time.cgi;
/cvsroot/mozilla/webtools/bugzilla/summarize_time.cgi,v  <--  summarize_time.cgi
new revision: 1.6; previous revision: 1.5
done
Checking in userprefs.cgi;
/cvsroot/mozilla/webtools/bugzilla/userprefs.cgi,v  <--  userprefs.cgi
new revision: 1.73; previous revision: 1.72
done
Checking in votes.cgi;
/cvsroot/mozilla/webtools/bugzilla/votes.cgi,v  <--  votes.cgi
new revision: 1.28; previous revision: 1.27
done
Checking in whineatnews.pl;
/cvsroot/mozilla/webtools/bugzilla/whineatnews.pl,v  <--  whineatnews.pl
new revision: 1.17; previous revision: 1.16
done
Status: ASSIGNED → RESOLVED
Closed: 19 years ago
Resolution: --- → FIXED
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: