Failing on PostgreSQL 9.1:
https://www.drupal.org/pift-ci-job/986641
https://www.drupal.org/pift-ci-job/991466

1) Drupal\KernelTests\Core\Database\SchemaTest::testSchemaChangePrimaryKey with data set "composite_primary_key" (array('test_field', 'other_test_field'), array('test_field_renamed', 'other_test_field'))
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
@@ @@
 Array (
-    0 => 'test_field_renamed'
-    1 => 'other_test_field'
+    0 => 'other_test_field'
+    1 => 'test_field_renamed'
 )

/var/www/html/core/tests/Drupal/KernelTests/KernelTestBase.php:1112
/var/www/html/core/tests/Drupal/KernelTests/Core/Database/SchemaTest.php:765

2) Drupal\KernelTests\Core\Database\SchemaTest::testSchemaChangePrimaryKey with data set "composite_primary_key_different_order" (array('other_test_field', 'test_field'), array('other_test_field', 'test_field_renamed'))
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
@@ @@
 Array (
-    0 => 'other_test_field'
-    1 => 'test_field_renamed'
+    0 => 'test_field_renamed'
+    1 => 'other_test_field'
 )

/var/www/html/core/tests/Drupal/KernelTests/KernelTestBase.php:1112
/var/www/html/core/tests/Drupal/KernelTests/Core/Database/SchemaTest.php:765

Comments

tacituseu created an issue. See original summary.

tstoeckler’s picture

Cannot reproduce this on Postgres 10.4, which I have locally.

I ran the test 100 hundred times and verified that it passed each time. For anyone following along at home, this is what I did:

for i in `seq 1 100`; do ../vendor/bin/phpunit --testdox-text output-$i.txt tests/Drupal/KernelTests/Core/Database/SchemaTest.php --filter testSchemaChangePrimaryKey &> /dev/null; done
cat output-*.txt | grep '\[x\]' | wc -l # This is the number of passes (300)
cat output-*.txt | grep '\[ \]' | wc -l # This is the number of fails (0)

Will now try to run it with 9.2 as that's the oldest version that's on Docker Hub.

tstoeckler’s picture

Hmm... same result with 9.2.23 :-/

tstoeckler’s picture

Status: Active » Postponed (maintainer needs more info)

Hehe, searching for "Postgres 9.1" on Docker Hub, I found this little fellow: drupalci/pgsql-9.1. So I ran the above again in the same environment that Drupal.org runs its tests and I again got the same results. So not really sure what to do here.

One thing I thought that may be happening is that due to the concurrency of the tests that there is a race between the different data sets composite_primary_key and composite_primary_key_different_order in ::testSchemaChangePrimaryKey()? I.e. while the first data set is being executed the second is being run as well and then the assertion on the first one fails because they are operating on the same table? That would explain the different order between expected and actual results.

When running the tests locally, a different database prefix is used for each data set, so I can't reproduce this locally, but I don't really know much about how the tests are run on Drupal.org, so...

alexpott’s picture

Status: Postponed (maintainer needs more info) » Needs work

postponed is not the correct status here. We have a confirmed random fail here.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

alexpott’s picture

Status: Needs work » Needs review
StatusFileSize
new538 bytes

Trying to have a patch that always exposes the problem.

Status: Needs review » Needs work

The last submitted patch, 7: 2982755-7.test-only.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

alexpott’s picture

Status: Needs work » Needs review
StatusFileSize
new8.48 KB

So we can reproduce it seemingly. I have a hunch that this is to do with the fact this query $result = $this->connection->query("SELECT a.attname, i.indkey FROM pg_index i JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) WHERE i.indrelid = '{" . $table . "}'::regclass AND i.indisprimary")->fetchAllKeyed(); is not quite working the way we expect it too. Seeing if the table name has anything to do with it.

Status: Needs review » Needs work

The last submitted patch, 9: 2982755-9.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

alexpott’s picture

Status: Needs work » Needs review
StatusFileSize
new2.29 KB

So the fix looks effective. Not sure how to explain it though. Also not entirely sure that that matters.

alexpott’s picture

#9 included an unrelated fix for cache clearing on menu save and that code in #9 can be ignored.

alexpott’s picture

oh that's good #9 still failed eventually for the same reason so #11 doesn't work. Hmmm.

daffie’s picture

StatusFileSize
new872 bytes

What also could be true is that the PostgreSQL table "pg_index" is not updated as fast as we are changing the table structure and testing the changes. It would explain why the patch from comment #10 would do the trick.

alexpott’s picture

@daffie we drop all the tables in the tests ::tearDown() method. One thought is that since I've not seen this locally and I've run this test on Postgres test a lot recently is that one difference between my local and Drupal is concurrency. Maybe that is having an impact.

Also the fix proposed in #11 turned out not to work.

daffie’s picture

Is it maybe an idea to add some sleep methods, so that the PostgreSQL database has some time to update the pg_index table.

alexpott’s picture

@daffie Postgres is a transactional database I'm pretty sure pg_index is consistent. If not this would bbe a major bug in postgres's ACID compliance.

daffie’s picture

@alexpott: You are sure that every Schema class call is its own transaction?

alexpott’s picture

@daffie it feels super weird if a schema change is not transactional. That said I'm not sure about how Postgres handles indexes that change as a result of schema changes. So maybe a sleep is worth trying.

daffie’s picture

@alexpott: We can also split up the test in smaller pieces, so that every test only does one index change. Every test should then be its own transaction.

alexpott’s picture

@daffie that doesn't help us get to the root cause - it'd be great to understand why this is happening. ATM it doesn't make much sense.

plach’s picture

Issue tags: +Random test failure

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

krzysztof domański’s picture

Status: Needs review » Needs work
StatusFileSize
new7.06 KB
new7.08 KB

After renaming one of the columns, the \Drupal\Core\Database\Driver\pgsql\DatabaseSchema::findPrimaryKeyColumns() method returns the correct primary keys but in the wrong order.

alexpott confirmed in #9 that using different table names does not solve this problem.

I'm testing the impact of the PostgreSQL version and the concurrency parameter. I limit other factors. I'm only testing the field name change.

public function testSchemaChangePrimaryKey(array $initial_primary_key, array $renamed_primary_key) {
  $find_primary_key_columns = new \ReflectionMethod(get_class($this->schema), 'findPrimaryKeyColumns');
  $find_primary_key_columns->setAccessible(TRUE);

  // Test making the field the primary key of the table upon creation.
  $table_name = 'test_table';
  $table_spec = [
    'fields' => [
      'test_field' => ['type' => 'int', 'not null' => TRUE],
      'other_test_field' => ['type' => 'int', 'not null' => TRUE],
    ],
    'primary key' => $initial_primary_key,
  ];
  $this->assertEquals($renamed_primary_key, $find_primary_key_columns->invoke($this->schema, $table_name));
  $this->schema->createTable($table_name, $table_spec);
  $this->assertTrue($this->schema->fieldExists($table_name, 'test_field'));
  $this->assertEquals($initial_primary_key, $find_primary_key_columns->invoke($this->schema, $table_name));

  // Rename the field and make sure the primary key was updated.
  $this->schema->changeField($table_name, 'test_field', 'test_field_renamed', ['type' => 'int', 'not null' => TRUE]);
  $this->assertTrue($this->schema->fieldExists($table_name, 'test_field_renamed'));
  $this->assertEquals($renamed_primary_key, $find_primary_key_columns->invoke($this->schema, $table_name));
}
krzysztof domański’s picture

StatusFileSize
new3.68 KB

See #26. The problem does not apply to the new version of PostgreSQL. Tests were successful in version 9.5. The number of tests run simultaneously matters (concurrency: 1).

Everything confirms alexpott's suggestion that the reason is in the SELECT query.

protected function findPrimaryKeyColumns($table) {
  if (!$this->tableExists($table)) {
    return FALSE;
  }

  // Fetch the 'indkey' column from 'pg_index' to figure out the order of the
  // primary key.
  // @todo Use 'array_position()' to be able to perform the ordering in SQL
  //   directly when 9.5 is the minimum  PostgreSQL version.
  $result = $this->connection->query("SELECT a.attname, i.indkey FROM pg_index i JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) WHERE i.indrelid = '{" . $table . "}'::regclass AND i.indisprimary")->fetchAllKeyed();
  if (!$result) {
    return [];
  }

  $order = explode(' ', reset($result));
  $columns = array_combine($order, array_keys($result));
  ksort($columns);
  return array_values($columns);
}

Maybe the solution is to add 'ORDER BY' to the SELECT query? Otherwise, the solution is unlikely to be trivial. I suggest that you first get rid of the troublesome random test failure. This is critical and applies only to tests run simultaneously when concurrency > 1.

I add a follow-up to solve this problem in another task. #3096608: On PostgreSQL 9.1 after renaming one of the fields, the DatabaseSchema::findPrimaryKeyColumns method returns the correct keys but in the wrong order.

mradcliffe’s picture

This might be related to orphaned sequences because of #3028706: New serial columns do not own sequences. Not specifically the addPrimaryKey assertion, but it being in the same test case as SchemaTest and due to concurrency, length of the key, and databases (re)used in test bots?

alexpott’s picture

krzysztof domański’s picture

I close #3096608 then.

krzysztof domański’s picture

StatusFileSize
new4.75 KB

Ignore

krzysztof domański’s picture

StatusFileSize
new4.75 KB

It also fails elsewhere.

krzysztof domański’s picture

StatusFileSize
new2.53 KB

Ignore

krzysztof domański’s picture

StatusFileSize
new6.02 KB

Stupid mistake. Ignore.

krzysztof domański’s picture

StatusFileSize
new6.02 KB

Looks like technical debt, but it works. One frequent random test failure less.

krzysztof domański’s picture

Status: Needs work » Needs review
StatusFileSize
new3.79 KB

See #26 and #27. Primary key sorting works on PostgreSQL 9.1 in the real world. The problem occurs only in tests, because we use many machines, concurrency > 1.

Proposed solution:

// If PostgreSQL less than 9.5, then arrays of primary key are sorted before comparison.
$canonicalize = FALSE;
if ($this->connection->driver() == 'pgsql' && version_compare($this->connection->clientVersion(), '9.5', '<')) {
  $canonicalize = TRUE;
}
$this->assertEquals($renamed_primary_key, $find_primary_key_columns->invoke($this->schema, $table_name), '', 0, 10, $canonicalize);
daffie’s picture

Status: Needs review » Needs work

@Krzysztof Domański: Great work, thanks.

The problem with the solution from comment #36 is that if you run the test with a concurrency is 1 and with PostgreSQL < 9.5, the test will fail and it should pass. My suggestion would be to skip the tests for PostgreSQL versions < 9.5.

krzysztof domański’s picture

krzysztof domański’s picture

Correction of a damaged previous patch.

krzysztof domański’s picture

Status: Needs work » Needs review

The problem with the solution from comment #36 is that if you run the test with a concurrency is 1 and with PostgreSQL < 9.5, the test will fail and it should pass. My suggestion would be to skip the tests for PostgreSQL versions < 9.5.

All pass in PostgreSQL 9.5. Also pass when you run the test with a concurrency is 1 and with PostgreSQL 9.1.

We do not need to exclude tests for older versions. Sorting arrays before comparison will solve the problem.

alexpott’s picture

+++ b/core/tests/Drupal/KernelTests/Core/Database/SchemaTest.php
@@ -816,6 +816,14 @@ public function testSchemaChangePrimaryKey(array $initial_primary_key, array $re
+      $canonicalize = TRUE;

I think the whole point of the test is to test the order of the columns so doing this makes the test invalid.

I think we need to understand why concurrency is impacting this test.

daffie’s picture

Status: Needs review » Needs work

Back to needs work.

catch’s picture

If we do end up wanting to sort the arrays, a note that the way to do that now in phpunit is ::assertEqualsCanonicalizing.

But given we're testing the sorting of the primary key, agreed that's not a viable fix anyway.

tstoeckler’s picture

BTW, I posted #3119170: Optimize pgsql/Schema::findPrimaryKeyColumns() for Postgres >= 9.6 which is probably of interest to people here. Not yet sure if it will actually impact this, but it's certainly related.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

krzysztof domański’s picture

Priority: Critical » Normal

1. Tests repeated 100 times confirmed that the problem only affects the old version (PostgreSQL 9.1). See #39.

2. PostgreSQL version requirements raised to 10.0 so this issue is no longer critical.
It may still appear in older versions of drupal (8.8 and 8.9), which is why I leave the issue open.

Affected Drupal 8 with PostgreSQL 9.1.

krzysztof domański’s picture

StatusFileSize
new2.24 KB

Confirmation that it is no longer occur in Drupal 9.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

krzysztof domański’s picture

Status: Needs work » Fixed

No longer occur in Drupal 9 because PostgreSQL version requirements raised to 10.0.. Let's close this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

xjm’s picture