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
Comment #1
jweowu commentedThe 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.
Comment #2
jweowu commentedPerhaps 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:
Comment #3
jweowu commentedHere a patch for the second approach. This approach is working nicely with PostgreSQL 9.2beta2 (using the equivalent D7 patch).
Comment #4
jweowu commentedAnd 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.
Comment #5
jweowu commentedActually, 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.
Comment #6
jweowu commentedComment #7
jweowu commentedI'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 TABLEomitted theSELECTkeyword, and so in the original version thepreg_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
SELECTkeyword for postgres was restored, which seems to render thepreg_replace()approach redundant (for all databases).It was left like that, though, and it's been that way ever since.
Comment #7.0
jweowu commentedUpdated issue summary.
Comment #8
danchadwick commentedWorks in d7. #6 is a simple patch, fixing a bug that wasted several hours of my life. RTBC?
Comment #9
jweowu commentedDan: 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!
Comment #10
jweowu commented6: drupal-8.x-db_query_temporary_whitespace-1868972-6.patch queued for re-testing.
Comment #12
jweowu commentedUgh, annoying. That's actually a test PASS (exactly as it used to do). The failure was:
(which is utterly unrelated to this patch).
Comment #13
danchadwick commented6: drupal-8.x-db_query_temporary_whitespace-1868972-6.patch queued for re-testing.
Comment #14
danchadwick commentedI 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.
Comment #15
chx commentedThis 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.
Comment #16
jweowu commentedThank you Dan, that's excellent. Hi chx :)
Comment #17
alexpottCommitted 03a7c55 and pushed to 8.x. Thanks!
Comment #18
danchadwick commentedD7 backport.
I tested it in devel with MySQL, and the surrounding code was unchanged from D8 back to D7.
Comment #19
chx commentedWell, that looks good.
Comment #20
jweowu commentedWow... 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 :)
Comment #21
jweowu commentedI 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.
Comment #22
danchadwick commentedBump for D7 backport commit?
Comment #23
bzrudi71 commentednevermind overread #17 ;-)
6: drupal-8.x-db_query_temporary_whitespace-1868972-6.patch queued for re-testing.
Comment #25
jweowu commented#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.
Comment #26
danchadwick commentedYes, please. RTBC Drupal 7.x. Please commit #18.
Comment #27
jweowu commentedI 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.
Comment #28
jweowu commented18: 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.
Comment #29
David_Rothstein commentedCommitted 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.
Comment #31
David_Rothstein commentedBy 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.
Comment #32
David_Rothstein commentedBut it does have the tag, so putting it back to Drupal 6 for now.
Comment #33
David_Rothstein commentedDocumentation followup regarding db_query_temporary(): #2259361: Document that db_query_temporary() can only be used on SELECT queries
Comment #34
jweowu commentedThank you David (and for that very useful info on the D7 patching process!).
Comment #35
David_Rothstein commentedNo problem, and thank you for your persistence on this issue also.
Comment #36
jweowu commentedD6 version.
Comment #37
xjm