Fix syntax of comment ordering subquery

jmpoure - March 9, 2009 - 19:22
Project:Drupal
Version:6.x-dev
Component:comment.module
Category:bug report
Priority:critical
Assigned:Unassigned
Status:needs work
Issue tags:PostgreSQL Surge
Description

    * warning: pg_query() [function.pg-query]: Query failed: ERREUR: clauses ORDER BY multiples non autorisées in /home/html/test/includes/database.pgsql.inc on line 139.
    * user warning: query: (SELECT thread FROM comments WHERE nid = 63743 AND status = 0 ORDER BY timestamp DESC LIMIT 136) ORDER BY thread DESC LIMIT 1 in /home/html/test/modules/comment/comment.module on line 366.
    * warning: pg_query() [function.pg-query]: Query failed: ERREUR: clauses ORDER BY multiples non autorisées in /home/html/test/includes/database.pgsql.inc on line 139.
    * user warning: query: (SELECT thread FROM comments WHERE nid = 67329 AND status = 0 ORDER BY timestamp DESC LIMIT 56) ORDER BY thread DESC LIMIT 1 in /home/html/test/modules/comment/comment.module on line 366.
    * warning: pg_query() [function.pg-query]: Query failed: ERREUR: clauses ORDER BY multiples non autorisées in /home/html/test/includes/database.pgsql.inc on line 139.
    * user warning: query: (SELECT thread FROM comments WHERE nid = 49805 AND status = 0 ORDER BY timestamp DESC LIMIT 326) ORDER BY thread DESC LIMIT 1 in /home/html/test/modules/comment/comment.module on line 366.
    * warning: pg_query() [function.pg-query]: Query failed: ERREUR: clauses ORDER BY multiples non autorisées in /home/html/test/includes/database.pgsql.inc on line 139.
    * user warning: query: (SELECT thread FROM comments WHERE nid = 66944 AND status = 0 ORDER BY timestamp DESC LIMIT 125) ORDER BY thread DESC LIMIT 1 in /home/html/test/modules/comment/comment.module on line 366.
    * warning: pg_query() [function.pg-query]: Query failed: ERREUR: clauses ORDER BY multiples non autorisées in /home/html/test/includes/database.pgsql.inc on line 139.
    * user warning: query: (SELECT thread FROM comments WHERE nid = 58110 AND status = 0 ORDER BY timestamp DESC LIMIT 14) ORDER BY thread DESC LIMIT 1 in /home/html/test/modules/comment/comment.module on line 366.
    * warning: pg_query() [function.pg-query]: Query failed: ERREUR: clauses ORDER BY multiples non autorisées in /home/html/test/includes/database.pgsql.inc on line 139.
    * user warning: query: (SELECT thread FROM comments WHERE nid = 66801 AND status = 0 ORDER BY timestamp DESC LIMIT 165) ORDER BY thread DESC LIMIT 1 in /home/html/test/modules/comment/comment.module on line 366.
    * warning: pg_query() [function.pg-query]: Query failed: ERREUR: clauses ORDER BY multiples non autorisées in /home/html/test/includes/database.pgsql.inc on line 139.
    * user warning: query: (SELECT thread FROM comments WHERE nid = 67321 AND status = 0 ORDER BY timestamp DESC LIMIT 58) ORDER BY thread DESC LIMIT 1 in /home/html/test/modules/comment/comment.module on line 366.
<code>

        <code>$result = db_query('(SELECT thread FROM {comments} WHERE nid = %d  AND status = 0 ORDER BY timestamp DESC LIMIT %d) ORDER BY thread DESC LIMIT 1', $node->nid, $new_replies);

Should be:

$result = db_query('SELECT thread FROM {comments} WHERE cid IN (SELECT cid FROM comments WHERE nid = %d AND status = 0 ORDER BY timestamp DESC LIMIT %d) ORDER BY thread DESC LIMIT 1', $node->nid, $new_replies);

The same applies for:

$result = db_query('(SELECT thread FROM {comments} WHERE nid = %d AND status = 0 ORDER BY timestamp DESC LIMIT %d) ORDER BY SUBSTRING(thread, 1, (LENGTH(thread) - 1)) LIMIT 1', $node->nid, $new_replies);

#1

jmpoure - March 9, 2009 - 19:40

SEcond query should be:

$result = db_query('(SELECT thread FROM {comments} WHERE cid IN (SELECT cid FROM {comments} WHERE nid = %d  AND status = 0 ORDER BY timestamp DESC LIMIT %d) ORDER BY SUBSTRING(thread, 1, (LENGTH(thread) - 1)) LIMIT 1', $node->nid, $new_replies);

#2

jmpoure - March 9, 2009 - 19:41
Priority:normal» critical

Update to critical as all forum running under PostgreSQL should have the same problem.

#3

Damien Tournoud - March 9, 2009 - 19:46

Could you point us to where this is actually documented?

#4

jmpoure - March 9, 2009 - 20:17

I don't know where this is documented. Actually the message displays "clauses ORDER BY multiples non autorisées", which means "multiple ORDER BY not authorized".

The query I provided is a traditional nested query. I can be executed on any database. MySQL query analyser should rewrite the query as a traditional nested query with some kind of IN. Therefore there is no degradation using the SQL query I provided.

If MySQL has some kind of SQL parser, try looking how MySQL rewrites the query.

#5

jmpoure - March 9, 2009 - 20:19

The idea behind this fix is that when you write this MySQLism, MySQL is going to rewrite the query inside the engine. It will surely not execute like that. So it is better to write the correct SQL directly and there is no time execution loss.

#6

Damien Tournoud - March 9, 2009 - 20:25

@jmpoure: you can really not call that query an "MySQLism", especially if you can't point us to where it is documented that you can't have an ORDER in a FROM-based subquery.

#7

jmpoure - March 9, 2009 - 21:58

A database should always know how it should run joins.

(SELECT thread FROM comments WHERE nid = 63743 AND status = 0 ORDER BY timestamp DESC LIMIT 136) ORDER BY thread DESC LIMIT 1
is not a standard query. I can be rewritten internaly in MySQL as a subquery or a join.

To know whether this is a standard query, you can access the analyser in the database and read how the query is rewritten.
MySQL will not execute(SELECT thread FROM comments WHERE nid = 63743 AND status = 0 ORDER BY timestamp DESC LIMIT 136) ORDER BY thread DESC LIMIT 1 directlty. The parser will rewrite it and you wron't see it.

So it is better to write a standard query where you see the JOIN or the SUBQUERY. Anyway PostgreSQL is picky and will not accept this SQL clause, probably because it is not standard. PostgreSQL hackers usualy don't accept non-standard queries. It could take them 5 minutes to modify PostgreSQL core, but if it is not standard, they don't accept it. If you know FFmpeg, the same applies for Video and Audio codecs. If it is not standard, even a simple hack, core hackers do not accept something non-compliant with standards. This is not my fault.

Using MySQL is like using Windows 95. You always run into problems that you don't know how to analyse because you don't see what is "inside".

If this is standard SQL, I apologize in advance, but I doubt.

#8

jaydub - March 23, 2009 - 19:21

All comments regarding standard or non-standard aside, the fact that the query fails should be of primary concern. I can verify that the query fails in PostgreSQL. I tried the suggested alternative query in the parent issue and while that -does- work in PostgreSQL, it does not work in MySQL 5:

mysql> SELECT thread FROM comments WHERE cid IN (SELECT cid FROM comments WHERE nid = 307 AND status = 0 ORDER BY timestamp DESC LIMIT 1) ORDER BY thread DESC LIMIT 1;
ERROR 1235 (42000): This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'

#9

jaydub - March 23, 2009 - 19:26

How about this?

$result = db_query('SELECT thread FROM {comments} c INNER JOIN (SELECT cid FROM {comments} WHERE nid = %d AND status = 0 ORDER BY timestamp DESC LIMIT %d) c2 ON c.cid = c2.cid ORDER BY thread DESC LIMIT 1', $node->nid, $new_replies);

That at least didn't throw an error both in MySQL and PostgreSQL.

#10

brianV - March 23, 2009 - 19:40
Version:6.10» 6.x-dev

According to PostgreSQL's documentation,

ORDER BY Clause

The optional ORDER BY clause has this general form:

ORDER BY expression [ ASC | DESC | USING operator ] [ NULLS { FIRST | LAST } ] [, ...]

The ORDER BY clause causes the result rows to be sorted according to the specified expression(s). If two rows are equal according to the leftmost expression, they are compared according to the next expression and so on. If they are equal according to all specified expressions, they are returned in an implementation-dependent order.

... snip ...

Note that ordering options apply only to the expression they follow; for example ORDER BY x, y DESC does not mean the same thing as ORDER BY x DESC, y DESC.

So it appears that multiple ORDER BY statements are legitimate.

#11

brianV - March 23, 2009 - 19:44

Jay,

You should work off the CVS code. The current CVS for this statement is:

<?php
$result
= db_query_range('(SELECT thread
      FROM {comment}
      WHERE nid = :nid
        AND status = 0
      ORDER BY timestamp DESC)
      ORDER BY SUBSTRING(thread, 1, (LENGTH(thread) - 1))'
, array(':nid' => $node->nid), 0, $new_replies)
      ->
fetchField();
?>

Actually, can you test if this CVS code gives the same issues under PostgreSQL?

#12

jmpoure - May 11, 2009 - 13:28

Sorry, I feel stupid. It added this code to my SVN drupal installation but don't know in which situation I should test it. What kind of comments does it display? Sorry, I feel lost and stupid.

#13

Damien Tournoud - May 11, 2009 - 14:02

In fact, it seems like this is just a syntax issue:

(SELECT thread FROM {comments} WHERE nid = %d  AND status = 0 ORDER BY timestamp DESC LIMIT %d) ORDER BY thread DESC LIMIT 1

... is an implicit FROM subquery. The syntax is apparently valid on MySQL, but not on PostgreSQL, so we should write:

SELECT thread FROM (SELECT thread FROM {comments} WHERE nid = %d  AND status = 0 ORDER BY timestamp DESC LIMIT %d) ORDER BY thread DESC LIMIT 1

#14

Norbert Poellmann - May 11, 2009 - 15:03

No, I don't agree to multiple ORDER BY statements in postgres.
http://www.postgresql.org/docs/8.3/interactive/sql-select.html
talks about The ORDER BY clause (not clauses) and the syntax shows only
one (optional) ORDER BY clause per SELECT statement.:

The optional ORDER BY clause has this general form:
ORDER BY expression [ ASC | DESC | USING operator ] [ NULLS { FIRST | LAST } ] [, ...]

That means: you can have one or more specified expression(s)
(indicated by [, ...]), but only one ORDER BY clause

Instead of:

(SELECT thread FROM comments WHERE nid = 63743 AND status = 0 ORDER BY timestamp DESC LIMIT 136) ORDER BY thread DESC LIMIT 1 ;

it should simply say:
SELECT thread FROM comments WHERE nid = 63743 AND status = 0
ORDER BY timestamp DESC , thread DESC LIMIT 1 ;

#15

Damien Tournoud - May 11, 2009 - 15:20

@Norbert: see my comment #13, it's nothing more then a syntax issue: MySQL read the query as an implicit sub-query on FROM, which is what we want. Making the subquery explicit should solve the issue.

Instead of:
(SELECT thread FROM comments WHERE nid = 63743 AND status = 0 ORDER BY timestamp DESC LIMIT 136) ORDER BY thread DESC LIMIT 1 ;

it should simply say:
SELECT thread FROM comments WHERE nid = 63743 AND status = 0
ORDER BY timestamp DESC , thread DESC LIMIT 1 ;

No, those are not the same queries (see the difference in the LIMIT clauses).

#16

Damien Tournoud - May 11, 2009 - 15:22
Version:6.x-dev» 7.x-dev

Bumping to D7 to quick fix that there (and add a test for that...), before backporting.

#18

Alex_Tutubalin - May 14, 2009 - 13:00

Folks!
(i'm author of bugreport and patch #237509 which is duplicate to this bug)

As I can see

1. PosgreSQL does not supports multiple ORDER BY
2. MySQL does not supports LIMIT within subquery
3. comment.module really needs two ORDER BYs (first select N latest comments, than select highest thread id in them)

The only way is to use
if ( $GLOBALS['db_type']=='pgsql')
{
pgsql query
}
else
{ mysql query.

So, patch is trivial, just combine my patch from http://drupal.org/node/462270 to these ugly if-s

#19

Damien Tournoud - May 14, 2009 - 13:24

@Alex Tutubalin:

MySQL does support LIMIT inside sub-queries (of course... the current code, that is an implicit subquery works). The only thing left to do is to roll a patch according to my suggestion in #13.

#20

Alex_Tutubalin - May 16, 2009 - 12:15

@Damien

This is different queries.
First one selects Top N threads by timestamp, than maximum thread ID in these N top-timestamp;
Second one will effectively selects latest (by timestamp).

Again, my solution is
- Left MySQL query as is (assuming that two order-by-s is doing right thing)
- Make special PgSQL query (subselect with Order-by/Limit)
Select between two queries at PHP level by GLOBALS[pg_type]

This should work, althought not beautiful solution.

#21

Damien Tournoud - May 16, 2009 - 12:17

@Alex_Tutubalin: Please read carefully. This query is what we want and it should work the same on both PostgreSQL and MySQL:

SELECT thread FROM (SELECT thread FROM {comments} WHERE nid = %d  AND status = 0 ORDER BY timestamp DESC LIMIT %d) ORDER BY thread DESC LIMIT 1

#22

Alex_Tutubalin - May 16, 2009 - 18:02

Have you check it on MySQL ?

Also, on PgSQL 8.3 you should use slightly different syntax
SELECT column FROM (SELECT ....) AS column ORDER BY... LIMIT...

#23

Damien Tournoud - May 16, 2009 - 18:09

SELECT thread FROM (SELECT thread FROM {comments} WHERE nid = %d  AND status = 0 ORDER BY timestamp DESC LIMIT %d) AS last_threads ORDER BY thread DESC LIMIT 1

works perfectly on MySQL.

#24

Alex_Tutubalin - May 19, 2009 - 07:32

So, my patch from http://drupal.org/node/462270 should work.

#26

Josh Waihi - July 10, 2009 - 04:04

tagging properly

#27

Shiny - July 10, 2009 - 04:23
Assigned to:Anonymous» Shiny
Status:active» needs review
Issue tags:-PostgreSQL Code Sprint

attaching patch by Alex_Tutubalin, which i am testing at the Postres Code Sprint

AttachmentSize
drupal-comment-module-order-by.diff 1.68 KB
Testbed results
drupal-comment-module-order-by.difffailedFailed: Failed to apply patch. Detailed results

#28

System Message - July 10, 2009 - 04:40
Status:needs review» needs work

The last submitted patch failed testing.

#29

Shiny - July 10, 2009 - 04:53
Status:needs work» needs review

So far i can't replicate this error - i've tried in D6 and D7
Can someone please add the steps to make this error occur? Then i can verify it is fixed by the patch.
(otherwise the patch looks very sane and safe for D6 - needs re-rolling for D7).

#30

andypost - July 13, 2009 - 17:35

#27 was for drupal6

Now patch for D7

But suppose reply for comments are broken in d7

AttachmentSize
comment_pg.patch 1.07 KB
Testbed results
comment_pg.patchpassedPassed: 11824 passes, 0 fails, 0 exceptions Detailed results

#31

System Message - July 23, 2009 - 07:31
Status:needs review» needs work

The last submitted patch failed testing.

#32

deekayen - July 23, 2009 - 07:32
Status:needs work» needs review

misbehaving bot

#33

andypost - July 24, 2009 - 01:02

Bot passed all tests

#34

Damien Tournoud - July 24, 2009 - 06:26
Status:needs review» reviewed & tested by the community

Makes total sense, and the bot is happy.

#35

Damien Tournoud - July 24, 2009 - 07:17
Title:PostgreSQL does not allow multiple ORDER BY clauses» Fix syntax of comment ordering subquery

There have been a lot of back and forth in this issue, so here is some clarification for the core committers.

The issue here is that we have a query that has an invalid syntax:

(SELECT thread FROM {comments} WHERE nid = %d  AND status = 0 ORDER BY timestamp DESC LIMIT %d) ORDER BY thread DESC LIMIT 1

It is an implicit FROM-based subquery (we first get the threads of the latest comments, then we order them by thread).

MySQL happily deals with this query, even if it is invalid, but PostgreSQL ignore the parenthesis and return a cryptic "Multiple ORDER BY clauses are not allowed).

The patch simply transforms this query into:

SELECT thread FROM (SELECT thread FROM {comments} WHERE nid = %d  AND status = 0 ORDER BY timestamp DESC LIMIT %d) AS thread ORDER BY thread DESC LIMIT 1

#36

andypost - July 24, 2009 - 10:16

I post new issue related to comment ordering #529374: Reply on comment is broken
Maybe this one should be fixed first

#37

System Message - July 24, 2009 - 11:10
Status:reviewed & tested by the community» needs work

The last submitted patch failed testing.

#38

andypost - July 24, 2009 - 22:43
Status:needs work» needs review

Let's re-test

#39

andypost - July 25, 2009 - 03:01
Status:needs review» reviewed & tested by the community

Looks like bot was broken so rtbc

#40

webchick - August 4, 2009 - 06:53
Status:reviewed & tested by the community» fixed

Committed to HEAD. Thanks!

#41

Damien Tournoud - August 4, 2009 - 08:19
Version:7.x-dev» 6.x-dev
Assigned to:Shiny» Anonymous
Status:fixed» patch (to be ported)

We need to backport this.

#42

andypost - August 12, 2009 - 01:01
Status:patch (to be ported)» needs review

Reroll #27

AttachmentSize
comment_pg_d6.patch 1.96 KB

#43

jmpoure - August 17, 2009 - 09:23

Thanks. Will review shorly as I am back from holidays. Sorry for the delay.

#44

jmpoure - August 21, 2009 - 07:41

The patch works as expected. I tested on my D6 forums.
Hope this can be applied ASAP.
It seems straightforward and will not break anything.

Thanks!

#45

jmpoure - August 22, 2009 - 12:47

Please inform us when it is committed to CVS.

#46

Dave Reid - August 26, 2009 - 23:18
Status:needs review» needs work

Extra space in the SQL.

@jmpoure: Please read http://drupal.org/node/317.

#47

andypost - August 27, 2009 - 20:33

So here reroll without extra space

AttachmentSize
comment_pg_d6.patch 1.96 KB

#48

andypost - August 27, 2009 - 20:34
Status:needs work» needs review

And status change... sorry

#49

jmpoure - August 28, 2009 - 13:13

Thank you Andy and Damien.

In Guidelines for writing MySQL and PostgreSQL compliant SQL
read MySQLism: avoid nested ORDER BY, use nested queries

This describes the SQL-99 standard and how to rewrite nested ORDER BY.

So you can apply the patch safely.
The query will execute very well under PostgreSQL and MySQL.

We hope that the quide will improve Drupal conformance to SQL-99.

#50

andypost - September 7, 2009 - 04:25

Need another review to rtbc this!

#51

lambic - September 17, 2009 - 15:08
Status:needs review» reviewed & tested by the community

looks good

#52

Gábor Hojtsy - September 28, 2009 - 13:29

Who tested this on an actual Drupal 6 setup on MySQL? I'm seeing people tested the standalone query which was formed into the patch and people also tested the patch on PostgreSQL, but nobody tested the patch on MySQL, right?

#53

Dave Reid - September 28, 2009 - 13:38
Status:reviewed & tested by the community» needs work

I don't get the same result between the new and old queries. New query also didn't pass the SQL-99 validator (http://developer.mimer.com/validator/parser99/index.tml)

 
 

Drupal is a registered trademark of Dries Buytaert.