Closed (fixed)
Project:
Drupal core
Version:
7.x-dev
Component:
database system
Priority:
Major
Category:
Bug report
Assigned:
Unassigned
Issue tags:
Reporter:
Created:
9 Jun 2018 at 22:45 UTC
Updated:
20 Dec 2022 at 20:23 UTC
Jump to comment: Most recent, Most recent file
Comments
Comment #2
elijahoyekunle commentedIt seems there is an error with the patch. I think I need help here, it worked on my computer.
Comment #3
shenzhuxi commentedI installed Drupal 7 with MySQL 8 successfully with this patch.
Comment #4
emilcarpenter commentedAdded server version check for MySQL 8.0.11 and an if condition based on that.
Comment #5
emilcarpenter commentedRemoved the line
$sql = str_replace("{system}", "{`system`}", $sql);introduced in the original patch by elijahoyekunle.
I do not know what that line is good for. Works for me without that line.
Comment #6
emilcarpenter commentedComment #7
emilcarpenter commentedReinserted the line
and wrapped also that in a version check. It is indeed needed for running D7 with MySQL 8.
I have now tested this patch on MySQL 5.6.40 and MySQL 8.0.11. (Without that line, on 8.0.11, I get an error on Cache Clear All.)
A drupal.org forum page about Drupal 7 with MySQL 8 is here and a there is a link with an explanation.
Comment #9
mmjvb commentedThat line is required because system became a reserved word in MySQL 8. The backticks allow it to be used as table name.
Consider the conditional setting of sql-mode the right thing to implement this issue. Would prefer a different implementation:
- initialize without
- extend when appropriate ( not 8 )
- append to init command.
In addition you might want to check the manipulation of information schema in the D8 patch. Several additions of `as .....`.
Comment #10
andyrandom commentedThis fix got Drupal working, but Drush is still problematic. That's because of the drush_module_list function which calls:
drush_db_select('system', 'name', 'type=:type AND status=:status', array(':type' => 'module', ':status' => 1));And this aliases the "system" table as "system," so the query ends up looking like:
SELECT system.name AS name FROM {system} system WHERE (type='module' AND status='1');So the patch escapes the
{system}in braces, but not thesystemalias.I "solved" this by adding a line before the patch:
$sql = str_replace("{system} system", "{system}", $sql);So now Drush works, and nothing else appears to be broken, but this probably isn't the way to do it.
Anybody got a more effective solution?
Comment #11
mmjvb commentedEach module needs to fix its own bugs. Suggest to check drush queue and raise an issue there.
You might try replacing 'system' with '`system`'
Not convinced the solution for reserved word system works properly. It shouldn't replace {system} with {`system`}, which only works with particular prefixes. It should translate {system} into `system` when table prefix is not a database prefix or no prefix provided.
Comment #12
sjerdoThe current patch will break other database drivers like PostgreSQL which do not use backticks to quote identifiers. Also this patch will break table prefixes.
I suggest we'll backport the patches in #2966523: MySQL 8 Support and #2986452: Database reserved keywords need to be quoted as per the ANSI standard once these are committed in D8.
Comment #13
xpiku commentedAlso using
will brake core simpletest
so it would rather work this way:
Comment #14
almaudoh commentedPorted some part of the patch in #2966523: MySQL 8 Support to Drupal 7. This patch still needs to resolve escaping table names that are reserved keywords particularly the `system` table which doesn't exist in Drupal 8. Still work in progress...
Comment #15
ayesh commentedComment #16
bmiro-apsl commentedHi,
There is any update on this?
Is still planned to support MySQL 8 in Drupal 7 or it has been dismissed?
Thanks
Comment #17
back-2-95Hello,
If anyone would like to use Drupal 7 with Digital Ocean's Mysql 8 DBaaS, this needs to be fixed.
Comment #18
polHi,
Tested with success as well on my side.
Comment #19
joseph.olstadComment #20
joseph.olstadTo increase our confidence in the test results for the above issue, these two should go in first so we can re-queue php 7.3 and php 5.3 tests.
#3047844: [Regression] Tests fail on PHP 5.3
#3025335: session_id() cannot be changed after session is started
Comment #21
fabianx commentedTo clarify some things:
- We will be supporting MySQL 8 in Drupal 7 — it is just not yet clear when.
There is several concerns with the patch and in the worst case we can commit it in stages:
- We should document that not only is NO_AUTO_CREATE_USER removed from MySQL 8, but also that it’s the new default since 5.7 (so no longer needed)
- We definitely need the tests ported from D8 as well
- We might want some more tests, too: If we say something is supported and old legacy D7 code breaks when switching due to an oversight, then we don’t actually support.
Main questions I have here are:
db_query() + reserved key words.
Expressions + keywords
Table prefixes and keywords
The information schema parts are fine and easy to commit though.
Comment #22
mcdruid commented@Fabianx and I also reviewed this:
...which is the same as (or very similar to) the D8 patch, but we don't generally use this syntax (anonymous function as the callback) in D7. It was introduced in PHP 5.3, so should work pretty much everywhere nowadays, but this would be the first usage of it to be introduced to D7 core.
Comment #23
berenddeboer commentedWhat I don't get is how difficult we make this. Why can't we not simply use back ticks for every table and column?
Comment #24
berenddeboer commentedHere's my much simpler patch to advance the discussion. This one doesn't require any fixes to Drush.
Comment #25
ayesh commentedIf we were to have a full ORM, we surely can inspect each and every table/field/alias and quote them with backticks. What we have is a half-baked system, that queries might come from structured data (which we can quote properly), and raw SQL queries which we cannot properly quote.
For example, the following is a valid SQL query:
Unless we parse the query, there is no bullet-proof way to decide which ones need backticks, which ones should not. That is why we have this solution to individually process tables, fields, and aliases. I personally think that we can improve the performance of the escaping step (for example, using a single
strtrcall instead of an array_reduce + lambda calls for each reserved keyword), but I don't think we can have a more simpler solution.Comment #26
berenddeboer commentedI don't think we need to parse:
What we currently don't handle is arbitrary quoting of arbitrary SQL. That's fine. I don't think any patch will ever do that. We can leave that stuff alone to either use db_select() or add their own quotes.
I.e. only quote the stuff we know for sure can be quoted. Leave the rest alone.
Comment #27
ayesh commentedI'm also in favor of quoting all fields/tables with backticks just so we can avoid iterating the rather big reserved keywords list and the future-proofing it provides with future reserved keywords MySQL might add.
Some related issues: #3020839: Drupal\Core\Database\Driver\mysql\Connection::quoteIdentifier in_array performance improvements, #2986452: Database reserved keywords need to be quoted as per the ANSI standard.
Comment #28
mustanggb commentedComment #29
ronlee commentedAny update on the status of this critical issue?
I'm not a strong enough programmer to help, but would love to start using MySQL 8. :)
Does this patch affect performance? Can I use it in production? mysql-8-2978575-24.patch
Comment #30
ayesh commentedRealistically, I suppose a lot more tests need to be done and written before this lands in Drupal core because there can be a lot of edge cases considering `system` is a reserved keyword in MySQL 8, which Drupal uses extensively.
I use a patched Drupal 7 core in several of my web sites, none of them fortune 500, but quite important ones. So far, it seems to be working well. Drush 8 is not working properly, so you will need some tinkering around to get it working.
Other than that, I encountered MySQL server has gone away errors with PHP 7.4. Not related to Drupal in particular though. See the linked article on a fix.
I'd say go for it. If you encounter any errors, post here, and the others will gladly work together for a fix, including myself.
I'm a bit concerned the excessive array scanning for reserved keywords. See #3020839: Drupal\Core\Database\Driver\mysql\Connection::quoteIdentifier in_array performance improvements. I use a similar patch from the linked issue (I'm the author), but the issue did not receive any attention. PHP's opcache should provide some optimizations given the pseudo-static nature of the reserved keywords array. This will be a micro-optimization and it will be unlikely a bottleneck for performance.This comment was regarding the patch in #14; not 24.
Comment #31
ronlee commentedThanks for your reply :)
Sorry, which mysql 8 patch are you using on your websites?
Comment #32
ayesh commentedPatch in #14.
Comment #33
ronlee commented@Ayesh Thank you!
FYI, patch #14 did not work for me, but patch #24 works...so far.
I'm running PHP 7.2.25 and MySQL 8.0.18.
Comment #34
izmeez commented@Ayesh "Drush 8 is not working properly, so you will need some tinkering around to get it working." Can you elaborate? We recently upgraded to drush 8 on php 7.3 because drush 7 fails. The only issue we encountered was the dependence on needing the php extension fileino for now, see #3085098: PHP Phar Stream Wrapper should be updated (PHP7.4 support, drop fileinfo dependency). Is there something else? So far drush 8.3.2 appears to be working fine for us. We are using MariaDB 10.
Comment #35
ayesh commentedThis is the error I'm getting from Drush (for
drush up):Comment #36
izmeez commentedHave you confirmed that the php extension fileinfo is loaded,
php -mComment #37
ayesh commentedYes it's loaded.
Comment #38
izmeez commentedFrom the findings it looks like there are two issues; (1) related to use of mysql 8 with drupal 7 itself, and (2) related to the use of drush.
Comment #39
izmeez commented@ayesh did you try the patch in #24 that does not affect drush?
Comment #40
izmeez commentedAlthough it does have a hunk to make drush work.
Comment #41
izmeez commentedPatch in #24 applies without difficulty to the latest drupal 7.69 and appears to work fine with MariaDB 5.5.5-10.2.29 and drush 8.3.2 with php 7.3.12 does not give errors with "drush up".
Comment #42
mustanggb commentedComment #43
seamus_lee commentedI have tested Patch #24 and appears to work for me and with drush 8
Comment #44
rjt1224Also make sure settings.php configuration has
MySQL 8 renamed tx_isolation to transaction_isolation
Comment #45
rjt1224#24 works great with Drush 8.1.x and MySQL 8.0.x & PHP 7.2.x and Drupal 7.69.
Will this release be sent with the latest Drupal 7 Core? It shows Drupal 7.70 target.
Comment #46
lakshmi_a commentedI have updated mysql 8.0.18 version with drupal 7.56... after update version i am facing mysql syntax error.I am applied path #24 after that my application issue has been resolved. thank you soomuch...
I am added
includes/database/mysql/database.inc
public function query($query, array $args = array(), $options = array()) {
$query = preg_replace('/{([^}]+)}/', '`\1`', $query);
// This to make Drush work
$query = str_replace(' system.', ' `system`.', $query);
$query = str_replace('`system` system', '`system` `system`', $query);
return parent::query($query, $args, $options);
}
And Setting.php i have added
'sql_mode' => "SET sql_mode = 'REAL_AS_FLOAT,PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE,STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO'",
);
mysql 80.18 PORT :3308
within database connection. it's working fine for me.
Comment #47
sivaprasadc commented@berenddeboer
Thanks for the patch #24. It work great with Drupal 7.69, MySQL 8.0.x and PHP 7.3.x.
Comment #48
izmeez commentedWould it be fair to say this is RTBC or is there something else left to do?
Comment #49
vensiresFrom my experience with the patch in #24 and the comments of the other users I set this as RTBC.
Comment #50
ronino commentedI have improved patch #24 to also handle queries that specify the database along with the table, like in "{db.table}". Those must be quoted to `db`.`table` instead of `db.table`. This is required e.g. by migrate when a different source database is queried and probably always if a database connection other than the default one is used. Without it, the query fails.
The quoting is also only done if we're indeed on MySQL 8 as it's unnecessary for version 5.
I reintroduced the conditional addition of NO_AUTO_CREATE_USER from patch #14 as it has gone into Drupal 8, too (see #2966523: MySQL 8 Support).
Before this can be RTBC, I guess we need test functions, like stated in #21.
Comment #51
ronino commentedThe test on PHP 7.3 & MySQL 8 fails as DrupalTestCase::insertAssert() wants to save a field "function" to the simpletest table which doesn't work as "function" is a reserved keyword. Seems like quoting table names is not enough and changes from patch #14 need to be incorporated.
Comment #52
shaneonabike commentedThis is a fairly critical for anyone using Ubuntu 19.04 since it is no longer being supported. Upgrading would result in Mysql 8 which is throwing bad errors :/
Comment #53
fietserwinPatches from #24 and #50 don't work when a table prefix has been set.
To me it seems more logical to have the code that replaces the { and } should add the back ticks, i.e. override method DatabaseConnection::setPrefix() instead of changing DatabaseConnection_mysql::query() as these patches do. So, patch #14 looks better, though if we override DatabaseConnection::setPrefix() to always back tick a table name, regardless if it is a reserved keyword or not, that code would be less complicated, see attached patch.
This patch solved for me:
- The problem with NO_AUTO_CREATE_USER being removed
- The error introduced by the patches from #24 and #50, namely something like "Table semaphore does not exist in ..." (I am using a database prefix, thus the table semaphore indeed does not exist, it should be prepended with the prefix).
This patch should solve for those who do not use a prefix:
- Tables having the same name as a reserved keyword giving errors because the table name is not being quoted.
Other problems with MySQL8 (quoting fields that are a reserved keyword?) are not solved by this patch.
Comment #54
mustanggb commentedWe seem to be split between people not liking the complexity of the #14 route even though it keeps us closer in-sync with D8, and people not liking the divergence and less complete solution of the #24 route.
So why not try to make both camps happy and do a backport of #2986452: Database reserved keywords need to be quoted as per the ANSI standard instead?
That said, #14 is working fine over here, with a very minor Drush patch (https://github.com/drush-ops/drush/pull/4313).
Comment #55
joergm commented#53 works fine for me.
However drush 8 is not working with #53.
I appreciate if someone can extend #53 so drush 8 is working, thx.
Comment #56
tr commented#53 fails for me when I visit /admin/config
The line it's failing on is clearly one case this patch was intended to fix, as it is accessing the
{system}table:$result = db_query("SELECT name, schema_version FROM {system} WHERE type = :type", array(':type' => 'module'));I made the priority higher because this started failing for me when I upgraded Ubuntu from 19.04 to 19.10. The latter is already 6 months old, and Ubuntu is about to release 20.04, but one must upgrade from 19.04 to 19.10 first before moving to 20.04. That said, I anticipate quite a few users encountering this problem over the next few months after 20.04 comes out. Likewise, the effort to make D7 run with PHP 7.4 won't be of much use if MySql 8 can't be used at the same time; most systems won't have a default configuration that includes a new version of PHP along with an old version of MySql - they will either have both new or both old.
As a temporary fix, I hacked core and changed the above query to use
{`system`}. That's not a good solution of course, but it will allow me to continue to test #53 to see if I encounter any other problems.EDIT: Yeah, there are a lot of problems still. Here's another, when visiting /admin/config/developement/testing (Simpletest):
Again, this is clearly a query that should have been fixed by the patch. Again I hacked core, and that simply got me to the next similar error. I don't think it makes sense to keep listing them here - just a few minutes of manual testing shows that #53 only works for some things, but not nearly enough.
Comment #57
fietserwinEDIT: forget this one, continue with #59
Comment #59
fietserwinThe patch did not work correctly due to the order of setting isMySql8 and calling setPrefix(). I tested with a prefix that obscured that (Tip: as a workaround work with a table prefix :))
Attached a correction of the patch form #53. Note that this still does not contain a fix for fields named after reserved words, though that still seems to be allowed in the query mentioned in #56 (type is a reserved word!).
Comment #60
tr commentedI triggered the testbot to test #59 against MySql 8, since that's the whole point of this issue....
Comment #61
joseph.olstadFails mysql 8
Comment #62
tr commented@joseph.olstad: Is that output from local testing? The testbot output in #59 shows that the failure is in
drush_db_select(), since drush is used by the testbot to set up the test environment. The testbot never gets around to testing this patch because the environment setup fails.But that does bring up once again that drush also needs to be fixed to support MySql 8, and that seems to be a prerequisite for getting this patch tested by the testbot. This has been mentioned in #10, #11, #30, #34, #38, and #55. Patch #24 and its derivatives make a special fix to accomodate drush, but as mentioned in #11 this should be fixed in drush instead - we can't be adding special cases for contributed modules into core, that should be fixed in the contributed module (drush).
Comment #63
joseph.olstadok so there's a fix for drush that hasn't gone in. Perhaps someone who knows more about this issue should perhaps follow up by creating a new issue in the drupalci_testbot queue with helpful explanation and instructions for the drupalci_testbot maintainer. Such as which drush patch to apply to the mysql 8 runner.
https://www.drupal.org/project/issues/drupalci_testbot
So , which patch does the drupalci_testbot drush require in order to be able to properly test against mysql 8?
I imagine this will cause some issues as the same version of drush might be used in other runners.
Comment #64
tr commented@joseph.olstad: Do you have a link to that drush fix? What I'm seeing in github is that this is a problem with Drush 8, and since Drush 8 is not supported there hasn't been much traction to fix this: https://github.com/drush-ops/drush/issues/3830
DrupalCI needs to use Drush 8 because Drush 9 doesn't work with Drupal 7.
Comment #65
joseph.olstadok looks like @MustangGB already created a pull request for Drush 8.x
I suggest that if the drush people do not respond in a timely manner, an option would be to fork drush 8 , OR for the drupalci_testbot project to patch their drush 8.x
here is the link to the pull request for drush 8.x
https://github.com/drush-ops/drush/pull/4313
**begin update**
I have commented on the upstream pull request and issue.
**end of update**
Comment #66
fietserwinSome thoughts:
To move forward: would it be an idea to split this issue in multiple smaller issues:
Comment #67
ayesh commentedThank you for your input @fietserwin. I also agree that this issue has become more complicated than it should have.
I propose that
NO_AUTO_CREATE_USERmode should not be used at all. It provides questionable security, and it is deprecated in MySQL 5.7 anyway. It will make out patch cleaner as well, because we do not need version-compare before adding the flag.I think this is where the most of out bikeshedding happens. Drupal 8's approach is more safer, as it has a known list of MySQL reserved keywords. It provides some sort of overhead compared to the regex approach in #24. It's worth noting that even if a table name is not a reserved keyword, there is absolutely no hard in quoting them with back-ticks either.
We can follow the D8 approach here, with the known list of keywords (although I personally would like to see use if
isset()as opposed toin_array). Patch in #24 does not provide protection for this.This is where I really like #24's regex approach. We can easily replace table names as long as they use
{table}pattern, which is recommended way anyway.We cannot fix
db_query()with a 100% assurance, because it's arbitrary SQL. This is the case for Drupal 8 as well.What I have is a hybrid of #14 and #24, that it tries to escape known database fields and tables, but also runs a a regex to catch the ones that did not make it (i.e came via db_query()). I will attach the Sparta comment below, but it is not to be taken seriously. It however works with pretty much every scenario I throw at it, even with Drush.
Comment #68
tr commentedComment #69
mcdruid commentedI've not had a chance to dig into the details of this one yet I'm afraid, but just wanted to mention that I think we'll need a very good reason to approach this differently to what's been committed to D8.
However it looks like #2986452: Database reserved keywords need to be quoted as per the ANSI standard has only gone into D9 and won't be backported to D8.9
So thanks for all the thorough comments and contributions so far... I think we'll need to approach this with the backport policy in mind and therefore thinking about how we can use the code (/ approach) that's already been committed to newer Drupal versions, and where possible avoid coming up with anything novel/different for D7.
Apologies if that's missing the point of recent comments; as mentioned I've not got into the details yet.
Comment #70
mustanggb commentedThere is no good reason, other than "too complicated".
The first pass backport of D8 is in #14, and I've not run into any problems with it, other than Drush which is a simple fix on their side.
Comment #71
joseph.olstadthe MustangGB MySQL 8 fix was merged into drush 8.x.
created #3128406: Drupal 7 MySQL 8 test runner needs a new version of drush 8.x
Comment #72
waqarit commented#67 has worked for me for latest drupal 7.69 with Mysql 8.0
Comment #73
arborrow commentedWith the release of Ubuntu 20.04 LTS which ships with MySQL 8, it would seem that the urgency to get a fix into core would perhaps be more pressing. I have a production server running D7 and CiviCRM that I'd like to get up to Ubuntu 20.04; however, I don't want to have to back install MySQL to make things work. Is there anything I can do to help move this issue forward? Peace - Anthony
Comment #74
mcdruid commentedTo be as transparent as possible about prioritisation of issues for the 7.x branch, compatibility with newer PHP versions is a higher priority at the moment than this, because it's more likely that D7 sites find themselves forced to update to new versions of PHP 7 than to upgrade to MySQL 8.
This is reflected in the Drupal 7 roadmap and release schedule at:
https://www.drupal.org/core/drupal7-roadmap-release-schedule#release-sch...
...where PHP and MySQL EOL dates are included for context.
Comment #75
mustanggb commentedLatest Drush 8.3.3 release should resolve things over there, so D8 backport should be good to go.
Comment #76
joseph.olstadIf for some reason there's still an issue with the test runner using the newly tagged drush 8.3.3, please follow up here: #3128406: Drupal 7 MySQL 8 test runner needs a new version of drush 8.x
Comment #77
tr commented@mcruid: As I mentioned in #56,
Specifically in the context of Ubuntu, where people who have been using the 18.04 LTS for the past two years will soon be upgrading to the new 20.04 LTS (the upgrade path for the LTS release means this will happen in July when 20.04.01 is released). Ubuntu switched to MySql 8 last year - EVERYONE who upgrades to 20.04 LTS will be using PHP 7.4 and MySql 8.
I don't know about other distributions and their timelines, but Ubuntu is a big one ...
Comment #78
fietserwinThe problem with this piece of code is that it still can go wrong: If I have a table 'fill' and a prefix 'zero', this query:
select * from {fill}will end up as:
select * from zerofill(I understand that this is thus also a minor bug in D8/9.)
Furthermore, the array_reduce is over about 270 entries so with 100 queries on a page (not uncommon) we are talking 27,000 str_replace calls and 100,000+ string concatenations. In the light of 100 DB queries probably nothing but still.
Oh and that list is probably a running target that may change again in the future.
So, I would definitely go with just escaping all tables: easier, faster, more robust.
Comment #79
ronlee commented@fietserwin Nice forwarding looking suggestion. I believe this is a valid concern we need to take seriously. Speed and future maintenance cannot be hindered. Let's do this right. :)
Comment #80
andrés chandía commented#67 has worked for me upgrade to latest drupal 7.70 with Mysql 8.0.20 and PHP 7.4.3
Comment #81
joseph.olstadI agree with 78 and 79
, so we need a D8 issue for this and this means it will require waiting for the D8/D9 fix to go in first according to policy. So please lets create the D8/D8 issue.Comment #82
mustanggb commentedI believe the D8/D9 issue is the one I mentioned above, namely #2986452: Database reserved keywords need to be quoted as per the ANSI standard.
Comment #83
joseph.olstadthanks @MustangGB, ok perfect so we're all set for doing the backport of that strategy.
Comment #84
ronino commented#67 works for me, apart from queries that specify the database along with the table, like {db.table}. As I wrote in #50, those must be quoted to `db`.`table` instead of `db.table`. This is required e.g. by migrate when a different source database is queried and probably always if a database connection other than the default one is used. Without it, the query fails.
I have merged the changes to DatabaseConnection_mysql::query from my patch #50 into #67 to fix this. There might be a more efficient way than doing this with preg_replace_callback() though.
Comment #85
fietserwinThis seems like a use case that is not supported as with current behavior this will become prefixdb.table instead of db.prefixtable???
Comment #86
ayesh commentedPlease note that the patch in #67 is not idea because it tries to fix the problem with both approaches. I'm personally in favor of escaping all keywords in the queries when possible, which in theory should work faster due to lack of vigorous
in_arraycalls.It works for now, but ideally I think we should discuss on one correct approach and stick with it.
Comment #87
andrés chandía commentedI had to apply again #67 patch to upgrade to latest drupal 7.71 with Mysql 8.0.20 and PHP 7.4.3
Comment #88
buddym commentedI had to install patch #67 after upgrading to Ubuntu 20.04 LTS with MySQL 8.0.20 and getting this message on Drupal 7.70:
PDOException: SQLSTATE[42000]: Syntax error or access violation: 1231 Variable 'sql_mode' can't be set to the value of 'NO_AUTO_CREATE_USER' in lock_may_be_available() (line 167 of /var/www/html/drupal/includes/lock.inc).
Thanks Ayesh, I was able to connect and all seems well even though there was one error applying the patch... Hunk #1 FAILED at 343.
$ more schema.inc.rej
Any idea how this error will affect my D7 site?
As Andrés said above, in order to upgrade from 7.70 to 7.71 running on PHP 7.4.3 and MySQL 8.0.20 this patch needs to be applied before running update.php. Otherwise, you will get this error message...
PDOException: SQLSTATE[42000]: Syntax error or access violation: 1231 Variable 'sql_mode' can't be set to the value of 'NO_AUTO_CREATE_USER' in drupal_get_installed_schema_version() (line 155 of /var/www/html/drupal/includes/install.inc).
Comment #89
ronlee commentedI'm unable to update drupal to 7.71 due to MySQL 8 issues.
Which patch is recommended? Patch #67 doesn't seem to exist on this page anymore.
Comment #90
ronino commented@ronlee wrote:
Please try the latest.
Somehow patches got out of order. I've rearranged them.
Comment #91
gislePatch in #84 applies cleanly and lets me install 7.x-dev on Ubuntu 20.04 LTS (MySQL ver 8.0.20) without a hitch.
Proceeding to test with some of the extensions I use on major production sites.
Changing tags, 7.70 is history. Let us try to get this one into Drupal 7.72.
Comment #92
saxmeisterAny release date for 7.72?
I'm stuck in a weird situation where I had to migrate a Drupal 7 site to PHP 7.2/MySQL 8 before I could get the migration to Drupal 8 out of the way. Now I have a Drupal 7 database from MySQL 8 that is incompatible with the Migrate Upgrade module (and several others) and I cannot create a migration properly due to the error that the module thinks that the D7 database is actually a D8 database.
I tried the patches above and none of them seemed to match or work. I've taken to creating my own patches at this point in an attempt to fix the issue. But, before I get through this does anyone even know if it will work - upgrading/migrating a D7 database to a D8 site on MySQL 8.0.20?
If I create a patch that works with the latest release I will contribute the product of the experiment.
Thanks!
Comment #93
gisleAgain: Patch in #84 applies cleanly to the HEAD of 7.x-dev and works fine. I've now tested this combo on 14 different Drubal 7 sites and no glitches so far.
Comment #94
tr commented@saxmeister: You're facing two distinct problems:
1) D7 doesn't work with MySql 8.
2) Migrate doesn't work in D8 with MySql 8. See #3093565: Migrate from Drupal 7 fails with mysql 8.0.3 or later: syntax error on select s.* from system
The patch here can probably fix 1) but doesn't help with 2).
Comment #95
pyqlo commentedIn my case, Patch #84 applied cleany to an existing site with Drupal 7.67 - using NO table prefixes
But applies not to an existing site (Drupal 7.70) - using table prefixes.
Error: PDOException: SQLSTATE[42S02]: Base table or view not found: 1146 Table 'juz.semaphore' doesn't exist: SELECT expire, value FROM `semaphore` WHERE name = :name; Array ( [:name] => variable_init ) in lock_may_be_available() (line 167 .... includes\lock.inc).
The table prefix ist false, in settings.php it is set as: 'prefix' => 'juz_'
Any ideas on this are very welcome. Thank! :-)
Comment #96
gislepyQlo, IMHO, It makes no sense to apply a patch to any other versions than the version of the dev-branch the patch was rolled against. What you try to do here is extremely unlikely to work.
This is about the dev-brach of Drupal 7. Don't try this on a production site running a tagged release.
Comment #97
pyqlo commentedYes, I totally agree. Unfortunately a very popular german hoster updates to MySQL 8 next week (announcement was 3 days ago...) - so I have to react.... :-(
Of course I didn't try it in production environment, but in my local dev site.
Updated to Drupal 7.71 and got the same error using table prefixes.
Comment #98
gislepyQlo,
this works for me:
1. Use git to clone the project.
2. Check out HEAD of the 7.x-dev branch.
3. Apply the patch in #84. It should apply cleanly.
4. Install the patched version on the site running MySQL 8.
Comment #99
pyqlo commentedgisle, thanks - I'll try that!
Comment #100
mmjvb commentedNormally that is the case for the active branch. For the D7 branch the files involved haven't been touched in 3 years. The patch applies to any release since then. That would be 7.65+ on the save side.
So, it is guaranteed to work as there have been no changes for so long.
Regarding table prefixes, are they pointing to a different database?
When the prefix uses a '.' it points to a different database. When '_' it refers tot the same database, just a different table name. Obviously, ending the prefix in a '.' is not going to solve the reserved words issue for system/groups as it only points to a different database. Having a prefix without ending in '.' should solve that, unless the new table name ends up being a reserved word.
Comment #101
pyqlo commentedThanks mmjvb.
I can also report back, that Patch #84 applies cleanly for dev and installation (even with table prefixes) worked.
For testing purposes I changed an existing site (7.71 - the one I mentioned above with prefix "juz") to use no table prefixes and here too #84 worked and the site runs without errors.
I understood, that this thread focusses on dev-branch, but it would be awesome, if the patch could be used for existing sites that are using table prefixes.
Comment #102
pyqlo commentedmmjvb - there ist only one datebase and the prefix was 'prefix' => 'juz_'.
No '.' is used even when the error looks like:
PDOException: SQLSTATE[42S02]: Base table or view not found: 1146 Table 'juz.semaphore' doesn't exist: SELECT expire, value FROM `semaphore` WHERE name = :name; Array ( [:name] => variable_init ) in lock_may_be_available() (line 167 .... includes\lock.inc).
Comment #103
mmjvb commentedDoes your database happen to be 'juz' ?
What does line 167 of lock.inc say? Does it say {semaphore} ?
Do you have a table juz_semaphore ?
You might be better of undoing the patch, override sql-mode in settings.php, use table prefix for tables that are considered reserved words without them.
Comment #104
pyqlo commentedDatabase is not called juz. Yes - 167 of lock.inc says {semaphore}. Yes, I have a table "juz_semaphore". Checked these points before.
The prefix was inserted during installation process years ago.
Yes, perhaps eliminating the prefix is the easiest way.
Comment #105
saxmeisterThanks for the quick responses. I assumed that was the current state of things but wanted to make sure.
Comment #106
mmjvb commentedYou would need the prefix for table system or use the patch.
Comment #107
mmjvb commentedShould have {} around it.
Like:
$quoted .= '`{' . $matches[3] . '}`';Comment #108
mmjvb commentedLooks like things are implemented in the wrong order. No need for checking for db.table format and ignoring prefix. Don't know about D7, but expect the prefix to introduce the db.table format. Not expecting existing code having {db.table}.
So, it should be the other way around. Change {table} to `{table}`, replace prefix, fix db.table format.
Comment #109
ronino commentedmmjvb wrote:
Good catch. It's not DatabaseConnection_mysql::query()'s job to remove the curly brackets from the query (DatabaseConnection::prefixTables() does that). After changing that, I also had to remove DatabaseConnection_mysql::prefixTables() which escaped table names that use reserved keywords (otherwise you'd get errors about "incorrect table names") - they are already escaped/quoted in query().
@pyQlo: This should now work with prefixes.
Comment #110
pyqlo commentedI'm in a hurry, but I did a quick test and #109 worked in another site using table prefixes. I'll test in detail later.
Great - thanks!!
Comment #112
ronino commentedFixed the regular expression in DatabaseConnection_mysql::query() so that tests should pass now.
Successfully tested with and without table prefixes.
Comment #113
ronino commentedComment #114
tr commentedWithout a patch, my D7/MySql 8 site won't even run ...
I have applied #112 and after about 20 minutes of clicking around on my site I haven't encountered any errors.
(This is already far better than the previous patches, where I quickly encountered fatal errors ... see #56)
So far so good.
Comment #115
buddym commentedAfter Drupal core 7.72 upgrade I reapplied patch 2978575-67. All is well again it seems, but there was one error when applying the patch... Hunk #1 FAILED at 343. I am still not sure what this affects. I am not seeing any issues on the site.
$ more schema.inc.rej
Just for kicks, I tried the latest patch and same failure... Hunk #1 FAILED at 343. All seems well on the site, despite this error.
$ more schema.inc.rej
Thanks for the patch!
Comment #116
tr commented@buddym: #67 has known problems, including problems that you yourself pointed out in #88.
Don't use #67!
Use #112 - that is the latest patch under consideration, and it fixes the known problems with #67. Let's try to move forward with this issue - does #112 work for you?
Comment #117
buddym commentedThe hunk failed error was due to my inexperience applying patches. I was moving the patch into the mysql directory and applying with a simple patch command like 'patch < patch.file'.
The solution was to move the patch.file to the root drupal directory and use this patch command 'patch -p1 < patch.file'.
The latest patch #112 applied without any failures. Thanks!
Comment #118
MixologicHere is the same patch as #112, with a demonstration of how to patch a custom drupalci.yml for d7 core tests. (includes core/drupalci.yml). This is only necessary until we are able to upgrade the drush version in the containers.
Comment #119
joseph.olstadJust FYI Drush 8.3.3 contains the fixes needed so that Drush will run with MySql 8 and php 7.4.
However the latest release is 8.3.5
https://github.com/drush-ops/drush/releases/tag/8.3.5
Comment #120
tr commented@Mixologic: Thanks! That's going to be really useful - now we can run tests and see if the patch works with all edge cases. That wasn't possible before. I don't think any of the patches over the past 4 months would have tested green (it was easy enough to manually find problems with them) so we've wasted a lot of time considering patches which weren't good enough.
Now with #112 we have a patch that fails only 1 out of 2053 cases - that gives me a lot of confidence that if we can fix that one tiny problem then this patch might be ready to commit.
What needs to be done now is to find and fix this 1 failure. ALL PATCHES posted here should now include the same drupalci.yml that is included with #118, and ALL PATCHES should run a test against MySql 8 - that's the whole point of this issue, to make sure this works with MySql 8!
Comment #121
gisleChanging tags. 7.72 is out.
Comment #122
tr commentedThis is a bug report at this point, right?
D7 is unusable on MySql 8 and upgrading to MySql 8 breaks sites.
MySql 8 has been out for 2 years and is currently the default in some major OS distributions.
If D7 errors on PHP 7.4, that's always been a bug, even before PHP 7.4 was officially supported. The DB version should get the same consideration.
Major or Critical? I'm leaning towards critical ...
Comment #123
mmjvb commented'`system` system' => '`system` `system`',Should probably be
'`{system}` system' => '`{system}` `system`',to fix that one problem left.
Comment #124
ronino commentedmmjvb wrote:
That indeed does the trick! I added that to #118.
But shouldn't the line above
then also become
? Works both ways...
Comment #125
tr commented@Ronino: This issue is large and complex - an interdiff would be greatly appreciated so we could see exactly what you changed without having to do a line-by-line comparison to your previous patch.
Comment #126
ronino commented@TR: That one time I didn't create an interdiff ;-). I changed exactly what mmjvb described, Here we go...
Comment #127
mmjvb commentedSuspect ' system.' to refer to the `system` in the line needing the change. So, it is the table alias used in a column reference. It shouldn't be prefixed.
Indeed, an interdiff would be welcome. Regardless whether you just add a file or change a line. It helps to see what was exactly changed. While you are at it, could you provide the missing one of #118 as well?
Comment #128
tr commented@Ronino: Thanks for the interdiff - that makes it a lot easier to follow what's going on here.
@mmjvb: The difference between #112 and #118 is just the drupalci.yml file. Here is the interdiff.
Comment #129
tr commentedMy only remaining question about #124 is why do we still need to have special-case code for Drush? If it's only a problem with Drush, this sounds like something that should be fixed in Drush.
Comment #130
mmjvb commentedTrusted that to be the case, but the interdiff helps, thanks
Looked into the pattern for 'db.table' and noticed it is not using \w, any reason for that? In addition I would have expected the same expression for db and table, but that doesn't work, any idea?
Finally, nobody addressed my concern of introducing 'db.table' by prefix. Any users using the prefix to refer to tables in different database that can confirm this works for them?
Comment #131
tr commentedIs this not covered by existing test cases? Do we need a new test case to ensure this works?
Comment #132
andrés chandía commentedUpgrading to D7.72, MySql 8.0.20-0ubuntu0.20.04.1, PHP 7.4.3, Apache/2.4.41 with patch #124 worked like a charm!! Thanks Ronino
Comment #133
trevorbradley commentedSimilarly, brand new Ubuntu 20.04LTS cloud server with D7.72, Mysql 8, PHP 7.4 - #124 also working for me.
Comment #134
ghost of drupal pastI fed
$query = '{foobar.baz}';into the regex and got the expected`foobar.`.`{baz}`.If I had any say left in these matters -- I am not sure I do -- I would strongly advocate for removing this regex and move the escaping into the prefixing code. Yes, it won't support database but that should be a followup -- are you sure you want to preg_replace_replace every fixed string query on every D7 page load out there just for the sake of the miniscule amount of queries hardwiring a database?? Perhaps it should be gated by a variable. Anyways, I would say it should be a followup so it doesn't hold up this any longer, it's been long enough.
Comment #135
mmjvb commentedTo fix either chop the match db. to get rid of the trailing .
or revert pattern to make a reference to db in addition to db. as it was before.
Haven't found a test case that uses per table prefix, but it appears that simpletest uses database prefix pointing to separate db. Don't understand why it works, but when tests are not failing. So, no need for additional test as every test is already testing it.
That also sounds like dealing with the db.table is needed for testing to work. So, not optional for later.
Comment #136
ronino commentedCharlie ChX Negyesi wrote:
Thanks! Here's a fix.
It seems like no test covers this...
Comment #137
ronino commentedAdded a test to DatabaseQueryTestCase to make sure that "{db.table}" is correctly prefixed.
Comment #138
MixologicThe php containers now include drush 8.3.5, so future patches can remove the /core/drupalci.yml file.
Comment #139
Scoville78 commentedThanks to everyone working on this topic! I'd really appreciate a quick 7.73 core update including this feature!
I don't understand why this wasn't tackled earlier, and still is not being considered as critical. My hosting provider announced a OS update on July 1st (from Debian 8 LTS to Debian 10) which also contains the update to MySQL 8. I still have some older projects running there for which an update to Drupal 8 is out of question. It's hard to explain to customers that there's a chance that there won't be a stable core update by then, and we'll probably have to apply some patch, which is still being developed, evaluated and subject to further change by the community. Things like this are exactly why I shifted more and more projects to WordPress in the last years.
Comment #140
pepeve commentedhello, we have a drupal 7 page for our sports club. the friend who made the website back then is unfortunately no longer with us. Unfortunately I don't understand how I can get the Drupal website up and running again. Is there a way to download the patched files? I also do not find a folder called Core in the Drupal 7 installation.
Comment #141
tfranz commented@pepeve: I uploaded the patched files for you (Drupal 7.72)
Unzip "core.zip" and put the content as folder /core/ in the root-folder (next to the folders "includes", "misc", "modules", ...).
Unzip "database-folder-patched-124.zip" and replace the folder /includes/database/
Hope it helps ...
Comment #142
mcdruid commentedRemoved drupalci.yml per #138, thanks @Mixologic
Removed the workaround for drush as it's no longer necessary now that https://github.com/drush-ops/drush/pull/4313 is in recent drush releases (h/t @MustangGB). I suppose this means anyone using earlier versions of drush will have problems, but I don't think we should keep the workaround in core for that reason.
Only other change I've made is that
$reservedKeyWordsneeds to use the old schoolarray()syntax (to cater for all the sites that run PHP 5.3 along with MySQL 8 ;)Some other thoughts:
1) As mentioned a few times in this issue, D9 no longer uses the list of reserved words but instead quotes everything. Just to see what would happen, I commented out the checking of the reserved words so that identifiers are always quoted, e.g.:
...and nothing bad happened AFAICS. I didn't test this extensively though. What would be wrong with this approach?
2) We're completely dropping
NO_AUTO_CREATE_USERin the recent patches, as proposed in #67 by @Ayesh, but that's not what happens in D8/9. The simplicity of omitting it is appealing though.3) Thanks for adding the test in #137 @Ronino. I believe there are more tests in the D8 issue that we should backport too.
4) The concerns expressed in #134 are concerning me.
5) Less of a concern, but I mentioned in #22 that the anonymous function as a callback here:
...would be the first usage of this pattern in D7 core. How easy would that be to refactor?
6) Nit: I'd like to see the keyword
AScapitalised in the schema queries, e.g.:...however, it looks like it's been committed lowercase to D8/9. I'll file a follow-up for that.
Comment #143
ayesh commentedIt should be trivially possible to refactor this into a standard class method, and refer it in the array_map() call.
For example:
If I recall correctly, we made some changes to D7 core that is not compatible with PHP 5.2, so I'd vote to keep the anonymous function as it makes the code much cleaner.
Comment #144
mcdruid commentedThanks @Ayesh. I don't think the anonymous function's a problem, but noteworthy if it's the first in D7 core; will see if @Fabianx is happy with it.
There's some syntax in the new test that PHP 5.3 doesn't like; hopefully this tweak will fix that.
The MySQL 8 test is also failing - that looks like the error from drush again doesn't it?
Comment #145
ayesh commentedUnrelated: DrupalCI probably should `drush --version` right after the existing `php -v` so we know exactly what's going on.
Comment #146
mcdruid commentedYes, that'd be helpful.
It looks like PHP 5.3 might not like the reference to
$thisinside the anonymous function? Perhaps we'll need to refactor that after all.However, it looks like recent versions of drush8 don't work with PHP 5.3 (because of
[]array syntax AFAICS) so I'm struggling to try and reproduce the error the testbot seems to be hitting doing the initially install, e.g.:So yes, I'm not sure what version of drush the PHP 5.3 testbot is using, but it seems unlikely it'll work with the MySQL 8 patch without the drush workaround included.
Comment #147
tr commentedIn #138 Mixologic said:
Comment #148
joseph.olstadis it possible to have a different version of drush run for php 5.3 ? IMHO, we can still support php 5.3, but not support php 5.3 with mysql 8
For my clients still using php 5.3, they are very unlikely to upgrade to mysql 8 without upgrading php.
I think it's reasonable to support mysql 8 for all versions of php except php 5.3. The key would be to change the drupal ci to use the older version of drush for ONLY php 5.3 and use drush 8.3.5 for all other versions of php.
automated testing for php 5.3 should be limited to mysql 5.5, 5.6, 5.7
automated testing for other php versions should include testing for mysql 8
Comment #149
mcdruid commentedAFAICS the current patch breaks PHP 5.3 regardless of the MySQL version, so we'll need to dig into that some more.
Here's a patch which restores the drush workaround in
\DatabaseConnection_mysql::query()conditionally.Let's see if doing so gets the MySQL 8 test passing again.
Comment #150
mcdruid commentedI'll have a look at what's going on with that one failing test.
Another issue in the meantime though; with all of the recent patches (e.g. back to #137), it looks like
drush site-installwill work if there's no existing database (which I think is what happens when the testbot runs). However, if there's already a db in place, I'm getting this error:Running
drush sql-dropseems to work fine, anddrush site-installworks okay after that.Comment #151
mcdruid commentedIt turns out that the "drush workaround" wasn't only fixing things for old versions of drush.
DatabaseTemporaryQueryTestCasedoes this:https://git.drupalcode.org/project/drupal/-/blob/7.72/modules/simpletest...
I believe that's the code that's hitting an error if we don't have the workaround (unconditionally) for the
systemtable in\DatabaseConnection_mysql::query().Specifically, it's this substitution which allows that test to run:
So we can either put that workaround back in (and not label it as being drush-specific), or figure out why this is necessary and see if it can be addressed another way.
It concerns me a little to have such a specific workaround; I'd imagine the problem could happen for other queries if there's an alias that is a reserved word which is not being quoted.
Comment #152
mcdruid commentedThe test is failing when this happens in the assertion in
\DatabaseTemporaryQueryTestCase::testTemporaryQueryThat does (comments added to show param values):
The
db_select()call results in the\SelectQuery::__construct()being called with$table = 'system'and$alias = NULL(the default).The last line of that constructor calls
\SelectQuery::addJoin()which then copies$tableto$aliasif the latter is empty.It would appear that alias is not escaped / quoted unless that happens via the "drush workaround" in
\DatabaseConnection_mysql::query.Without the workaround, this query is executed:
...and that unquoted alias causes the error in MySQL 8 as it's a reserved word.
We can reproduce the error using drush:
I'm not entirely sure what the best solution to this would be; I don't think leaving it as a hacky workaround is very satisfactory.
One reason why is that the workaround is obviously specific to the one table name "system". The problem would be the same though if a site had another table with a name that conflicts with reserved words. For example:
Exactly the same problem with the table name being copied as the alias with no quoting.
The code looks very similar, FWIW, in D8's
\Drupal\Core\Database\Query\Select::addJoin.Comment #153
fietserwinTo solve, I see 2 options:
- Also always quote each and any alias that gets generated.
- Generate aliases that will never result in a reserved word, e.g. by letting it start with an underscore (seems to be allowed).
Comment #154
mcdruid commentedThe "always quote each and any alias that gets generated" is part of what was done in #2986452: Database reserved keywords need to be quoted as per the ANSI standard IIUC, but that required such significant changes that it only went into D9 and wasn't backported to D8.
I'm not intimately familiar with every moving part of the DB API here, so I may well be missing something, but I'm not yet certain why the query causing the test fail is apparently not having its alias quoted already by
\DatabaseConnection_mysql::escapeAlias(); it is a reserved word so if that method was being called, it would be quoted.It may be because this particular way of constructing and executing a SelectQuery doesn't end up invoking
\SelectQuery::__toStringand thereforeescapeAliasis not called. I'm not certain about that yet though.I don't expect the approach in this patch to be the eventual solution, but I'd like to see whether with this all tests for MySQL 8 pass.
As suggested in #134 perhaps we could look at moving this into somewhere like
\DatabaseConnection::setPrefixinstead.Oh, and I did also look at changing the alias being added in
\SelectQuery::addJoin()when the table name is copied, but doing so broke a lot of queries as the field names being used no longer matched the alias. There's probably a better way of doing that if we wanted to take this approach though.Comment #155
mcdruid commentedAFAICS this might be a problem in 8.9.x too:
So perhaps there's a difference in test coverage? (Trying hard to avoid any topical references about that here... ;)
We'll have to decide how we're going to approach this in 7.x
There seem to be a few things we'll need to address AFAICS; I'll try to summarise them in due course.
I've been contemplating implementing a different DB driver for MySQL 8, which I think may have some advantages for D7; it would hopefully avoid any regressions (performance or functionality) for sites that remain on MySQL 5.x
Comment #156
mcdruid commentedFiled #3155563: select query should quote aliases which are reserved words in MySQL against 8.9.x
Comment #157
mcdruid commentedI think I've made some progress in the 8.9.x issue, and hopefully this patch will get tests passing without any hacky workarounds to fix reserved word tablenames with unquoted aliases.
Comment #158
mcdruid commentedSo I think it was definitely a bug in
includes/database/select.inc's\SelectQuery::__toStringwhere the alias was being passed throughescapeTablerather thanescapeAliasand therefore the mysql driver wasn't quoting the alias if it was a reserved word. I believe that's actually why drush was breaking; with the latest patch (#157) even older versions of drush seem to work okay. The D8 issue for that may need more work changing the type of test etc... but I think we should definitely keep the change in the overall MySQL 8 patch here. (See the interdiff in #157 for details).I mentioned there being a few more issues I think we need to address; here are some of them:
Much as I can see the appeal in the simplicity of doing this:
...I think we ought to make that change conditional on the MySQL version. That's what D8 does, and it reduces the risk of unintended consequences of making this change unconditionally.
I have some concerns in general about the performance cost of the changes we're making here - e.g. running all queries through a regex to quote table names (see #134).
If there was no need to do this for MySQL versions before 8, I think we should avoid doing it for older versions if we can.
I tried to do some performance testing locally but it takes a really long time for the whole test suite to run for me; perhaps we can try a few A/B tests with the testbot infra to see if it looks like there is a noticeable performance degradation introduced by the recent patches. That would be interesting to know.
For that purpose, I'm attaching a noop patch which we can use to compare how long the test suite takes to run without the changes in e.g. #157.
Similarly, it looks like some of the changes being introduced break PHP 5.3, e.g.:
...again if we don't need to do this for older versions of MySQL, I wonder if we can avoid the PHP 5.3 problem by making it conditional on the MySQL version. That may or may not work as some syntax issues will cause problems whether the code is actually executed or not.
There is #3155190: [meta] Should Drupal 7 drop support for older PHP versions? if anyone has thoughts on continued support for older PHP versions.
I did look at the option of having a completely separate driver for MySQL 8 (or perhaps MySQL > 5). It wasn't hard to get that working for core, but my initial experiments ran into problems with drush not being able to find the appropriate SQL class, which would be a deal breaker. It may be that it's possible to work around it, but I didn't carry on much beyond establishing that using a different driver name than
mysqlseemed to make drush (8) break.All that said, I think it would be good to make any of the changes we're making inside the mysql driver conditional on the MySQL version. If we can do that cheaply, I think there may be benefits in terms of avoiding (performance and functionality) regressions with older versions of both PHP and MySQL (which D8/9 are not as concerned about as D7).
I would also like to see more tests.
Comment #159
mcdruid commentedQuick attempt to (re-)implement the approach of making changes in the mysql driver conditional on whether the db is actually MySQL 8 (or later).
One potential problem is that the getter method
$this->connection->isMySQL8OrLater()only exists in the mysql driver's implementation of this class. I don't think practically that should be a problem, but my IDE at least is trying to warn me about this being potentially polymorphic.Let's see if it actually works before worrying too much about that.
Also, naming things is hard :) I'm not especially keen on
$this->mysql8OrLaterand am certainly open to alternative suggestions.Comment #161
mcdruid commentedHmm, more speed lest haste. Let's try that again.
Comment #162
mcdruid commented...and again without the
[]array copy-pasta.Comment #165
mcdruid commentedInteresting, the only test which is failing is the new one introduced with these patches.
Now that we've made this conditional on the MySQL version:
...that test isn't going to work with MySQL 5.x.
Is this testing the right thing? Being able to specify both
{db.table}within the curly braces doesn't actually seem like part of adding MySQL 8 support to me, unless I'm missing something?I appreciate that if we're quoting table names for MySQL 8 then that code has to work if the db has been specified too, but does this test failing with MySQL 5.x reflect something that's actually broken? If so, I'd think that would be a separate issue.
Comment #166
ayesh commentedAdding a new patch to queue and run tests in Drupal infra.
- Refactored the regular expression to not use backslashes where not necessary.
- Made the database name match (group 2) a lazy qualifier for performance.
- Renamed `isMySQL8OrLater` to `needsReservedKeywordEscape` to make the intend clear. This follows other class properties available.
- Added a new `escapeFields` method to connection class that accepts an array of fields, and runs `escapeField` on it. This avoids the closure usage.
- Refactored table escaping to a separate method. It's currently a protected method, it can probably make it easier to run tests in unit-test style because it's database connection-independent.
- Updated the test run `SELECT.... {table}` and `SELECT... {database.table}` style queries, so the tests are more verbose.
Comment #167
ayesh commentedI think this brings an interesting point, as to if we should even bother with {db.table} style keywords. This is not the correct practices, and Drupal should be let to add the prefix, or the caller should take care to properly add database AND prefix. Right now, we let Drupal prefix a table when a database name is explicitly mentioned, which might not be the same database Drupal knows the prefix for.
Comment #169
ayesh commentedTrying this again with {db.table} support removed.
Comment #170
ayesh commentedComment #172
ayesh commentedComment #173
ronino commentedmcdruid wrote in #165:
The reason this must be handled for MySQL 8 is that every table name is quoted and you cannot quote {db.table} to `db.table`, but rather need to make it `db`.`table`. This special handling is not necessary for MySQL 5 as there's no quotation.
Patches up to #157 worked fine with both MySQL 5 and 8.
Ayesh wrote in #169:
The migrate module relies on that to query a migration database that is different from the Drupal database. Specifying db.table is perfectly valid SQL syntax, worked with MySQL 5 and must work with MySQL 8, too.
Comment #174
mmjvb commentedAgree with the above mentioned in #173.
Do think changing db.table is easier after applying prefix instead of before. A matter of replacing . with `.` when there is a . within ``. The benefit would be when the per table prefix is used to refer to a table in different database it will be treated correctly.
Comment #175
mcdruid commented@Ayesh thanks, I like a lot of your improvements, particularly
needsReservedKeywordEscape:)@Ronino, @mmjvb yup okay, I can see that if we're quoting all table names for MySQL that we don't want to end up with
`db.table`(which IIUC might be why the most recent patches failed?)I wasn't sure though why the new test failed for #162 when there should have been no quoting happening.
AFAICS in 7.x without any of the patches from this issue (and a MySQL 5 db), these all work:
db_query("SELECT COUNT(1) FROM {drupal7x.users};")db_query("SELECT COUNT(1) FROM drupal7x.{users};")db_query("SELECT COUNT(1) FROM {drupal7x}.users;")...and produce the same SQL queryString, namely:
SELECT COUNT(1) FROM drupal7x.users;However, if we use a prefix for the db tables
db_query("SELECT COUNT(1) FROM drupal7x.{users};")works okay....but
drush ev 'var_dump(db_query("SELECT COUNT(1) FROM {drupal7x.users};"));'doesn't.That seems to be because Drupal tries to apply the prefix to the db name e.g.
"SELECT COUNT(1) FROM drupal7x.foousers;"is the working SQL but{drupal7x.users}gives us:Whereas with the quoting code in place
db_query("SELECT COUNT(1) FROM {drupal7x.users};")produces:SELECT COUNT(1) FROM `drupal7x`.`foousers`;So that'd be why the new test failed for
{db.table}without the quoting for MySQL 8; when prefixes are being used (which they always are on the testbot){db.table}doesn't work in D7 as it stands. I think the quoting is almost fixing that by accident (apologies if not and others were aware of exactly how this was working already!)I think that's more what I meant by "Is this testing the right thing?". If e.g. migrate uses db.table syntax now (which makes sense) I'm guessing it doesn't wrap both in curly braces.
Comment #176
ayesh commentedThanks for the explanations @mcdruid, @Ronino, @mmjvb.
It's probably better if we establish some test strings, so the query escape method can be tested easily.
1.
SELECT * FROM system->SELECT * FROM system(we cannot do this with a regex query easily)2.
SELECT * FROM {system}->SELECT * FROM `{system}`(parent class should add the prefix)3.
SELECT * FROM {db.system}->SELECT * FROM `db`.`{system}`(Is this right? This seems to be the correct syntax for parent class to add prefix)4.
SELECT * FROM db.system->SELECT * FROM db.system: Same as #1Current patches until #166 appears to do this string transformation correctly: https://3v4l.org/YtN8T
Comment #177
ayesh commentedTrying this again, with flexible {db.table} and {table}
Comment #178
ayesh commented178th time's the charm.
Rebased the patch from 157, where the last time we had MySQL 8 builds passing.
Comment #179
mcdruid commentedI think recent patches are missing this fix (from #157):
...which is why we'd be seeing MySQL 8 fails like:
Comment #180
mcdruid commented@Ronino said in #173
Yes
db.tablehas to work, but{db.table}doesn't work in D7 if prefixes are in use, hence the reason that the new test for that is failing when we don't do quoting for MySQL 5 (i.e. don't change the existing behaviour for MySQL 5).Does migrate - or anything else - actually use
{db.table}where the db name is inside the curly braces? If it does then it seems to me that would not work with table prefixes. If not, I think we could simplify the quoting, possibly doing away with the regex entirely and going back to adding quotes as part of the prefixing code (e.g. #59).AFAICS the only reason
{db.table}is working when the regex-based quoting code is added is that we're removing the curly braces and replacing them only around the table name.I've got a patch working which goes back to the approach in #59 whereby
\DatabaseConnection_mysql::setPrefixis used to inject backticks into the prefix replacement patterns.One slight annoyance is that this method is called before we've had a chance to set the
needsReservedKeywordEscapeproperty in the constructor, so I've moved the order of that around a little. An alternative to this would be to always put the backticks in without checking the MySQL version; that would mean we wouldn't have to change the order of the function calls in\DatabaseConnection::__constructwhich might be preferable.This means we don't need to implement the
querymethod in the mysql driver to run all queries through a regex.So yes in this case
{db.table}will not work, but it doesn't work now (at least it's not compatible with table prefixes) with MySQL 5.I've updated the new tests to reflect what should and should not be working in terms of the
db.tablesyntax.@Ronino you've said a few times that
{db.table}needs to work; can you provide some examples of where that pattern is used? If migrate does that, I think we could argue it's a bug in migrate as I don't think that'll work today if table prefixes are in place.Before I forget, there's a comment in
\DatabaseConnection_mysql::quoteIdentifierwhich I think comes from the D8 implementation, and I'm not sure it's actually relevant / accurate for D7? Let's check that. That method - incidentally - takes care of the db.table syntax if it's passed an identifier that includes a dot; perhaps that's sufficient?I've also got a patch for drush 8 without which some commands don't work under MySQL 8; the patch wraps backticks around each table name when drush uses
SHOW TABLES;in thelistTablesmethod of its mysql class. Otherwise you can't e.g drop all tables which meanssite-installonly works if the db is empty to begin with. I'll submit that patch to drush and link the PR here. With any luck this doesn't cause a problem on the testbot as we start with an empty db.Also, more tests would be good :)
Comment #181
mcdruid commentedhttps://github.com/drush-ops/drush/pull/4456 "Wrap mysql table names in backticks to avoid errors in MySQL 8"
Comment #182
mcdruid commentedI think this fixes the first failing test; we were ending up with table names like
'simpletest858168`foo`if the prefix is already wrapped in backticks, so this removes a backtick at the end of the prefix replacement pattern.I am not certain how we end up with a prefix already wrapped in backticks though, and that would be good to know.
Comment #183
mcdruid commentedI think this should hopefully fix the next test fail; some schema queries such as those called by
db_find_tables()don't want the backticks to be wrapped around table names, so this removes any that are there.Restoring the getter for
needsReservedKeywordEscapemakes this hack a little cleaner, but technically we're back to the polymorphic problem.Again, if we could avoid putting the backticks there in the first place that'd be better.
Comment #184
mcdruid commentedLooks like all the remaining failures are EntityFieldQuery related.
I'm not sure what's going on there yet, but FWIW here's an example of the query that was generated locally by the "Test sort entity entity_id property in descending order, with a field condition" test:
The error message is:
So yes,
test_entity_bundle_key.ftidis not in the SELECT list. I am not sure why this is happening yet though. The prefixing and quoting actually looks like it's working okay, but perhaps something's interfering with the logic when the EntityQuery is being assembled?Comment #185
mcdruid commentedOh dear, I am an idiot.
In #159 I took the
$sql_modecode from D8 and the string is quite different to D7. I'm actually surprised it didn't break more things, but that's the reason that EntityQuery tests have been failing since then; specifically it'sONLY_FULL_GROUP_BYwhich has been causing the "Expression #1 of ORDER BY clause is not in SELECT list, references column $XYZ which is not in SELECT list; this is incompatible with DISTINCT" errors. Sorry about that.So, with that fixed we should hopefully be back to looking at what I think are two options.
1) Use
\DatabaseConnection_mysql::setPrefixto put backticks into theprefixReplacepatterns so that when prefixing is applied to table names the curly braces are effectively replaced by backticks (plus any actual prefix).This has the advantage that we're not running every single query through a regex; in fact we're not really adding any extra processing to the SQL.
It has meant making a few seemingly hacky changes to make tests pass (see interdiffs for #182 and #183). I would perhaps like to understand why we're having to do those workarounds better, but AFAICS they may be down to tests doing slightly unusual things with the prefixing anyway.
2) Go with a regex in
\DatabaseConnection_mysql::querywhich searches for{table}in SQL and wraps that in backticks before the prefix step.That approach doesn't seem to require the same workarounds to get tests passing, but it's potentially quite expensive. See #134
We need to establish whether we actually need to accommodate
{db.table}in the regex; see discussion of that in e.g. #180This patch uses option 1. I'll also get one ready that does 2.
Comment #186
mcdruid commented...and here's a patch using option 2 outlined above.
This undoes the workaround to remove backticks from schema queries which seems to be necessary with the prefix approach.
It's also not necessary to change the order of calls in
\DatabaseConnection::__construct(which is required if we overridesetPrefixfor mysql).This patch uses the simple
preg_replacewhich only looks for{table}and not also{db.table}.Comment #187
ayesh commentedThanks a lot @mcdruid for working on this. I would have never figured out the SQL mode changes which lead to the MySQL 8 failures.
I would personally, and strongly prefer the approach in #1 (with setPrefix) because it looks cleaner on the long run. It looks a bit like a hack to get those table prefixes applied (i.e using str_replace), but I think it aligns with how rest of the things work in core. It's also a one-time cost, so we definitely score some points in the performance front.
I also like the fact that we don't even need to override the quote() method.
---
In this patch, I worked a little bit on #185 patch (approach #1).
- Moved
$this->needsReservedKeywordEscape = version_compare($mysql_server_version, '8', '>=');to constructor. This makes the property is available for all other purposes. I'm not 100% sure about this, but I think Drupal infra tests pass because it always contains the prefix.- Replaced a
subtrcall with an string-array-access call (micro performance improvement)- Removed a
$info = $this->connection->getConnectionOptions();because we no longer use this variable.As Chx mentioned about the regular expression, I would like to propose that we take a look at our excessive
in_array()calls. As of now, it is called for everyquoteIdentifier, which can about as many, if not multiple times more, of the number of queries we run during a page load. The array contains ~260 items, and because they are reserved keywords, it is likely that thein_array()yields no results.in_array()calls can be optimized easily with a flipped array lookup withisset().in_array()is an O(n) operation whileisset()is O(1).isset()is about 50-100 times faster: https://3v4l.org/2R4DgIf there is a room to improve it, I like to propose that we make that optimization at the same time on similar grounds that we find the regex call concerning. I will attach a patch for reference. In my sites, I already use it with
isset()tweaks (base patch #65).Comment #188
mcdruid commentedThanks @Ayesh, but I cross posted this with your patch. I need to stop for the day now so I'll repost this as-is.
Any of #187's changes would need to be re-applied to this patch; the change I'm making here is very simple and specific, but this is certainly not me rejecting anything from 187... I just don't have time to look at it and merge changes right now.
Original comment:
The reason we're having to do the extra checking for a prefix which has already been quoted in backticks (see interdiff in #182) is that
\DrupalUnitTestCase::setUpdoes this:...so that prefix is wrapped in curly braces so that it itself will be prefixed :) As we're using
\DatabaseConnection_mysql::setPrefixto put backticks into theprefixReplacepatterns we end up with weird table names like'simpletest858168`foo`.That's very specific to
DrupalUnitTestCasethough, so if we want to avoid the added complexity in the mysql driver'ssetPrefixmethod, we could look to adjust the way thatDrupalUnitTestCaseis setting up the prefixed db for testing and try to avoid the situation where the prefix is wrapped in backticks before we try to add our own.On the other hand, the
setPrefixmethod doesn't get called many times, so a little extra logic in there to ensure that MySQL unit tests keep working isn't the end of the world.This patch is - I think - a more correct way of avoiding problems with
DrupalUnitTestCase; the previous approach of just removing a backtick at the end of the prefix didn't work if the site running the tests also uses a prefix.Interdiff against 185 rather than 186 because this is an enhancement to approach 1 as outlined there; pretty much a different feature branch to 186.
Comment #189
ayesh commentedThanks @mcdruid.
I rebased the patch in #187 for #188.
Comment #190
ayesh commentedComment #191
ayesh commentedTurns out moving the
version_compare()call to construct failed the tests.Patch in #190 is based on #185.
- Replaced a
subtrcall with an string-array-access call (micro performance improvement)- Removed a
$info = $this->connection->getConnectionOptions();because we no longer use this variable.Comment #192
ayesh commentedGiving up :)
I vote for the one in #188 where all tests work for all PHP versions tested against.
Comment #193
mcdruid commentedThanks @Ayesh. Yeah, I think
setPrefixis called earlier than where we were setting$this->needsReservedKeywordEscapein the constructor, which is why I moved that assignment there.We could check the PDO attribute directly within
setPrefixand still move the assignment back into the constructor, which might be cleaner to read. I didn't want to do the assignment twice though.Great that we're passing tests again. Remaining todos AFAICS:
{db.table}to work.\DatabaseConnection_mysql::quoteIdentifierwhich I think comes from D8 and may not apply in D7.I agree that it's not ideal that we're iterating through the array of reserved words, but that's what D8's doing. I don't see a massive performance impact from the timings of test runs, but haven't looked in too much detail. If we wanted to change that implementation (e.g.
issetinstead ofin_array) that might be something to address in D8 first and backport.Anything else?
Would be good to get some testing of #188 on some real sites before we mark this as RTBC.
Comment #194
mcdruid commentedLittle bit of tidying up as mentioned in the last comment:
$this->needsReservedKeywordEscapeto the constructor, and check the MySQL version directly insetPrefix.table.columnbeing passed as an identifier to\DatabaseConnection_mysql::quoteIdentifier.setPrefixwhich removes any backticks already in the prefix.More tests is still a todo.
Interdiff with #188 per #192
Comment #195
mcdruid commentedComment #196
ayesh commentedI'm using the latest patch #194 on MySQL 8.0.20 on a test site (with PHP 7.4), and everything looks to be working great. I agree that we might need more tests added though.
Submitting a patch with a new test containing 3 more assertions.
Comment #198
kevin morse commentedFWIW I've been running the patch from #141 on a production site for the last week with no issues. Would be happy to try a newer patch but I might need some help applying it.
The server is Ubuntu 20.04 so am running PHP 7.4.3 with MySQL 8.0.20-0ubuntu0.20.04.1
Would be great to get this into core.
Comment #199
mcdruid commentedI've moved the new tests into their own test case, and added a few more which involve a test table that uses reserved keywords that MySQL 8 doesn't like if they're not escaped.
It's slightly tricky (as we saw with the test fail in #196) to generate SQL that MySQL 5 tolerates but that would fail in MySQL 8 without the escaping.
I'm also not sure what other dbs (postgres / SQLite) are going to make of these tests; we could look at skipping the tests if the db driver is not mysql, which I think happens elsewhere.
Comment #200
mcdruid commentedAh, I think I've come across a problem when working on improving the tests using the new test table.
Looks like the alias in the SELECT list is not being quoted / escaped there, and indeed it doesn't work:
I'll get that query into the test, but will also have a look at what we need to do to make it pass.
Comment #201
mcdruid commentedHmm, I think passing the table name in the select query through
escapeAliasfixes this, but I feel a bit uncomfortable about it. It's not really an alias, it is the actual table name as things stand.We can't implement
escapeTablein the mysql driver and pass that through\DatabaseConnection_mysql::quoteIdentifieras that breaks pretty much everything AFAICS.Let's see what happens with the overall test suite, and possibly for other databases.
Comment #202
mcdruid commentedBeen looking at the table/alias escaping in 8.9.x in #3155563: select query should quote aliases which are reserved words in MySQL.
I don't think I was correct saying:
__toStringfor select queries is pretty much exactly the same in D8, and I believe in D7 and D8 there are three places where table aliases are incorrectly escaped byescapeTablerather thanescapeAlias.The changes are already there in #201, but I think they can be considered a backport (although we'll see how we get on getting the change committed to D8).
https://github.com/drush-ops/drush/pull/4456 is also passing tests now, to get better support for escaping table names into Drush 8.
Unfortunately it looks like 7.x tests don't pass at present for PostgreSQL or SQLite; that's the case with or without this patch (same fails can be seen in the noop patch in #158). I spoke about this with @Mixologic and apparently the problem may be the test suite with the other dbs "not handling the parallelism we throw at it during testing properly" as opposed to something in core being actually broken. We'll try to look into that some more, but it's unfortunate not being able to verify that changes we're making here outside of the mysql driver are not causing problems with the other dbs.
So - please test #201. If anyone can test it on PostgreSQL and/or SQLite that'd also be great! I'll have a look at running tests for them locally but it's likely to be pretty slow I think.
Comment #203
mcdruid commentedGood news and bad news.
We're making progress in #3155563: select query should quote aliases which are reserved words in MySQL and hopefully with the drush PRs too (https://github.com/drush-ops/drush/pull/4456 and https://github.com/drush-ops/drush/pull/4458).
New patch here only adds to tests; adding the tests from the D8/9 issue above so that table aliases in select queries are well covered.
If anyone's testing #201 on a site, no need to update to this patch as the additional tests are the only difference.
The problem that the extra testing has revealed is that reserved keyword aliases that are not escaped / quoted can be a problem in MySQL 5.7 as well as MySQL 8 (this is illustrated by the test fail for MySQL 5.7 in #201).
So where does that leave us when the current patch has this logic?
We could drop that version number to 5.7, which I think would probably be okay to do.
Or we could go back to just doing the escaping unconditionally.
Personally, I am still inclined to reduce the risk of introducing changes on sites that don't need it - so anything running MySQL 5.6 or earlier.
Any thoughts?
Comment #205
mcdruid commentedI have pondered this a fair bit.
The test failures for all MySQL 5.x versions for #203 show that queries with certain table aliases can fail going back several versions.
We could remove those particular tests and treat that as a separate issue, but that's not going to be fixable without doing the escaping of identifiers for versions earlier than MySQL 8.
I was a little nervous about removing the conditional escaping that we introduced several patches back, so I did some rudimentary performance testing. I took a vanilla D7 install + views with a few thousand article nodes via devel generate, then used ab to test the performance of the
/frontpageviews display listing the teasers of 50 articles, requested a thousand times. That's not a sophisticated test, but would hopefully show if introducing the checking of identifiers against the reserved keywords list was introducing a horrible performance regression. My conclusion is that it looks like it does not; in fact it hardly made any noticeable difference at all. I tested MySQL 5.6, 5.7 and 8 (although the latter obviously doesn't work at all without the escaping).So here's a patch which does the escaping for all MySQL versions. Again :)
I've also tidied up comments a little in various places, and split the tests out (more like the D8/9 patches for the table alias problems) in the hope that this will make it clearer what's failing if anything does.
Comment #206
mcdruid commentedHaving said all that I've realised I had this a bit wrong in my head; we weren't making the quoting of identifiers conditional anyway, so it's not likely to have changed the performance to make the table name escaping unconditional (checking identifiers against the array of keywords is the potentially expensive thing). Oops.
My tests didn't suggest that adding recent patches made performance worse on MySQL 5.6 and 5.7, so that's reassuring anyway.
Comment #207
mcdruid commentedSame change as the D8 patch in #3155563-45: select query should quote aliases which are reserved words in MySQL which hopefully makes it clearer why
$field['table']is the table alias and should be escaped as such.Comment #208
mcdruid commentedI think I'm pretty happy with this patch now, but we may get some more feedback from the D8/9 issue mentioned above.
In the meantime, I'd welcome testing on real sites and/or reviews to get it to RTBC before I put it forward for final review from our illustrious Framework Manager @Fabianx.
Oh, and the drush PRs were merged so hopefully the next releases will work better with MySQL 8.
Comment #209
joseph.olstadwow, impressive, you even fixed php 5.3.x!
Comment #210
andrew_rs commented#207 did the trick for me on Ubuntu 20.04, PHP 7.4.3, and MySQL 8.0.20.
Comment #211
mcdruid commentedThanks @andrew_rs!
The D8/9 patches were committed so... "Needs review" applies :)
Comment #212
fabianx commentedHere is the review from the illustrious framework manager :D - Thx mcdruid for spearheading this work!
This change should be unnecessary - now that the check for the MySQL version has been removed.
This is a far-fetching change that affects also third party drivers like Oracle.
In fact, let's add a comment here so that it never gets moved before the __construct again -- if it is crucial to be in this order.
Thx - Nice cleanup compared to my first review :)
This whole prefix business vs non-prefix also tripped me up a lot for the Oracle driver for D8.
That said:
This needs some more comments to why prefixSearch could start with {, but not end with }.
Also is this the right function to do so?
Isn't prefixTables() better as that has more control about things and is nearer to the actual replacement of {table}?
I presume this and the following functions is verbatim how PostgresSQL driver does it right?
We quote always with '"'?
And backticks was a MySQL extension only used for table names, right?
Double-checking as I can't remember the most safe quoting rules right now.
In theory that should not matter, but in practice it might.
That said if Postgres driver also uses that, it would be pretty safe as there was still some fair amount of complex sites on Postgres.
We don't need to update the UpdateQuery class as it piggy backs on insert, right?
I had HUGE problems with UPDATE and reserved keywords in Oracle for D8 - hence double checking again.
That's the thing where I dislike the automatic escaping, but it's really hard to know when it should be quoted for which purpose.
I think this is good enough however.
Why are those changes needed?
Also should 'AS' not be uppercase if we introduce this newly?
Maybe we should use $table_alias to make it clear what it is?
How do we solve the conflict here?
Do we need something that adds the 'AS' or was that comment only relevant for fields and it works fine for MySQL without AS for table names + aliases?
Overall looks good, but needs some more scrutinizing :)
Thanks everyone for the great work on this difficult issue so far - this has come miles since I last reviewed it!
Comment #213
ronlee commentedWow! We're almost there!
Great job, everyone.
Love drupal 7 :)
Comment #214
tr commentedI have been using #207 on my test site for a week now with no apparent problems. I've done a lot of clicking around, using the UI to see if any errors are generated - this tests different things than the test cases and this is the method that quickly revealed shortcomings in the previous patches (see for example #56) even when those previous patches tested green on DrupalCI.
I've also done some non-scientific profiling using the test cases, and I haven't seen any significant differences in runtime with/without this patch (within the large variation of how long it takes to run the tests with other things going on - as I said, non-scientific because I haven't strictly controlled the environment). I have noticed however that the MySql 8 tests are consistently faster than the MySql 5 tests, so being able to move to MySql 8 is a win.
Comment #215
ronlee commentedI'm not seeing much issues, is anyone else?
Comment #216
mcdruid commentedThanks for the review in #212 @Fabianx.
1) Well spotted; reverted.
2) :)
3) Sorry, I don't understand this suggestion:
As for:
setPrefix() sets up the search/replace patterns that prefixTables() uses to swap out {table}. I'm not sure how we'd wrap the backticks around the tablenames here:
... in a better way than using prefixSearch and prefixReplace to swap
{and}both for`?4) The PostgresSQL driver doesn't implement escapeField or have a quoteIdentifier method (nor does SQLite).
5) in D8
\Drupal\Core\Database\Driver\mysql\Connection::quoteIdentifieruses double quotes.The quoting of table names was implemented in D9 (#2986452: Database reserved keywords need to be quoted as per the ANSI standard) and also uses double quotes for all 3 RDMS supported by core, e.g.:
https://git.drupalcode.org/project/drupal/-/blob/9.0.x/core/lib/Drupal/C...
So yes, we could try using double quotes around table names instead of backticks; comments in D8/9 mention double quotes being the ANSI standard whereas I think backticks are more of a MySQL-ism.
Happy to try using
"instead of`around table names for consistency.Note, however, that we've now added backticks around table names in MySQL in drush but that shouldn't conflict with what we're doing here.
I'll run tests on an updated patch with the first 5 points addressed, and will look at the remaining items after that.
Comment #217
mcdruid commentedOkay, looks like double quotes around table names works fine (as we'd expect).
212.6 was a great catch, thanks @Fabianx
Turns out we should override
UpdateQueryin order to escape its fields (which wasn't as simple as a straight copy fromInsertQueryas the field names we need to escape are array keys in this case).We don't need to do anything to
MergeQueryas it's the one that piggy backs on Insert and/or Update.I've added explicit tests for all of the query classes in includes/database/query.inc which helped figure out what we needed to do for Update.
Running tests again and will address the remaining points ASAP.
Comment #218
mcdruid commented212.7) Yes, agreed this feels a bit messy but I don't think we have much choice but to make small compromises retro-fitting table name quoting into D7.
212.8)
...is a good question. I wasn't sure and had to dig into this a little...
#2966523: MySQL 8 Support still has this listed under "Remaining tasks":
...and indeed the
information_schema.tablescolumn names are uppercase in all versions of MySQL I've looked at; perhaps that was the motivation, but it's not actually been implemented exactly like that.There is some discussion of the table alias being required for kernel tests to pass around #2966523-10: MySQL 8 Support.
I haven't verified whether not adding the table alias causes any problems in D7, but I'd vote to keep this change for consistency with D8/9 if nothing else.
That was bothering me :) #3159982: AS keyword should be capitalised in SQL queries has been committed to D8/9 so yes let's make that change in our patch too.
212.9) Yes maybe
$table_aliaswould make it clearer. However, that would diverge from the changes we've recently made to D8/9 in the same code - see #3155563: select query should quote aliases which are reserved words in MySQL.I'd vote to leave that for a follow-up (against D9 for backport to D7 and maybe D8).
212.10) I think that comment just means "don't include an AS keyword between table name and table alias because of compatibility problems e.g. with Oracle". AFAICS it's always worked without the "AS" for all RDMS and we don't need to change that. All we're changing is passing the alias through the appropriate escape method as it is an alias and not a table name here.
So the only change in this new patch is to uppercase "AS" in a few schema queries.
I think I've covered all your points from #212 @Fabianx, so back to you for re-review.
Comment #219
aparna.kondala commentedSo far I haven't seen any issues after applying patch to Drupal 7.72. I think we can apply to core.
Comment #220
bernig commentedOn a fresh Drupal 7.72, I cannot install any contrib module with the patch 2978575-218.
Reverting, and applying patch 2978575-14 works.
Tried with drush and via the BO.
Mysql version: 8.0.20
PHP : 7.2
OS : Ubuntu 19.10
The error I'm getting is:
Maybe something is wrong with my environment, but the fact is that the new patch fails in my case.
Comment #221
ronino commentedThis refers to the discussion on whether the "{db.table}" syntax should be supported.
I investigated a bit more thoroughly what migrate actually does so a migration fails when using the latest patch(es) with something like this:
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'db.db.migrate_map_migrateclass' doesn't exist(note the double "db").Now what does migrate do? MigrateSourceSQL::performRewind() adds a left join to a query on a table that includes the database if no prefixes are used (see MigrateSQLMap::getQualifiedMapTable()). So a string like "`db`.table" will be generated, then reduced to "db.table" by DatabaseConnection::escapeTable() (which is well aware that there can be a DOT in a table name, most likely for a given database...).
SelectQuery::__toString() will then convert this to '{db.table}' which will be quoted to '"db.table"' by DatabaseConnection::prefixTables() as DatabaseConnection_mysql::setPrefix() now ads double quotes to $this->prefixReplace in the latest patches. This will make MySQL think that "db.table" is indeed just the table name as it's quoted.
I'm not sure if it's migrate's fault here, as it used to work pre MySQL 8 when there was no quoting. So passing "db.table" to SelectQuery::leftJoin() was perfectly fine. I don't know about other modules doing this.
Possible solutions:
1. Make SelectQuery::__toString() convert table names to "db.{table}" instead of "{db.table}" (and probably in other classes like DeleteQuery).
2. Make the migrate module's MigrateSQLMap::getQualifiedMapTable() not prepend a database. I don't know what side effects this would have.
Comment #222
mcdruid commentedThanks for digging into that @Ronino - my impression is that it seems like quite an edge case in migrate and I don't think we should introduce expensive code into core to work around it. In other words, I'd vote for your option 2.
I've filed #3171091: \MigrateSQLMap::getQualifiedMapTable may break with MySQL 8 support in D7 core in migrate's queue about this.
...is an interesting point. Anyone else know of other modules that do this? I'm inclined to think it's pretty unusual; it sounds like in migrate it's an optimisation that only works in the absence of table prefixing.
Comment #223
mcdruid commentedI've posted a patch for migrate in #3171091-5: \MigrateSQLMap::getQualifiedMapTable may break with MySQL 8 support in D7 core - I believe the problem is actually migrate wrapping the dbname in backticks (which shouldn't be necessary any more once core itself quotes MySQL table names).
So - unless I'm missing something - that issue doesn't represent a blocker for this one. Thanks again for digging into it @Ronino
Comment #224
mcdruid commentedMy previous comment about backticks in the migrate code was incorrect, but there is a working (I think) patch for migrate now.
So I don't think we're blocked here.
Comment #225
izmeez commentedSimilar problem as described in comment #220.
Using Centos7, php 7.4.10 and 5.5.5-10.2.33-MariaDB.
Updated system to Drupal core 7.73 with patches including the patch 2978575-218.
Clear all caches with drush or drupal UI results in errors as described in comment #220.
Comment #226
izmeez commentedWorking backwards, same problem with patch 2978575-217.patch
Comment #227
izmeez commentedWorking backwards, same problem with 2978575-216.patch
However, 2978575-207.patch works.
Comment #228
izmeez commentedTaking another look with patch 2978575-218 and the error in drupal UI flush caches on Centos7, php 7.4.10 and 5.5.5-10.2.33-MariaDB. Attached is the system diff between applying patch in 207 that works and the patch in 218 that results in the following error:
Hope this is more helpful.
Comment #229
mcdruid commented@izmeez @bernig what drush version are you using when you hit these errors?
I cannot reproduce with drush 8.4.0 (and PHP 7.2 / MySQL 8.0.21).
The paths in #226 suggest you may have been using drush 8.3.5 ? If so, there were some MySQL 8 fixes included in the release after that.
I'm not certain it'll fix the problem you're seeing, but please try updating drush (looks like 8.4.2 was released a few days ago) and try again.
If you're still getting the same errors after that... I am curious - could you please try swapping the double quotes back to backticks in
\DatabaseConnection_mysql::setPrefix? There would be a couple of other places the change would need to be made to revert that properly, but I am curious whether it'd prevent the particular error you're seeing.It's hard to debug further without being able to reproduce the issue; any more details that might help?
Comment #230
izmeez commented@mcdruid Thanks for reply.
I'm pretty sure it's not a drush thing because it happens within the drupal UI "flush caches" but will update to drush 8.4.2 and test again.
I am wondering if it is related to php 7.4.10 which is more strict on errors. But, I will try your suggestions and report back.
Comment #231
izmeez commentedGood news.
Update to drush 8.4.2 makes no difference but is still worthwhile.
Using patch 2978575-218 and reverting following section of patch to backticks in
allows "drush cc all" and also drupal UI "Flush all caches" to work.
Will await new patch with additional changes you alluded to for testing.
Thanks.
Comment #232
mcdruid commentedThanks @izmeez
I need to dig into this some more - not sure if it's related to the way that PDO works or something like that, but my MySQL 8 db seems to prefer backticks:
Comment #233
izmeez commented@mcdruid
I'm confused. You say
That's what I just did on your suggestion, reverted double quotes to backticks and it worked for MariaDB.
Comment #234
mfbSounds like maybe sql_mode is incorrect?
SELECT COUNT(1) FROM "system";should work fine as long as you first runSET sql_mode = 'ANSI_QUOTES';Comment #235
mcdruid commentedIt definitely seems that backticks are the default quoting syntax for MySQL:
https://dev.mysql.com/doc/refman/8.0/en/identifiers.html
However, D8/9 have gone with double quotes which are the ANSI standard (see #216 5).
MySQL optionally supports the ANSI standard (same source):
...and Drupal tries to turn that on:
https://git.drupalcode.org/project/drupal/-/blob/7.73/includes/database/...
So I'm not sure why double quotes instead of backticks would apparently sometimes cause problems; it looks like Drupal should be setting the SQL mode such that they'd work (not always the case when using the MySQL cli directly e.g. #232).
There are advantages to staying consistent with D8/9 which is why we made the change around #216 after @Fabianx's review in #212 5.
However, if that's causing problems in some cases we should consider going back to using backticks.
I'm not comfortable diverging from what's been done in D8/9 without good reason though, which is what I meant by "dig into this some more".
I'll work on a patch which reverts to backticks instead of double quotes, unless someone beats me to it :)
Comment #236
mcdruid commented@mfb sorry I cross-posted with you - yes, I think you're right.
Not sure why double quotes would apparently not work sometimes when Drupal's set up the connection with
ANSI_QUOTESbut on the other hand, why wouldn't we just go with the MySQL default to avoid such problems?Comment #237
mfbThe only thing I could think of is that folks manually changed init_commands sql_mode in settings.php to disable ANSI_QUOTES? There are advantages of using ANSI syntax - portability of the SQL - and I suppose of the MySQL syntax too (portability of the SQL to MySQL w/ default sql_mode :)
Comment #238
mcdruid commented@izmeez @bernig can you please run something like this in the environment where you had the errors?
It'd be good to confirm whether you have
ANSI_QUOTESand/or anything else other than what D7 sets as the connection options.Comment #239
gisleThis may be an extremely stupid question, but please bear with me.
To me, it looks like the really difficult issue to resolve is captured in this comment in the code:
Finding the right formula for escaping "system" (and any other reserved keyword) seems to be pretty hard, given the requirement that it should work not only on the latest version of MySQL, but also work on the ancient versions of MySQL that is still part of CentOS, and work on PostgreSQL, which has other escaping mechanisms.
Here is my question: Why don't we change the schema so that table names do not collide with reserved keywords?
For instance, can we change the name the "system" table to "drupalsystemtable", and then escape (pun intended) from the need to escape the table name?
Comment #240
izmeez commented@mcdruid In followup to #238
drush ev 'print_r(db_query("SHOW VARIABLES LIKE '\''%sql_mode%'\''")->fetchAssoc());'returns:
Looks the same as the example except the extra NO_AUTO_CREATE_USER.
Comment #241
mcdruid commented@izmeez thanks. I am curious why you (and @bernig) seem to have hit errors with the
ANSI_QUOTESwhen it looks like your config should be switching that option on. I suppose if you haveNO_AUTO_CREATE_USERyou either don't have the patch applied or are not using MySQL 8 (or equivalent) though? (edit: I see in #225 you mentioned 5.5.5-10.2.33-MariaDB)I am moving this back to NR as I'm in two minds about reverting to using backticks; I can see an argument for doing so as they are the default for MySQL but the double quotes should work, are the ANSI standard, and have been implemented in D9.
If we don't understand the problem you're hitting, I'm not sure we should be changing the patch to try and work around a mystery issue.
@gisle I don't think it's stupid to suggest that we avoid using reserved words for table names, but on the other hand I'm not convinced that changing the name of the system table is a viable alternative to quoting table names.
As has been mentioned before retro-fitting table name quoting to D7 is not without its problems, but I think it's the right solution.
Comment #242
izmeez commented@mcdruid Fair enough, we can stay at patch #207 while keeping an eye on this issue. At the time of running the "drush ev" command the patch from #207 was in effect. The "NO_AUTO_CREATE_USER" may be a MariaDB default setting. Correct this is a system that is not running MySQL8 and the Maria10 version is the latest update available but may need further fixes for ANSI_QUOTES. Either way, I'd assume the patch should be backward compatible, otherwise this will require some sort of conditional.
Comment #243
izmeez commentedMore on MariaDB, https://mariadb.com/kb/en/sql-mode/
Comment #244
izmeez commentedMaybe this is beyond the scope of this particular thread, however, I have executed grep and found that the contrib module backup_migrate is also using sql_mode in the following:
I am wondering if anyone can spot where the conflict with these settings and the patch may be arising. Thanks.
Comment #245
mcdruid commentedANSI was changed to a specific list of sql_mode settings in #2545480: Don't use the ANSI SQL mode since it has different meanings for different MySQL versions (and breaks MySQL 5.7 support in Drupal 7) but I believe both should effectively enable ANSI_QUOTES.
I tried the patch from #218 with mariadb 5.5 on centos7 and had no problems, so I don't understand the issue that you're encountering @izmeez
It's obviously a bit of a concern that we might introduce a change into D7 which would cause catastrophic problems for sites that don't really need the identifier quoting; e.g. anything on earlier versions of MySQL like 5.5, 5.6 and perhaps even 5.7.
With this in mind, I've updated the patch to use a variable to determine the character that should be used to quote identifiers. This would allow sites to set this variable in settings.php in order to use backticks instead of double quotes if that works better for them.
It's also possible to disable identifier quoting altogether by setting the
mysql_identifier_quote_charactervariable to an empty string. We could tweak a couple of places we use the variable to check specifically for this condition e.g.\DatabaseSchema_mysql::buildTableNameConditionwhere we could avoid a pointless call tostr_replace()if the variable is empty. It seems to work without doing that though.Obviously I want to ensure nothing breaks in the tests with this change in place, but I'm hopeful that it might provide some flexibility for any sites that encounter problems for whatever reason with the introduction of identifier quoting.
@izmeez could you give this a try with the variable set to a backtick (and/or an empty string) in settings.php please?
Comment #246
izmeez commented@mcdruid Tested patch in #245. The default fails as before with patch #218. Using variable in settings.php set to backtick and empty string both work. Looking at that section of the patch,
I would suggest change to include backtick (the default) in example for improved readability
+# $conf['mysql_identifier_quote_character'] = '`';I also find it confusing that using an empty string will switch this off and yet things still work, is this because drupal is not conflicting with reserved words in earlier versions of MySQL as it is with reserved words in MySQL8?
Also, thank you for testing a similar environment with Centos7 and MariaDB 5.5.x(10.2.x) and reporting no problems. This leaves me wondering if another core patch that we are using is resulting in the conflict. This will require more sleuthing.
Comment #247
tr commented@mcdruid: Since this patch is in very good shape at this point, can you write a draft change notice that outlines the changes made here and includes the above instructions for changing the backticks/disabling the quoting?
Comment #248
mcdruid commented@izmeez thanks for testing; yes the example value in default.settings.php was meant to be a backtick; good catch.
I've also fixed wording in one comment in the patch.
Yes, the identifier quoting is required for MySQL 8, mainly because of the
systemtable, but sites may have problems with other parts of their schema provided by contrib or custom modules etc..Earlier versions of MySQL typically don't require quoting, although it is possible to name things in the schema such that they cause issues. At least one of the tests we're adding here will fail on MySQL 5.7 without identifier quoting.
@TR yes, we'll definitely want a Change Record / Notice for this. I'll draft one soon (unless anybody beats me to it).
Comment #249
izmeez commented@mcdruid Thanks for update to patch in #248.
The patch looks good, applies without difficulty and works on 5.5.5-10.2.33-MariaDB, equivalent to older version of MySQL, without breaking things. In our case the settings.php variable to set identifier quoting to backtick is essential to avoid pdo exception error with "drush cc all" or drupal UI "flush all caches". Testing on our setup with drupal 7.73 and only this patch along with the contrib modules and theme in use still require the identifier quoting to be a backtick. The other core patches we use are not a source of conflict with this patch which is a relief.
I would like to change the status to RTBC but would like to see others weigh in especially on testing with MySQL 8 and possibly if the change introduced also helps with PostgreSQL.
Thanks for all your efforts.
Comment #250
gisleI am testing this on Ubuntu 20.04 LTS with the following configuration.
The following line (from
default.settings.php) was added tosettings.php:Testing with
drush cc allworks as expected.So I will say that this one is good to go for MySQL 8.
Not setting
$conf[mysql_identifier_quote_character](i.e. using the ANSI quote character (")) won't fly. Without adding that line tosettings.php,drush cc alltriggers the PDOException.There probably should be release notes with instructions for all DBMSes that don't work with the ANSI standard character. For MySQL, I suggest the following text:
Comment #251
mcdruid commentedThanks @izmeez and @gisle for testing and providing detailed feedback.
However, it's a bit of a concern that with sample size of two, 100% of D7 sites have had to change to backticks to avoid an apparent problem with ANSI_QUOTES.
I'd like to understand what's going on but have been unable to reproduce the problem with just core. Could you please share a list of the modules you have enabled (e.g.
drush pml --status=Enabled)? Also, do you have any customisations to the db settings such as custominit_commandsetc...? You can PM me in Drupal slack or use my d.o contact form if you'd prefer not to share details here.In the absence of a simple explanation / fix for the issues you're hitting, I'm inclined to say we should default to using backticks in D7, but retain the
mysql_identifier_quote_charactervariable so that sites can use ANSI_QUOTES if they prefer to for some reason, and more importantly opt-out of identifier quoting if they don't need it and it causes problems.Comment #252
gisleYou're right, it can't be reproduced by the core alone.
The site I am testing this on is a clean install of Drupal 7.74-dev + patch with absolutely no customizations.
I use a single contributed module: Backup and Migrate. To reproduce, just install that. (Also, see comment #244 above).
As this module has nearly a quarter million installs and MySQL probably by far the most popular DBMS among Drupalers, I guess if you make
ANSI_QUOTESthe default, we are going to see a Drupal 7 version of a forum topic that is already very popular with the Drupal 8 community 😊.Here is the output of the drush command you wanted to see:
Comment #253
mcdruid commentedThanks @gisle, it looks like we have an answer. Noting that @izmeez also mentioned backup_migrate in #244.
It looks like backup_migrate sets an empty
sql_modeduring cache clear:https://git.drupalcode.org/project/backup_migrate/-/blob/7.x-3.9/include...
...and that's obviously a problem when all db queries are relying on ANSI_QUOTES being in place.
A git bisect suggests that this was initially introduced in backup_migrate - sort of - in #1128620: Module needs to back table properties up as well albeit that introduced the
sql_modereset intoincludes/destinations.db.mysql.inc.The problem we're hitting is in the corresponding
includes/sources.db.mysql.incand it looks like it was introduced in this commit: https://git.drupalcode.org/project/backup_migrate/-/commit/9c0e6d3b7b3ae......but (to me at least) the history behind that is not entirely clear.
This looks like a bug in backup_migrate. It's setting
sql_modeto an empty string on cache clear but it looks like the intention was to enable the module to capture certain metadata about the database before making a backup. I'm surprised this hasn't caused more problems.Where does that leave us?
I'll file an issue for backup_migrate, similar to the one for migrate.
However, pragmatically we may want to go with the option of using backticks by default in D7 rather than relying on ANSI_QUOTES when we know at least one very popular module will cause that to break sites.
Comment #254
mcdruid commentedFiled #3172388: backup_migrate resets sql_mode causing problems with D7's MySQL 8 support for backup_migrate.
I think it would probably be a bad idea to block this core issue on resolution of that though, when the alternative is fairly simple (backticks by default, keep the variable).
Comment #255
mcdruid commentedChanged the default to backticks.
I've introduced a constant to make it easier to change the default.
Here's a thought; would the lowest-risk approach be to default to an empty string (i.e. no identifier quoting) and require sites to set their preferred
mysql_identifier_quote_characterin settings.php if they need it?I actually think backticks are pretty safe as they work by default with all versions of MySQL. The exception will be where contrib/custom modules do unusual things in their SQL like we saw with migrate around #221.
Comment #256
trevorbradley commentedA few days late, but a quick note that my Drupal 7+mysql 8 sites were working great until the recent Drupal 7.73 update. I installed the patch from #218, cleared the cache and they're all working great again!
Comment #257
mcdruid commentedThanks for the feedback @TrevorBradley, however the 7.73 release didn't touch anything to do with the database:
https://git.drupalcode.org/project/drupal/-/compare/7.72...7.73
Are you able to test the most recent patch and provide feedback?
Comment #258
izmeez commentedI'm not sure about the change suggested in #255.
First, it complicates the language with a variable mysql_identifier_quote_character and also a constant MYSQL_IDENTIFIER_QUOTE_CHARACTER_DEFAULT the naming of which grates with all the discussion on MySQL default identifier and ANSI_QUOTES identifier. It is just another layer of wording that doesn't really add much.
Secondly, even with just a variable, a module (such as backup_migrate) could temporarily change the variable if needed to a backtick or an empty variable or whatever is needed.
I still think the introduction of the variable in settings.php is good and if the issue in backup_migrate can be resolved the default could be double quotes in keeping with ANSI_QUOTES.
Comment #259
izmeez commented@TrevorBradley Are your sites using the backup_migrate module?
Comment #260
mustanggb commentedThis was going to be my suggestion, MySQL is the de facto D7 database, so I'd have thought it would have the most frictionless "out of the box" experience. Then other databases can tweak
$conforsql_modeif needed.Comment #261
mcdruid commented@izmeez re. #258 the constant is just a convenience for core; it makes it easier if we want to change the default again, but sites shouldn't change it. (Think of the kittens ;) It makes no difference to the functionality, it's just arguably neater than maintaining the default in every relevant call to
variable_get()(of which there are only 3 at present, so we could easily live without the constant).@MustangGB yup, backticks are the MySQL default so should be safe. One of the main reasons I think we might be better defaulting to them is that I think it's highly likely there will be other contrib / custom modules that do things that have always "just worked" with MySQL but that break if we use double quotes and rely on the corresponding
ANSI_QUOTESsetting insql_mode.We've found two issues with popular contrib modules more-or-less by luck based on helpful contributors here testing patches with more than just core. I don't think we can be very confident that we've found them all though.
So yes, backticks by default would hopefully be more "frictionless" as you say, rather than relying on (potentially a lot of) sites to un-break themselves, even if we have made that relatively easy with the variable. (I think in many cases problems really ought to be fixed in the non-core code, but that can be pretty far from "frictionless".)
In general, the identifier quoting and
sql_modeonly relate to MySQL. We're not introducing any quoting for other dbs.It's concerning that the status of automated tests on SQLite and PostgreSQL is somewhat unknown (see #202). I will try and look into that some more, but it looks like they're not working against 7.x HEAD on the drupalci system at present without any of the patches here.
This issue does have some changes to the database code outside of the MySQL driver, but identifier quoting and
sql_modechanges are MySQL-specific.With any luck sites that use non-MySQL databases shouldn't have to change anything.
Comment #262
izmeez commented@mcdruid Constant makes sense, thanks for detailed response. Default to backtick will be less disruptive to contrib modules.
Thanks for test patch in #3172388: backup_migrate resets sql_mode causing problems with D7's MySQL 8 support. It isn't needed with the current patch in #255 but demonstrates the fix needed in that contrib module if ansi double quotes are to be used.
Comment #263
mcdruid commentedI had a look into testing on SQLite and came across #1713332: The SQLite database driver fails to drop simpletest tables which still needs to be fixed in D7.
Before finding that, I'd applied a crude hack (based on
$connection_info['default']['driver'] == 'sqlite') to avoid this failing every time on SQLite:https://git.drupalcode.org/project/drupal/-/blob/7.73/modules/simpletest...
...and with that in place was able to get most tests passing locally.
Results with patch #255 in place, results were:
Re-running just the 24 tests which had at least one fail, but this time without this issue's patch, results were:
Then re-applying patch #255 and running those 24 tests again:
So all a bit crude and messy, but it looks like the fails were consistent whether we had the patch from this issue in place or not.
I'd prefer cleaner results obviously (and I think we should aim to get them passing properly), but this is at least somewhat reassuring that the recent patches are not significantly breaking other databases.
In general, clicking around the site with the SQLite db seemed to work okay.
Comment #264
trevorbradley commented@izmeez, no, just plain old regular D7 sites that stopped working after the 7.73 update until I patched them.
Comment #265
mustanggb commentedJust wondering in what versions of MySQL these contrib modules break.
I mean ideally they would only have problems with MySQL 8, and still work fine with MySQL 5 even with this patch here.
Do we know if this is the case?
Comment #266
mcdruid commented@MustangGB the problems we've seen with migrate and backup_migrate didn't only affect MySQL 8, they affected all MySQL versions.
This is because we're doing quoting for all MySQL versions.
Around #158 we started (not for the first time) trying to keep the MySQL 8 changes separate and leave earlier versions unchanged to reduce this sort of risk.
However, around #203 we worked out that sometimes MySQL 5.7 may need quoting (e.g. reserved words used as aliases) - we briefly pondered drawing a line in the sand at MySQL 5.7 in terms of quoting but by #205 we were back to doing the same quoting for all MySQL versions.
Lots of special-casing everywhere for different MySQL versions is messy and hard to maintain. I did look at having a completely separate driver for MySQL 8 (would that have had to become MySQL 5.7?) and later, and it wasn't too hard to get something working in core, but immediately drush was broken because it didn't have a corresponding "new MySQL" driver.
So that's why we've ended up with quoting for all MySQL versions, and why there's a risk that there may be more gremlins hiding in contrib.
Backticks by default likely reduces that risk, and giving sites an easy way to switch off quoting if it causes problems and they don't need it hopefully mitigates it further still.
I suppose you could look at doing something like overriding
mysql_identifier_quote_characterin$confto turn off quoting if the site's on a version of MySQL < 5.7. IIRC it might not be as simple as it sounds because of the order that things happen in the db driver constructors (look at earlier patches that made more of the logic conditional). I'm not certain how much I like that idea in general, but will have a think about it.Comment #267
mcdruid commentedLittle update here; #3172878: Fix SQLite tests in Drupal 7 looks like there are a few things we need to do in D7 and at least one patch to drupalci to get SQLite tests passing.
However, it seems quite achievable - I'm seeing fewer than 5 test fails with SQLite with a few patches in place.
I think it's worthwhile pursuing these in parallel as we gain some reassurance around changing shared db code.
On the issue of defaulting to no identifier quoting for older versions of MySQL (probably < 5.7) what does anyone else think? 5.6 is soon to be EoL but obviously there will be distros that support older versions for several more years.
Comment #268
gisleI am testing this on Ubuntu 20.04 LTS with the following configuration:
I did not modify
settings.phpTesting with
drush cc allnow works as expected.So I will say that this one is good to go for MySQL 8.
However, I am not moving to RTBC yet, as it should be tested on other configurations (MySQL 5, PostgreSQL, etc.) as well.
Comment #269
izmeez commentedThe patch in #255 passes all the tests and has some community testing successes so is it ready for RTBC, yet? I wonder who else wants to pipe in with their success comment?
Comment #270
mcdruid commentedNow that SQLite tests are passing for D7 core, I tested #255 with SQLite.
There was one error, but it looks like it's in the new tests we're introducing:
I'll take a closer look at that, unless anyone beats me to it.
Comment #271
mcdruid commentedI think we're going to have to skip that particular test for SQLite; I've explained why in a comment (see interdiff).
Comment #272
joseph.olstadInterdiff #271 looks great, totally makes sense and great detective work on that. The only thing I can say about it is maybe punctuation for the comments. I think there's a missing period or two and capitalization to start a new line.
I'm not going to complain though just though just mentioning. I myself have a hard time understanding the coding standard when it comes to comment blocks and comments. I am with Linus Torvalds on the 80 column limit, I think it's time to increase that.
Comment #273
izmeez commentedI wouldn't say anything about the punctuation myself as when comments are about code sometimes periods joining words causes issues with formal sentence structure, even a comma ends up looking odd. I guess the only other option is to drop the capitalization of "So ..." and have a long run on sentence.
Not really an issue, I think the way it is is not uncommon in comments about code.
Comment #274
mcdruid commentedHopefully the comment's better. Thanks for the feedback.
Comment #275
mcdruid commentedOh, quite a few problems with the MySQL 8 tests now.
I think what's happened is that in #1713332: The SQLite database driver fails to drop simpletest tables we backported the newer implementation of
\Drupal\Core\Database\Schema::findTables(which is now\DatabaseSchema::findTablesD8in D7) butfindTableswas later tweaked in the original #2966523: MySQL 8 SupportSo we need to backport that change into the new
findTablesD8. As far as I can see that's actually a very simple change (which we'd already made to what's now the BCfindTablesfunction).(I have also moved a comma in the comment for the SQLite test skipping).
Comment #276
izmeez commented@mcdruid, I am sure you are clear on this but I it is a little confusing for me. It looks like the findTablesD8 was a transient element that was then backported to D7 and is now gone with only findTables remaining, is that correct? It surprises me there was ever an introduction of naming with D8 in the name, what about D9, D10, etc. Maybe I'm missing something.
Comment #277
mcdruid commentedMarking this as RTBC as I believe it's ready for re-review by @Fabianx
To make that a bit easier, here's a brief summary since the last Framework Manager review.
mysql_identifier_quote_characterwhich allows sites to change between " and ` and also an empty string, which disables identifier quoting altogether.Given the problems we've come across in two popular contrib modules, now thinking we should default to backticks for identifier quoting; Drupal 9 uses double quotes per the ANSI standard, but that relies on the sql_mode being set appropriately. Backticks are the MySQL default, and it seems a safer bet that contrib and custom code will use backticks if they've quoted anything. (see #235)
#263 to #271 getting SQLite tests to pass; there's one new test in this issue which we have to skip with SQLite (see #271).
One remaining question is "what do we use for the default quoting character?". I am now inclined to say that we use a backtick by default in order to minimise the risk.
You could argue we could really keep changes to a minimum by defaulting to no quoting (empty string) for MySQL 5.6 and below, and default to backticks for MySQL 5.7 and above.
The issues that we've filed for migrate and backup_migrate are up to those projects now, but using backticks means backup_migrate won't break sites when they update core. If we use double quotes, it will cause problems unless the backup_migrate issue is fixed and sites update the module before they update core.
My vote is default to backticks, and obviously we'll have a Change Record and a Release Note to make it clear how sites can disable identifier quoting (set the variable to an empty string) if it causes them problems and they don't need it.
Comment #278
joseph.olstad@mcdruid
you might want to have a look at #2003746: field_read_fields() omits indexes from hook_field_schema
maybe related to this
Comment #279
alexpottI think it is important to note in the CR / advice given is that only identifiers are reserved word in MySQL8 will be quoted. IN D9 we just quote every identifier as this makes it much simpler in terms of db differences.
Comment #280
izmeez commentedPatch in #274 applies without difficulty to drupal 7.73 but patch in #275 fails which was a bit of a surprise.
Comment #281
joseph.olstadizmeez, your local was not clean when you ran this dry-run
I just tested 275 :
275 is clean, no issues, please make sure to delete any unstaged files and reset any others before reapplying
Comment #282
Mixologic@izmeez patches are not intended to be used against tags, they are intended to work against 7.x branch. You should not expect them to apply to 7.73.
Comment #283
izmeez commentedThanks guys.
Yes, I understand patches are intended to work against 7.x HEAD. But some workflows may only use tagged release for production and may encounter this.
The patch --dry-run tests were done on a fresh clone of drupal with checkout of 7.73 and 7.x and, while it is not an issue with the patch in #275, there are a number of changes in /includes/database/schema.inc in 7.x including the section where the new second hunk applies:
I don't think there is anything wrong with the patch in #275, it just looks like we are at the stage where this patch needs to be finalized and committed for those who want to use it on production.
Comment #284
ronino commented#274 works great for me on D7.73 (#275 should, too, but I didn't test HEAD), even with the migrate module after applying #3171091-16: \MigrateSQLMap::getQualifiedMapTable may break with MySQL 8 support in D7 core.
Comment #285
fabianx commentedThis is good to go and approved.
I am okay with backticks by default and under the rare circumstance that it breaks any sites using MySQL 5.6 or similar, they can always set the variable to '' to circumvent the issue.
We are fixing a bug here as reserved keywords should really not be used as is for certain things, so that is why it is okay to change the default behavior even if it potentially risks breaking a site somewhere.
It is just important that the release notes very clearly state this.
Comment #286
alexpottWe're in the process of changing this in Drupal 8 - see #3089902: "Azure Database for MySQL server" reports wrong database version
the tldr; is that we're removing NO_AUTO_CREATE_USER from the list. With Drupal 7 you might want to go the other way and check for mysql smaller the 5.7 and add the NO_AUTO_CREATE_USER.
Comment #287
mcdruid commentedThanks @alexpott
So do we need to override
\DatabaseConnection::versionfor MySQL too? Similar to:We'd probably also need to do the same replacement in
\DatabaseConnection_mysql::__construct()Comment #288
mcdruid commentedAssuming we do want to query MySQL directly for the version rather than trusting the PDO constant... let's see what the tests say.
Comment #289
alexpott@mcdruid I wouldn't do the query for the version - that's an extra query on every request. The Azure issue is that their MySQL 8 reports 5.7 via the PDO param so using the PDO param should be okay here.
Comment #290
mcdruid commentedOkay, makes sense. Thanks.
n.b. interdiff against #275
Comment #291
trevorbradley commentedI just went to update my D7+MySQL 8 sites with the latest D7.74 security update, previously patched with 2978575-218.patch
I hit some real problems with #290.
I was able to download a fresh copy of Drupal 7.74, replace includes and modules/simpletest, and then apply #218 again, which seems to work fine.
Just to make sure, I downloaded a fresh D7.74, and attempted to apply #290 against it, and it similarly fails.
Unfortunately, this needs to need to go back to "Needs work" after the latest D7.74 update.
Comment #292
mfbSetting back to needs review as patch applies to 7.x branch. It fails on 7.74 because 7.x branch has a new method committed last month: https://git.drupalcode.org/project/drupal/-/commit/6d33276740c0f8c9f4cfb...
Comment #293
mcdruid commentedYup, security releases are not tagged from the head of the branch, they're based on the previous tag.
So all the commits from #3172878: Fix SQLite tests in Drupal 7 (and one or two others) are in 7.x but not 7.74
Patches need to apply to the branch (7.x) but may not apply to the most recent tag especially if that was a security release.
I'm hopeful that we'll commit this soon and it'll be in the next release due on 2020-12-02.
Comment #294
joseph.olstad@mcdruid , thanks for the illustration. Looking forward to 2020-12-02! It's shaping up nicely thanks for spending so much time on this release.
Once we get these top priority bug fixes in and mysql 8 support in then we can focus on php 8.x support. Hopefully php 8.x support will be easier than mysql 8 support and php 7.4.x support (that was a big one too).
Comment #295
izmeez commentedYes and then it will include the commit acknowledging mcdruid's promotion to a full D7 maintainer, well deserved :-)
Comment #296
joseph.olstadcomposer issues may be related to packagist forcing composer 2.x on everyone.
I recently fixed composer issues on a project by adding the flag option for composer self update as follows:
composer self-update --1see MR :if this is suspected to be an issue, perhaps open a new issue in one of these issue queues:
https://www.drupal.org/project/drupalci_testbot
https://www.drupal.org/project/drupalci
Comment #297
alexpottSo this bit has got a bit more complex with further discussions on #3089902: "Azure Database for MySQL server" reports wrong database version. I'm not 100% sure of the impacts here. Azure will report a version 5.6!!!!! So on Drupal 7 it'll be installable because you min version allows it, but with the current code you'll still set NO_AUTO_CREATE_USER.
I think you're fine but it might need documenting. I think in such cases the user will need to override the sql_mode init command by setting it on the connection options... in the database array.
Something like https://3v4l.org/8h9vH
Comment #298
mcdruid commentedHmm, that's pretty messy.
So IIUC Drupal's sometimes connecting to a db proxy (e.g. in Azure) which can mean
PDO::ATTR_SERVER_VERSIONcan be completely wrong; e.g. reporting 5.6 when the actual backend db server is MySQL 8.I'm inclined to go back to #275 where we do:
...and ensure that we document overriding sql_mode in settings.php if sites have problems with Azure or other proxied MySQL setups.
Or we could borrow from #3089902-69: "Azure Database for MySQL server" reports wrong database version and query the db for the version, along with some static caching to avoid doing so more than once.
Adding one simple query is likely not that significant for a D7 site.
How about we do both? Here's a patch which adds a
\DatabaseConnection_mysql::version()method (with a static cache) - overriding the parent method, which we now call from the constructor. In the constructor, the version comparison is back to checking for< 8.0.11which I believe should be correct if we're asking MySQL directly for the version.Is there a reason we shouldn't call this method from the constructor? Doing so seems to work fine AFAICS.
Comment #299
mcdruid commentedDiscussed this in slack with @alexpott and we concluded that going back to #275 is probably the best option.
So we're not adding more queries, and we're checking against the correct version details for the change in MySQL 8.
We'll want to make sure that the potential problem with proxies is mentioned in the release notes / CR with a link to docs on how to change
sql_modein settings.php if a D7 site encounters the problem with the PDO constant misreporting the MySQL version e.g. on Azure.#275 is the patch that @Fabianx reviewed and approved, so I think we're okay to commit that in the next day or so before the release next week.
Thanks for bringing this nasty gotcha to our attention though @alexpott!
Comment #300
izmeez commentedIn terms of
is it important enough to also include a note in default.settings.php ?
Comment #302
mcdruid commentedThank you to the many contributors here!
The CR / release notes will need some work, but it's great to get this committed to D7.
Comment #303
mustanggb commentedThanks everyone!
PHP 8 next :P
Comment #304
joseph.olstadFantastic work, it was a doozy, I helped out with the drush upstream.
Yes for PHP 8
Comment #305
gisleThank you so much! Great work.
Comment #306
jan-e commentedQuote from https://www.drupal.org/project/drupal/issues/2978575#comment-13701126
This seems to be broken in Drupal 7.76. See my comment here: https://www.drupal.org/node/3185889#comment-13924331
Comment #307
mcdruid commented@Jan-E yes it looks like identifier quoting will cause problems with table prefixing if you're using a dot in the prefix to refer to a different schema / database.
However, it also looks like you can avoid this by setting the variable to an empty string as per the example in default.settings.php
The Change Record mentions:
It looks like you may need to do that (for now at least) if you want to use prefixes including dots.
Can we file a new issue for this please?
Comment #308
jan-e commented@mcdruid Thanks. I can confirm adding
$conf['mysql_identifier_quote_character'] = '';works. However, I would not know what the relation to identifier quoting is, so it did not ring a bell when I read the Change Record. I assumed it was an unexpected side effect of the support for MySQL 8.Comment #309
mcdruid commentedFiled #3186120: Table name quoting for MySQL 8 breaks sharing tables via prefixes containing dots.