Commit 8bc8ca17 authored by Dave Reid's avatar Dave Reid
Browse files

Issue #3201480 by Dave Reid, Afxsoft: Fixed queries throw exceptions comparing...

Issue #3201480 by Dave Reid, Afxsoft: Fixed queries throw exceptions comparing entity IDs (bigint) to xmlsitemap IDs (varchar) on PostgreSQL by adding XmlSitemapLinkStorage::getEntityLinkQuery() and getEntityQuery() methods.
parent f033a3b6
Loading
Loading
Loading
Loading
+90 −1
Original line number Diff line number Diff line
@@ -5,8 +5,13 @@ namespace Drupal\xmlsitemap;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Query\Merge;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Entity\EntityChangedInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\Query\QueryInterface;
use Drupal\Core\Entity\Query\Sql\Query;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\File\FileUrlGeneratorInterface;
use Drupal\Core\Language\LanguageInterface;
@@ -55,6 +60,20 @@ class XmlSitemapLinkStorage implements XmlSitemapLinkStorageInterface {
   */
  protected $fileUrlGenerator;

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * The entity field manager.
   *
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
   */
  protected $entityFieldManager;

  /**
   * Constructs a XmlSitemapLinkStorage object.
   *
@@ -66,13 +85,19 @@ class XmlSitemapLinkStorage implements XmlSitemapLinkStorageInterface {
   *   The database connection.
   * @param \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator
   *   The file URL generator.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
   *   The entity field manager.
   */
  public function __construct(StateInterface $state, ModuleHandlerInterface $module_handler, Connection $connection, FileUrlGeneratorInterface $file_url_generator) {
  public function __construct(StateInterface $state, ModuleHandlerInterface $module_handler, Connection $connection, FileUrlGeneratorInterface $file_url_generator, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager) {
    $this->state = $state;
    $this->moduleHandler = $module_handler;
    $this->anonymousUser = new AnonymousUserSession();
    $this->connection = $connection;
    $this->fileUrlGenerator = $file_url_generator;
    $this->entityTypeManager = $entity_type_manager;
    $this->entityFieldManager = $entity_field_manager;
  }

  /**
@@ -351,4 +376,68 @@ class XmlSitemapLinkStorage implements XmlSitemapLinkStorageInterface {
    return $links;
  }

  /**
   * {@inheritdoc}
   */
  public function getEntityLinkQuery(string $entity_type_id, array $bundles = []): SelectInterface {
    $query = $this->connection->select('xmlsitemap', 'x');
    /** @var \Drupal\Core\Field\FieldDefinitionInterface[] $definitions */
    $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
    $definitions = $this->entityFieldManager->getBaseFieldDefinitions($entity_type_id);
    $id_definition = $definitions[$entity_type->getKey('id')];
    if ($id_definition->getType() === 'integer') {
      $types = [
        'mysql' => 'UNSIGNED',
        'pgsql' => 'BIGINT',
      ];
      $type = $types[\Drupal::database()->databaseType()] ?? 'INTEGER';
      $query->addExpression("CAST(x.id AS $type)", 'id');
    }
    else {
      $query->addField('x', 'id');
    }
    $query->condition('type', $entity_type_id);
    if (!empty($bundles)) {
      $query->condition('subtype', $bundles, 'IN');
    }
    return $query;
  }

  /**
   * {@inheritdoc}
   */
  public function getEntityQuery(string $entity_type_id, array $bundles = [], SelectInterface $subquery = NULL, string $subquery_operator = 'IN'): QueryInterface {
    $storage = $this->entityTypeManager->getStorage($entity_type_id);
    $entity_type = $storage->getEntityType();
    $query = $storage->getQuery();
    $id_field = $entity_type->getKey('id');

    if ($bundles && $bundle_key = $entity_type->getKey('bundle')) {
      $query->condition($bundle_key, $bundles, 'IN');
    }

    // Access for entities is checked individually for the anonymous user
    // when each item is processed. We can skip the access check for the
    // query.
    $query->accessCheck(FALSE);

    if (!isset($subquery)) {
      $subquery = $this->getEntityLinkQuery($entity_type_id, $bundles);
    }

    // If the storage for this entity type is not using a SQL backend, then
    // we need to convert our subquery into an actual array of values since we
    // cannot perform a direct subquery with our entity query.
    if (!($query instanceof Query)) {
      $subquery = $subquery->execute()->fetchCol();
    }
    $query->condition(
      $id_field,
      $subquery,
      $subquery_operator
    );

    return $query;
  }

}
+33 −0
Original line number Diff line number Diff line
@@ -2,7 +2,9 @@

namespace Drupal\xmlsitemap;

use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\Query\QueryInterface;

/**
 * Provides an interface defining a XmlSitemapLinkStorage service.
@@ -138,4 +140,35 @@ interface XmlSitemapLinkStorageInterface {
   */
  public function loadMultiple(array $conditions = []);

  /**
   * Get a select query for entity XML sitemap link IDs.
   *
   * @param string $entity_type_id
   *   The entity type ID.
   * @param string[] $bundles
   *   The entity bundle IDs.
   *
   * @return \Drupal\Core\Database\Query\SelectInterface
   *   The select query.
   */
  public function getEntityLinkQuery(string $entity_type_id, array $bundles = []): SelectInterface;

  /**
   * Get an entity query for XML sitemap indexing or querying.
   *
   * @param string $entity_type_id
   *   The entity type ID.
   * @param string[] $bundles
   *   The entity bundle IDs.
   * @param null|\Drupal\Core\Database\Query\SelectInterface $subquery
   *   The optional subquery on the xmlsitemap table to match against the
   *   entity ID values.
   * @param string $subquery_operator
   *   The optional subquery operator. Possible values are 'IN' or 'NOT IN'.
   *
   * @return \Drupal\Core\Entity\Query\QueryInterface
   *   The entity query object.
   */
  public function getEntityQuery(string $entity_type_id, array $bundles = [], SelectInterface $subquery = NULL, string $subquery_operator = 'IN'): QueryInterface;

}
+2 −24
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@ use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Query\Sql\Query;
use Drupal\Core\Entity\RevisionableInterface;
use Drupal\Core\Entity\TranslatableInterface;
use Drupal\Core\File\FileSystemInterface;
@@ -1580,30 +1579,9 @@ function xmlsitemap_xmlsitemap_index_links($limit) {
    }

    try {
      $query = $entity_type_manager->getStorage($entity_type_id)->getQuery();
      $linkStorage = \Drupal::service('xmlsitemap.link_storage');
      $query = $linkStorage->getEntityQuery($entity_type_id, $bundles, NULL, 'NOT IN');
      $query->range(0, $limit);
      if (!empty($info['entity keys']['bundle'])) {
        $query->condition($info['entity keys']['bundle'], $bundles, 'IN');
      }

      // Perform a subquery against the xmlsitemap table to ensure that we are
      // only looking for items that we have not already indexed.
      $subquery = \Drupal::database()->select('xmlsitemap', 'x');
      $subquery->addField('x', 'id');
      $subquery->condition('type', $entity_type_id);
      // If the storage for this entity type is against a SQL backend, perform
      // a direct subquery condition to avoid needing to load all the IDs.
      if ($query instanceof Query) {
        $query->condition($info['entity keys']['id'], $subquery, 'NOT IN');
      }
      else {
        $query->condition($info['entity keys']['id'], $subquery->execute()->fetchCol(), 'NOT IN');
      }

      // Access for entities is checked individually for the anonymous user
      // when each item is processed. We can skip the access check for the
      // query.
      $query->accessCheck(FALSE);
      $query->addTag('xmlsitemap_index_links');

      if ($ids = $query->execute()) {
+8 −18
Original line number Diff line number Diff line
@@ -25,28 +25,18 @@ function xmlsitemap_post_update_reindex_future_revision_content() {
      $entity_type_ids = $plugin->getEntityTypes();
      foreach ($entity_type_ids as $entity_type_id) {
        if ($bundles = $plugin->getBundlesForEntityType($entity_type_id)) {
          $storage = \Drupal::entityTypeManager()->getStorage($entity_type_id);
          $entity_type = $storage->getEntityType();
          $query = $storage->getQuery();
          /** @var \Drupal\xmlsitemap\XmlSitemapLinkStorageInterface $linkStorage */
          $linkStorage = \Drupal::service('xmlsitemap.link_storage');
          $subquery = $linkStorage->getEntityLinkQuery($entity_type_id, $bundles);
          $subquery->condition('access', 0);
          $subquery->condition('status', 1);
          $query = $linkStorage->getEntityQuery($entity_type_id, $bundles, $subquery);
          $entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
          $query->currentRevision();
          if ($entity_type->hasKey('bundle')) {
            $query->condition($entity_type->getKey('bundle'), $bundles, 'IN');
          }
          // Disable the access check because we DO want items that are not
          // accessible.
          $query->accessCheck(FALSE);
          if ($entity_type->hasKey('published')) {
            $query->condition($entity_type->getKey('published'), TRUE);
          }
          // Add a subquery on any currently indexed links where the current
          // link is included but not accessible.
          $subquery = \Drupal::database()->select('xmlsitemap', 'x');
          $subquery->addField('x', 'id');
          $subquery->condition('type', $entity_type_id);
          $subquery->condition('subtype', $bundles, 'IN');
          $subquery->condition('access', 0);
          $subquery->condition('status', 1);
          $query->condition($entity_type->getKey('id'), $subquery, 'IN');
          $query->addTag(__FUNCTION__);
          if ($entity_ids = $query->execute()) {
            $limit = \Drupal::config('xmlsitemap.settings')->get('batch_limit');
            $chunks = array_chunk($entity_ids, $limit);
+1 −1
Original line number Diff line number Diff line
@@ -6,7 +6,7 @@ services:
      - { name: xmlsitemap_generator }
  xmlsitemap.link_storage:
    class: Drupal\xmlsitemap\XmlSitemapLinkStorage
    arguments: ['@state', '@module_handler', '@database', '@file_url_generator']
    arguments: ['@state', '@module_handler', '@database', '@file_url_generator', '@entity_type.manager', '@entity_field.manager']
    tags:
      - { name: xmlsitemap.link_storage }
  logger.channel.xmlsitemap: