Problem/Motivation
Currently, the Postgres driver treats key column specifiers for UNIQUE indices as if they were always strings. Passing a column with a prefix length, as allowed by the Schema API, leads to an error.
Steps to reproduce
Create any table (in custom or contrib code) with a UNIQUE index that has a column with prefix length specified.
Works perfectly fine on MySQL and SQLite, but fails on Postgres. (Same for adding such a UNIQUE index via addUniqueKey(), addField() or changeField().)
Proposed resolution
Fix createTableSql() and addUniqueKey() in \Drupal\Core\Database\Driver\pgsql\Schema to use createPrimaryKeySql() for creating the column list for UNIQUE keys. This will already correctly disregard any specified prefix lengths, as Postgres cannot handle them in PRIMARY or UNIQUE indices.
Remaining tasks
Write a regression test.
User interface changes
None.
API changes
None.
Data model changes
None.
Release notes snippet
N/A
| Comment | File | Size | Author |
|---|---|---|---|
| #76 | 1148856-76--postgres_unique_key_prefix_length.patch | 7.02 KB | drunken monkey |
Comments
Comment #1
damien tournoud commentedIndeed.
Triaging, bumping to 8.x to fix it there first.
The patch contains debug statements that need to be removed, but otherwise looks good.
Comment #2
xatoo commentedRerolled patch for 8.x without debug statements.
Let get this in, for 7.x asap as well.
Comment #3
damien tournoud commentedActually, no, this needs work.
We need to use functional indexes here, and there is already a
DatabaseSchema_pgsql::_createKeySql()function to do that. Simply ignoring the column prefixes is just not an option for the unique and primary keys, because it would change the behavior of the key.Comment #4
xatoo commentedFor unique indexes I agree but I doubt whether Postgres allows functional primary keys.
Comment #5
xatoo commentedThis version uses _createKeySql() in unique indexes. Since Postgres does not allow functions to be used in primary key indexes it still ignores the length limit for those.
In the rare case that a length is provided for a primary key, an alternative would be to add an extra functional unique key constraint. Any thoughts on that?
Comment #6
xatoo commented#5: 1148856-pgsql_schema_fix_issue_of_mysql_key_length-5.patch queued for re-testing.
Comment #7
kriation commentedComment #8
andypostHow the changes are affrected #1852896: Throw an exception if a schema defines a key that would be over 1000 bytes in MySQL
Postgresql does not have limits on key size!
Comment #9
xjmThis sounds like a pretty critical bug for postgres environments... In fact, the only reason I'm leaving it at normal is that no one seems to have posted on it in years. Are there duplicates of this issue about? Does this problem still occur in D8 whenever you install a something with the index length restricted, like for example when you install, oh, taxonomy? #1871032: Taxonomy module fails to install on MyISAM due to too long schema index
Can someone manually test installing Postgres and the standard profile on D8?
Comment #10
damien tournoud commented@xjm: this is only a problem in practice when you try to use a UNIQUE key on a prefix... but this is really not something we should support in Drupal in the first place.
Comment #11
xjmAh, thanks @Damien Tournoud. Sounds like we just need a summary update then.
Comment #12
ben coleman commentedI ran into this during the DrupalCamp Atlanta Code Sprint. The comment module is now using keylength for the comment_entity_statistics table, so Drupal8 won't install on PostgreSQL without this being fixed. And the PostgreSQL driver code has been moved to core/lib/Drupal/Core/Database/Driver/pgsql/ since the patch in #5. Attached is patch #5 redone for the new code layout.
Comment #13
ben coleman commentedFixing the Component that was erroneously set to ajax system
Comment #14
andypostNot sure it's possible to write tests special for PostgreSQL
But the patch fixes installing.
{comment} table also have 'comment_num_new' index with 'entity_type' and 'field_name' "shorten" to 32 bites same as {locales_source} for 'source_context' index
Comment #15
ben coleman commentedWell, there is a test client with a PostgreSQL/PHP5.3 environment in the test system, so it seems it might be possible. I haven't dug enough into the test system to know how to write tests for that, though.
Comment #16
stefan.r commentedBackport to D7
Comment #17
stefan.r commentedComment #18
stefan.r commentedComment #19
mradcliffePart of the patch in #12 was done in #2181283: Prevent pgsql driver from trying to implode primary key fields that are defined by an array.
Patch needs to be modified to remove _createPrimaryKeySql as that became createPrimaryKeySql, and is already there.
_createKeySql could be renamed to createKeySql as that was a code review issue in the above mentioned issue.
Comment #20
stefan.r commentedComment #23
bzrudi71 commentedComment #24
bzrudi71 commentedComment #25
bzrudi71 commentedNext try, missed $this->ensureIdentifiersLength ;-)
Comment #26
damien tournoud commentedThe changes here look pretty random. If we want to support this properly, we should generate a functional index here.
http://www.postgresql.org/docs/9.1/static/indexes-expressional.html
Comment #27
bzrudi71 commented@Damien Thanks for the link! The first thing that made me thinking is the requirement of PostgreSQL version >= 9.x. Do we really wan't to break 8.x because of this? The second is about performance.
Comment #28
mradcliffeYes, I agree that I don't think we should support key length as a constraint mechanism for indexes. It seems like it's adding functionality that is not relevant for other drivers and that may confuse developers.
Index expressions are supported since 7.4: http://www.postgresql.org/docs/7.4/static/indexes-expressional.html
Comment #29
jyotisankar commentedComment #30
andypostonly docs gate
needs doc block once we are on that
Comment #31
pwieck commentedComment #32
catchUpdating title to make it clear why this is a minor use case (at best).
Comment #37
rosk0Not sure how to test this so not sure is it actually still an issue.
Comment #45
drunken monkeyRe-roll plus comment for
createKeySql(). This problem still exists – ran into this in #3228210: Fix current test failures, where I now have the choice between fixing the code for MySQL or Postgres.Still needs tests, though.
Comment #46
daffie commented@drunken monkey: If you add the testing, then I will do the reviewing.
Comment #47
drunken monkeyThanks for the quick reaction!
Here you go – was (luckily) pretty simple anyways. (Interdiff is the same as the tests-only patch.)
Comment #48
drunken monkeyComment #49
daffie commentedCan we instead of doing a unittest, do a kerneltest. Then we have a database. Use the Drupal\KernelTests\Core\Database\DatabaseTestBase to extend from. Then use one of the tables with data in it. Create the unique indexes on that table and test those indexes. Then we test if it really works on a PostgreSQL database.
Comment #50
drunken monkeyOK, then how about this? (I don’t have Postgres set up locally, though, so not 100% sure it will work.)
Also, as our SQLite driver ignores the prefix length for key column specifiers, we need to test differently for that DB.
Comment #51
mondrakeIMHO this should should be wontfix, for the same reason as #3068310-12: Composite primary key with specified index length fails the NOT NULL check
Comment #52
mondrakeComment #53
drunken monkeyI strongly disagree: the functionality of specifying a prefix length for any index key (primary, unique or index) is part of the API (unless #3082239: Forbid limited length primary and unique keys, allow only in indexes actually gets accepted), so having a driver where actually using this functionality leads to an error isn’t acceptable.
We can discuss whether we want to actually comply with the specified prefix length, or ignore it like SQLite, but the current behavior is definitely a bug. Furthermore, note that MySQL (or at least some versions of it) requires a prefix length when using
TEXTorBLOBcolumns as part of a key specification (see here), so having this functionality in the Schema API can be crucial in some (though admittedly probably rather rare) cases.Comment #54
daffie commentedCould we instead do in the function setup():
Edit: The Is is saying that the bug that we are fixing does not exists with SQLite??? What is it?
Why is this comment here? Can it be removed?
Can we add in the docblock a @coversDefaultClass \Drupal\Core\Database\Schema
Can we rename the file to: SchemaUniquePrefixedKeysIndexTest.php.
Can we add to the docblock: "@covers ::createTable" and "@covers ::CreateTableSql".
Can we add to the docblock: "@covers ::addUniqueKey".
Comment #55
drunken monkeyShoot. As said, I don’t have Postgres set up, so I just followed the proposed patch in #29. Seems Postgres (or at least some versions of it?) doesn’t like that syntax after all. Then I’d say we just ignore prefix length, like we already do for SQLite. Then it’s only supported in MySQL, but that’s also (apparently) where it’s most needed, and at least this won’t trigger an error anymore.
The bug is that using this functionality on Postgres causes an error – so, no, the bug doesn’t exist in SQLite. SQLite ignores the setting, which is also not ideal (I’d say), but not the issue at hand. Therefore, even if SQLite doesn’t recognize the setting, it should still definitely be part of the test, as we still want to make sure using the setting doesn’t cause an error. That’s what the regression would be, too, in Postgres.
Neither of those have changes? Same goes for
_createIndexSql()now, by the way. (And before it was only a method rename, so wouldn’t have said it required extra tests.)I’m not sure that’s appropriate, as we really just test that class’ sub-classes? Also, there isn’t even a
\Drupal\Core\Database\Schema::createTableSql()method.But fine by me either way.
No.
Comment #56
daffie commentedI have searched the PostgreSQL documentation and I could not find anywhere that this database supports unique indexes with prefix limits. Also SQLite does not support it. Therefor it will be an MySQL only thing. It would be however very nice if the database drivers of SQLite and PostgreSQL do not choke when there is a prefix limit is set.
Can we rewrite these 2 cases to:
Comment #57
drunken monkeyWhy? I’d say it’s still better to test something than nothing.
E.g., current code would catch it if the (SQLite/Postgres) driver would just ignore any
UNIQUEkeys with prefix length altogether (maybe try with wrong syntax and then fail silently), instead of just ignoring the prefix length.Sure, makes sense. Patch attached.
Comment #58
daffie commentedI have only 2 remarks, after that it is RTBC for me.
Now we have this piece of code 4 times. Can we move it to its own method.
Comment #59
mondrakeThe call to
createPrimaryKeySql()sounds a bit strange in the context of unique keys; however, reading the code in the method it is clear that what it does is actually disregard the partial length and implode the fields names, quoted with the appropriate identifiers.I would suggest to either (a) add a comment on why we're using this method here, or (b) adjust the
Schema::fieldNamesmethod in the base class to take in also the identifiers if needed and return quoted field names as well as unquoted ones.Comment #60
drunken monkeyShould all be fixed in the attached patch revision.
I also attempted to bring the issue summary up to date. Is that what you meant, daffie?
Comment #61
daffie commentedThe bug is fixed and testing haas been added for the fix.
The patch without the fix is correctly failing for PostgreSQL.
All points of @mondrake and myself have been addressed.
For me it is RTBC.
@drunken_monkey: Good work!
Comment #62
alexpottI've went back on forth about silently ignoring the index length configuration and whether this is the correct thing to do. However, we already do this for primary keys on postrges and all keys on SQLite. So I think the approach is correct. However, I think we should improve documentation as part of this change.
\Drupal\Core\Database\Driver\pgsql\Schema::createPrimaryKeySql()currently say:* Create the SQL expression for primary keys.This should say:
* Create the SQL expression for primary and unique keys.This can be improved.
Comment #63
mondrake#62.2 is actually being done in #3082239: Forbid limited length primary and unique keys, allow only in indexes which could be a follow up of this issue?
Comment #64
alexpott@mondrake I think we should fix the docs here to represent HEAD where different DBs do it differently. We have no idea how long #3082239: Forbid limited length primary and unique keys, allow only in indexes will take to land.
Comment #66
drunken monkeyHere are the doc improvements requested in #62. I went for this addition to the second paragraph:
Comment #68
jonathanshawComment #70
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It either no longer applies to Drupal core, or fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #71
drunken monkeyRe-roll.
Comment #72
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".
Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #73
drunken monkeyOops, wrong patch file.
Comment #74
jonathanshawComment #75
jonathanshawMy bad, the postgres test failures need addressing.
Comment #76
drunken monkeyThis should do it.
Escaping of the field names already happens in
createPrimaryKeySql(), so no need to callescapeField(), too.Comment #77
andypostChanges looks good and covered with tests, queued tests for pgsql and sqlite
Comment #78
larowlanUpdating issue credits
Comment #81
larowlanCommitted to 10.1.x and 10.0.x
Doesn't apply to 9.5.x, marking as patch to be ported.
Thanks folks!
Comment #83
larowlanRolled this back from 10.0.x, ::getPrefix doens't exist there so HEAD was broken
Comment #84
catchSince we don't have the ::getPrefix method in 10.0.x, I think we should leave this in 10.1.x - makes it not a straight backport.
Comment #85
mondrakeGiven the conditional
the test should have been a
DriverSpecificDatabaseTestBaseextending test, no?Sorry for late feedback
Comment #86
daffie commented@mondrake: I have created the followup for extending DriverSpecificDatabaseTestBase. See: #3351886: Change SchemaUniquePrefixedKeysIndexTest to extend DriverSpecificDatabaseTestBase