Problem/Motivation
#2503755: Switch from user login block to login menu link and search block in standard profile got committed and then needed to be reverted because there was a single test failure in both SQLite and Postgres, that was only discovered after commit (because we don't test every patch against those two DBs, but we do test HEAD against them).
After a long search, the root cause was discovered at #2503755-142: Switch from user login block to login menu link and search block in standard profile:
I found the root cause.
There are two.
On MySQL,
ToolbarAdminMenuTest::testLocaleTranslationSubtreesHashCacheClear(), where it saves a translation ($this->drupalPostForm('admin/config/regional/translate', $edit, t('Save translations'));) is posting the translation forMenusto the first text area on this page:
On SQLite, it looks like this:
On SQLite, but without this patch's
DefaultMenuLinkTreeManipulatorsandMenuLinkBasehunks, it looks like this:
- Root cause one:
config_translationbehaves differently in SQLite/Postgres vs. Mysql- Note how there is one match in MySQL versus 3 in SQLite. Note how MySQL appears to be doing case-sensitive matching, and SQLite case-insensitive.
- Root cause two: different ordering with this patch
- Somehow, the changes in
DefaultMenuLinkTreeManipulatorsand/orMenuLinkBaseare causing a different ordering of those 3 matches.- Conclusion
- The test assumes there will be only one match. It's only by chance that HEAD is passing, because in HEAD the first match just happens to be the right one. It should actually only show one match.
So the test translates the WRONG string, and therefore the toolbar menu is the same before and after, and hence the subtrees hash doesn't change on SQLite/Postgres.Clearly, there are much deeper problems at play here.
This was one giant WTF! It'd be funny if it wasn't just before RC and I now have to somehow fix SQLite support in code totally unrelated to this issue…
Then, Gábor Hojtsy did a further analysis, figuring out why it's behaving differently on SQLite/Postgres, i.e. where the DB mismatch exists, over at #2503755-144: Switch from user login block to login menu link and search block in standard profile:
Looking at the locale schema (the bug has nothing to do with config translation):
function locale_schema() { $schema['locales_source'] = array( 'description' => 'List of English source strings.', 'fields' => array( // ... 'source' => array( 'type' => 'text', 'mysql_type' => 'blob', 'not null' => TRUE, 'description' => 'The original string in English.', ), // ... ), 'primary key' => array('lid'), 'indexes' => array( 'source_context' => array(array('source', 30), 'context'), ), ); $schema['locales_target'] = array( 'description' => 'Stores translated versions of strings.', 'fields' => array( // ... 'translation' => array( 'type' => 'text', 'mysql_type' => 'blob', 'not null' => TRUE, 'description' => 'Translation string value in this language.', ), // ... );So in both cases, specifies blob only for MySQL specifically. I don't know if there was a reason to not specify blob for other DB engines given the 'type' key itself supports blob as per our docs:
* - 'type': The generic datatype: 'char', 'varchar', 'text', 'blob', 'int', * 'float', 'numeric', or 'serial'. Most types just map to the according * database engine specific datatypes. Use 'serial' for auto incrementing * fields. This will expand to 'INT auto_increment' on MySQL. * A special 'varchar_ascii' type is also available for limiting machine * name field to US ASCII characters. * - 'mysql_type', 'pgsql_type', 'sqlite_type', etc.: If you need to * use a record type not included in the officially supported list * of types above, you can specify a type for each database * backend. In this case, you can leave out the type parameter, * but be advised that your schema will fail to load on backends that * do not have a type specified. A possible solution can be to * use the "text" type as a fallback.As per our database.api.php. OTOH looking at the SQLite schema driver, it would only make a text field case insensitive if we did explictly provide a binary key with a FALSE value:
if (in_array($spec['sqlite_type'], array('VARCHAR', 'TEXT'))) { if (isset($spec['length'])) { $sql .= '(' . $spec['length'] . ')'; } if (isset($spec['binary']) && $spec['binary'] === FALSE) { $sql .= ' COLLATE NOCASE_UTF8'; } }Did not find the relevant code in the PgSQL driver.
Proposed resolution
Adjust locale.module's DB schema per Gábor's findings.
Remaining tasks
TBD
User interface changes
None.
API changes
None.
Data model changes
None.
| Comment | File | Size | Author |
|---|---|---|---|
| #28 | 2580671-27-28-interdiff.txt | 2.45 KB | rosk0 |
| #28 | 2580671-28.patch | 4.44 KB | rosk0 |
| #27 | 2580671-22-27-interdiff.txt | 1.15 KB | rosk0 |
| #27 | 2580671-27.patch | 4.5 KB | rosk0 |
| #22 | interdiff-2580671-19-22.txt | 2.78 KB | valthebald |



Comments
Comment #2
gábor hojtsyWe should make it case sensitive, NOT case insensitive. If it is insensitive, then "Menus" = "menus", while you don't want that to get translations for things that don't match your source string case. It used to be at least prohibitive to try and match case insensitive columns sensitively at runtime (and vice-versa I believe was impossible). So our solution has been to make the field case sensitive, so each string is unique if there is a different in cases, so "menus" != "Menus".
Comment #3
gábor hojtsyRetitle again for the bug itself instead of what to do :)
Comment #4
wim leersLOL, oops — sorry about that typo! :D
Comment #5
catchI think this is a duplicate of #2490976: Locale caching algorithm is broken on Non MySQL/PostgreSQL databases although it has a better issue summary.
Comment #6
david_garcia commentedRevisiting this from the related issue there are 2 possible ways of fixing it:
1 - Use "serial => 'true'" to indicate that this should be case sensitive.
2 - Use "type => 'blob'"
I'd rather use (1) because after all we are storing strings here and that makes the locale related tables more usable and readable.
Binary storage should be used for stuff that actualy makes sense to store as binary.
Anyways, anything such as this:
should be removed from core there is a database abstraction layer for something...
And this is happening in D7 too.
Comment #7
gábor hojtsyWhere is that serial => TRUE option? I also see a serial type and that would be a number so not suitable.
Comment #8
david_garcia commentedI meant binary not serial.
Comment #9
gábor hojtsy@david_garcia: oh, ok! Wanna take a first stab at the patch / tests?
Comment #10
david_garcia commentedI don't work on *nix or MySQL so I did not test on that platform, but I am so sure that the MySQL driver will not handle the switch from BLOB back to text.
Let's see what the test bot has to say about it.
No tests yet. I feel there is a lot more to test here than just this specific use case such as:
- Underlying driver properly handling the BINARY specificacion. This is not covered by core tests.
- Testing the ability to switch between BLOB and other Data Types, I have a feeling that none of the current drivers will be able to do so.
Comment #11
gábor hojtsyLooking good on all tested environments, woot :) Would need tests for the update itself as well as the new schema :)
Should be on one line. This shows up on update.php
Should not use the runtime schema. This may change in later releases which would change the behavior of this function. The newly desired schema for the two fields should be included verbatim.
Comment #12
david_garcia commentedLet's try luck with this test.
Comment #14
david_garcia commentedSo looks like blob should be case sensitive and accent sensitive... makes sense...
Comment #17
andypostComment #18
valthebaldWorking on this as part of SprintWeekend2017 in Sofia
Comment #19
valthebaldRemoving 'Needs tests', as test is in place
Changed location of SchemaTest
Applied code style changes
Removed deprecated functions/methods calls
Comment #20
david_garcia commentedPlease do not use the "query" method of the connection object. We have a database abstraction layer for something....
Comment #21
david_garcia commentedAdded the performance tag. This issue prevents prevents the locale cache from working as expected on non mysql databases.
Comment #22
valthebald@david_garcia fair point about db_query(). I've changed the query, and also dared to exclude accent-insensitive part of test, since:
a) This consistently fails under sqlite
b) I have found no clear evidence on cross-database way to handle accent sensitivity.
IMO it's more important to have test coverage about case (in)sensitivity, than accent sensitivity
Comment #25
rosk0Comment #26
rosk0MySQL: Patch fails because we assuming that we use accent sensitive collation, but we don't according to https://dev.mysql.com/doc/refman/5.7/en/charset-collation-implementation.... See also http://mysqlserverteam.com/sushi-beer-an-introduction-of-utf8-support-in...
PostgreSQL: According to documentation all collations work in accent sensitive manner. Refer to https://www.postgresql.org/docs/9.1/static/textsearch-dictionaries.html https://www.postgresql.org/docs/9.1/static/unaccent.html
Based on this I'm going to remove accented words from test matrix.
Comment #27
rosk0Comment #28
rosk0Removed all mentions of accent from the patch because we are not testing it and improved assertion message.
I think this issue is block by #2464481: PostgreSQL: deal with case insensitivity and #1518506: Normalize how case sensitivity is handled across database engines.
Comment #40
smustgrave commentedCame here for stale-issue-content but still appears to be blocked on the issues in #28