do_search() fails hard on Postgres

Crell - August 18, 2008 - 06:44
Project:Drupal
Version:6.x-dev
Component:search.module
Category:bug report
Priority:critical
Assigned:Unassigned
Status:patch (to be ported)
Issue tags:PostgreSQL Surge
Description

Whilst running unit tests for Postgres for DB TNG, I hit a failure on the test of do_search() that occurs only on Postgres. Specifically, do_search generates a query akin to the following:

SELECT COUNT(*)
FROM (
  SELECT i.type, i.sid, (? * COALESCE((n.sticky), 0)) AS score
    FROM search_index i
      INNER JOIN search_total t ON i.word = t.word
      INNER JOIN node n ON n.nid = i.sid
    WHERE n.STATUS = 1 AND (i.word = ?) AND i.type = ?
    GROUP BY i.type, i.sid HAVING COUNT(*) >= ?
)

(? being the generic placeholder for the new DB API.) Under Postgres, that results in the following error:

SQLSTATE[42803]: Grouping error: 7 ERROR: column "n.sticky" must appear in the GROUP BY clause or be used in an aggregate function

The code inside do_search() is simply a nightmare of string parsing so I am not even going to attempt to fix it as part of DB TNG. Instead, I'm marking it here for those who actually comprehend search.module to fix when updating that function for the new API. :-)

#1

chx - August 18, 2008 - 06:50

As score is not aggregated this is right to fail. We need like MAX MIN or SUM in there.

#2

Damien Tournoud - August 20, 2008 - 12:26
Status:active» needs review

There are two issues on PostgreSQL: first, PostgreSQL mandates that aggregate functions be used in all non-grouping columns (fair enough), second POWER() on two numeric columns is treated in arbitrary codecision and thus fails with an overflow error.

Before the patch:

MySQL 5.0:
Search engine queries 105 passes, 0 fails, 0 exceptions
Bike shed 16 passes, 0 fails, 0 exceptions
Search engine ranking 26 passes, 0 fails, 0 exceptions

PostgreSQL 8.3:
Search engine queries 105 passes, 0 fails, 0 exceptions
Bike shed 16 passes, 0 fails, 0 exceptions
Search engine ranking 21 passes, 5 fails, 20 exceptions

After the patch:

MySQL 5.0:
Search engine queries 105 passes, 0 fails, 0 exceptions
Bike shed 16 passes, 0 fails, 0 exceptions
Search engine ranking 26 passes, 0 fails, 0 exceptions

PostgreSQL 8.3:
Search engine queries 105 passes, 0 fails, 0 exceptions
Bike shed 16 passes, 0 fails, 0 exceptions
Search engine ranking 26 passes, 0 fails, 0 exceptions

I still have a failure on Search engine ranking on MySQL 5.1 (see #276039: Test "Search engine ranking" fails on MySQL 5.1), both before and after the patch.

AttachmentSizeStatusTest resultOperations
296624-search-grouping.patch2.56 KBIdleFailed: Failed to apply patch.View details | Re-test

#3

alexanderpas - October 1, 2008 - 09:08
Priority:normal» critical

Because this breaks the functionality on PostgreSQL, this is a critical issue.

#4

Anonymous (not verified) - November 11, 2008 - 13:05
Status:needs review» needs work

The last submitted patch failed testing.

#5

Damien Tournoud - November 26, 2008 - 22:33
Title:do_search() fails hard on Postgres» PostgreSQL surge #6: do_search() fails hard on Postgres

Promoted to the PostgreSQL surge.

#6

jmpoure - November 27, 2008 - 07:51

I looked at Search module and found this issue:

line 952:
$select = "SELECT SUM(i.score * t.count) AS calculated_score FROM {search_index} i $join WHERE $conditions GROUP BY i.type, i.sid HAVING COUNT(*) >= %d ORDER BY calculated_score DESC";

This query will always fail under PostgreSQL.

The correct query would be:
$select = "SELECT SUM(i.score * t.count) AS calculated_score, i.type, i.sid FROM {search_index} i $join WHERE $conditions GROUP BY i.type, i.sid HAVING COUNT(*) >= %d ORDER BY calculated_score DESC";

as GROUP BY columns must appear in the SELECT clause.

My knowledge of D7 internals is not sufficient to provide a patch, because I don't understand (yet) how is used $select.
Can someone fix and submit a patch?

#7

Damien Tournoud - November 27, 2008 - 14:08

@jmpoure: there is already a patch in #2 (that may need a reroll).

#8

Crell - November 28, 2008 - 07:00

The patch in #2 is wrong anyway as this is a perfect case where the query builder should make the code 10x more readable. That probably requires considerable refactoring, though. If anyone who knows the search module well enough to refactor the query is interested, I can help with the DBTNG parts. I just don't grok search module in the slightest. :-)

#9

Damien Tournoud - November 29, 2008 - 12:02

No, we need to backport this, so let's fix it in the old way, and only then convert those parts to the new database API.

#10

Damien Tournoud - November 29, 2008 - 12:06
Status:needs work» needs review

Here we are, reroll of #2.

AttachmentSizeStatusTest resultOperations
296624-fix-do-search.patch3.21 KBIdleUnable to apply patch 296624-fix-do-search.patchView details | Re-test

#11

Damien Tournoud - November 29, 2008 - 14:47

Ok there was three issues here:
(1) As explained above, the queries were wrong, because all some non grouping columns were not aggregated
(2) PostgreSQL needs explicit casts in expressions, without them it defaults to INTEGER
(3) the test was not refreshing the variables AND the "recent" ranking score computing was wrong (it was (0.5^t)/t0 instead of 0.5^(t/t0))... and this only appears in our test when we fixed (1)...

Fixed. Ran flawlessly on MySQL, PostgreSQL and SQLite.

AttachmentSizeStatusTest resultOperations
296624-fix-do-search.patch6.14 KBIdleUnable to apply patch 296624-fix-do-search_0.patchView details | Re-test

#12

swentel - November 29, 2008 - 15:41
Status:needs review» reviewed & tested by the community

Patch fixes the search tests!

#13

Dave Reid - November 29, 2008 - 16:00
Status:reviewed & tested by the community» needs review

Wow. Awesome work Damien!

#14

Dave Reid - November 29, 2008 - 16:01
Status:needs review» reviewed & tested by the community

Arg...did not mean to change status.

#15

Josh Waihi - November 30, 2008 - 23:10

have tested patch: 188 passes, 0 fails, and 0 exceptions for simple tests search module

works great :)

#16

Dries - December 2, 2008 - 21:28
Status:reviewed & tested by the community» fixed

I've committed this patch to CVS HEAD. Admittedly, despite the fact that I spent about 20 minutes looking at the patch, I'm not sure I 100% grok the SUM()-related changes to do_search(). Frustrating patch, but I'll trust Damien's research and judgment. The fact I don't have PostgreSQL installed doesn't really help. :)

#17

Damien Tournoud - December 2, 2008 - 21:44
Status:fixed» patch (to be ported)

This will require a partial backport to Drupal 6.

Some background on the SUM() part: the standard search query is:

  SELECT i.type, i.sid, i.relevance AS score
    FROM {search_index} i
      INNER JOIN search_total t ON i.word = t.word
    WHERE i.word = ? AND i.type = ?
    GROUP BY i.type, i.sid HAVING COUNT(*) >= ?

i.type and i.sid are part of the GROUP BY clause, but not i.relevance. This is invalid SQL, even if we know that because (1) (type, sid) is a unique key for {search_index} and (2) t.work is a primary key, we can only have one i.relevance per (i.type, i.sid) couple (we are smarter than the planner...). We simply transform all i.relevance into SUM(i.relevance) to make the query planner happy.

Note that both MySQL and SQLite happily deals with that situation, even if the result can be quite undetermined, when the result set is not unique.

#18

Damien Tournoud - December 2, 2008 - 21:44
Version:7.x-dev» 6.x-dev

#19

Damien Tournoud - January 5, 2009 - 11:27
Title:PostgreSQL surge #6: do_search() fails hard on Postgres» do_search() fails hard on Postgres

#20

Scott_Marlowe - January 12, 2009 - 21:58

Given the off chance that some future change could result in > 1 relevance row per grouped set, it might be best to go with max(relevance) so that we don't start summing multiple rows together and getting odd results unexpectedly.

Unless summing them IS what we'd want should that somehow ever change (i.e. someone adds a table to the query that makes relevance non-unique).

Note that both MySQL / SQLLite and PostgreSQL technically misbehave as regards group by. MySQL's acceptance of items in the select list that aren't grouped by, aggregated, and can't be guaranteed unique is against spec (and some would say common sense.)

PostgreSQL OTOH, according to the spec, should note that the relevance field is unique per tuple output by group by and allow the query to run. But it's a check that isn't made and while it is a TODO item for pgsql, I don't think it's a priority for anyone.

 
 

Drupal is a registered trademark of Dries Buytaert.