In #337926: Force connection with PDO::CASE_LOWER, a default PDO database connection attribute was added that converts all fetched field/column names into lowercase. That is, for no particular reason.
This turns integration with third-party web services into a pain.
Schema functions, however, still create fields with their natural letter casing.
Example:
db_create_table('foo');
db_add_field('foo', 'contentId', array(
'type' => 'int',
'not null' => FALSE,
));
db_insert('foo')
->fields(array('contentId' => 3)
->execute();
debug(db_query('SELECT * FROM {foo} WHERE contentId = :id', array(':id' => 3))->fetchObject());
stdClass::__set_state(array(
'contentid' => '3',
))
Note that the fetched column name is lowercase, contentid instead of contentId.
The only ugly workaround for the moment:
function foo_db_query($query, array $args = array(), array $options = array()) {
if (empty($options['target'])) {
$options['target'] = 'default';
}
$connection = Database::getConnection($options['target']);
// Backup and restore PDO::ATTR_CASE afterwards, 'cause it sticks.
$backup = $connection->getAttribute(PDO::ATTR_CASE);
$connection->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL);
// Execute the query statement.
$result = $connection->queryRange($query, $from, $count, $args, $options);
// Restore the attribute.
$connection->setAttribute(PDO::ATTR_CASE, $backup);
return $result;
}
It might be possible to set PDO::ATTR_CASE on a statement only (instead of the connection). At least there's http://php.net/manual/en/pdostatement.setattribute.php, living right next to http://www.php.net/manual/en/pdostatement.setfetchmode.php, which we already support through $options = array('fetch' => PDO::FETCH_...)
Comment | File | Size | Author |
---|---|---|---|
#32 | 1171866-drupal7-query-case.patch | 3.27 KB | Alan Evans |
#22 | drupal8.query-case.22.patch | 2.77 KB | sun |
#17 | drupal8.query-case.17.patch | 3.91 KB | sun |
#8 | drupal8.query-case.8.patch | 3.77 KB | sun |
#1 | attr_rollback.patch | 1.82 KB | catch |
Comments
Comment #1
catchThat was a messy issue, but this patch should be a straight rollback of the change that caused the actual bug.
Comment #3
catch#1: attr_rollback.patch queued for re-testing.
Comment #5
sun#1: attr_rollback.patch queued for re-testing.
Comment #7
sunSo the issue is that schema queries return information with always varying letter-cases (also experienced this flu in Demo module, it's different from mysql to mysqli to pdo_mysql to PHP5.2 to PHP5.3 to WHATNOT... not. fun.)
Comment #8
sunThis would work, but I've no idea whether the implementation is correct. Thus, only one converted one example, which should lead to one test failure less.
A potential issue with this approach is that a query throwing an exception would possibly not revert the PDO::ATTR_CASE attribute on the connection. While the exception may be caught upstream, the database connection attribute won't be reverted. :-/
Comment #9
Crell CreditAttribution: Crell commentedPer the previous issue, I don't see why this should be per-query. It should be per-connection, and should be a connection option to disable the case folding.
I'm not sure why that ended up getting left out of the previous patch, but that is, IMO, the correct solution.
Comment #10
sun@Crell: Changing it on the connection is basically what both #1 and #8 are doing: Columns are fetched in their unchanged natural casing again.
However, in the very same database connection, we are also querying database schema information -- and especially MySQL is extremely inconsistent in what casing it returns for schema info. All of the keys in the array in #7 are currently expected to be all-lowercase throughout Drupal core. Unless you enforce columns to be fetched in lowercase, you'll get varying results depending on platform and version.
I don't think we want to instantiate an entirely new database connection when querying schema information...? Instead, only a handful schema queries want to enforce lowercase column names to ensure that consistent array keys are available and accessed in subsequent code.
Possibly I didn't fully understand what you meant though. If you meant with "change it per connection" to invoke some magic DatabaseConnection::setConnectionOptions() method (which doesn't seem to exist), then I could use some more detailed pointers to what you've thought of ;)
Comment #11
Crell CreditAttribution: Crell commentedI was thinking something along the lines of:
Or alternatively we could allow a key to override arbitrary PDO options, although that might cause more trouble than its worth.
As for MySQL and its schema... *expletive deleted*.
Comment #12
sunApparently, my message didn't come through. Trying once again:
schema.inc
must fetch column names in lowercase, regardless of connection and current connection options.If this is still unclear, please ping me in IRC. ;)
Comment #13
xjmTagging issues not yet using summary template.
Comment #14
Sylvain Lecoy CreditAttribution: Sylvain Lecoy commentedI fully support this as I created a similar issue #1315330: entity_load does not respect case on entity keys without finding this one.
It is effectively a pain in the ass for services integration. Here is a work-around for now:
Comment #15
catchLooks like we need Crell's feedback on approach here.
Comment #16
Crell CreditAttribution: Crell commentedNow that #1309278: Make PDO connection options configurable is in for Drupal 8, does that change anything here? (Basically item 2 from #12 just happened anyway.)
That patch would have broken the latest patch here anyway, so marking back to needs work.
Comment #17
sunJust so everyone gets it:
All queries performed in
schema.inc
must fetch column names in lowercase, regardless of connection options.Re-rolled against latest branch HEAD.
I don't really care whether case-folding is configurable, as long as we have natural casing by default. People overriding it to lowercase in their connection options just simply won't be able to use modules relying on natural casing.
In other words, the actual fix in this patch is that schema queries enforce the lowercase, and everything else remains natural. In concert with that, I'd personally recommend to hard-code and enforce natural casing on everyone.
Comment #18
chx CreditAttribution: chx commentedI tried to review #337926: Force connection with PDO::CASE_LOWER but all I could find is another case of hswong3i's irrational demands. So doing this looks good to me. The backport, alas, will be much less, we can only change schema.inc but we can not change connection options. We can make them configurable yes and that willl help you but the change to database.inc is not backportable.
Comment #19
catchConfigurable connection options is on its way #1309278: Make PDO connection options configurable.
Comment #20
Crell CreditAttribution: Crell commentedWith the connection options configurable now I don't think we need to have the separate "case" option. We just change the default options. People can still change them if they really really need, give or take the warning that they could break everything by doing so. I'm fine at this point with moving back to "native" casing as the default.
As for MySQL, <expletive deleted>. If we have to force lowercase there, we should document inline why we're doing so.
Comment #21
sunMy last known revision on why schema queries need to return lowercase keys/results is because there are differences between MySQL 5.0 and 5.1+ as well as differences between PDO and non-PDO:
http://drupalcode.org/project/demo.git/blob/refs/heads/7.x-1.x:/database...
Comment #22
sunI keep on running into this issue in contributed modules for D7.
To make them work at all, I'm committing more and more
_mymodule_db_query()
helper functions to the respective modules. This is insane.Attached patch is the most simple fix:
It removes the bogus lowercase case-folding and makes the MySQL driver look for the original, natural-cased
'Key_name'
that's returned by SHOW INDEX.I really really hope that this can be backported.
Lastly, I don't really see how to write tests for this... the test failures for #1 show that the case-folding option is implicitly tested by the database system's schema functionality already.
Comment #23
daniel.hunt CreditAttribution: daniel.hunt commentedSun, would you mind pasting an example of what your _mymodule_db_query() functions contain?
Comment #24
sphism CreditAttribution: sphism commentedGlad this is being fixed for drupal 8, was a right pain in drupal 7
Thanks for that.
Comment #25
Crell CreditAttribution: Crell commentedLet's just go ahead and do #22. I don't know if we can backport it, but I am OK with doing so if webchick is.
Comment #26
meustrus CreditAttribution: meustrus commentedIf you remove the case folding in the PDO fetch mode, please be ABSOLUTELY SURE that every database driver will behave consistently with mixed-case column names. This shouldn't affect schema install/uninstall, updates, or insertions; PDO::ATTR_CASE only affects returned result sets, AFAIK. I read somewhere that the PostgreSQL driver itself returns case folded column names, even if the PDO::ATTR_CASE is set to PDO::CASE_NATURAL; note the PDO documentation:
I am pleased enough to see that the database connection is being set to intentionally case fold in a consistent manner with the current setup. Not sure why the options are at the driver level since it's a PDO function and not a driver function. The tables are created with the correct case, and I just need to use case folded column names when fetching data.
Comment #27
webchickThis looks a bit too scary for committing this close to 7.13. I can take another look after Wednesday.
Comment #28
Crell CreditAttribution: Crell commentedThis patch is against 8.x anyway, so it has to go there first. :-) Once that's done let us know if you're OK with a D7 backport and we can get xjm and her minions to handle that.
Comment #29
catchI've committed/pushed this to 8.x. Moving to 7.x for backport. I think it makes sense to commit this just after the next stable 7.x release rather than just before too.
Comment #30
catchUnassigning Crell since he's signed off on this, and tagging novice for the backport.
Comment #31
Alan Evans CreditAttribution: Alan Evans commentedI realise this has been signed-off, but for the record here are a few checks and verifications I did last week (hoping to add confidence to the backport):
Comment #32
Alan Evans CreditAttribution: Alan Evans commentedAttaching the backport ...
Comment #33
bvirtual CreditAttribution: bvirtual commentedLong Beach Drupal Meetup is going to test the D7 patch tonight, March 28. We have 5 members working on this. 3 experienced, and 2 novice.
Comment #34
frobrtbced #32
Tested patch 32 and verified with help from btmash.
Used this code to create table:
http://drupalbin.com/21097
then added a data to the table and pulled the table structure with:
debug(db_query("SELECT * FROM {foo} where formatId = 0")->fetchObject());
Comment #35
bvirtual CreditAttribution: bvirtual commentedRTBCED #32 wrapping up the Long Beach Contrib Meeting. We had 3 members test the same way, on 2 different OS (linux and mac). I tested on Gentoo with PHP 5.2.17 and mysql 5.1.56. I did test using a lower case name for the SELECT Frank posted in #34. It worked.
Ashok points out this patch could be reviewed in D7 using sqlite, SQL, and I thought what my MySQL was switched to case sensitive mode for field names (does it still have that option?). Ashok's opinion was the code was all the same for all these databases, and should work.
Comment #36
Alan Evans CreditAttribution: Alan Evans commented@frob/@bvirtual: wow, that's some seriously efficient reviewing work going on there :) Nice work ...
@bvirtual: did you intend to switch the status back? That looks to me like it may have been a concurrent edit.
Comment #37
Crell CreditAttribution: Crell commentedSweet jebus that's a lot of testing. :-)
Different databases have different case folding rules. Technically this patch reverts back to "let the database do whatever it's going to do", for better or worse. So a quick verification on SQLite and Postgres that nothing explodes is probably a good idea, especially for D7. A quick run through some subset of the database unit tests should be sufficient, since otherwise testing SQLite and Postgres takes a LONG time. :-(
Comment #38
Alan Evans CreditAttribution: Alan Evans commentedCouple of odd fails on D8/postgresql. Will come back to them once I've been through the rest. Given it's 2/2.5K fails/success, I doubt it's caused by this patch.
Ran all of database and Field API (for field_sql_storage). The fails are reliable.
Comment #39
Alan Evans CreditAttribution: Alan Evans commentedD8 sqlite:
2664 passes, 2 fails, 1 exception, and 81 debug messages
The exception is the same between sqlite/postgresql, the fails are different.
Comment #40
Alan Evans CreditAttribution: Alan Evans commentedD8/sqlite tests fail in the same places when patch is reverted, so no regression here ...
Comment #41
Alan Evans CreditAttribution: Alan Evans commentedD8/pgsql also has the same fails when reverted, so ok there too...
Will come back to the actual fails/exception in a separate issue.
Comment #42
Alan Evans CreditAttribution: Alan Evans commentedPrior to patching, pgsql shows similar fails on d7
PDOException: SQLSTATE[42803]: Grouping error: 7 ERROR: column "test.age" must appear in the GROUP BY clause or be used in an aggregate function LINE 3: (SELECT test.age AS age, age + 1 AS expression, 1 AS express... ^: SELECT COUNT(*) AS expression FROM (SELECT test.age AS age, age + 1 AS expression, 1 AS expression_2 FROM {test} test HAVING (age + 1 > 0) ) subquery; Array ( ) in PagerDefault->execute() (line 74 of /Users/alan/Sites/d7/includes/pager.inc). Uncaught exception database.inc 2136 PDOStatement->execute()
Number of affected rows are returned. Other database_test.test 810 DatabaseUpdateTestCase->testExpressionUpdate()
And sqlite also:
PDOException: SQLSTATE[HY000]: General error: 1 a GROUP BY clause is required before HAVING: SELECT COUNT(*) AS expression FROM (SELECT test.age AS age, age + 1 AS expression, 1 AS expression_2 FROM {test} test HAVING (age + 1 > 0) ) subquery; Array ( ) in PagerDefault->execute() (line 74 of /Users/alan/Sites/d7/includes/pager.inc). Uncaught exception database.inc 240 PDO->prepare()
Update succeeded. Other field_sql_storage.test 321 FieldSqlStorageTestCase->testFieldUpdateFailure()
So, first of all looking that no new fails are introduced by the patch...
Comment #43
Alan Evans CreditAttribution: Alan Evans commentedsqlite shows the same fails as prior to patching.
pgsql also.
I'll check again what mysql has to say about the couple of fails I've seen, but I'm not expecting to see any problems. I think we're probably good here.
Comment #44
Alan Evans CreditAttribution: Alan Evans commentedFWIW all clear on that same small subset of tests D7/8 MySQL
Comment #45
Crell CreditAttribution: Crell commentedSo... if I understand Alan Evans correctly, SQLite and Postgres are broken now in D7 and D8, and are no more or less broken with this patch in either D7 or D8. Correct?
If that's the case, then I think we can go ahead and commit the backport to D7 (webchick-permitting) and then separately try and figure out why those DBs are broken again.
Comment #46
Alan Evans CreditAttribution: Alan Evans commentedRight - MySQL is all clear with the patch (which we know anyway from the testbot), sqlite and postgres show the same fails/exceptions with and without the patch. I haven't run a full battery of tests on either to see what else might be lurking, just all of "Database" and "Field API" groups.
My first guess on the PDO Exception is just some MySQL-specific SQL within the test. The failed field API update is more of a concern. The nested transaction issue is also not good. I'll report them separately. On postgres I'd also wonder if there's a server version-specific issue involved (I just downloaded and installed the latest).
Comment #47
Crell CreditAttribution: Crell commentedOK, then I think the D7 version of this is ready. Thanks Alan and crew for the super-detailed testing!
Comment #48
Alan Evans CreditAttribution: Alan Evans commentedThanks Crell!
Comment #49
webchickAgreed; thank you so much for all of your great testing!
Committed and pushed to 7.x.
Comment #50
sphism CreditAttribution: sphism commentedYay, I can finally remove the only core hack on one of my sites :)
Thanks for all the hard work.
Comment #51
meustrus CreditAttribution: meustrus commentedGreat, now I have to go through all my code and change the case folded names back to normal.
Comment #52
onelittleant CreditAttribution: onelittleant commentedThis is a very disruptive core change. This breaks all migrations that use the migration module and sql data sources with column names that use uppercase letters. I would have considered this more of a questionable architectural decision than a bug. If your sql migrations are broken after an update to 7.14 as mine were, this is likely the reason. You will need to modify your migrations to use the proper source column names with capital letters where before this change you were required to convert all column names to lowercase. I would expect that this change will break a lot of custom integration code that relies on DBTNG to access legacy or 3rd party databases.
Comment #53
mikeryanYes, from the migration standpoint (and if you check the original issue, that's not the only reason for this change) this is going to be disruptive in the short term. But think back to when you were first implementing those migrations from a mixed-case DB - how long did it take you, after initially doing the obvious thing and using the original case, to figure out you had to lower-case everything? Didn't you say, WTF;)? Future generations of migrators will be grateful to express their mappings in a natural way.
Since users of the Migrate module are most likely to run into trouble with this upgrade, I've added a note to the project page. Hopefully at least some people will be forewarned before upgrading.
Comment #54
onelittleant CreditAttribution: onelittleant commentedI did say WTF, but then just assumed that it was an arbitrary decision to default to PDO::CASE_LOWER and dealt with it. I wrote a number of migrations from many camelcased tables and was only alterted to the fact that this was considered a bug when my migrations broke with a core update. My only suggestion is that this late in the game (7.14) it may have been better to:
1) Keep the PDO::CASE_LOWER default in D7.
2) Provide an option in D7 to change to CASE_NATURAL programtically.
3) Change the default to CASE_NATURAL in D8.
Comment #55
sphism CreditAttribution: sphism commentedOh I thought it was just going to be optional, maybe youcould define a constant in settings.php and use that for the pdo case ???
Comment #56
Heine CreditAttribution: Heine commentedI think you can opt for pdo options in the db conf array in settings.php in 7.14 and possibly per query.
That said; This change did come as a nasty surprise though as it was quite buried in the release notes:
Additional communication about the change (its presence, impact, workarounds) in the release announcements and additional docs similar to a Drupal 6.1 API change would have been nice.
Comment #57
mfer CreditAttribution: mfer commentedI agree entirely with Heine on this one. This change broke backup and migrate among other things. Had no idea where to look to deal with this. #1576812: Could not complete the backup.
Comment #58
webchickWell, I definitely welcome any and all help keeping an eye on the RTBC queue/commit log to spot things that break backwards compatibility. We never intentionally break APIs.
Comment #59
catchYes this could have done with a change notice, re-opening for that.
Comment #60
Leeteq CreditAttribution: Leeteq commentedFor reference: ("Known issues" at the release statement) http://drupal.org/drupal-7.14
Comment #61
mikeryanRegarding the drupal-7.14 release notes, they currently say:
Actually, it's not the table names, it's field names in mappings. Better:
Comment #62
sunAdded a change notice: http://drupal.org/node/1595218
Comment #64
sunFollow-up for Postgres: #1622982: PostgreSQL auto-converts column names into lowercase
Comment #65
Dave Cohen CreditAttribution: Dave Cohen commentedI'm new to this thread and realize it's already closed. Still, I want to express my emphatic agreement with comment #54. A change that will obviously break modules should be in a major release. What is this, drupal 4.x?
I have a module broken by this change. As far as I can tell, I need to create a new branch to support < 7.14 vs >=7.14.