This issue is part of #2157455: [Meta] Make Drupal 8 work with PostgreSQL or remove support from core before release.

Problem/Motivation

Currently some tests fail because PostgreSQL tries to create some indexes twice. We already have a method to prevent that (indexExists()) but this method is never ever called from within createTableSQL() where the index create statements are build.

Proposed resolution

Add an indexExist check to createTableSql() to prevent PostgreSQL from creating indexes twice. This is because indexes in PostgreSQL are global.

The pgsql driver already prefixes the index with the table name so this would mean the indexes are unique per table (see comment #12 for details). The solution, at this time, should only be to check if the index exists and create it if it does not exist.

MD5 (postgresql-index-exists.patch) = 5206e5687da7065aa75fc79f7caba208
MD5 (postgresql-index-exists-17.patch) = 5206e5687da7065aa75fc79f7caba208

Remaining tasks

Review.

User interface changes

None

API changes

None

Beta phase evaluation

Reference: https://www.drupal.org/core/beta-changes
Issue category Bug because of broken tests
Issue priority Major because of broken test environment
Disruption None disruptive for core/contributed and custom modules/themes because it is a bugfix only

Comments

andypost’s picture

+++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php
@@ -216,7 +216,11 @@ protected function createTableSql($name, $table) {
       foreach ($table['indexes'] as $key_name => $key) {
-        $statements[] = $this->_createIndexSql($name, $key_name, $key);
+        // Prevent from creating an index twice. This could happen as PostgreSQL
+        // indexes are global.
+        if (!$this->indexExists($name, $key_name)) {
+          $statements[] = $this->_createIndexSql($name, $key_name, $key);

This looks like hack, maybe better solution to make index name generation "prefixed" somehow?

jaredsmith’s picture

Status: Needs review » Reviewed & tested by the community

I have reviewed this code both on a manual installation on my laptop and using the drupalci test infrastructure, and this patch solves a number of failing simpletest tests and doesn't appear to cause any additional failures. I am comfortable marking this as RTBC and asking that it be committed.

alexpott’s picture

Does this mean that we have indexes with the same name on different tables?

alexpott’s picture

I'm concerned that this will cause us not to create important indexes.

jaredsmith’s picture

Issue tags: -PostgreSQL +PostgreSQL latinamerica2015
alexpott’s picture

Issue tags: -PostgreSQL latinamerica2015 +PostgreSQL, +LatinAmerica2015
alexpott’s picture

Status: Reviewed & tested by the community » Needs review

Setting back to needs review to get answers to #3/#4

jaredsmith’s picture

StatusFileSize
new18.12 KB

Does this mean that we have indexes with the same name on different tables?

Absolutely, it does -- and the more I think about it, this patch really would prevent us from building indices on important tables.

To show that we really do have indices with the same name on different tables, I ran the following query on MariaDB 10.0.15 after a fresh installation of 8.0.x HEAD (installed yesterday):

SELECT DISTINCT INDEX_NAME, TABLE_NAME FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = 'drupal8' ORDER BY INDEX_NAME, TABLE_NAME;

(Obviously, you can replace 'drupal8' with the name of your own database.)

If you look at the attached file, it shows that we have thirteen tables that all have an index named "revision_id".

Looks like we're back to the drawing board on this patch, perhaps by prefixing the index name with the table name.

bzrudi71’s picture

I don't have much time but just to give some feedback:

Does this mean that we have indexes with the same name on different tables?

No. That could never ever happen as PostgreSQL indexes are unique and global. In other words you can't have an index named 'my_index' twice in PostgreSQL.

I'm concerned that this will cause us not to create important indexes.

No worries I guess. As above, it does not prevent from creating an index, just from creating it twice, which will fail because it's not possible with global indexes. In addition, I didn't count it, but this happens on a complete test bot run not more than 5 times or so. But I'm with you, maybe this is just required because of weird tests scenarios...

jaredsmith’s picture

StatusFileSize
new686 bytes

Another attempt at a patch for this issue, this time by prefixing the index name with the table name, so that we don't have conflicting index names in PostgreSQL. Please let me know if this resolves the concerns from comments 1, 3, and 4.

bzrudi71’s picture

@jaredsmith: First off all thanks for taking care of PostgreSQL :-)

I'm sorry, but this issue is not about forcing to create another (duplicate) index with a different name if an index with the same name already exists. This issue is about preventing from creating an index twice. Please see ensureIdentifiersLength() method which does all the magic of index naming already (including table name+index name), and even to make sure we do not exceed the maximal length of an index name - in case of PostgreSQL 63 chars.

To give an example:

d8=# CREATE TABLE test_table (id int null, data varchar(64) null);
CREATE TABLE
d8=# CREATE INDEX first_index ON test_table (id); 
CREATE INDEX
d8=# CREATE INDEX second_index ON test_table (id); 
CREATE INDEX
d8=# CREATE INDEX second_index ON test_table (data); 
ERROR:  relation "second_index" already exists
d8=#

So you can create as many duplicated indexes on the same table and even on the same column as long as the name differs, but you can't create a second index, even on a different table if the index name is the same, because it's global. So in short - I just tried to prevent creating duplicate indexes, as that would result in an exception :-)

jaredsmith’s picture

I'm sorry, but this issue is not about forcing to create another (duplicate) index with a different name if an index with the same name already exists. This issue is about preventing from creating an index twice.

Before I get into the specifics of your last comment, let me just give some background so that we can all agree where we're coming from.

  • With the MySQL driver, Drupal might create several different indices with the same name. For example, there's an index named "bundle" on both the block_content__body and the block_content_revision__body table.
  • This isn't a problem for MySQL, because index names are per-table.
  • This is a problem for PostgreSQL, because index names are per-schema, not per-table.
  • This explains the most likely reason why indices with duplicate names are being written when using the PostgreSQL driver.
  • Simply avoiding writing a duplicate index, while it might solve the problem, causes the problem identified by Alex in comment #3, where we might not create indices that we actually need. Going back to my earlier example, let's say we're using the PostgreSQL driver and during installation Drupal creates the block_content__body field, and then creates an index named bundle on it. So far, so good. Next, it then creates the block_content_revision__body table, and attempts to create an index called bundle on it. If we simply said "Oh, there's already an index named bundle, let's not duplicate it", then the second table would never get an index.
  • Instead, we need to make sure that all the tables get their proper indices created, but with non-conflicting names
  • One simple way to create non-conflicting index names is to prefix the index name with the name of the table.
  • I wasn't aware until I looked into it further today that the ensureIdentifiersLength() function already concatenates the table name plus the index name plus 'idx' when creating an index in PostgreSQL, in addition to making sure the name of the table/index/constraint doesn't exceed PostgreSQL's limit. So, in short, it's already doing what my new patch proposed to do, so my patch is superfluous.

    To answer the other comments on this issue after more investigation and reflection:

    Comment #1: We already prefix the table name, so that's taken care of.
    Comment #3: We do in fact in MySQL, but it doesn't really matter there. We don't in PostgreSQL.
    Comment #4: Because we're fixing up the index names in PostgreSQL, this isn't a problem.

    Which brings us all the way back around to @bzrudi71's patch, which really is the correct thing to do in this situation -- because we can now be sure that it really is an attempt to create a duplicate index on the same table, and not a naming conflict on the indices.

    I'm setting this back to RTBC, as I don't see any other issues with @bzrudi71's patch.

    jaredsmith’s picture

    Status: Needs review » Reviewed & tested by the community
    alexpott’s picture

    Status: Reviewed & tested by the community » Needs review

    Wow can we get a followup to give ensureIdentifiersLength() a better name. Also the variadic nature of the method seems unnecessarily fragile and complex.

    So now my question is why are we trying to create duplicate indexes on a table? Since this has nothing to do with the global nature of postgres's indexes. (Setting back to needs review for this)

    Plus can @bzrudi71's patch be re-uploaded since the rtbc retest only retests the latest patch - it doesn't check the hidden files property (afaik).

    alexpott’s picture

    Add an indexExist check to createTableSql() to prevent PostgreSQL from creating indexes twice. This is because indexes in PostgreSQL are global.

    According to the latest comments this is not why we're doing this change so this needs updating.

    andypost’s picture

    Title: Prevent PostgreSQL from creating indexes twice » Prevent PostgreSQL from creating duplicated index names within schema

    I think here should be a follow-up to extend schema generators to not produce a duplicated names for indexes, tables, constrains there's no reason to relay on storage and introduce hacks within drivers.

    Oracle contrib driver exposes own identifier constraints. I'm pretty sure that mssql has the same issue.
    So better to extend schema generator once it has previous state knowledge now.

    mradcliffe’s picture

    Issue summary: View changes
    Issue tags: -Needs issue summary update
    StatusFileSize
    new818 bytes

    I haven't really read this issue in depth, but I read over comment #12 and updated the issue summary. I downloaded the patch, copied it to a new file name, and ran interdiff and md5 to confirm the files were the same.

    mradcliffe’s picture

    Issue summary: View changes

    Created follow-up issues.

    mradcliffe’s picture

    Status: Needs review » Reviewed & tested by the community
    alexpott’s picture

    Status: Reviewed & tested by the community » Needs work

    But why are duplicate indexes being created #14 still needs an answer.

    bzrudi71’s picture

    @jaredsmith: many thanks for the great summary write up of this issue, I couldn't explain it any better!
    I'm totally +1 for making my patch obsolete if we could handle all this within the schema creators as suggested by @andypost. To address @alexpott concerns why we are trying to create indexes twice. I don't have the complete overview but it only happens in very, very rare cases during tests. Identified test groups are:

    • file
    • taxonomy
    • options

    and in particular:

    • file->FileFieldValidateTest()
    • taxonomy->VocabularyCrudTest()
    • options->OptionsFloatFieldImportTest()

    So these tests seem to do something special compared to all other test. To remember, if we do a table drop, we can be sure that PostgreSQL will take care of complete removal of all! indexes and whatever else. That could mean we do some operations on existing tables that lead to those errors. To trace this down I did some debug on the FileFieldValidateTests and ran them locally.

    As expected the test does the usual, enable modules, create role, create user and so on. Now to the interesting part :-)

    1. The test creates a content type article to attach a file field, so far so good
    2. During create we create the node tables for the article
    3. Then test tries to create an index name of simpletest845018node_revision__fdtwrve2__fdtwrve2_target_id__idx that exceeds the PG limit of 63 chars
    4. ensureIdentifiersLength() takes care of that an rewrites the index name to drupal_Z_WsWR9d7FLUJXO39jjBUIE6QdolJoJS_EnpuxeA4lY_idx, no problems so far
    5. Now that actual tests start up (and pass), still no problems
    6. The mess starts right after passing the 'File entry exists after uploading to the required field.' test (line50)
    7. On line 53 there is just a call to $storage->delete() to take care of field deletion, not the article content entity itself
    8. And right after that test does $this->createFileField() which seems to be the problematic part. From the debug $this->createFileField does create a lot of indexes and yes we hit it, it to tries to create simpletest845018node_revision__fdtwrve2__fdtwrve2_target_id__idx (drupal_Z_WsWR9d7FLUJXO39jjBUIE6QdolJoJS_EnpuxeA4lY_idx ) again, gotcha!

    As I'm all but an entity insider and it's late after midnight I leave this open for suggestions from the entity experts!
    [UPDATE] I had a second look this morning and the test itself is easy to fix. The second call to $this->createFileField() uses the same $field_name again and that causes the problem. By using a different $field_name I can make test pass. So what are the next steps here?

    - Try to identify every single broken test and try to fix it?
    - Use my patch hack to prevent it globally?
    - Try to fix that globally within the schema generators and not in driver space?

    alexpott’s picture

    So this is a problem with renaming tables in postgres. We need to rename any keys they have too. So the patch #17 is definitely wrong because the tables for the new fields will not have the correct indexes. Basically SqlContentEntityStorageSchema::onFieldStorageDefinitionDelete() calls Drupal\Core\Database\Driver\pgsql\Schema::renameTable() which should take care of renaming the indexes but, from @bzrudi71's report, this appears not to be working.

    bzrudi71’s picture

    Okay, if the SqlContentEntityStorageSchema::onFieldStorageDefinitionDelete() calls Drupal\Core\Database\Driver\pgsql\Schema::renameTable() internally then the mother of all problems is already known: #1013034: PostgreSQL constraints do not get renamed by db_rename_table() ;-)
    Guess we should focus on this now and see if that will fix all exceptions in this issue.

    andypost’s picture

    bzrudi71’s picture

    Just did a complete test run with latest patch from #1013034: PostgreSQL constraints do not get renamed by db_rename_table() and all reported problems here (and some others too) are gone! So I think this one, as well as the follow up #2426581: Fix schema generators to prevent creating duplicate table, indexes, and other constraints so that database drivers do not need to implement their own solutions can be marked as closed/fixed. Regarding #2426579: Change method name for pgsql driver's ensureIdentifiersLength() to accurately describe what the method is doing this is now considered a follow up of #1013034: PostgreSQL constraints do not get renamed by db_rename_table() to fix pkeys that are currently prefixed by four underscores instead of two. This is more a cosmetic issue but we should take care of unique naming conventions.
    So anyone, let's please focus on the db renaming issue now to get this long term showstopper (4 years!) to RTBC state - and committed.

    bzrudi71’s picture

    Status: Postponed » Closed (fixed)