I discovered this when using a complex query which was formatted with lots of whitespace. I was previously running it through db_query() (which was fine) but immediately ran into an error trying to use db_query_temporary() with the same arguments.

I found that db_query_temporary() does still return an appropriate table name (which didn't help when trying to fathom what was going wrong), but attempting to use that table name in a subsequent query resulted in "PDOException: SQLSTATE[42P01]: Undefined table: 7 ERROR: relation "db_temporary_0" does not exist".

Trimming the leading whitespace from the SQL resolved the issue.

This is easily replicated in database_test_db_query_temporary() by inserting a space at the start of the query.

Database is PostgreSQL 9.2beta2 on Ubuntu 10.04.4 LTS


The fix boils down to the following manner of change:

- $this->query(preg_replace('/^SELECT/i', 'CREATE TEMPORARY TABLE {' . $tablename . '} AS SELECT', $query), $args, $options);
+ $this->query('CREATE TEMPORARY TABLE {' . $tablename . '} AS ' . $query, $args, $options);

The goal is to replace SELECT ... with CREATE TEMPORARY TABLE {tablename} AS SELECT ...

The original code made the assumption that the query would start with a 'SELECT' statement, but added the unnecessary constraint that there could be no whitespace or comments in the query string prior to that 'SELECT'.

We can eliminate that constraint (and the preg_replace() call) by simply prepending the new statements to the front of the original query string.

(i.e. We're still making the same essential assumption about the SQL that the original code made, but without the additional constraints that the search+replace was imposing.)

See comment #7 for more details.

Comments

jweowu’s picture

Version: 7.x-dev » 8.x-dev
Status: Active » Needs review
StatusFileSize
new2.28 KB

The cause was fairly obvious, so I've rolled a patch for 8.x-dev.

I note that there is also the potential issue of leading comments in the SQL, which would also break this functionality, so ideally would also be accounted for. I don't know if there's existing code in Drupal for removing comments from raw SQL, though? I didn't spot anything obvious.

jweowu’s picture

Perhaps it's reasonable to assume that we do want to retain anything prior to the first SELECT we find, and copy it verbatim into the modified query? That would account for both comments and whitespace.

Something like:

preg_replace('/^.*?SELECT/is', 'CREATE TEMPORARY TABLE {' . $tablename . '} AS \0', $query)
jweowu’s picture

Here a patch for the second approach. This approach is working nicely with PostgreSQL 9.2beta2 (using the equivalent D7 patch).

jweowu’s picture

And here's #3 with a test included. I don't have a D8 dev site at present, so I'll see what the test bot thinks of it.

jweowu’s picture

Actually, that's still going to break if there's a leading comment containing the string "select". It's an improvement on the existing behaviour, though.

Edit: Come to think of it, the whole search & replace approach might be flawed?

The original code made the assumption that the query would start with a 'SELECT' statement. If that's a valid assumption, then presumably we can just use the replacement text up to (but not including) 'SELECT' as a prefix, concatenate the original SQL to that, and not bother with preg_replace() at all?

We'd still be making the same essential assumption about the SQL that the original code made, but without the additional constraints that the search+replace was imposing.

Edit 2: Ah, in fact the presence of the word "select" in a leading comment doesn't break the version in #4 as I initially thought (as the remainder of the comment and the real SELECT will still appear), but that exact behaviour definitely isn't the intent of the code, and it emphasises that we really just want to be concatenating the strings.

jweowu’s picture

jweowu’s picture

Issue tags: -Needs backport to D7

I've just looked back through the Drupal 5 commit history to see if there was any documented reason for the way this was done.

The code hasn't really changed much since it was introduced in commit 909d6928acb47cb9b50740e589b5e7447353e19c, but I believe we can see the source of the issue there, as initially Steven had thought that the Postgres syntax for CREATE TEMPORARY TABLE omitted the SELECT keyword, and so in the original version the preg_replace() for the postgres case was removing that keyword.

That turned out to be a mistake (see #36255: Postgres problem with db_query_temporary()), and the SELECT keyword for postgres was restored, which seems to render the preg_replace() approach redundant (for all databases).

It was left like that, though, and it's been that way ever since.

jweowu’s picture

Issue summary: View changes

Updated issue summary.

danchadwick’s picture

Issue tags: +Needs backport to D7

Works in d7. #6 is a simple patch, fixing a bug that wasted several hours of my life. RTBC?

jweowu’s picture

Dan: I'm not sure anyone else is looking at this, unfortunately.

I spent enough time investigating this at the time, that I'm pretty happy with the patch and the reasoning behind it.

If you've actually reviewed the code (it's not clear to me from your "simple patch" comment), you could please go ahead and update the issue status to RTBC?

It's an annoying bug, and the cause isn't at all obvious when it occurs, so it would be nice to get some extra eyes on it, and get this fix committed!

jweowu’s picture

Status: Needs review » Needs work

The last submitted patch, 6: drupal-8.x-db_query_temporary_whitespace-1868972-6.patch, failed testing.

jweowu’s picture

Ugh, annoying. That's actually a test PASS (exactly as it used to do). The failure was:

59,755 pass(es), 1 fail(s), and 0 exception(s)

Non-pass

Test name Pass Fail Exception
Drupal\system\Tests\Theme\ThemeTest 61 1 0

Message Group Filename Line Function Status
The test did not complete due to a fatal error. Completion check ThemeTest.php 263 Drupal\system\Tests\Theme\ThemeTest->testFindThemeTemplates()

(which is utterly unrelated to this patch).

danchadwick’s picture

Status: Needs work » Needs review
danchadwick’s picture

Issue summary: View changes
Status: Needs review » Reviewed & tested by the community
Issue tags: +Needs backport to D7, +needs backport to 6.x

I agree that this is an annoying and unnecessary bug. This fix is unusual in that it is cleaner, shorter, and easier to understand.

I have tested this on Drupal 7 with MySQL and I have carefully reviewed the code for all 3 database drivers. I looked at the PGSQL doc and didn't see any red flags. It looks good to me.

The only functional difference that I can imagine is that someone could omit the leading SELECT before and now cannot. I would consider this a programming error (and maybe even an error to NOT fail).

I have re-queue #6 for testing in the hopes that the spurious bot failure won't recur.

The backport to d6 will of course have to be slightly different since D6 predates DBTNG.

chx’s picture

This is absolutely fantastic -- in general, DBTNG tries (very hard) to avoid parsing SQL strings because that is brittle as is visible in this issue and this one as proved by the great research in #7 by jweowu is an ancient vestige. Also, it's really great to see people whom I rarely see in core work on this issue. Thanks much.

jweowu’s picture

Thank you Dan, that's excellent. Hi chx :)

alexpott’s picture

Version: 8.x-dev » 7.x-dev
Status: Reviewed & tested by the community » Patch (to be ported)

Committed 03a7c55 and pushed to 8.x. Thanks!

danchadwick’s picture

Status: Patch (to be ported) » Needs review
StatusFileSize
new3.06 KB

D7 backport.

I tested it in devel with MySQL, and the surrounding code was unchanged from D8 back to D7.

chx’s picture

Status: Needs review » Reviewed & tested by the community

Well, that looks good.

jweowu’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new3.07 KB

Wow... 12 hours for that to get through the test queue.

The functional changes are all good -- and it'll be a similarly trivial backport to D6 -- but I've edited the test for consistency, so that in D7 we're querying {system} (as per the other temporary table tests), rather than the {test} table used in the D8 tests (the testbot didn't complain about it, but it just looked wrong).

Hopefully the test queue is smaller by now :)

jweowu’s picture

Status: Needs review » Reviewed & tested by the community

I can see now that querying {test} is 100% fine in D7 as well, so I'm just setting this back to RTBC. #18 is good to commit.

danchadwick’s picture

Bump for D7 backport commit?

bzrudi71’s picture

nevermind overread #17 ;-)
6: drupal-8.x-db_query_temporary_whitespace-1868972-6.patch queued for re-testing.

The last submitted patch, 6: drupal-8.x-db_query_temporary_whitespace-1868972-6.patch, failed testing.

jweowu’s picture

#18 is still ready and awaiting a 7.x commit.

If it helps to hurry this along, can I just reiterate that the essentials of the patch are identical for all of 8.x, 7.x, and 6.x (because this code is essentially unchanged since 5.x), so this (#18) really is good to go.

danchadwick’s picture

Yes, please. RTBC Drupal 7.x. Please commit #18.

jweowu’s picture

I don't know whether to keep bumping this in the hopes that a Drupal 7 maintainer notices it, or if it's going to get processed in due course and I should just leave it be. It's impossible for me to tell given the lack of response, so I really don't know what to do about it -- I don't want it to fade from view entirely.

It's doubly frustrating because the change has already been committed to 8.x and the exact same change is reviewed and tested in 7.x, so I feel that this is an absolute no-brainer.

jweowu’s picture

18: drupal7x-db_query_temporary_whitespace-1868972-18.patch queued for re-testing.

Re-submitted the correct RTBC patch for 7.x to the test bot, because bzrudi71 re-submitted the already-committed 8.x patch after we'd switched to 7.x, which left a test failure message in the more recent comments.

David_Rothstein’s picture

Status: Reviewed & tested by the community » Fixed
Issue tags: +7.28 release notes

Committed to 7.x - thanks!

I committed this although am slightly concerned that nowhere do we document db_query_temporary() only works on SELECT queries (whereas now we are enforcing it)... However, it's pretty unlikely that anyone is trying to use it for a non-SELECT query (and if they are I would think they are doing something wrong already) so as long as it gets mentioned in the release notes, I think it should be OK even if it theoretically could break something.

  • Commit 20c04c5 on 7.x by David_Rothstein:
    Issue #1868972 by jweowu, DanChadwick: Db_query_temporary() fails to...
David_Rothstein’s picture

By the way, bumping RTBC patches actually makes it less likely they'll be committed (at least until #2242183: Store first-RTBC timestamp and add column to issue listings is fixed).

There isn't anyone except me actively committing non-documentation-related Drupal 7 patches right now, so they often wait a while before a committer looks at them. Also, I deliberately let patches sit for a while to get more reviews/testing, since it's rare that Drupal 7 patches get enough of that (many in the RTBC queue are not actually ready to be committed in practice). Hopefully it shouldn't matter too much as long as patches that actually are ready get reviewed/included in the next release, though; i.e. the last Drupal 7 bugfix release was on January 2, the Drupal 7 patch in this issue first reached RTBC on January 26, and it's now all queued up to be in the first bugfix release it could be in. So getting it committed quicker wouldn't have changed a whole lot.

Although with a patch marked for Drupal 6 backport like this one, I guess there's theoretically some more incentive to get it in quickly... However, Drupal 6 isn't really getting non-security-related fixes in practice these days anyway.

David_Rothstein’s picture

Version: 7.x-dev » 6.x-dev
Status: Fixed » Patch (to be ported)

But it does have the tag, so putting it back to Drupal 6 for now.

David_Rothstein’s picture

Documentation followup regarding db_query_temporary(): #2259361: Document that db_query_temporary() can only be used on SELECT queries

jweowu’s picture

Thank you David (and for that very useful info on the D7 patching process!).

David_Rothstein’s picture

No problem, and thank you for your persistence on this issue also.

jweowu’s picture

Status: Patch (to be ported) » Needs review
StatusFileSize
new1.81 KB

D6 version.

xjm’s picture

Status: Needs review » Closed (outdated)

Automatically closed because Drupal 6 is no longer supported. If the issue verifiably applies to later versions, please reopen with details and update the version.