Verified Commit f00184db authored by Alex Pott's avatar Alex Pott
Browse files

Issue #3365945 by larowlan, sakthi_dev, alexpott, catch, daffie, mkimmet,...

Issue #3365945 by larowlan, sakthi_dev, alexpott, catch, daffie, mkimmet, Olarin, rakesh.gectcr, JvE, borisson_, eelkeblok: Errors: The following table(s) do not have a primary key: forum_index
parent 6514a929
Loading
Loading
Loading
Loading
Loading
+38 −0
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@
 * Install, update, and uninstall functions for the Forum module.
 */

use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\taxonomy\Entity\Term;

@@ -161,6 +162,7 @@ function forum_schema() {
      'created' => ['created'],
      'last_comment_timestamp' => ['last_comment_timestamp'],
    ],
    'primary key' => ['nid', 'tid'],
    'foreign keys' => [
      'tracked_node' => [
        'table' => 'node',
@@ -204,3 +206,39 @@ function forum_update_10100(&$sandbox = NULL) {
    $connection->schema()->changeField('forum_index', 'last_comment_timestamp', 'last_comment_timestamp', $new);
  }
}

/**
 * Repopulate the forum index table.
 */
function forum_update_10101(&$sandbox = NULL): PluralTranslatableMarkup {
  $query = \Drupal::database()->select('forum_index', 'fi')
    ->fields('fi', ['nid', 'tid'])
    ->groupBy('nid')
    ->groupBy('tid');
  $query->addExpression('count(*)', 'count');
  $query->having('count(*) > 1');
  $results = $query->execute();
  $nids_to_rebuild = [];
  foreach ($results as $row) {
    \Drupal::database()->delete('forum_index')->condition('tid', $row->tid)->condition('nid', $row->nid)->execute();
    $nids_to_rebuild[] = $row->nid;
  }
  \Drupal::state()->set('forum_update_10101_nids', $nids_to_rebuild);
  return new PluralTranslatableMarkup(count($nids_to_rebuild), 'Removed 1 duplicate entry from forum_index', 'Removed @count duplicate entries from forum_index');
}

/**
 * Add a primary key to forum_index.
 */
function forum_update_10102(&$sandbox = NULL) {
  $connection = \Drupal::database();
  if ($connection->schema()->tableExists('forum_index')) {
    // Data in this table could have duplicates. The data can be re-constructed
    // from other data in the site. To avoid duplicate key errors we delete any
    // rows that are duplicates and then recreate them in a post-update hook.
    // @see \forum_post_update_recreate_forum_index_rows().
    $connection->schema()->addPrimaryKey('forum_index', ['nid', 'tid']);
    return \t('Added primary key to the forum_index table.');
  }
  return \t('Index already exists');
}
+61 −0
Original line number Diff line number Diff line
<?php

/**
 * @file
 * Contains post update functions.
 */

use Drupal\Core\Site\Settings;
use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\node\NodeInterface;

/**
 * Repopulate the forum index table.
 */
function forum_post_update_recreate_forum_index_rows(&$sandbox = NULL): TranslatableMarkup {
  $entityStorage = \Drupal::entityTypeManager()->getStorage('node');
  if (!isset($sandbox['ids'])) {
    // This must be the first run. Initialize the sandbox.
    $sandbox['ids'] = \Drupal::state()->get('forum_update_10101_nids', []);
    $sandbox['max'] = count($sandbox['ids']);
  }

  $ids = array_splice($sandbox['ids'], 0, (int) Settings::get('entity_update_batch_size', 50));
  $insert = \Drupal::database()->insert('forum_index')->fields([
    'nid',
    'title',
    'tid',
    'sticky',
    'created',
    'last_comment_timestamp',
    'comment_count',
  ]);
  $do_insert = FALSE;
  foreach ($entityStorage->loadMultiple($ids) as $entity) {
    $do_insert = TRUE;
    assert($entity instanceof NodeInterface);
    $insert->values([
      $entity->id(),
      $entity->label(),
      $entity->taxonomy_forums->target_id,
      (int) $entity->isSticky(),
      $entity->getCreatedTime(),
      $entity->comment_forum->last_comment_timestamp,
      $entity->comment_forum->comment_count,
    ]);
  }
  if ($do_insert) {
    $insert->execute();
  }
  $sandbox['#finished'] = empty($sandbox['max']) || empty($sandbox['ids']) ? 1 : ($sandbox['max'] - count($sandbox['ids'])) / $sandbox['max'];
  if ($sandbox['#finished'] === 1) {
    \Drupal::state()->delete('forum_update_10101_nids');
    return new TranslatableMarkup('Finished updating forum index rows.');
  }
  return new PluralTranslatableMarkup($sandbox['max'] - count($sandbox['ids']),
    'Processed @count entry of @total.',
    'Processed @count entries of @total.',
    ['@total' => $sandbox['max']],
  );
}
+63.1 KiB

File added.

Preview size limit exceeded, changes collapsed.

+71 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Tests\forum\Functional;

use Drupal\Core\Site\Settings;
use Drupal\FunctionalTests\Update\UpdatePathTestBase;

/**
 * Tests addition of the forum_index primary key.
 *
 * @group forum
 */
final class ForumIndexUpdateTest extends UpdatePathTestBase {

  /**
   * {@inheritdoc}
   */
  protected function setDatabaseDumpFiles() {
    $this->databaseDumpFiles = [
      dirname(__DIR__, 2) . '/fixtures/update/drupal-10.1.0.empty.testing.forum.gz',
    ];
  }

  /**
   * Tests the update path to add the new primary key.
   */
  public function testUpdatePath(): void {
    // Set the batch size to 1 to validate the sandbox logic in the update hook.
    $settings = Settings::getInstance() ? Settings::getAll() : [];
    $settings['entity_update_batch_size'] = 1;
    new Settings($settings);

    $schema = \Drupal::database()->schema();
    // We can't reliably call ::indexExists for each database driver as sqlite
    // doesn't have named indexes for primary keys like mysql (PRIMARY) and
    // pgsql (pkey).
    $find_primary_key_columns = new \ReflectionMethod(get_class($schema), 'findPrimaryKeyColumns');
    $columns = $find_primary_key_columns->invoke($schema, 'forum_index');
    $this->assertEmpty($columns);
    $count = \Drupal::database()->select('forum_index')->countQuery()->execute()->fetchField();
    $this->assertEquals(9, $count);
    $duplicates = \Drupal::database()->select('forum_index')->condition('nid', 1)->countQuery()->execute()->fetchField();
    $this->assertEquals(2, $duplicates);
    $duplicates = \Drupal::database()->select('forum_index')->condition('nid', 2)->countQuery()->execute()->fetchField();
    $this->assertEquals(3, $duplicates);
    $this->runUpdates();
    $this->assertEquals(['nid', 'tid'], $find_primary_key_columns->invoke($schema, 'forum_index'));
    $count = \Drupal::database()->select('forum_index')->countQuery()->execute()->fetchField();
    $this->assertEquals(6, $count);
    $duplicates = \Drupal::database()->select('forum_index')->condition('nid', 1)->countQuery()->execute()->fetchField();
    $this->assertEquals(1, $duplicates);
    $duplicates = \Drupal::database()->select('forum_index')->condition('nid', 2)->countQuery()->execute()->fetchField();
    $this->assertEquals(1, $duplicates);
    // This entry is associated with two terms so two records should remain.
    $duplicates = \Drupal::database()->select('forum_index')->condition('nid', 4)->countQuery()->execute()->fetchField();
    $this->assertEquals(2, $duplicates);
    $entry = \Drupal::database()->select('forum_index', 'f')->fields('f')->condition('nid', 5)->execute()->fetchAssoc();
    $this->assertEquals([
      'nid' => 5,
      'title' => 'AFL',
      'tid' => 5,
      'sticky' => 0,
      'created' => 1695264369,
      'last_comment_timestamp' => 1695264403,
      'comment_count' => 1,
    ], $entry);
  }

}
+56 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Tests\forum\Kernel;

use Drupal\KernelTests\KernelTestBase;

/**
 * Defines a class for testing the forum_index table.
 *
 * @group forum
 */
final class ForumIndexTest extends KernelTestBase {

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'system',
    'user',
    'node',
    'history',
    'taxonomy',
    'forum',
    'comment',
    'options',
    'text',
  ];

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $this->installEntitySchema('node');
    $this->installEntitySchema('user');
    $this->installEntitySchema('comment');
    $this->installEntitySchema('taxonomy_term');
    $this->installSchema('forum', ['forum_index']);
  }

  /**
   * Tests there's a primary key on the forum_index table.
   */
  public function testForumIndexIndex(): void {
    $schema = \Drupal::database()->schema();
    $this->assertTrue($schema->tableExists('forum_index'));
    // We can't reliably call ::indexExists for each database driver as sqlite
    // doesn't have named indexes for primary keys like mysql (PRIMARY) and
    // pgsql (pkey).
    $find_primary_key_columns = new \ReflectionMethod(get_class($schema), 'findPrimaryKeyColumns');
    $this->assertEquals(['nid', 'tid'], $find_primary_key_columns->invoke($schema, 'forum_index'));
  }

}